From 167f364cb4dd4671f3f643de40e6daf20a8c611d Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 2 Jul 2024 19:40:15 +0200 Subject: [PATCH 01/53] feat: wallets state refactored --- apps/desktop/src/app/App.tsx | 66 +++--- apps/desktop/src/electron/sseEvetns.ts | 32 +-- apps/desktop/src/libs/aptabaseElectron.ts | 9 +- apps/desktop/src/libs/hooks.ts | 20 +- apps/extension/src/App.tsx | 6 +- apps/extension/src/libs/hooks.ts | 14 +- .../libs/service/dApp/tonConnectService.ts | 4 +- apps/extension/src/libs/service/dApp/utils.ts | 4 - apps/twa/src/App.tsx | 6 +- apps/twa/src/libs/hooks.ts | 6 +- apps/twa/src/libs/twaNotification.ts | 12 +- apps/web/src/App.tsx | 4 +- apps/web/src/libs/hooks.ts | 14 +- packages/core/src/AppSdk.ts | 14 +- packages/core/src/Keys.ts | 11 +- packages/core/src/entries/account.ts | 7 +- packages/core/src/entries/password.ts | 33 ++- packages/core/src/entries/wallet.ts | 82 ++++++- packages/core/src/service/accountService.ts | 195 ----------------- packages/core/src/service/ledger/transfer.ts | 12 +- packages/core/src/service/mnemonicService.ts | 28 +-- packages/core/src/service/passwordService.ts | 73 +++++++ packages/core/src/service/proService.ts | 44 ++-- packages/core/src/service/signerService.ts | 8 +- .../core/src/service/suggestionService.ts | 16 +- .../src/service/tonConnect/connectService.ts | 42 ++-- .../service/tonConnect/connectionService.ts | 12 +- packages/core/src/service/transfer/common.ts | 12 +- .../src/service/transfer/jettonService.ts | 10 +- .../src/service/transfer/multiSendService.ts | 47 ++-- .../core/src/service/transfer/nftService.ts | 18 +- .../core/src/service/transfer/tonService.ts | 20 +- packages/core/src/service/tron/tronService.ts | 9 +- .../src/service/tron/tronTransferService.ts | 6 +- .../src/service/wallet/contractService.ts | 8 +- .../core/src/service/wallet/storeService.ts | 62 ------ packages/core/src/service/walletService.ts | 203 ++++++++---------- packages/core/src/service/walletsService.ts | 196 +++++++++++++++++ packages/uikit/src/components/Header.tsx | 54 ++--- .../components/PairKeystoneNotification.tsx | 8 +- .../src/components/PairSignerNotification.tsx | 11 +- .../activity/NotificationCommon.tsx | 15 +- .../activity/ton/ActivityActionDetails.tsx | 9 +- .../activity/ton/ContractDeployAction.tsx | 4 +- .../activity/ton/JettonActivity.tsx | 10 +- .../activity/ton/JettonNotifications.tsx | 6 +- .../components/activity/ton/NftActivity.tsx | 12 +- .../components/activity/ton/StakeActivity.tsx | 8 +- .../activity/ton/SubscribeAction.tsx | 6 +- .../activity/ton/TonActivityAction.tsx | 15 +- .../connect/TonConnectNotification.tsx | 15 +- .../connect/TonConnectSubscription.tsx | 8 +- .../connect/TonTransactionNotification.tsx | 27 +-- .../src/components/connect/connectHook.ts | 10 +- .../src/components/create/ChangePassword.tsx | 38 ++-- .../src/components/create/CreateAuth.tsx | 104 ++------- .../src/components/create/WalletName.tsx | 75 ++----- .../components/dashboard/DashboardTable.tsx | 10 +- .../components/desktop/aside/AsideHeader.tsx | 8 +- .../components/desktop/aside/AsideMenu.tsx | 33 ++- .../desktop/aside/PreferencesAsideMenu.tsx | 6 +- .../desktop/history/ton/HistoryCell.tsx | 4 +- .../history/ton/JettonDesktopActions.tsx | 6 +- .../desktop/history/ton/NftDesktopActions.tsx | 7 +- .../ton/SmartContractExecDesktopAction.tsx | 8 +- .../history/ton/TonTransferDesktopAction.tsx | 6 +- .../MultiSendConfirmNotification.tsx | 5 +- .../desktop/multi-send/MultiSendTable.tsx | 15 +- .../uikit/src/components/home/AccountView.tsx | 9 +- .../uikit/src/components/home/Balance.tsx | 19 +- .../components/home/BuyItemNotification.tsx | 11 +- packages/uikit/src/components/nft/LinkNft.tsx | 23 +- .../uikit/src/components/nft/NftAction.tsx | 7 +- .../uikit/src/components/nft/NftDetails.tsx | 6 +- .../components/settings/AccountSettings.tsx | 47 ++-- .../src/components/settings/ClearSettings.tsx | 6 +- .../settings/LogOutNotification.tsx | 36 ++-- .../src/components/settings/ProSettings.tsx | 50 ++--- .../wallet-name/WalletNameNotification.tsx | 2 +- .../components/transfer/ConfirmListItem.tsx | 4 +- .../src/components/transfer/RecipientView.tsx | 5 +- .../src/components/transfer/ShowAddress.tsx | 4 +- .../components/transfer/SuggestionAddress.tsx | 4 +- .../components/transfer/SuggestionList.tsx | 23 +- .../transfer/amountView/AmountViewUI.tsx | 9 +- .../uikit/src/components/transfer/common.tsx | 9 +- .../src/components/transfer/nft/Common.tsx | 4 +- .../transfer/nft/ConfirmNftView.tsx | 11 +- .../src/components/transfer/nft/hooks.ts | 7 +- .../history/DesktopHistoryPage.tsx | 9 +- .../src/desktop-pages/notcoin/NotcoinPage.tsx | 35 +-- .../settings/DesktopWalletSettingsPage.tsx | 26 +-- .../settings/DesktopWalletSettingsRouting.tsx | 2 +- .../uikit/src/hooks/analytics/amplitude.ts | 13 +- packages/uikit/src/hooks/analytics/google.ts | 22 +- packages/uikit/src/hooks/analytics/gtag.ts | 11 +- packages/uikit/src/hooks/analytics/index.ts | 28 ++- packages/uikit/src/hooks/appContext.ts | 13 -- .../nft/useAreNftActionsDisabled.ts | 8 +- .../blockchain/useEstimateMultiTransferFee.ts | 5 +- .../src/hooks/blockchain/useEstimateTonFee.ts | 11 +- .../hooks/blockchain/useEstimateTransfer.ts | 20 +- .../hooks/blockchain/useExecuteTonContract.ts | 11 +- .../hooks/blockchain/useSendMultiTransfer.ts | 9 +- .../src/hooks/blockchain/useSendTransfer.ts | 9 +- .../src/hooks/blockchain/useTonRecipient.ts | 4 +- packages/uikit/src/hooks/useStorage.ts | 13 ++ packages/uikit/src/libs/queryKey.ts | 1 + .../uikit/src/pages/activity/Activity.tsx | 9 +- packages/uikit/src/pages/coin/Jetton.tsx | 7 +- packages/uikit/src/pages/coin/Ton.tsx | 10 +- packages/uikit/src/pages/coin/Tron.tsx | 13 +- packages/uikit/src/pages/home/Unlock.tsx | 20 +- .../src/pages/home/UnlockNotification.tsx | 33 +-- packages/uikit/src/pages/import/Create.tsx | 30 ++- packages/uikit/src/pages/import/Import.tsx | 31 ++- packages/uikit/src/pages/import/Ledger.tsx | 2 +- packages/uikit/src/pages/import/Password.tsx | 68 +++--- packages/uikit/src/pages/import/Subscribe.tsx | 12 +- packages/uikit/src/pages/settings/Account.tsx | 37 ++-- packages/uikit/src/pages/settings/Dev.tsx | 6 +- .../uikit/src/pages/settings/Localization.tsx | 20 +- .../uikit/src/pages/settings/Notification.tsx | 31 ++- .../uikit/src/pages/settings/Recovery.tsx | 27 ++- .../uikit/src/pages/settings/Security.tsx | 11 +- packages/uikit/src/pages/settings/Version.tsx | 22 +- packages/uikit/src/pages/settings/index.tsx | 2 +- packages/uikit/src/pages/signer/LinkPage.tsx | 7 +- .../src/pages/signer/PublishNotification.tsx | 9 +- packages/uikit/src/state/account.ts | 70 ------ packages/uikit/src/state/activity.ts | 11 +- .../src/state/dashboard/useDashboardData.ts | 31 ++- packages/uikit/src/state/home.ts | 16 +- packages/uikit/src/state/jetton.ts | 80 +++---- packages/uikit/src/state/keystone.ts | 20 +- packages/uikit/src/state/language.ts | 59 +++++ packages/uikit/src/state/ledger.ts | 14 +- packages/uikit/src/state/mnemonic.ts | 69 +++--- packages/uikit/src/state/nft.ts | 36 +--- packages/uikit/src/state/password.ts | 9 - packages/uikit/src/state/pro.ts | 43 ++-- packages/uikit/src/state/signer.ts | 5 +- packages/uikit/src/state/subscribe.ts | 4 +- packages/uikit/src/state/suggestions.ts | 18 +- .../uikit/src/state/swap/useEncodeSwap.ts | 7 +- packages/uikit/src/state/tonConnect.ts | 37 ++-- packages/uikit/src/state/tron/tron.ts | 44 ++-- packages/uikit/src/state/wallet.ts | 186 ++++++++-------- 148 files changed, 1760 insertions(+), 1860 deletions(-) delete mode 100644 packages/core/src/service/accountService.ts create mode 100644 packages/core/src/service/passwordService.ts delete mode 100644 packages/core/src/service/wallet/storeService.ts create mode 100644 packages/core/src/service/walletsService.ts create mode 100644 packages/uikit/src/hooks/useStorage.ts delete mode 100644 packages/uikit/src/state/account.ts create mode 100644 packages/uikit/src/state/language.ts diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 296a25b32..887c3dea3 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -39,7 +39,7 @@ import { DesktopWalletSettingsRouting } from '@tonkeeper/uikit/dist/desktop-page import { DesktopSwapPage } from '@tonkeeper/uikit/dist/desktop-pages/swap'; import { DesktopTokens } from '@tonkeeper/uikit/dist/desktop-pages/tokens/DesktopTokens'; import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude'; -import { AppContext, WalletStateContext } from '@tonkeeper/uikit/dist/hooks/appContext'; +import { AppContext } from '@tonkeeper/uikit/dist/hooks/appContext'; import { AfterImportAction, AppSdkContext, @@ -55,12 +55,11 @@ import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotif import ImportRouter from '@tonkeeper/uikit/dist/pages/import'; import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/import/Initialize'; 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, useCanPromptTouchId } from '@tonkeeper/uikit/dist/state/password'; import { useProBackupState } from '@tonkeeper/uikit/dist/state/pro'; import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; -import { useActiveWallet } from '@tonkeeper/uikit/dist/state/wallet'; +import { useActiveWalletQuery, useWalletsStateQuery } from '@tonkeeper/uikit/dist/state/wallet'; import { Container, GlobalStyleCss } from '@tonkeeper/uikit/dist/styles/globalStyle'; import { FC, Suspense, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -80,6 +79,7 @@ import { DeepLinkSubscription } from './components/DeepLink'; import { TonConnectSubscription } from './components/TonConnectSubscription'; import { DesktopDns } from '@tonkeeper/uikit/dist/desktop-pages/nft/DesktopDns'; import { DesktopCollectables } from '@tonkeeper/uikit/dist/desktop-pages/nft/DesktopCollectables'; +import { useUserLanguage } from '@tonkeeper/uikit/dist/state/language'; const queryClient = new QueryClient({ defaultOptions: { @@ -254,19 +254,19 @@ const FullSizeWrapperBounded = styled(FullSizeWrapper)` `; export const Loader: FC = () => { - const { data: activeWallet } = useActiveWallet(); + const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); + const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); + const { data: lang, isLoading: isLangLoading } = useUserLanguage(); const lock = useLock(sdk); const { i18n } = useTranslation(); - const { data: account } = useAccountState(); - const { data: auth } = useAuthState(); const { data: fiat } = useUserFiat(); const tonendpoint = useTonendpoint({ targetEnv: TARGET_ENV, build: sdk.version, network: activeWallet?.network, - lang: activeWallet?.lang, + lang, platform: 'desktop' }); const { data: config } = useTonenpointConfig(tonendpoint); @@ -274,27 +274,24 @@ export const Loader: FC = () => { const navigate = useNavigate(); useAppHeight(); - const { data: tracker } = useAnalytics(sdk.version, account, activeWallet); + const { data: tracker } = useAnalytics(sdk.version, activeWallet, wallets); useEffect(() => { - if ( - activeWallet && - activeWallet.lang && - i18n.language !== localizationText(activeWallet.lang) - ) { - i18n.reloadResources([localizationText(activeWallet.lang)]).then(() => - i18n.changeLanguage(localizationText(activeWallet.lang)) + if (lang && i18n.language !== localizationText(lang)) { + i18n.reloadResources([localizationText(lang)]).then(() => + i18n.changeLanguage(localizationText(lang)) ); } - }, [activeWallet, i18n]); + }, [lang, i18n]); useEffect(() => { window.backgroundApi.onRefresh(() => queryClient.invalidateQueries()); }, []); if ( - auth === undefined || - account === undefined || + activeWalletLoading || + isLangLoading || + isWalletsLoading || config === undefined || lock === undefined || fiat === undefined @@ -305,9 +302,7 @@ export const Loader: FC = () => { const network = activeWallet?.network ?? Network.MAINNET; const context = { api: getApiConfig(config, network, REACT_APP_TONCONSOLE_API), - auth, fiat, - account, config, tonendpoint, standalone: true, @@ -378,24 +373,19 @@ export const Content: FC<{ } return ( - - - - - - } /> - } /> - } /> - } - /> - } /> - - - - - + + + + + } /> + } /> + } /> + } /> + } /> + + + + ); }; diff --git a/apps/desktop/src/electron/sseEvetns.ts b/apps/desktop/src/electron/sseEvetns.ts index 83b2525d6..3549d04f2 100644 --- a/apps/desktop/src/electron/sseEvetns.ts +++ b/apps/desktop/src/electron/sseEvetns.ts @@ -1,5 +1,4 @@ import { TonConnectAppRequest } from '@tonkeeper/core/dist/entries/tonConnect'; -import { accountSelectWallet, getAccountState } from '@tonkeeper/core/dist/service/accountService'; import { replyBadRequestResponse, replyDisconnectResponse @@ -13,20 +12,21 @@ import { getLastEventId, subscribeTonConnect } from '@tonkeeper/core/dist/service/tonConnect/httpBridge'; -import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService'; +import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; import { delay } from '@tonkeeper/core/dist/utils/common'; import { Buffer as BufferPolyfill } from 'buffer'; import log from 'electron-log/main'; import EventSourcePolyfill from 'eventsource'; import { MainWindow } from './mainWindow'; import { mainStorage } from './storageService'; +import { isStandardTonWallet, WalletId } from '@tonkeeper/core/dist/entries/wallet'; globalThis.Buffer = BufferPolyfill; export class TonConnectSSE { private lastEventId: string; private connections: AccountConnection[]; - private dist: Record; + private dist: Record; private closeConnection: () => void | null = null; private static instance: TonConnectSSE = null; @@ -48,18 +48,19 @@ export class TonConnectSSE { public async init() { this.lastEventId = await getLastEventId(mainStorage); - const account = await getAccountState(mainStorage); + const walletsState = (await walletsStorage(mainStorage).getWallets()).filter( + isStandardTonWallet + ); this.connections = []; this.dist = {}; - for (const key of account.publicKeys) { - const wallet = await getWalletState(mainStorage, key); + for (const wallet of walletsState) { const walletConnections = await getAccountConnection(mainStorage, wallet); this.connections = this.connections.concat(walletConnections); walletConnections.forEach(item => { - this.dist[item.clientSessionId] = key; + this.dist[item.clientSessionId] = wallet.id; }); } } @@ -78,7 +79,14 @@ export class TonConnectSSE { }; private onDisconnect = async ({ connection, request }: TonConnectAppRequest) => { - const wallet = await getWalletState(mainStorage, this.dist[connection.clientSessionId]); + const wallet = await walletsStorage(mainStorage).getWallet( + this.dist[connection.clientSessionId] + ); + + if (!wallet || !isStandardTonWallet(wallet)) { + return; + } + await disconnectAppConnection({ storage: mainStorage, wallet, @@ -101,14 +109,14 @@ export class TonConnectSSE { payload: JSON.parse(params.request.params[0]) }; - const walletPublicKey = this.dist[params.connection.clientSessionId]; + const walletId = this.dist[params.connection.clientSessionId]; - const account = await getAccountState(mainStorage); + const activeWalletId = await walletsStorage(mainStorage).getActiveWalletId(); const window = await MainWindow.bringToFront(); - if (account.activePublicKey !== walletPublicKey) { - await accountSelectWallet(mainStorage, walletPublicKey); + if (activeWalletId !== walletId) { + await walletsStorage(mainStorage).setActiveWalletId(walletId); window.webContents.send('refresh'); await delay(500); } diff --git a/apps/desktop/src/libs/aptabaseElectron.ts b/apps/desktop/src/libs/aptabaseElectron.ts index 0b146c281..7ee616e87 100644 --- a/apps/desktop/src/libs/aptabaseElectron.ts +++ b/apps/desktop/src/libs/aptabaseElectron.ts @@ -1,6 +1,7 @@ import { trackEvent } from '@aptabase/electron/renderer'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { Analytics } from '@tonkeeper/uikit/dist/hooks/analytics'; +import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; export class AptabaseElectron implements Analytics { private user_properties: Record = {}; @@ -8,16 +9,16 @@ export class AptabaseElectron implements Analytics { init = ( application: string, walletType: string, - account?: any, - wallet?: any, + activeWallet?: WalletState, + wallets?: WalletsState, 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; + activeWallet?.network === Network.TESTNET ? 'testnet' : 'mainnet'; + this.user_properties['accounts'] = wallets?.length ?? 0; this.user_properties['version'] = version; this.user_properties['platform'] = platform; }; diff --git a/apps/desktop/src/libs/hooks.ts b/apps/desktop/src/libs/hooks.ts index 4a3f13b4b..00b2e5d11 100644 --- a/apps/desktop/src/libs/hooks.ts +++ b/apps/desktop/src/libs/hooks.ts @@ -1,7 +1,11 @@ import { useQuery } from '@tanstack/react-query'; import { AppKey } from '@tonkeeper/core/dist/Keys'; -import { AccountState } from '@tonkeeper/core/dist/entries/account'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { DeprecatedAccountState } from '@tonkeeper/core/dist/entries/account'; +import { + DeprecatedWalletState, + WalletsState, + 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'; @@ -50,8 +54,8 @@ declare const REACT_APP_AMPLITUDE: string; export const useAnalytics = ( version: string, - account?: AccountState, - wallet?: WalletState | null + activeWallet?: WalletState, + wallets?: WalletsState ) => { const sdk = useAppSdk(); @@ -77,15 +81,15 @@ export const useAnalytics = ( tracker.init( 'Desktop', - toWalletType(wallet), - account, - wallet, + toWalletType(activeWallet), + activeWallet, + wallets, version, `${window.backgroundApi.platform()}-${window.backgroundApi.arch()}` ); return tracker; }, - { enabled: account != null } + { enabled: wallets != null && activeWallet !== undefined } ); }; diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 6348fd4ad..3ad1117de 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { localizationFrom } from '@tonkeeper/core/dist/entries/language'; import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { DeprecatedWalletState } 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'; @@ -203,8 +203,6 @@ export const Loader: FC = React.memo(() => { const context: IAppContext = { api: getApiConfig(config, network), - account, - auth, fiat, config, tonendpoint, @@ -246,7 +244,7 @@ const InitialRedirect: FC = ({ children }) => { }; export const Content: FC<{ - activeWallet?: WalletState | null; + activeWallet?: DeprecatedWalletState | null; lock: boolean; }> = ({ activeWallet, lock }) => { const location = useLocation(); diff --git a/apps/extension/src/libs/hooks.ts b/apps/extension/src/libs/hooks.ts index 48ec904fd..d034b06d2 100644 --- a/apps/extension/src/libs/hooks.ts +++ b/apps/extension/src/libs/hooks.ts @@ -1,8 +1,8 @@ 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 { DeprecatedAccountState } from '@tonkeeper/core/dist/entries/account'; +import { DeprecatedWalletState, WalletsState, 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'; @@ -34,8 +34,8 @@ export const useAppWidth = () => { export const useAnalytics = ( storage: IStorage, - account?: AccountState, - wallet?: WalletState | null, + activeWallet?: WalletState, + wallets?: WalletsState, version?: string ) => { return useQuery( @@ -59,10 +59,12 @@ export const useAnalytics = ( new Amplitude(process.env.REACT_APP_AMPLITUDE!, userId) ); - tracker.init(extensionType ?? 'Extension', toWalletType(wallet), account, wallet); + tracker.init(extensionType ?? 'Extension', toWalletType(activeWallet), + activeWallet, + wallets,); return tracker; }, - { enabled: account != null } + { enabled: wallets != null && activeWallet !== undefined } ); }; diff --git a/apps/extension/src/libs/service/dApp/tonConnectService.ts b/apps/extension/src/libs/service/dApp/tonConnectService.ts index ad9876246..565ddb3d6 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 { walletsStorage } from "@tonkeeper/core/dist/service/walletsService"; const storage = new ExtensionStorage(); @@ -106,7 +106,7 @@ export const tonConnectTransaction = async ( ); } - await accountSelectWallet(storage, connection.wallet.publicKey); + await walletsStorage(storage).setActiveWalletId(connection.wallet.active.rawAddress); 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/twa/src/App.tsx b/apps/twa/src/App.tsx index a7dea9845..d09447c6b 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { DeprecatedWalletState } 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'; @@ -240,9 +240,7 @@ export const Loader: FC<{ sdk: IAppSdk }> = ({ sdk }) => { const network = activeWallet?.network ?? Network.MAINNET; const context: IAppContext = { api: getApiConfig(config, network), - auth, fiat, - account, config, tonendpoint, standalone: false, @@ -313,7 +311,7 @@ const InitPages = () => { }; const Content: FC<{ - activeWallet?: WalletState | null; + activeWallet?: DeprecatedWalletState | null; lock: boolean; showQrScan: boolean; }> = ({ activeWallet, lock, showQrScan }) => { diff --git a/apps/twa/src/libs/hooks.ts b/apps/twa/src/libs/hooks.ts index a858f9fd2..ee6d04db2 100644 --- a/apps/twa/src/libs/hooks.ts +++ b/apps/twa/src/libs/hooks.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { AccountState } from '@tonkeeper/core/dist/entries/account'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { DeprecatedAccountState } from '@tonkeeper/core/dist/entries/account'; +import { DeprecatedWalletState } from '@tonkeeper/core/dist/entries/wallet'; import { Analytics, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics'; import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag'; import { useAppSdk } from '@tonkeeper/uikit/dist/hooks/appSdk'; @@ -71,7 +71,7 @@ export const useTwaAppViewport = (setAppHeight: boolean) => { }, [sdk, viewport]); }; -export const useAnalytics = (account?: AccountState, wallet?: WalletState | null) => { +export const useAnalytics = (account?: DeprecatedAccountState, wallet?: DeprecatedWalletState | null) => { return useQuery( [QueryKey.analytics], async () => { diff --git a/apps/twa/src/libs/twaNotification.ts b/apps/twa/src/libs/twaNotification.ts index 3f77efa3c..87ddee2d8 100644 --- a/apps/twa/src/libs/twaNotification.ts +++ b/apps/twa/src/libs/twaNotification.ts @@ -1,5 +1,5 @@ import { NotificationService } from '@tonkeeper/core/dist/AppSdk'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { StandardTonWalletState } from "@tonkeeper/core/dist/entries/wallet"; import { toTonProofItem, tonConnectProofPayload @@ -7,6 +7,7 @@ import { import { walletStateInitFromState } from '@tonkeeper/core/dist/service/wallet/contractService'; import { InitResult } from '@twa.js/sdk'; import { Configuration, DefaultApi } from '../twaApi'; +import { getServerTime } from "@tonkeeper/core/dist/service/transfer/common"; const apiConfig = new Configuration({ basePath: 'https://twa-api.tonkeeper.com' }); const twaApi = new DefaultApi(apiConfig); @@ -22,15 +23,16 @@ export class TwaNotification implements NotificationService { return Buffer.from(initDataRaw, 'utf8').toString('base64'); } - private getTonConnectProof = async (wallet: WalletState, mnemonic: string[]) => { + private getTonConnectProof = async (wallet: StandardTonWalletState, mnemonic: string[]) => { const domain = 'https://twa.tonkeeper.com/'; const { payload } = await twaApi.getTonConnectPayload(); - const proofPayload = tonConnectProofPayload(domain, wallet.active.rawAddress, payload); + const timestamp = await getServerTime(api); + const proofPayload = tonConnectProofPayload(Date.now(), domain, wallet.rawAddress, payload); const stateInit = walletStateInitFromState(wallet); return await toTonProofItem(mnemonic, proofPayload, stateInit); }; - subscribe = async (wallet: WalletState, mnemonic: string[]) => { + subscribe = async (wallet: StandardTonWalletState, mnemonic: string[]) => { try { await this.components.webApp.requestWriteAccess(); } catch (e) { @@ -41,7 +43,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..129e8565b 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 { DeprecatedWalletState } 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'; @@ -259,7 +259,7 @@ export const Loader: FC = () => { }; export const Content: FC<{ - activeWallet?: WalletState | null; + activeWallet?: DeprecatedWalletState | null; lock: boolean; standalone: boolean; }> = ({ activeWallet, lock, standalone }) => { diff --git a/apps/web/src/libs/hooks.ts b/apps/web/src/libs/hooks.ts index 580b290f3..f31df03dd 100644 --- a/apps/web/src/libs/hooks.ts +++ b/apps/web/src/libs/hooks.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { AccountState } from '@tonkeeper/core/dist/entries/account'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { DeprecatedAccountState } from '@tonkeeper/core/dist/entries/account'; +import { DeprecatedWalletState, WalletsState, 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 { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web'; @@ -49,8 +49,8 @@ export const useAppWidth = (standalone: boolean) => { }; export const useAnalytics = ( - account?: AccountState, - wallet?: WalletState | null, + activeWallet?: WalletState, + wallets?: WalletsState, version?: string ) => { return useQuery( @@ -65,10 +65,12 @@ export const useAnalytics = ( new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID) ); - tracker.init('Web', toWalletType(wallet), account, wallet); + tracker.init('Web', toWalletType(activeWallet), + activeWallet, + wallets,); return tracker; }, - { enabled: account != null } + { enabled: wallets != null && activeWallet !== undefined } ); }; diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 44596b9e4..433813b21 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -2,20 +2,14 @@ import { IStorage, MemoryStorage } from './Storage'; 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 { StandardTonWalletState } from './entries/wallet'; import { TonTransferParams } from './service/deeplinkingService'; import { KeystoneMessageType, KeystonePathInfo } from './service/keystone/types'; import { LedgerTransaction } from './service/ledger/connector'; export type GetPasswordType = 'confirm' | 'unlock'; -export type GetPasswordParams = { - auth: AuthState; - type?: GetPasswordType; -}; - export type TransferInitParams = { transfer?: TonTransferParams; asset?: string; @@ -37,10 +31,10 @@ 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}; + keystone: { message: Buffer; messageType: KeystoneMessageType; pathInfo?: KeystonePathInfo }; loading: void; transfer: TransferInitParams; receive: ReceiveInitParams; @@ -71,7 +65,7 @@ export interface TouchId { } export interface NotificationService { - subscribe: (wallet: WalletState, mnemonic: string[]) => Promise; + subscribe: (wallet: StandardTonWalletState, mnemonic: string[]) => Promise; unsubscribe: (address?: string) => Promise; subscribeTonConnect: (clientId: string, origin: string) => Promise; diff --git a/packages/core/src/Keys.ts b/packages/core/src/Keys.ts index d168a724c..baccd4e09 100644 --- a/packages/core/src/Keys.ts +++ b/packages/core/src/Keys.ts @@ -1,15 +1,18 @@ export enum AppKey { - ACCOUNT = 'account', - WALLET = 'wallet', + DEPRECATED_ACCOUNT = 'account', + DEPRECATED_WALLET = 'wallet', + WALLETS = 'wallets', + ACTIVE_WALLET_ID = 'active_wallet_id', WALLET_CONFIG = 'wallet_config', - MNEMONIC = 'mnemonic', + DEPRECATED_MNEMONIC = 'mnemonic', THEME = 'theme', UI_PREFERENCES = 'ui_preferences', MULTI_SEND_LISTS = 'multi_send_lists', FIAT = 'fiat', + LANGUAGE = 'language', - 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..74134325a 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -1,8 +1,11 @@ -export interface AccountState { +/** + * @deprecated + */ +export interface DeprecatedAccountState { publicKeys: string[]; activePublicKey?: string; } -export const defaultAccountState: AccountState = { +export const defaultAccountState: DeprecatedAccountState = { publicKeys: [] }; diff --git a/packages/core/src/entries/password.ts b/packages/core/src/entries/password.ts index f8fce908f..e08b13796 100644 --- a/packages/core/src/entries/password.ts +++ b/packages/core/src/entries/password.ts @@ -1,10 +1,9 @@ -import { KeystonePathInfo } from "../service/keystone/types"; +import { KeystonePathInfo } from '../service/keystone/types'; export type AuthState = - | AuthNone | AuthPassword | WebAuthn - | KeychainPassword + | AuthKeychain | AuthSigner | AuthSignerDeepLink | AuthLedger @@ -16,9 +15,10 @@ export interface AuthNone { export interface AuthPassword { kind: 'password'; + encryptedMnemonic: string; } -export interface KeychainPassword { +export interface AuthKeychain { kind: 'keychain'; } @@ -27,7 +27,7 @@ export interface AuthSigner { } export interface AuthSignerDeepLink { - kind: 'signer-deeplink'; + kind: 'signer-deeplink'; } export interface AuthLedger { @@ -47,4 +47,25 @@ export interface WebAuthn { transports?: AuthenticatorTransport[]; } -export const defaultAuthState: AuthState = { kind: 'none' }; +export const defaultAuthState: DeprecatedAuthState = { kind: 'none' }; + +/** + * @deprecated + */ +export type DeprecatedAuthState = + | AuthNone + | DeprecatedAuthPassword + | WebAuthn + | DeprecatedKeychainPassword + | AuthSigner + | AuthSignerDeepLink + | AuthLedger + | AuthKeystone; + +export interface DeprecatedAuthPassword { + kind: 'password'; +} + +export interface DeprecatedKeychainPassword { + kind: 'keychain'; +} diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index 8830246f8..5adb3e9c5 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -1,7 +1,8 @@ import { Language } from './language'; import { Network } from './network'; -import { AuthState } from './password'; +import { AuthKeychain, AuthPassword, AuthState, DeprecatedAuthState } from './password'; import { WalletProxy } from './proxy'; +import { BLOCKCHAIN_NAME } from './crypto'; export enum WalletVersion { V3R1 = 0, @@ -29,21 +30,25 @@ export const walletVersionText = (version: WalletVersion) => { }; export const walletVersionFromText = (value: string) => { - switch (value) { - case 'v3R1': + switch (value.toUpperCase()) { + case 'V3R1': return WalletVersion.V3R1; - case 'v3R2': + case 'V3R2': return WalletVersion.V3R2; - case 'v4R2': + case 'V4R2': return WalletVersion.V4R2; case 'W5': + case 'W5R1': return WalletVersion.W5; default: throw new Error('Unsupported version'); } }; -export interface WalletAddress { +/** + * @deprecated + */ +export interface DeprecatedWalletAddress { friendlyAddress: string; rawAddress: string; version: WalletVersion; @@ -56,10 +61,13 @@ export interface WalletVoucher { voucher: string; } -export interface WalletState { +/** + * @deprecated + */ +export interface DeprecatedWalletState { publicKey: string; - active: WalletAddress; - auth?: AuthState; + active: DeprecatedWalletAddress; + auth?: DeprecatedAuthState; name?: string; emoji: string; @@ -80,6 +88,62 @@ export interface WalletState { tron?: TronWalletStorage; } +export type WalletId = string; + +export interface WalletBasic { + blockchain: BLOCKCHAIN_NAME; + id: WalletId; +} + +export interface TonWalletStateBasic extends WalletBasic { + blockchain: BLOCKCHAIN_NAME.TON; + rawAddress: string; + name: string; + emoji: string; + network: Network; +} + +export interface StandardTonWalletState extends TonWalletStateBasic { + type: 'standard'; + publicKey: string; + version: WalletVersion; + auth: AuthState; +} + +export interface MultisigTonWalletState extends TonWalletStateBasic { + type: 'multisig'; +} + +export type TonWalletState = StandardTonWalletState | MultisigTonWalletState; + +export type WalletState = TonWalletState; +export type WalletsState = WalletState[]; + +export const defaultWalletsState = []; + +export function isTonWallet(state: WalletState): state is TonWalletState { + return state.blockchain === BLOCKCHAIN_NAME.TON; +} + +export function isStandardTonWallet(state: WalletState): state is StandardTonWalletState { + return state.blockchain === BLOCKCHAIN_NAME.TON && state.type === 'standard'; +} + +export function isPasswordAuthWallet( + state: WalletState +): state is WalletState & { auth: AuthPassword } { + return isStandardTonWallet(state) && state.auth.kind === 'password'; +} + +export function isMnemonicAuthWallet( + state: WalletState +): state is WalletState & { auth: AuthPassword | AuthKeychain } { + return ( + isStandardTonWallet(state) && + (state.auth.kind === 'password' || state.auth.kind === 'keychain') + ); +} + export interface ActiveWalletConfig { pinnedTokens: string[]; hiddenTokens: string[]; 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/ledger/transfer.ts b/packages/core/src/service/ledger/transfer.ts index 66d10015a..70ce925be 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 { StandardTonWalletState } from '../../entries/wallet'; import { TonRecipientData } from '../../entries/send'; import BigNumber from 'bignumber.js'; import { @@ -21,7 +21,7 @@ import { LedgerSigner } from '../../entries/signer'; export const createLedgerTonTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, weiAmount: BigNumber, isMax: boolean, @@ -48,7 +48,7 @@ export const createLedgerTonTransfer = async ( export const createLedgerJettonTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipientAddress: string, amount: AssetAmount, jettonWalletAddress: string, @@ -71,7 +71,7 @@ export const createLedgerJettonTransfer = async ( queryId: getTonkeeperQueryId(), amount: jettonAmount, destination: Address.parse(recipientAddress), - responseDestination: Address.parse(walletState.active.rawAddress), + responseDestination: Address.parse(walletState.rawAddress), forwardAmount: jettonTransferForwardAmount, forwardPayload, customPayload: null @@ -84,7 +84,7 @@ export const createLedgerJettonTransfer = async ( export const createLedgerNftTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipientAddress: string, nftAddress: string, nftTransferAmount: bigint, @@ -105,7 +105,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..75db4fe0f 100644 --- a/packages/core/src/service/mnemonicService.ts +++ b/packages/core/src/service/mnemonicService.ts @@ -1,14 +1,13 @@ import { mnemonicValidate } from '@ton/crypto'; -import { AppKey } from '../Keys'; -import { IStorage } from '../Storage'; import { decrypt } from './cryptoService'; +import { WalletState } from '../entries/wallet'; +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: WalletState & { 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 +15,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..1309de9bc --- /dev/null +++ b/packages/core/src/service/passwordService.ts @@ -0,0 +1,73 @@ +import { IStorage } from '../Storage'; +import { WalletsStorage } from './walletsService'; +import { isPasswordAuthWallet } from '../entries/wallet'; +import { decrypt, encrypt } from './cryptoService'; +import { mnemonicValidate } from '@ton/crypto'; +import { decryptWalletMnemonic } from './mnemonicService'; + +export class PasswordStorage { + private readonly walletStorage: WalletsStorage; + + constructor(storage: IStorage) { + this.walletStorage = new WalletsStorage(storage); + } + + async getIsPasswordSet() { + const wallets = await this.getPasswordAuthWallets(); + return wallets.length > 0; + } + + async isPasswordValid(password: string): Promise { + try { + const walletToCheck = (await this.getPasswordAuthWallets())[0]; + if (!walletToCheck) { + throw new Error('None wallet has a password auth'); + } + + const mnemonic = (await decrypt(walletToCheck.auth.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 wallets = await this.getPasswordAuthWallets(); + + const updatedWallets = await Promise.all( + wallets.map(async wallet => { + const mnemonic = await decryptWalletMnemonic(wallet, oldPassword); + const newEncrypted = await encrypt(mnemonic.join(' '), newPassword); + return { + ...wallet, + auth: { ...wallet.auth, encryptedMnemonic: newEncrypted } + }; + }) + ); + + await this.walletStorage.updateWalletsInState(updatedWallets); + } + + private async getPasswordAuthWallets() { + const wallets = await this.walletStorage.getWallets(); + return wallets.filter(isPasswordAuthWallet); + } +} + +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..bc26afc60 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -11,7 +11,11 @@ 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, + StandardTonWalletState, + walletVersionFromText +} from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; import { FiatCurrencies as FiatCurrenciesGenerated, @@ -31,7 +35,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 { walletsStorage } from './walletsService'; export const setBackupState = async (storage: IStorage, state: ProSubscription) => { await storage.set(AppKey.PRO_BACKUP, state); @@ -42,7 +46,10 @@ 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: StandardTonWalletState +): Promise => { try { return await loadProState(storage, wallet); } catch (e) { @@ -51,7 +58,7 @@ export const getProState = async (storage: IStorage, wallet: WalletState): Promi hasWalletAuthCookie: false, wallet: { publicKey: wallet.publicKey, - rawAddress: wallet.active.rawAddress + rawAddress: wallet.rawAddress } }; } @@ -67,22 +74,30 @@ const toEmptySubscription = (): ProSubscriptionInvalid => { export const loadProState = async ( storage: IStorage, - fallbackWallet: WalletState + fallbackWallet: StandardTonWalletState ): 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 walletsStorage(storage).getWallets(); + const actualWallet = wallets + .filter(isStandardTonWallet) + .find( + w => + w.publicKey === user.pub_key && + user.version && + w.version === walletVersionFromText(user.version) + ); if (!actualWallet) { throw new Error('Unknown wallet'); } wallet = { publicKey: actualWallet.publicKey, - rawAddress: actualWallet.active.rawAddress + rawAddress: actualWallet.rawAddress }; } @@ -131,19 +146,14 @@ export const checkAuthCookie = async () => { export const authViaTonConnect = async ( api: APIConfig, - wallet: WalletState, + wallet: StandardTonWalletState, 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 +162,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 34599ca46..23123d748 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 { StandardTonWalletState, WalletVersion } from '../entries/wallet'; import { BlockchainApi } from '../tonApiV2'; import { externalMessage, getWalletSeqNo } from './transfer/common'; import { walletContractFromState } from './wallet/contractService'; @@ -64,7 +64,7 @@ export const storeTransactionAndCreateDeepLink = async ( export const publishSignerMessage = async ( sdk: IAppSdk, api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, signatureHex: string ) => { const messageBase64 = await sdk.storage.get(AppKey.SIGNER_MESSAGE); @@ -72,12 +72,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).asBuilder(); const transfer = beginCell(); - if (walletState.active.version === WalletVersion.W5) { + if (walletState.version === WalletVersion.W5) { transfer.storeBuilder(message).storeBuffer(signature); } else { transfer.storeBuffer(signature).storeBuilder(message); diff --git a/packages/core/src/service/suggestionService.ts b/packages/core/src/service/suggestionService.ts index 13bee652e..ca743ac60 100644 --- a/packages/core/src/service/suggestionService.ts +++ b/packages/core/src/service/suggestionService.ts @@ -2,7 +2,7 @@ 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 { DeprecatedWalletState, StandardTonWalletState, TonWalletState } from '../entries/wallet'; import { AppKey } from '../Keys'; import { IStorage } from '../Storage'; import { AccountsApi } from '../tonApiV2'; @@ -44,7 +44,7 @@ export const deleteFavoriteSuggestion = async ( const getTronSuggestionsList = async ( api: APIConfig, - wallet: WalletState, + wallet: DeprecatedWalletState, seeIfAddressIsAdded: (list: LatestSuggestion[], address: string) => boolean ) => { const list = [] as LatestSuggestion[]; @@ -74,13 +74,13 @@ const getTronSuggestionsList = async ( const getTonSuggestionsList = async ( api: APIConfig, - wallet: WalletState, + wallet: TonWalletState, 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 +90,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 +112,7 @@ const getTonSuggestionsList = async ( export const getSuggestionsList = async ( sdk: IAppSdk, api: APIConfig, - wallet: WalletState, + wallet: StandardTonWalletState, acceptBlockchains: BLOCKCHAIN_NAME[] = [BLOCKCHAIN_NAME.TON, BLOCKCHAIN_NAME.TRON] ) => { const favorites = await getFavoriteSuggestions(sdk.storage, wallet.publicKey); @@ -129,8 +129,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 33e4f7171..3454b7f8c 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -20,9 +20,8 @@ import { TonConnectAccount, TonProofItemReplySuccess } from '../../entries/tonConnect'; -import { WalletState } from '../../entries/wallet'; +import { isStandardTonWallet, StandardTonWalletState } from '../../entries/wallet'; import { walletContractFromState } from '../wallet/contractService'; -import { getCurrentWallet, getWalletState } from '../wallet/storeService'; import { TonConnectParams, disconnectAccountConnection, @@ -31,8 +30,7 @@ import { AccountConnection } from './connectionService'; import { SessionCrypto } from './protocol'; -import { AccountState } from '../../entries/account'; -import { AppKey } from '../../Keys'; +import { walletsStorage } from '../walletsService'; export function parseTonConnect(options: { url: string }): TonConnectParams | string { try { @@ -101,7 +99,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 +169,10 @@ export const getDappConnection = async ( storage: IStorage, origin: string, account?: TonConnectAccount -): Promise<{ wallet: WalletState; connection: AccountConnection } | undefined> => { +): Promise<{ wallet: StandardTonWalletState; 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,20 +194,15 @@ 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: StandardTonWalletState; connections: AccountConnection[] }[]> => { + const wallets = (await walletsStorage(storage).getWallets()).filter(isStandardTonWallet); + if (!wallets.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); @@ -222,7 +213,7 @@ export const getAppConnections = async ( export const checkWalletConnectionOrDie = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; webViewUrl: string; }) => { const connections = await getAccountConnection(options.storage, options.wallet); @@ -252,7 +243,7 @@ export const tonReConnectRequest = async ( return [toTonAddressItemReply(connection.wallet)]; }; -export const toTonAddressItemReply = (wallet: WalletState) => { +export const toTonAddressItemReply = (wallet: StandardTonWalletState) => { const contract = walletContractFromState(wallet); const result: TonAddressItemReply = { name: 'ton_addr', @@ -326,7 +317,7 @@ export const tonConnectProofPayload = ( export const toTonProofItemReply = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; signTonConnect: (bufferToSign: Buffer) => Promise; proof: ConnectProofPayload; }): Promise => { @@ -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); @@ -369,13 +360,16 @@ export const toTonProofItem = async ( }; export const tonDisconnectRequest = async (options: { storage: IStorage; webViewUrl: string }) => { - const wallet = await getCurrentWallet(options.storage); - await disconnectAccountConnection({ ...options, wallet }); + const wallet = await walletsStorage(options.storage).getActiveWallet(); + + if (wallet && isStandardTonWallet(wallet)) { + await disconnectAccountConnection({ ...options, wallet }); + } }; export const saveWalletTonConnect = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; 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..c76910de0 100644 --- a/packages/core/src/service/tonConnect/connectionService.ts +++ b/packages/core/src/service/tonConnect/connectionService.ts @@ -1,6 +1,6 @@ import { Network } from '../../entries/network'; import { ConnectRequest, DAppManifest, KeyPair } from '../../entries/tonConnect'; -import { WalletState } from '../../entries/wallet'; +import { DeprecatedWalletState, StandardTonWalletState } from '../../entries/wallet'; import { AppKey } from '../../Keys'; import { IStorage } from '../../Storage'; @@ -20,7 +20,7 @@ export interface AccountConnection { export const getAccountConnection = async ( storage: IStorage, - wallet: Pick + wallet: Pick ) => { const result = await storage.get( `${AppKey.CONNECTIONS}_${wallet.publicKey}_${wallet.network ?? Network.MAINNET}` @@ -30,7 +30,7 @@ export const getAccountConnection = async ( export const setAccountConnection = async ( storage: IStorage, - wallet: Pick, + wallet: Pick, // TODO migrate items: AccountConnection[] ) => { await storage.set( @@ -41,7 +41,7 @@ export const setAccountConnection = async ( export const saveAccountConnection = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; manifest: DAppManifest; params: TonConnectParams; webViewUrl?: string; @@ -68,7 +68,7 @@ export const saveAccountConnection = async (options: { */ export const disconnectAccountConnection = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; webViewUrl: string; }) => { let connections = await getAccountConnection(options.storage, options.wallet); @@ -83,7 +83,7 @@ export const disconnectAccountConnection = async (options: { */ export const disconnectAppConnection = async (options: { storage: IStorage; - wallet: WalletState; + wallet: StandardTonWalletState; clientSessionId: string; }) => { let connections = await getAccountConnection(options.storage, options.wallet); diff --git a/packages/core/src/service/transfer/common.ts b/packages/core/src/service/transfer/common.ts index 91e509695..8d0832b6e 100644 --- a/packages/core/src/service/transfer/common.ts +++ b/packages/core/src/service/transfer/common.ts @@ -18,7 +18,7 @@ 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 { StandardTonWalletState } from '../../entries/wallet'; import { Account, AccountsApi, LiteServerApi, WalletApi } from '../../tonApiV2'; import { walletContractFromState } from '../wallet/contractService'; import { NotEnoughBalanceError } from '../../errors/NotEnoughBalanceError'; @@ -84,11 +84,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: StandardTonWalletState) => { 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; }; @@ -105,7 +105,7 @@ export const seeIfTimeError = (e: unknown): e is Error => { export const createTransferMessage = async ( wallet: { seqno: number; - state: WalletState; + state: StandardTonWalletState; signer: BaseSigner; timestamp: number; }, @@ -144,7 +144,7 @@ signEstimateMessage.type = 'cell' as const; export async function getKeyPairAndSeqno(options: { api: APIConfig; - walletState: WalletState; + walletState: StandardTonWalletState; fee: TransferEstimationEvent; amount: BigNumber; }) { diff --git a/packages/core/src/service/transfer/jettonService.ts b/packages/core/src/service/transfer/jettonService.ts index 933a34e66..a3aa82a69 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 { StandardTonWalletState } from '../../entries/wallet'; import { BlockchainApi, EmulationApi } from '../../tonApiV2'; import { createLedgerJettonTransfer } from '../ledger/transfer'; import { walletContractFromState } from '../wallet/contractService'; @@ -47,7 +47,7 @@ export const jettonTransferBody = (params: { const createJettonTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipientAddress: string, amount: AssetAmount, jettonWalletAddress: string, @@ -60,7 +60,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 +86,7 @@ const createJettonTransfer = async ( export const estimateJettonTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, amount: AssetAmount, jettonWalletAddress: string @@ -117,7 +117,7 @@ export const estimateJettonTransfer = async ( export const sendJettonTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, amount: AssetAmount, jettonWalletAddress: string, diff --git a/packages/core/src/service/transfer/multiSendService.ts b/packages/core/src/service/transfer/multiSendService.ts index 2f104ca75..439161f82 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 { StandardTonWalletState, WalletVersion } from '../../entries/wallet'; import { BlockchainApi, EmulationApi } from '../../tonApiV2'; import { unShiftedDecimals } from '../../utils/balance'; import { walletContractFromState } from '../wallet/contractService'; @@ -50,7 +50,7 @@ const checkMaxAllowedMessagesInMultiTransferOrDie = ( export const estimateTonMultiTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, transferMessages: TransferMessage[] ) => { const timestamp = await getServerTime(api); @@ -59,10 +59,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, @@ -83,7 +80,7 @@ export const estimateTonMultiTransfer = async ( export const sendTonMultiTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, transferMessages: TransferMessage[], feeEstimate: BigNumber, signer: CellSigner @@ -94,10 +91,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, @@ -117,7 +111,7 @@ export const sendTonMultiTransfer = async ( const createTonMultiTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, transferMessages: TransferMessage[], signer: CellSigner ) => { @@ -143,7 +137,7 @@ const createTonMultiTransfer = async ( export const estimateJettonMultiTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, jettonWalletAddress: string, transferMessages: TransferMessage[] ) => { @@ -155,10 +149,7 @@ export const estimateJettonMultiTransfer = async ( wallet ); - checkMaxAllowedMessagesInMultiTransferOrDie( - transferMessages.length, - walletState.active.version - ); + checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version); const cell = await createJettonMultiTransfer( timestamp, @@ -181,7 +172,7 @@ export const estimateJettonMultiTransfer = async ( export const sendJettonMultiTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, jettonWalletAddress: string, transferMessages: TransferMessage[], feeEstimate: BigNumber, @@ -191,10 +182,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); @@ -242,7 +230,7 @@ export const sendJettonMultiTransfer = async ( const createJettonMultiTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, jettonWalletAddress: string, transferMessages: TransferMessage[], attachValue: BigNumber, @@ -264,7 +252,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 }) @@ -284,7 +272,7 @@ export type NftTransferMessage = { export const createNftMultiTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, transferMessages: NftTransferMessage[], attachValue: BigNumber, signer: CellSigner @@ -304,7 +292,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 }) @@ -317,7 +305,7 @@ export const createNftMultiTransfer = async ( export const sendNftMultiTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, transferMessages: NftTransferMessage[], feeEstimate: BigNumber, signer: CellSigner @@ -326,10 +314,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..5c131d836 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 { StandardTonWalletState } from '../../entries/wallet'; import { BlockchainApi, EmulationApi, NftItem } from '../../tonApiV2'; import { createLedgerNftTransfer } from '../ledger/transfer'; import { @@ -68,7 +68,7 @@ const nftLinkBody = (params: { queryId: bigint; linkToAddress: string }) => { const createNftTransfer = ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipientAddress: string, nftAddress: string, nftTransferAmount: bigint, @@ -78,7 +78,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 +91,7 @@ const createNftTransfer = ( export const estimateNftTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, nftItem: NftItem ): Promise => { @@ -120,7 +120,7 @@ export const estimateNftTransfer = async ( export const sendNftTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, nftItem: NftItem, fee: TransferEstimationEvent, @@ -171,7 +171,7 @@ export const sendNftTransfer = async ( export const sendNftRenew = async (options: { api: APIConfig; - walletState: WalletState; + walletState: StandardTonWalletState; nftAddress: string; fee: TransferEstimationEvent; signer: CellSigner; @@ -199,7 +199,7 @@ export const sendNftRenew = async (options: { export const estimateNftRenew = async (options: { api: APIConfig; - walletState: WalletState; + walletState: StandardTonWalletState; nftAddress: string; amount: BigNumber; }) => { @@ -224,7 +224,7 @@ export const estimateNftRenew = async (options: { export const sendNftLink = async (options: { api: APIConfig; - walletState: WalletState; + walletState: StandardTonWalletState; nftAddress: string; linkToAddress: string; fee: TransferEstimationEvent; @@ -253,7 +253,7 @@ export const sendNftLink = async (options: { export const estimateNftLink = async (options: { api: APIConfig; - walletState: WalletState; + walletState: StandardTonWalletState; 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 8ee5c554a..3b15afdd2 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 { StandardTonWalletState } from '../../entries/wallet'; import { AccountsApi, BlockchainApi, EmulationApi } from '../../tonApiV2'; import { createLedgerTonTransfer } from '../ledger/transfer'; import { walletContractFromState } from '../wallet/contractService'; @@ -44,7 +44,7 @@ export const toStateInit = ( const createTonTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, weiAmount: BigNumber, isMax: boolean, @@ -73,7 +73,7 @@ const createTonTransfer = async ( const createTonConnectTransfer = async ( timestamp: number, seqno: number, - walletState: WalletState, + walletState: StandardTonWalletState, params: TonConnectTransactionPayload, signer: CellSigner ) => { @@ -99,7 +99,7 @@ const createTonConnectTransfer = async ( export const estimateTonTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, weiAmount: BigNumber, isMax: boolean @@ -133,11 +133,11 @@ export type ConnectTransferError = { kind: 'not-enough-balance' } | { kind: unde export const tonConnectTransferError = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, params: TonConnectTransactionPayload ): Promise => { const wallet = await new AccountsApi(api.tonApiV2).getAccount({ - accountId: walletState.active.rawAddress + accountId: walletState.rawAddress }); const total = params.messages.reduce( @@ -154,7 +154,7 @@ export const tonConnectTransferError = async ( export const estimateTonConnectTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, params: TonConnectTransactionPayload ): Promise => { const timestamp = await getServerTime(api); @@ -180,12 +180,12 @@ export const estimateTonConnectTransfer = async ( export const sendTonConnectTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, params: TonConnectTransactionPayload, signer: CellSigner ) => { const timestamp = await getServerTime(api); - const seqno = await getWalletSeqNo(api, walletState.active.rawAddress); + const seqno = await getWalletSeqNo(api, walletState.rawAddress); const external = await createTonConnectTransfer(timestamp, seqno, walletState, params, signer); @@ -200,7 +200,7 @@ export const sendTonConnectTransfer = async ( export const sendTonTransfer = async ( api: APIConfig, - walletState: WalletState, + walletState: StandardTonWalletState, recipient: TonRecipientData, amount: AssetAmount, isMax: boolean, diff --git a/packages/core/src/service/tron/tronService.ts b/packages/core/src/service/tron/tronService.ts index 451775d1f..5b07a70b4 100644 --- a/packages/core/src/service/tron/tronService.ts +++ b/packages/core/src/service/tron/tronService.ts @@ -3,9 +3,8 @@ 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 { TronWalletState, TronWalletStorage, DeprecatedWalletState } from '../../entries/wallet'; import { Configuration, TronApi } from '../../tronApi'; -import { setWalletState } from '../wallet/storeService'; import { calculateCreate2 } from './addressCalculation'; import { TronAddress } from './tronUtils'; @@ -32,7 +31,7 @@ const getOwnerAddress = async (mnemonic: string[]): Promise => { export const getTronWallet = async ( tronApi: Configuration, mnemonic: string[], - wallet: WalletState + wallet: DeprecatedWalletState ): Promise => { const ownerWalletAddress = await getOwnerAddress(mnemonic); @@ -52,14 +51,14 @@ 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; }; diff --git a/packages/core/src/service/tron/tronTransferService.ts b/packages/core/src/service/tron/tronTransferService.ts index c2f4afbb6..a2c1c1f7a 100644 --- a/packages/core/src/service/tron/tronTransferService.ts +++ b/packages/core/src/service/tron/tronTransferService.ts @@ -4,7 +4,7 @@ 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 { TronWalletStorage, DeprecatedWalletState } from '../../entries/wallet'; import { Configuration, EstimatePayload, @@ -78,7 +78,7 @@ async function estimateTronFee({ address, amount }: { - wallet: WalletState; + wallet: DeprecatedWalletState; tronApi: Configuration; address: TronRecipient; amount: AssetAmount; @@ -105,7 +105,7 @@ export async function estimateTron({ amount: AssetAmount; isMax: boolean; tronApi: Configuration; - wallet: WalletState; + wallet: DeprecatedWalletState; balances: TronBalances | undefined; }): Promise> { if (isMax) { diff --git a/packages/core/src/service/wallet/contractService.ts b/packages/core/src/service/wallet/contractService.ts index 3b24dd4df..38110b6bf 100644 --- a/packages/core/src/service/wallet/contractService.ts +++ b/packages/core/src/service/wallet/contractService.ts @@ -4,11 +4,11 @@ import { WalletContractV3R2 } from '@ton/ton/dist/wallets/WalletContractV3R2'; import { WalletContractV4 } from '@ton/ton/dist/wallets/WalletContractV4'; import { WalletContractV5 } from '@ton/ton/dist/wallets/WalletContractV5'; import { Network } from '../../entries/network'; -import { WalletState, WalletVersion } from '../../entries/wallet'; +import { StandardTonWalletState, WalletVersion } from '../../entries/wallet'; -export const walletContractFromState = (wallet: WalletState) => { +export const walletContractFromState = (wallet: StandardTonWalletState) => { const publicKey = Buffer.from(wallet.publicKey, 'hex'); - return walletContract(publicKey, wallet.active.version, wallet.network); + return walletContract(publicKey, wallet.version, wallet.network); }; const workchain = 0; @@ -37,7 +37,7 @@ export const walletContract = ( } }; -export const walletStateInitFromState = (wallet: WalletState) => { +export const walletStateInitFromState = (wallet: StandardTonWalletState) => { 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 3ed77d3b2..3f57b7277 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -4,41 +4,51 @@ import { Address } from '@ton/core'; import { mnemonicToPrivateKey } from '@ton/crypto'; import { WalletContractV4 } from '@ton/ton/dist/wallets/WalletContractV4'; 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 { + StandardTonWalletState, + TonWalletState, + WalletState, + WalletVersion, + WalletVersions +} from '../entries/wallet'; import { WalletApi } from '../tonApiV2'; -import { encrypt } from './cryptoService'; import { walletContract } from './wallet/contractService'; -import { getFallbackWalletEmoji, getWalletStateOrDie, setWalletState } from './wallet/storeService'; +import { BLOCKCHAIN_NAME } from '../entries/crypto'; +import { walletsStorage } from './walletsService'; +import { emojis } from '../utils/emojis'; -export const createNewWalletState = async (api: APIConfig, mnemonic: string[], name?: string) => { +export const createNewStandardTonWalletStateFromMnemonic = async ( + api: APIConfig, + mnemonic: string[], + auth: AuthPassword | AuthKeychain, + name?: string +) => { const keyPair = await mnemonicToPrivateKey(mnemonic); const publicKey = keyPair.publicKey.toString('hex'); - const active = await findWalletAddress(api, publicKey); + const address = await findWalletAddress(api, publicKey); - const state: WalletState = { + const state: TonWalletState = { + blockchain: BLOCKCHAIN_NAME.TON, + id: address.rawAddress, + type: 'standard', publicKey, - active, - revision: 0, - name, - emoji: getFallbackWalletEmoji(publicKey) + rawAddress: address.rawAddress, + version: address.version, + name: name || getFallbackWalletName(Address.parse(address.rawAddress).toString()), + emoji: getFallbackWalletEmoji(publicKey), + auth, + network: Network.MAINNET }; - // state.tron = await getTronWallet(api.tronApi, mnemonic, state).catch(() => undefined); - return state; }; -export const encryptWalletMnemonic = async (mnemonic: string[], password: string) => { - return encrypt(mnemonic.join(' '), password); -}; - const versionMap: Record = { wallet_v3r1: WalletVersion.V3R1, wallet_v3r2: WalletVersion.V3R2, @@ -57,7 +67,10 @@ const findWalletVersion = (interfaces?: string[]): WalletVersion => { throw new Error('Unexpected wallet version'); }; -const findWalletAddress = async (api: APIConfig, publicKey: string) => { +const findWalletAddress = async ( + api: APIConfig, + publicKey: string +): Promise<{ rawAddress: string; version: WalletVersion }> => { try { const result = await new WalletApi(api.tonApiV2).getWalletsByPublicKey({ publicKey: publicKey @@ -73,13 +86,10 @@ const findWalletAddress = async (api: APIConfig, publicKey: string) => { .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 @@ -90,27 +100,20 @@ const findWalletAddress = async (api: APIConfig, publicKey: string) => { workchain: 0, publicKey: Buffer.from(publicKey, 'hex') }); - const wallet: WalletAddress = { + return { rawAddress: contact.address.toRawString(), - friendlyAddress: contact.address.toString(), version: WalletVersion.V4R2 }; - - return wallet; }; export const getWalletAddress = ( publicKey: Buffer, version: WalletVersion, network?: Network -): WalletAddress => { +): { address: Address; version: WalletVersion } => { const { address } = walletContract(publicKey, version, network); return { - rawAddress: address.toRawString(), - friendlyAddress: address.toString({ - testOnly: network === Network.TESTNET, - bounceable: false - }), + address, version }; }; @@ -118,7 +121,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'); } @@ -128,47 +131,25 @@ 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 ( storage: IStorage, - wallet: WalletState, - props: Partial< - Pick< - WalletState, - | 'name' - | 'hiddenJettons' - | 'shownJettons' - | 'orderJettons' - | 'lang' - | 'network' - | 'emoji' - > - > + wallet: TonWalletState, + props: Partial> ) => { const updated: WalletState = { ...wallet, - ...props, - revision: wallet.revision + 1 + ...props }; - await setWalletState(storage, updated); + await walletsStorage(storage).updateWalletInState(updated); }; -export const walletStateFromSignerQr = async (api: APIConfig, qrCode: string) => { +export const walletStateFromSignerQr = async ( + api: APIConfig, + qrCode: string +): Promise => { if (!qrCode.startsWith('tonkeeper://signer')) { throw new Error('Unexpected QR code'); } @@ -188,59 +169,61 @@ export const walletStateFromSignerQr = async (api: APIConfig, qrCode: string) => const active = await findWalletAddress(api, publicKey); - const state: WalletState = { + return { + type: 'standard', + blockchain: BLOCKCHAIN_NAME.TON, + id: active.rawAddress, + network: Network.MAINNET, + version: active.version, + rawAddress: active.rawAddress, publicKey, - active, - revision: 0, - name: name ? name : undefined, + name: name || getFallbackWalletName(Address.parse(active.rawAddress)), auth: { kind: 'signer' }, emoji: getFallbackWalletEmoji(publicKey) }; - - return state; }; export const walletStateFromSignerDeepLink = async ( api: APIConfig, publicKey: string, name: string | null -) => { +): Promise => { const active = await findWalletAddress(api, publicKey); - const state: WalletState = { + return { publicKey, - active, - revision: 0, - name: name ? name : undefined, + rawAddress: active.rawAddress, + version: active.version, + type: 'standard', + blockchain: BLOCKCHAIN_NAME.TON, + id: active.rawAddress, + network: Network.MAINNET, + name: name || getFallbackWalletName(Address.parse(active.rawAddress)), auth: { kind: 'signer-deeplink' }, emoji: getFallbackWalletEmoji(publicKey) }; - - return state; }; export const walletStateFromLedger = (walletInfo: { address: string; publicKey: Buffer; accountIndex: number; -}) => { +}): StandardTonWalletState => { const address = Address.parse(walletInfo.address); const publicKey = walletInfo.publicKey.toString('hex'); - const state: WalletState = { + return { + network: Network.MAINNET, + blockchain: BLOCKCHAIN_NAME.TON, + type: 'standard', publicKey, - active: { - friendlyAddress: address.toString({ bounceable: false }), - rawAddress: address.toRawString(), - version: WalletVersion.V4R2 - }, - revision: 0, + id: address.toRawString(), + rawAddress: address.toRawString(), + version: WalletVersion.V4R2, name: `Ledger ${walletInfo.accountIndex + 1}`, auth: { kind: 'ledger', accountIndex: walletInfo.accountIndex }, emoji: getFallbackWalletEmoji(publicKey) }; - - return state; }; export const walletStateFromKeystone = (ur: UR) => { @@ -249,20 +232,19 @@ export const walletStateFromKeystone = (ur: UR) => { 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 = { + const state: StandardTonWalletState = { publicKey: account.publicKey, - active: wallet, - revision: 0, - name: account.name ?? `Keystone`, + rawAddress: contact.address.toRawString(), + version: WalletVersion.V4R2, + type: 'standard', + blockchain: BLOCKCHAIN_NAME.TON, + id: contact.address.toRawString(), + network: Network.MAINNET, + name: account.name ?? 'Keystone', auth: { kind: 'keystone', info: pathInfo }, emoji: getFallbackWalletEmoji(account.publicKey) }; @@ -270,17 +252,16 @@ export const walletStateFromKeystone = (ur: UR) => { return state; }; -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 getFallbackWalletEmoji(publicKey: string) { + const index = Number('0x' + publicKey.slice(-6)) % emojis.length; + return emojis[index]; +} + +export function getFallbackWalletName(friendlyAddress: string | Address) { + return ( + 'Wallet ' + + (friendlyAddress instanceof Address ? friendlyAddress.toString() : friendlyAddress).slice( + -4 + ) + ); +} diff --git a/packages/core/src/service/walletsService.ts b/packages/core/src/service/walletsService.ts new file mode 100644 index 000000000..550d145ea --- /dev/null +++ b/packages/core/src/service/walletsService.ts @@ -0,0 +1,196 @@ +import { AppKey } from '../Keys'; +import { IStorage } from '../Storage'; +import { + defaultWalletsState, + DeprecatedWalletState, + isStandardTonWallet, + WalletId, + WalletsState, + WalletState +} from '../entries/wallet'; +import { DeprecatedAccountState } from '../entries/account'; +import { Network } from '../entries/network'; +import { BLOCKCHAIN_NAME } from '../entries/crypto'; +import { AuthState, DeprecatedAuthState } from '../entries/password'; +import { notNullish } from '../utils/types'; + +export class WalletsStorage { + constructor(private storage: IStorage) {} + + getWallets = async () => { + let state = await this.storage.get(AppKey.WALLETS); + if (!state) { + state = await migrateToWalletState(this.storage); + if (state) { + await this.setWallets(state); + } + } + return state ?? defaultWalletsState; + }; + + setWallets = async (state: WalletsState) => { + await this.storage.set(AppKey.WALLETS, state); + }; + + getActiveWalletId = async () => { + let state = await this.storage.get(AppKey.ACTIVE_WALLET_ID); + if (!state) { + state = await this.migrateToActiveWalletIdState(); + if (state !== null) { + await this.setActiveWalletId(state); + } + } + return state ?? null; + }; + + getActiveWallet = async (): Promise => { + const id = await this.getActiveWalletId(); + if (id !== null) { + const state = await this.getWallets(); + return state.find(w => w.id === id) || null; + } + return null; + }; + + getWallet = async (id: WalletId): Promise => { + const state = await this.getWallets(); + return state.find(w => w.id === id) || null; + }; + + setActiveWalletId = async (activeWalletId: WalletId | null) => { + await this.storage.set(AppKey.ACTIVE_WALLET_ID, activeWalletId); + }; + + addWalletToState = async (wallet: WalletState) => { + await this.addWalletsToState([wallet]); + }; + + addWalletsToState = async (wallets: WalletState[]) => { + const state = await this.getWallets(); + await this.storage.set( + AppKey.WALLETS, + state.concat(wallets.filter(wallet => state.every(w => w.id !== wallet.id))) + ); + }; + + /** + * Replace found wallets with same id in state and replace them with new ones with no array order changes + * @param wallets + */ + updateWalletsInState = async (wallets: WalletState[]) => { + const state = await this.getWallets(); + + for (let i = 0; i < state.length; i++) { + const wallet = wallets.find(w => w.id === state[i].id); + if (!wallet) { + continue; + } + + state[i] = wallet; + } + + await this.storage.set(AppKey.WALLETS, state); + }; + + /** + * Replace found wallet with same id in state and replace it with new one with no array order changes + * @param wallet + */ + updateWalletInState = async (wallet: WalletState) => { + return this.updateWalletsInState([wallet]); + }; + + removeWalletFromState = async (id: WalletId) => { + const state = await this.getWallets(); + const activeWalletId = await this.getActiveWalletId(); + + if (activeWalletId === id) { + await this.setActiveWalletId(state[0]?.id || null); + } + + await this.storage.set( + AppKey.WALLETS, + state.filter(w => w.id !== id) + ); + }; + + private migrateToActiveWalletIdState = async (): Promise => { + const state = await this.storage.get(AppKey.DEPRECATED_ACCOUNT); + if (!state || !state.activePublicKey) { + return null; + } + + const wallets = await this.getWallets(); + return wallets.find(w => isStandardTonWallet(w) && w.publicKey === state.activePublicKey)! + .id; + }; +} + +export const walletsStorage = (storage: IStorage): WalletsStorage => new WalletsStorage(storage); + +async function migrateToWalletState(storage: IStorage): Promise { + const state = await storage.get(AppKey.DEPRECATED_ACCOUNT); + if (!state) { + return null; + } + + const wallets: (WalletState | null)[] = await Promise.all( + state.publicKeys.map(async pk => { + 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 { + auth = walletAuth; + } + + return { + blockchain: BLOCKCHAIN_NAME.TON, + name: w.name || 'Wallet ' + w.active.friendlyAddress.slice(-4), + emoji: w.emoji, + type: 'standard' as const, + network: w.network || Network.MAINNET, + id: w.active.rawAddress, + version: w.active.version, + publicKey: w.publicKey, + rawAddress: w.active.rawAddress, + auth + }; + }) + ); + + return wallets.filter(notNullish); +} diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index 21bd08f2b..3673e1978 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -2,12 +2,10 @@ 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, useMutateActiveWallet, useWalletsState } from '../state/wallet'; import { DropDown } from './DropDown'; import { DoneIcon, DownIcon, PlusIcon, SettingsIcon } from './Icon'; import { ColumnText, Divider } from './Layout'; @@ -17,6 +15,7 @@ import { ScanButton } from './connect/ScanButton'; import { ImportNotification } from './create/ImportNotification'; import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; +import { TonWalletState } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div<{ center?: boolean; @@ -115,33 +114,24 @@ const Row = styled.div` `; const WalletRow: FC<{ - activePublicKey?: string; - publicKey: string; - index: number; + walletState: TonWalletState; onClose: () => void; -}> = ({ activePublicKey, publicKey, index, onClose }) => { +}> = ({ walletState, onClose }) => { const { mutate } = useMutateActiveWallet(); - const { t } = useTranslation(); - const { data: wallet } = useWalletState(publicKey); - const address = wallet - ? toShortValue(formatAddress(wallet.active.rawAddress, wallet.network)) - : undefined; + const address = toShortValue(formatAddress(walletState.rawAddress, walletState.network)); + const activeWallet = useActiveWallet(); return ( { - mutate(publicKey); + mutate(walletState.id); onClose(); }} > - {wallet && } - - {activePublicKey === publicKey ? ( + + + {activeWallet?.id === walletState.id ? ( @@ -156,10 +146,14 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ onCreate }) => { const navigate = useNavigate(); - const { account } = useAppContext(); const { t } = useTranslation(); + const wallets = useWalletsState(); + + if (!wallets) { + return null; + } - if (account.publicKeys.length === 1) { + if (wallets.length === 1) { return ( { @@ -176,14 +170,8 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ } else { return ( <> - {account.publicKeys.map((publicKey, index) => ( - + {wallets.map(wallet => ( + ))} void; onCreate: () => void }> = ({ export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => { const { t } = useTranslation(); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const [isOpen, setOpen] = useState(false); - const { account } = useAppContext(); - const shouldShowIcon = account.publicKeys.length > 1; + const wallets = useWalletsState(); + const shouldShowIcon = wallets.length > 1; 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/activity/NotificationCommon.tsx b/packages/uikit/src/components/activity/NotificationCommon.tsx index 6bec3b1b0..f459317d7 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 { useActiveWallet } 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 wallet = useActiveWallet(); return ( <> @@ -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 wallet = useActiveWallet(); return ( <> @@ -222,7 +223,7 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean }) => { const { t } = useTranslation(); const sdk = useAppSdk(); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); return ( <> @@ -245,7 +246,7 @@ 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 wallet = useActiveWallet(); const address = formatAddress(beneficiary.address, wallet.network, true); return ( @@ -302,7 +303,7 @@ export const ActionDeployerAddress: FC<{ address?: string }> = ({ address }) => }; export const ActionDeployerDetails: FC<{ deployer: string }> = ({ deployer }) => { - const wallet = useWalletContext(); + const wallet = useActiveWallet(); return ; }; @@ -435,7 +436,7 @@ export const TronActionDetailsBlock: FC> event, children }) => { - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const url = wallet.network === Network.TESTNET ? 'https://nile.tronscan.org/#/transaction/%s' 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..39e4add40 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 { useActiveWallet } 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 wallet = useActiveWallet(); if (!contractDeploy) { return ; } diff --git a/packages/uikit/src/components/activity/ton/JettonActivity.tsx b/packages/uikit/src/components/activity/ton/JettonActivity.tsx index 282e77577..fd67018e0 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 { useActiveWallet } from '../../../state/wallet'; export interface JettonActionProps { action: Action; @@ -26,7 +26,7 @@ export interface JettonActionProps { } export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ action, date }) => { - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const { jettonTransfer } = action; const format = useFormatCoinValue(); @@ -37,7 +37,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 ( = ({ action, date }) => { const { t } = useTranslation(); const { jettonBurn } = action; const format = useFormatCoinValue(); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); if (!jettonBurn) { return ; @@ -151,7 +151,7 @@ export const JettonMintAction: FC = ({ action, date }) => { const { t } = useTranslation(); const { jettonMint } = action; const format = useFormatCoinValue(); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); if (!jettonMint) { return ; 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..7e56d92d5 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 { useActiveWallet, useNftItemData } from '../../../state/wallet'; import { InfoCircleIcon, VerificationIcon } from '../../Icon'; import { ListBlock } from '../../List'; import { Body1, Body2 } from '../../Text'; @@ -141,13 +140,13 @@ export const NftItemTransferAction: FC<{ date: string; }> = ({ action, date }) => { const { t } = useTranslation(); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const { nftItemTransfer } = action; if (!nftItemTransfer) { return ; } - if (nftItemTransfer.recipient?.address === wallet.active.rawAddress) { + if (nftItemTransfer.recipient?.address === wallet.rawAddress) { return ( @@ -376,7 +375,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 +384,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..f8c05ac13 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 { useActiveWallet } 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 wallet = useActiveWallet(); const format = useFormatCoinValue(); if (!depositStake) { @@ -48,7 +48,7 @@ export const WithdrawStakeAction: FC<{ }> = ({ action, date }) => { const { t } = useTranslation(); const { withdrawStake } = action; - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const format = useFormatCoinValue(); if (!withdrawStake) { return ; @@ -80,7 +80,7 @@ export const WithdrawRequestStakeAction: FC<{ }> = ({ action, date }) => { const { t } = useTranslation(); const { withdrawStakeRequest } = action; - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const format = useFormatCoinValue(); if (!withdrawStakeRequest) { return ; diff --git a/packages/uikit/src/components/activity/ton/SubscribeAction.tsx b/packages/uikit/src/components/activity/ton/SubscribeAction.tsx index c9d770c30..f323d13bd 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 { useActiveWallet } 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 wallet = useActiveWallet(); if (!unSubscribe) { return ; @@ -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 wallet = useActiveWallet(); if (!subscribe) { return ; diff --git a/packages/uikit/src/components/activity/ton/TonActivityAction.tsx b/packages/uikit/src/components/activity/ton/TonActivityAction.tsx index ad2f32bac..0a6312aa2 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,13 +36,14 @@ import { WithdrawStakeAction } from './StakeActivity'; import { SubscribeAction, UnSubscribeAction } from './SubscribeAction'; +import { 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 format = useFormatCoinValue(); @@ -52,7 +52,7 @@ 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(); if (!smartContractExec) { return ; } - if (seeIfAddressEqual(smartContractExec.contract.address, wallet.active.rawAddress)) { + if (seeIfAddressEqual(smartContractExec.contract.address, wallet.rawAddress)) { return ( @@ -143,7 +143,7 @@ const AuctionBidAction: FC<{ }> = ({ action, date }) => { const { t } = useTranslation(); const { auctionBid } = action; - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const format = useFormatCoinValue(); if (!auctionBid) { @@ -181,10 +181,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 d51b95704..c4da68dc2 100644 --- a/packages/uikit/src/components/connect/TonConnectNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx @@ -4,12 +4,11 @@ import { ConnectRequest, DAppManifest } from '@tonkeeper/core/dist/entries/tonConnect'; -import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { isStandardTonWallet, 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,6 +20,7 @@ import { Body2, Body3, H2, Label2 } from '../Text'; import { Button } from '../fields/Button'; import { ResultButton } from '../transfer/common'; import { useConnectTonConnectAppMutation } from '../../state/tonConnect'; +import { useActiveWallet } from '../../state/wallet'; const Title = styled(H2)` text-align: center; @@ -84,7 +84,7 @@ const ConnectContent: FC<{ const sdk = useAppSdk(); const [done, setDone] = useState(false); - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const { t } = useTranslation(); @@ -109,7 +109,7 @@ const ConnectContent: FC<{ } }; - const address = formatAddress(wallet.active.rawAddress, wallet.network); + const address = formatAddress(wallet.rawAddress, wallet.network); let shortUrl = manifest.url; try { @@ -118,6 +118,10 @@ const ConnectContent: FC<{ /* eslint-stub */ } + if (!isStandardTonWallet(wallet)) { + return <>; + } + return ( @@ -129,8 +133,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)} +
{toShortValue(address)}
{walletVersionText(wallet.version)}
diff --git a/packages/uikit/src/components/connect/TonConnectSubscription.tsx b/packages/uikit/src/components/connect/TonConnectSubscription.tsx index 0b002e95e..4d76955be 100644 --- a/packages/uikit/src/components/connect/TonConnectSubscription.tsx +++ b/packages/uikit/src/components/connect/TonConnectSubscription.tsx @@ -7,7 +7,6 @@ import { import { subscribeTonConnect } from '@tonkeeper/core/dist/service/tonConnect/httpBridge'; import React, { useCallback, useEffect, useState } from 'react'; import { useSendNotificationAnalytics } from '../../hooks/amplitude'; -import { useWalletContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { TonTransactionNotification } from './TonTransactionNotification'; import { SendTransactionAppRequest, useResponseSendMutation } from './connectHook'; @@ -17,7 +16,8 @@ import { useAppTonConnectConnections, useTonConnectLastEventId } from '../../state/tonConnect'; -import { useMutateActiveWallet } from '../../state/account'; + +import { useActiveWallet, useMutateActiveWallet } 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(); @@ -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 70f33dac3..55ca31e92 100644 --- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx +++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx @@ -7,10 +7,10 @@ import { sendTonConnectTransfer, tonConnectTransferError } from '@tonkeeper/core/dist/service/transfer/tonService'; -import { toShortValue } from '@tonkeeper/core/dist/utils/common'; +import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { FC, useCallback, useEffect, useState } 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 { TxConfirmationCustomError } from '../../libs/errors/TxConfirmationCustomError'; @@ -33,6 +33,7 @@ import { Button } from '../fields/Button'; import { WalletEmoji } from '../shared/emoji/WalletEmoji'; import { ResultButton } from '../transfer/common'; import { EmulationList } from './EstimationLayout'; +import { useActiveStandardTonWallet, useActiveWallet, useWalletsState } from '../../state/wallet'; const ButtonGap = styled.div` ${props => @@ -56,7 +57,7 @@ const ButtonRowStyled = styled.div` `; const useSendMutation = (params: TonConnectTransactionPayload, waitInvalidation?: boolean) => { - const wallet = useWalletContext(); + const wallet = useActiveStandardTonWallet(); const sdk = useAppSdk(); const { api } = useAppContext(); const client = useQueryClient(); @@ -64,7 +65,7 @@ const useSendMutation = (params: TonConnectTransactionPayload, waitInvalidation? const { mutateAsync: checkTouchId } = useCheckTouchId(); return useMutation(async () => { - const signer = await getSigner(sdk, wallet.publicKey, checkTouchId); + const signer = await getSigner(sdk, wallet.id, checkTouchId); let boc: string; switch (signer.type) { @@ -78,7 +79,7 @@ const useSendMutation = (params: TonConnectTransactionPayload, waitInvalidation? } const invalidationPromise = client.invalidateQueries({ - predicate: query => query.queryKey.includes(wallet.active.rawAddress) + predicate: query => query.queryKey.includes(wallet.rawAddress) }); if (waitInvalidation) { await invalidationPromise; @@ -233,7 +234,7 @@ const ConnectContent: FC<{ const useEstimation = (params: TonConnectTransactionPayload, errorFetched: boolean) => { const { api } = useAppContext(); - const wallet = useWalletContext(); + const wallet = useActiveStandardTonWallet(); return useQuery( [QueryKey.estimate, params], @@ -247,7 +248,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); @@ -271,7 +272,7 @@ const WalletInfoStyled = styled.div` `; const NotificationTitleWithWalletName: FC<{ onClose: () => void }> = ({ onClose }) => { - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const { t } = useTranslation(); return ( @@ -283,7 +284,7 @@ const NotificationTitleWithWalletName: FC<{ onClose: () => void }> = ({ onClose {t('confirmSendModal_wallet')}  - {wallet.name ?? toShortValue(wallet.active.friendlyAddress)} + {wallet.name ?? toShortValue(formatAddress(wallet.rawAddress))} = ({ params, handleClose, waitInvalidation }) => { const { t } = useTranslation(); - const { account } = useAppContext(); + const wallets = useWalletsState(); 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/CreateAuth.tsx b/packages/uikit/src/components/create/CreateAuth.tsx index 657b678c1..b05d824fa 100644 --- a/packages/uikit/src/components/create/CreateAuth.tsx +++ b/packages/uikit/src/components/create/CreateAuth.tsx @@ -1,7 +1,3 @@ -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'; @@ -10,6 +6,7 @@ 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; @@ -18,69 +15,12 @@ const Block = styled.form` 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 sdk = useAppSdk(); const ref = useRef(null); @@ -92,13 +32,16 @@ const FillPassword: FC<{ 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); + if (!validatePassword(password)) { + sdk.hapticNotification('error'); + return setError('password'); + } + if (password !== confirm) { + sdk.hapticNotification('error'); + return setError('confirm'); } + + return afterCreate(password); }; useEffect(() => { @@ -140,8 +83,8 @@ const FillPassword: FC<{ fullWidth primary marginTop - loading={isLoading || isCreating} - disabled={isCreating || error != null} + loading={isLoading} + disabled={!!error} type="submit" > {t('continue')} @@ -155,24 +98,5 @@ 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 ; - } + return ; }; diff --git a/packages/uikit/src/components/create/WalletName.tsx b/packages/uikit/src/components/create/WalletName.tsx index 40769c19b..a134c1d45 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,35 @@ 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; +}> = ({ walletEmoji, submitHandler, name: nameProp }) => { 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,21 +62,12 @@ export const UpdateWalletName: FC< value={name} onChange={onChange} label={t('Wallet_name')} - disabled={isLoading} - isValid={!isError} + isValid={isValid} rightElement={emoji ? : null} /> - diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index d9dbd43f4..5689b156a 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -101,10 +101,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 = useWalletsState(); + const mainnetIds = wallets?.filter(w => w && w.network !== Network.TESTNET).map(w => w!.id); const [isResizing, setIsResizing] = useState(false); const [hoverOnColumn, setHoverOnColumn] = useState(undefined); @@ -159,7 +157,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 +222,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..365ef786d 100644 --- a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx @@ -2,8 +2,7 @@ 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 { useActiveWallet } from '../../../state/wallet'; import { useTranslation } from '../../../hooks/translation'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { useAsideActiveRoute } from '../../../hooks/desktop/useAsideActiveRoute'; @@ -63,14 +62,13 @@ const DoneIconStyled = styled(DoneIcon)` export const AsideHeader: FC<{ width: number }> = ({ width }) => { const { t } = useTranslation(); - const { account } = useAppContext(); - const { data: wallet } = useWalletState(account.activePublicKey!); + const wallet = useActiveWallet(); 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 = wallet ? formatAddress(wallet.rawAddress) : ''; const timeoutRef = useRef | undefined>(undefined); diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index dbc17e0e5..04673518d 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -8,9 +8,8 @@ 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 { useActiveWallet, useMutateActiveWallet, useWalletsState } from "../../../state/wallet"; import { fallbackRenderOver } from '../../Error'; import { GlobeIcon, PlusIcon, SlidersIcon, StatsIcon } from '../../Icon'; import { Label2 } from '../../Text'; @@ -19,6 +18,7 @@ import { AsideMenuItem } from '../../shared/AsideItem'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; import { AsideHeader } from './AsideHeader'; import { SubscriptionInfo } from './SubscriptionInfo'; +import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -92,18 +92,17 @@ const SubscriptionInfoStyled = styled(SubscriptionInfo)` padding: 6px 16px 6px 8px; `; -export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = ({ - publicKey, +export const AsideMenuAccount: FC<{ wallet: WalletState; isSelected: boolean }> = ({ + wallet, isSelected }) => { const { t } = useTranslation(); - const { data: wallet } = useWalletState(publicKey); const { mutateAsync } = useMutateActiveWallet(); const navigate = useNavigate(); const location = useLocation(); - const { account } = useAppContext(); - const shouldShowIcon = account.publicKeys.length > 1; + const wallets = useWalletsState(); + const shouldShowIcon = wallets.length > 1; const handleNavigateHome = useCallback(() => { const navigateHomeFromRoutes = [AppProRoute.dashboard, AppRoute.settings, AppRoute.browser]; @@ -115,8 +114,8 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = }, [location.pathname]); const onClick = useCallback(() => { - mutateAsync(publicKey).then(handleNavigateHome); - }, [publicKey, mutateAsync, handleNavigateHome]); + mutateAsync(wallet.id).then(handleNavigateHome); + }, [wallet.id, mutateAsync, handleNavigateHome]); if (!wallet) { return null; @@ -137,7 +136,9 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation(); const [isOpenImport, setIsOpenImport] = useState(false); - const { account, proFeatures } = useAppContext(); + const { proFeatures } = useAppContext(); + const wallets = useWalletsState(); + const activeWallet = useActiveWallet(); const navigate = useNavigate(); const location = useLocation(); const { ref, closeBottom } = useIsScrolled(); @@ -209,15 +210,11 @@ const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { {t('aside_dashboard')} )} - {account.publicKeys.map(publicKey => ( + {wallets.map(wallet => ( ))} diff --git a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx index c47773745..983b9c0c4 100644 --- a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx @@ -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 { useWalletsState } 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 = useWalletsState(); 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/history/ton/HistoryCell.tsx b/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx index d5c80fc50..0975de3fe 100644 --- a/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx +++ b/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx @@ -6,8 +6,8 @@ import { FC, ReactNode } from 'react'; import { Body2, Body2Class } from '../../../Text'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { useFormatCoinValue } from '../../../../hooks/balance'; -import { useWalletContext } from '../../../../hooks/appContext'; import { HistoryGridCell, HistoryGridCellFillRow } from './HistoryGrid'; +import { useActiveWallet } from '../../../../state/wallet'; export const HistoryCellAction = styled(HistoryGridCell)` display: flex; @@ -103,7 +103,7 @@ export const HistoryCellAccount: FC<{ account?: { address?: string; name?: string }; fallbackAddress?: string; }> = ({ account, fallbackAddress }) => { - const wallet = useWalletContext(); + const wallet = useActiveWallet(); const { t } = useTranslation(); return ( 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..4ec267cb4 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, useNftCollectionData, useNftItemData } from '../../../../state/wallet'; import styled, { css } from 'styled-components'; import { Body2 } from '../../../Text'; import { Skeleton } from '../../../shared/Skeleton'; @@ -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..1730fe3af 100644 --- a/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx +++ b/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx @@ -10,7 +10,7 @@ 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 { useActiveWallet } from '../../../state/wallet'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; import { useTranslation } from '../../../hooks/translation'; import { useEstimateMultiTransfer } from '../../../hooks/blockchain/useEstimateMultiTransferFee'; @@ -165,8 +165,7 @@ const MultiSendConfirmContent: FC<{ willBeSentBN?.multipliedBy(rate?.prices || 0) ); - const { account } = useAppContext(); - const { data: wallet } = useWalletState(account.activePublicKey!); + const wallet = useActiveWallet(); const { mutateAsync: estimate, diff --git a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx index 25700acf5..20300e2b2 100644 --- a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx +++ b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx @@ -2,7 +2,7 @@ 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 { StandardTonWalletState, WalletVersion } 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'; @@ -12,7 +12,7 @@ import { Controller, FormProvider, useFieldArray, useForm, useFormContext } from 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 { @@ -52,6 +52,7 @@ 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; @@ -321,9 +322,9 @@ const MultiSendAddMore: FC<{ const { data } = useEnableW5(); const { mutate } = useEnableW5Mutation(); - const wallet = useWalletContext(); + const wallet = useActiveWallet() as StandardTonWalletState; - if (fieldsNumber < MAX_ALLOWED_WALLET_MSGS[wallet.active.version]) { + if (fieldsNumber < MAX_ALLOWED_WALLET_MSGS[wallet.version]) { return ( + + + )} + + ); +}; diff --git a/packages/uikit/src/components/create/Words.tsx b/packages/uikit/src/components/create/Words.tsx index d4c2aa26e..44e1f22db 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/desktop/header/DesktopHeader.tsx b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx index c15d973d5..f6d43e326 100644 --- a/packages/uikit/src/components/desktop/header/DesktopHeader.tsx +++ b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx @@ -8,7 +8,6 @@ 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'; @@ -18,6 +17,7 @@ import { Link } from 'react-router-dom'; import { AppProRoute } from '../../../libs/routes'; import { BuyNotification } from '../../home/BuyAction'; import { Skeleton } from '../../shared/Skeleton'; +import { useWalletTotalBalance } from "../../../state/asset"; const DesktopHeaderStyled = styled.div` padding-left: 1rem; diff --git a/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx b/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx index 4ec267cb4..8d753fbe9 100644 --- a/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx +++ b/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx @@ -11,7 +11,7 @@ import { ErrorRow, HistoryCellActionGeneric } from './HistoryCell'; -import { useActiveWallet, 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'; @@ -19,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; diff --git a/packages/uikit/src/components/desktop/tokens/TokensPieChart.tsx b/packages/uikit/src/components/desktop/tokens/TokensPieChart.tsx index 8cc9c5fd8..d07e79bee 100644 --- a/packages/uikit/src/components/desktop/tokens/TokensPieChart.tsx +++ b/packages/uikit/src/components/desktop/tokens/TokensPieChart.tsx @@ -1,8 +1,8 @@ import { Cell, Pie, PieChart } from 'recharts'; -import { TokenDistribution } from '../../../state/wallet'; import { FC, memo, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Body3, Label3 } from '../../Text'; +import { TokenDistribution } from "../../../state/asset"; const Container = styled.div<{ activeAddress: string | undefined }>` container-type: inline-size; diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index 10855c4c0..64d786d66 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -6,12 +6,13 @@ import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { formatFiatCurrency } from '../../hooks/balance'; import { QueryKey } from '../../libs/queryKey'; -import { useActiveWallet, useWalletTotalBalance } from '../../state/wallet'; +import { useActiveWallet } from '../../state/wallet'; import { Body3, Label2, Num2 } from '../Text'; import { Badge } from '../shared'; import { SkeletonText } from '../shared/Skeleton'; import { AssetData } from './Jettons'; import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet'; +import { useWalletTotalBalance } from "../../state/asset"; const Block = styled.div` display: flex; diff --git a/packages/uikit/src/components/nft/LinkNft.tsx b/packages/uikit/src/components/nft/LinkNft.tsx index 70fde37e4..085dbe835 100644 --- a/packages/uikit/src/components/nft/LinkNft.tsx +++ b/packages/uikit/src/components/nft/LinkNft.tsx @@ -19,7 +19,7 @@ import { useTonRecipient } from '../../hooks/blockchain/useTonRecipient'; import { useTranslation } from '../../hooks/translation'; import { useNotification } from '../../hooks/useNotification'; import { useQueryChangeWait } from '../../hooks/useQueryChangeWait'; -import { useActiveWallet, useNftDNSLinkData } from '../../state/wallet'; +import { useActiveWallet } from '../../state/wallet'; import { ColumnText, Gap } from '../Layout'; import { ListItem, ListItemPayload } from '../List'; import { Notification, NotificationBlock } from '../Notification'; @@ -38,6 +38,7 @@ import { ConfirmViewTitleSlot } from '../transfer/ConfirmView'; import { ConfirmAndCancelMainButton } from '../transfer/common'; +import { useNftDNSLinkData } from "../../state/nft"; export const LinkNft: FC<{ nft: NFTDNS }> = ({ nft }) => { const toast = useToast(); diff --git a/packages/uikit/src/components/nft/NftDetails.tsx b/packages/uikit/src/components/nft/NftDetails.tsx index 1898b3964..f0fea685a 100644 --- a/packages/uikit/src/components/nft/NftDetails.tsx +++ b/packages/uikit/src/components/nft/NftDetails.tsx @@ -7,11 +7,12 @@ import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { useDateFormat } from '../../hooks/dateFormat'; import { useTranslation } from '../../hooks/translation'; -import { useActiveWallet, useNftDNSExpirationDate, useNftItemData } from '../../state/wallet'; +import { 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%; diff --git a/packages/uikit/src/components/nft/NftView.tsx b/packages/uikit/src/components/nft/NftView.tsx index 72d2478af..c04368f82 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 { useActiveWalletConfig } 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'; 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 958dcf859..f3ed9574f 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -16,7 +16,8 @@ import { WalletsIcon } from './SettingsIcons'; import { SettingsItem, SettingsList } from './SettingsList'; -import { useActiveWallet, useWalletNftList, useWalletsState } from "../../state/wallet"; +import { useActiveWallet, useWalletsState } from "../../state/wallet"; +import { useWalletNftList } from "../../state/nft"; const SingleAccountSettings = () => { const [logout, setLogout] = useState(false); diff --git a/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx b/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx index 0b71d8be8..22080dc3f 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 { useActiveWalletConfig } 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'; @@ -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 69a5508be..3f80e50cc 100644 --- a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx +++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx @@ -16,13 +16,13 @@ const RenameWalletContent: FC<{ }> = ({ animationTime, afterClose, wallet }) => { const { t } = useTranslation(); - const { mutateAsync, isLoading, isError } = useMutateRenameWallet(wallet); + const { mutateAsync, isLoading, isError } = useMutateRenameWallet(); const [name, setName] = useState(wallet.name ?? ''); const [emoji, setEmoji] = useState(wallet.emoji ?? ''); const onSubmit: React.FormEventHandler = async e => { e.preventDefault(); - await mutateAsync({ name, emoji }); + await mutateAsync({ id: wallet.id, name, emoji }); afterClose(() => null); }; diff --git a/packages/uikit/src/components/transfer/SuggestionList.tsx b/packages/uikit/src/components/transfer/SuggestionList.tsx index b5259a639..d435f249f 100644 --- a/packages/uikit/src/components/transfer/SuggestionList.tsx +++ b/packages/uikit/src/components/transfer/SuggestionList.tsx @@ -22,7 +22,7 @@ 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'; @@ -237,7 +237,7 @@ export const SuggestionList: FC<{ return ( <> - + ); } 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 index b72a6ef29..3dd264042 100644 --- a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx +++ b/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx @@ -21,7 +21,7 @@ import { delay, formatAddress } 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 { SkeletonListWithImages } from '../../components/Skeleton'; import { Body1, Body2, Label2 } from '../../components/Text'; import { ImportNotification } from '../../components/create/ImportNotification'; import { @@ -328,7 +328,7 @@ const BurnBlock: FC<{ data: NftItem[] | undefined }> = ({ data }) => { }; if (!data) { - return ; + return ; } if (ok) { diff --git a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx index e770943d0..85b9bb0a0 100644 --- a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx +++ b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx @@ -13,7 +13,8 @@ import { JettonAsset, TonAsset } from '../../components/home/Jettons'; import { useTranslation } from '../../hooks/translation'; import { useAssets } from '../../state/home'; import { useMutateUserUIPreferences, useUserUIPreferences } from '../../state/theme'; -import { useAssetsDistribution } from '../../state/wallet'; + +import { useAssetsDistribution } from "../../state/asset"; const DesktopAssetStylesOverride = css` background-color: transparent; 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/useDebuggingTools.ts b/packages/uikit/src/hooks/useDebuggingTools.ts new file mode 100644 index 000000000..14b572f93 --- /dev/null +++ b/packages/uikit/src/hooks/useDebuggingTools.ts @@ -0,0 +1,25 @@ +import { useAppSdk } from './appSdk'; +import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; +import { WalletsState } from '@tonkeeper/core/dist/entries/wallet'; + +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; + } + walletsStorage(sdk.storage).setWallets(null as unknown as WalletsState); + } + }; + } +}; diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index 2ad3efdca..d579f5d34 100644 --- a/packages/uikit/src/libs/queryKey.ts +++ b/packages/uikit/src/libs/queryKey.ts @@ -21,6 +21,7 @@ export enum QueryKey { syncDate = 'syncDate', analytics = 'analytics', language = 'language', + walletVersions = 'walletVersions', tonConnectConnection = 'tonConnectConnection', tonConnectLastEventId = 'tonConnectLastEventId', diff --git a/packages/uikit/src/pages/activity/Activity.tsx b/packages/uikit/src/pages/activity/Activity.tsx index 1c9990940..6924833a4 100644 --- a/packages/uikit/src/pages/activity/Activity.tsx +++ b/packages/uikit/src/pages/activity/Activity.tsx @@ -3,7 +3,7 @@ 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 } from '../../hooks/appContext'; @@ -82,7 +82,7 @@ const Activity: FC = () => { - {isFetchingNextPage && } + {isFetchingNextPage && } ); diff --git a/packages/uikit/src/pages/home/Home.tsx b/packages/uikit/src/pages/home/Home.tsx index 42ac6520b..b975acbce 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/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index ba065c491..a350545f1 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -1,6 +1,5 @@ import { mnemonicNew } from '@ton/crypto'; -import { AuthState } from '@tonkeeper/core/dist/entries/password'; -import { FC, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { IconPage } from '../../components/Layout'; import { CreateAuthState } from '../../components/create/CreateAuth'; import { UpdateWalletName } from '../../components/create/WalletName'; @@ -13,29 +12,30 @@ import { } from '../../components/lottie/LottieIcons'; import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; -import { FinalView, useAddWalletMutation } from './Password'; +import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; -import { useWalletsState } from '../../state/wallet'; +import { defaultWalletVersion, StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { useCreateStandardTonWalletsByMnemonic, useWalletsState } from '../../state/wallet'; -const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { +const Create = () => { const sdk = useAppSdk(); const { t } = useTranslation(); const { - mutateAsync: checkPasswordAndCreateWalletAsync, - isLoading: isConfirmLoading, - reset - } = useAddWalletMutation(); + mutateAsync: createWalletsAsync, + isLoading: isCreateWalletLoading, + reset: resetCreateWallets + } = useCreateStandardTonWalletsByMnemonic(); const existingWallets = useWalletsState(); - const [mnemonic, setMnemonic] = useState([]); + const [mnemonic, setMnemonic] = useState(); const [wallet, setWallet] = 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 [createdPassword, setCreatedPassword] = useState(); + const [passName, setPassName] = useState(false); const [passNotifications, setPassNotification] = useState(false); useEffect(() => { @@ -45,14 +45,31 @@ const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { }, []); useEffect(() => { - if (mnemonic.length) { + if (mnemonic) { setTimeout(() => { setCreate(true); }, 1500); } }, [mnemonic]); - if (mnemonic.length === 0) { + const authExists = createdPassword || existingWallets.length >= 1; + + useEffect(() => { + if (authExists && mnemonic && checked) { + createWalletsAsync({ + mnemonic, + password: createdPassword, + versions: [defaultWalletVersion], + activateFirstWallet: true + }).then(result => { + setWallet(result[0]); + }); + } + + return resetCreateWallets; + }, [authExists, createdPassword, mnemonic, checked, createWalletsAsync]); + + if (!mnemonic) { return } title={t('create_wallet_generating')} />; } @@ -91,52 +108,55 @@ const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { setCheck(false)} - onConfirm={() => - checkPasswordAndCreateWalletAsync({ - mnemonic, - supportedAuthTypes: listOfAuth - }).then(state => { - setChecked(true); - if (state === false) { - setHasPassword(false); - } else { - setHasPassword(true); - setWallet(state); - } - }) - } - isLoading={isConfirmLoading} + onConfirm={() => setChecked(true)} + isLoading={isCreateWalletLoading} /> ); } - if (!hasPassword) { - return ( - { - reset(); - checkPasswordAndCreateWalletAsync({ mnemonic, password }).then(state => { - if (state !== false) { - setHasPassword(true); - setWallet(state); - } - }); - }} - isLoading={isConfirmLoading} - /> - ); + if (authExists) { + if (!wallet) { + return ( + setCheck(false)} + onConfirm={() => setChecked(true)} + isLoading={isCreateWalletLoading} + /> + ); + } + } else { + if (!checked) { + return ( + setCheck(false)} + onConfirm={() => setChecked(true)} + /> + ); + } + + if (!wallet) { + return ( + + ); + } } - if (wallet && existingWallets.length > 1) { + if (existingWallets.length > 1 && !passName) { return ( + submitHandler={val => { setWallet(w => ({ ...w!, ...val - })) - } + })); + setPassName(true); + }} walletEmoji={wallet.emoji} /> ); diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx index 529476a3b..5157f32ee 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -1,87 +1,105 @@ -import { AuthState } from '@tonkeeper/core/dist/entries/password'; -import React, { FC, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { CreateAuthState } from '../../components/create/CreateAuth'; import { UpdateWalletName } from '../../components/create/WalletName'; import { ImportWords } from '../../components/create/Words'; import { useAppSdk } from '../../hooks/appSdk'; -import { FinalView, useAddWalletMutation } from './Password'; +import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { useWalletsState } from '../../state/wallet'; -import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { useCreateStandardTonWalletsByMnemonic, useWalletsState } from '../../state/wallet'; +import { StandardTonWalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions'; -const Import: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { +const Import = () => { const sdk = useAppSdk(); - const [mnemonic, setMnemonic] = useState([]); - const [wallet, setWallet] = useState(undefined); - const [hasPassword, setHasPassword] = useState(false); + const [mnemonic, setMnemonic] = useState(); + const [wallets, setWallets] = useState(undefined); + const [selectedVersions, setSelectedVersions] = useState( + undefined + ); + + const [createdPassword, setCreatedPassword] = useState(undefined); + const [passName, setPassName] = useState(false); const [passNotifications, setPassNotification] = useState(false); const existingWallets = useWalletsState(); const { - mutateAsync: checkPasswordAndCreateWalletAsync, - isLoading: isConfirmLoading, - reset - } = useAddWalletMutation(); + mutateAsync: createWalletsAsync, + isLoading: isCreatingWallets, + reset: resetCreateWallets + } = useCreateStandardTonWalletsByMnemonic(); - if (mnemonic.length === 0) { - return ( - { - checkPasswordAndCreateWalletAsync({ - mnemonic: m, - supportedAuthTypes: listOfAuth - }).then(state => { - setMnemonic(m); - if (state === false) { - setHasPassword(false); - } else { - setHasPassword(true); - setWallet(state); - } - }); - }} - /> - ); + const authExists = createdPassword || existingWallets.length >= 1; + + useEffect(() => { + if (authExists && selectedVersions && mnemonic) { + createWalletsAsync({ + mnemonic, + password: createdPassword, + versions: selectedVersions, + activateFirstWallet: true + }).then(setWallets); + } + + return resetCreateWallets; + }, [authExists, createdPassword, selectedVersions, mnemonic, createWalletsAsync]); + + if (!mnemonic) { + return ; } - if (!hasPassword) { - return ( - { - reset(); - checkPasswordAndCreateWalletAsync({ mnemonic, password }).then(state => { - if (state !== false) { - setHasPassword(true); - setWallet(state); - } - }); - }} - isLoading={isConfirmLoading} - /> - ); + if (authExists) { + if (!wallets) { + return ( + { + setWallets(undefined); + setMnemonic(undefined); + }} + isLoading={isCreatingWallets} + /> + ); + } + } else { + if (!selectedVersions) { + return ( + { + setWallets(undefined); + setMnemonic(undefined); + }} + /> + ); + } + + if (!wallets) { + return ( + + ); + } } - if (existingWallets.length > 1 && wallet) { + if (existingWallets.length > 1 && wallets.length === 1 && !passName) { return ( - setWallet(w => ({ - ...w!, - ...val - })) - } - walletEmoji={wallet.emoji} + name={wallets[0].name} + submitHandler={val => { + setWallets(w => [{ ...w![0], ...val }]); + setPassName(true); + }} + walletEmoji={wallets[0].emoji} /> ); } - if (sdk.notifications && !passNotifications) { + if (sdk.notifications && !passNotifications && wallets.length === 1) { return ( setPassNotification(true)} /> diff --git a/packages/uikit/src/pages/import/Ledger.tsx b/packages/uikit/src/pages/import/Ledger.tsx index 3e6abb955..75b72edd7 100644 --- a/packages/uikit/src/pages/import/Ledger.tsx +++ b/packages/uikit/src/pages/import/Ledger.tsx @@ -17,12 +17,11 @@ import { useNativeBackButton } from '../../components/BackButton'; import { SpinnerIcon } from '../../components/Icon'; import { ListBlock, ListItem } from '../../components/List'; import { formatAddress } from '@tonkeeper/core/dist/utils/common'; -import { formatter } from '../../hooks/balance'; -import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance'; import { Checkbox } from '../../components/fields/Checkbox'; import { LedgerConnectionSteps } from '../../components/ledger/LedgerConnectionSteps'; import { UpdateWalletName } from '../../components/create/WalletName'; import { getFallbackWalletEmoji } from '@tonkeeper/core/dist/service/walletService'; +import { toFormattedTonBalance } from "../../hooks/balance"; const ConnectLedgerWrapper = styled.div` display: flex; @@ -174,10 +173,6 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: () return `${userFriendlyAddress.slice(0, 8)}...${userFriendlyAddress.slice(-8)}`; }; - const toFormattedBalance = (weiBalance: number) => { - return formatter.format(shiftedDecimals(weiBalance, 9)); - }; - const onAdd = () => { const chosenIndexes = Object.entries(selectedIndexes) .filter(([, v]) => v) @@ -216,7 +211,7 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: () ·   - {toFormattedBalance(account.balance)} TON + {toFormattedTonBalance(account.balance)} TON { - if (!sdk.keychain) { - throw new Error('Keychain is not define'); - } - - const state = await createNewStandardTonWalletStateFromMnemonic(api, mnemonic, { - kind: 'keychain' - }); - state.auth = { kind: 'keychain' }; - - await sdk.keychain.setPassword(state.publicKey, mnemonic.join(' ')); - - await walletsStorage(sdk.storage).addWalletToState(state); - - await client.invalidateQueries([QueryKey.account]); - return state; -}; - -const createWallet = async ( - client: QueryClient, - api: APIConfig, - sdk: IAppSdk, - mnemonic: string[], - password: string -) => { - if (!password) { - throw new Error('Missing encrypt password key'); - } - - const encryptedMnemonic = await encrypt(mnemonic.join(' '), password); - const state = await createNewStandardTonWalletStateFromMnemonic(api, mnemonic, { - kind: 'password', - encryptedMnemonic - }); - await walletsStorage(sdk.storage).addWalletToState(state); - - await client.invalidateQueries([QueryKey.account]); - return state; -}; - -export const useAddWalletMutation = () => { - const sdk = useAppSdk(); - const { api } = useAppContext(); - const client = useQueryClient(); - - return useMutation< - false | StandardTonWalletState, - Error, - { mnemonic: string[]; password?: string; supportedAuthTypes?: AuthState['kind'][] } - >(async ({ mnemonic, password, supportedAuthTypes }) => { - const valid = await mnemonicValidate(mnemonic); - if (!valid) { - throw new Error('Mnemonic is not valid.'); - } - - if ( - supportedAuthTypes && - supportedAuthTypes.length === 1 && - supportedAuthTypes[0] === 'keychain' - ) { - return createWalletWithKeychain(client, api, sdk, mnemonic); - } - - const walletsState = await walletsStorage(sdk.storage).getWallets(); - if (walletsState.length === 0 && password === undefined) { - return false; - } - - if (!password) { - password = await getPasswordByNotification(sdk); - } - - return createWallet(client, api, sdk, mnemonic, password); - }); -}; const ConfettiBlock = styled.div` position: fixed; diff --git a/packages/uikit/src/pages/import/index.tsx b/packages/uikit/src/pages/import/index.tsx index 042a9465d..affe2e881 100644 --- a/packages/uikit/src/pages/import/index.tsx +++ b/packages/uikit/src/pages/import/index.tsx @@ -1,5 +1,3 @@ -import { AuthState } from '@tonkeeper/core/dist/entries/password'; -import { FC } from 'react'; import { Route, Routes } from 'react-router-dom'; import { ImportRoute } from '../../libs/routes'; import Create from './Create'; @@ -8,11 +6,11 @@ import { PairKeystone } from './Keystone'; import { PairLedger } from './Ledger'; import { PairSigner } from './Signer'; -const ImportRouter: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { +const ImportRouter = () => { return ( - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index d9c44af26..9418f8a4e 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -14,7 +14,7 @@ import { DropDown } from '../../components/DropDown'; import { EllipsisIcon, ReorderIcon } from '../../components/Icon'; import { ColumnText, Divider } from '../../components/Layout'; import { ListBlock, ListItem, ListItemElement, ListItemPayload } from '../../components/List'; -import { SkeletonListPayload } from '../../components/Skeleton'; +import { SkeletonListPayloadWithImage } from '../../components/Skeleton'; import { SubHeader } from '../../components/SubHeader'; import { Label1 } from '../../components/Text'; import { ImportNotification } from '../../components/create/ImportNotification'; @@ -56,7 +56,7 @@ const WalletRow: FC<{ const [remove, setRemove] = useState(false); if (!wallet) { - return ; + return ; } const address = formatAddress(wallet.rawAddress, wallet.network); diff --git a/packages/uikit/src/pages/settings/Jettons.tsx b/packages/uikit/src/pages/settings/Jettons.tsx index 5f4e4a1ba..26d803fa1 100644 --- a/packages/uikit/src/pages/settings/Jettons.tsx +++ b/packages/uikit/src/pages/settings/Jettons.tsx @@ -13,7 +13,7 @@ import { InnerBody } from '../../components/Body'; import { InvisibleIcon, PinIcon, ReorderIcon, VisibleIcon } from '../../components/Icon'; import { ColumnText } from '../../components/Layout'; import { ListBlock, ListItemElement, ListItemPayload } from '../../components/List'; -import { SkeletonList } from '../../components/Skeleton'; +import { SkeletonListWithImages } from '../../components/Skeleton'; import { SubHeader } from '../../components/SubHeader'; import { H3 } from '../../components/Text'; import { useCoinFullBalance } from '../../hooks/balance'; @@ -222,7 +222,7 @@ const JettonSkeleton = () => { <> - + ); diff --git a/packages/uikit/src/pages/settings/Version.tsx b/packages/uikit/src/pages/settings/Version.tsx index 31d73ae1b..5614ede3d 100644 --- a/packages/uikit/src/pages/settings/Version.tsx +++ b/packages/uikit/src/pages/settings/Version.tsx @@ -1,63 +1,197 @@ import { - WalletVersion as WalletVersionEnum, + StandardTonWalletState, + WalletVersion as WalletVersionType, WalletVersions, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; -import { getWalletAddress } from '@tonkeeper/core/dist/service/walletService'; -import { toShortValue } from '@tonkeeper/core/dist/utils/common'; -import { useMemo } from 'react'; +import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import React, { FC, useState } from 'react'; import styled from 'styled-components'; import { InnerBody } from '../../components/Body'; -import { CheckIcon } from '../../components/Icon'; import { SubHeader } from '../../components/SubHeader'; -import { Body2 } from '../../components/Text'; -import { SettingsItem, SettingsList } from '../../components/settings/SettingsList'; -import { useAppContext } from '../../hooks/appContext'; +import { Body2, Label1 } from '../../components/Text'; import { useTranslation } from '../../hooks/translation'; -import { useEnableW5 } from '../../state/experemental'; import { useIsActiveWalletKeystone } from '../../state/keystone'; import { useIsActiveWalletLedger } from '../../state/ledger'; -import { useActiveStandardTonWallet } from '../../state/wallet'; +import { + useActiveStandardTonWallet, + useCreateStandardTonWalletsByMnemonic, + useMutateActiveWallet, + useMutateRenameWallet, + useStandardTonWalletVersions, + useWalletsState +} from '../../state/wallet'; +import { ListBlock, ListItem, ListItemPayload } from '../../components/List'; +import { toFormattedTonBalance } from '../../hooks/balance'; +import { Button } from '../../components/fields/Button'; +import { Address } from '@ton/core'; +import { useNavigate } from 'react-router-dom'; +import { AppRoute } from '../../libs/routes'; +import { Notification } from '../../components/Notification'; +import { UpdateWalletName } from '../../components/create/WalletName'; +import { useCheckTouchId } from '../../state/password'; +import { getMnemonicAndPassword } from '../../state/mnemonic'; +import { useAppSdk } from '../../hooks/appSdk'; +import { SkeletonList } from '../../components/Skeleton'; const LedgerError = styled(Body2)` margin: 0.5rem 0; color: ${p => p.theme.accentRed}; `; +const TextContainer = styled.span` + flex-direction: column; + display: flex; + align-items: flex-start; +`; + +const Body2Secondary = styled(Body2)` + color: ${props => props.theme.textSecondary}; +`; + export const WalletVersion = () => { const { t } = useTranslation(); - const { experimental } = useAppContext(); + const sdk = useAppSdk(); const isLedger = useIsActiveWalletLedger(); const isKeystone = useIsActiveWalletKeystone(); - const { data: enableW5 } = useEnableW5(); - const wallet = useActiveStandardTonWallet(); + const currentWallet = useActiveStandardTonWallet(); + const connectedWallets = useWalletsState(); + const { mutateAsync: selectWallet, isLoading: isSelectWalletLoading } = useMutateActiveWallet(); + const navigate = useNavigate(); + const { mutateAsync: checkTouchId } = useCheckTouchId(); - const items = useMemo(() => { - const publicKey = Buffer.from(wallet.publicKey, 'hex'); - const list = [...WalletVersions]; + const { data: wallets } = useStandardTonWalletVersions( + currentWallet.publicKey, + currentWallet.network + ); + + const { mutateAsync: createWalletAsync, isLoading: isCreateWalletLoading } = + useCreateStandardTonWalletsByMnemonic(); + const { mutateAsync: renameWallet, isLoading: isRenameWalletLoading } = useMutateRenameWallet(); - if (experimental && enableW5) { - list.push(WalletVersionEnum.W5); + const onOpenWallet = async (address: Address) => { + if (address.toRawString() !== currentWallet.rawAddress) { + await selectWallet(address.toRawString()); } + navigate(AppRoute.home); + }; - return list.map(item => ({ - name: walletVersionText(item), - secondary: toShortValue( - getWalletAddress(publicKey, item, wallet.network).address.toString() - ), - icon: wallet.version === item ? : undefined, - action: () => {} - })); - }, [wallet, experimental, enableW5]); + const [editWalletNameNotificationPayload, setEditWalletNameNotificationPayload] = useState< + StandardTonWalletState | undefined + >(); + + const onAddWallet = async (w: { version: WalletVersionType; address: Address }) => { + const { mnemonic, password } = await getMnemonicAndPassword( + sdk, + currentWallet.id, + checkTouchId + ); + const newWallet = await createWalletAsync({ + mnemonic, + versions: [w.version], + password + }); + if (!newWallet) { + return; + } + setEditWalletNameNotificationPayload(newWallet[0]); + }; + + const onChangeName = async (args: { name: string; emoji: string; id: string }) => { + await renameWallet(args); + setEditWalletNameNotificationPayload(undefined); + }; + + if (!wallets) { + return ( + <> + + + + + + ); + } + + const isLoading = isSelectWalletLoading || isCreateWalletLoading || isRenameWalletLoading; return ( <> - + {!isLedger && !isKeystone && ( + + {wallets.map(wallet => { + const isWalletAdded = connectedWallets.some( + w => w.rawAddress === wallet.address.toRawString() + ); + + return ( + + + + {walletVersionText(wallet.version)} + + {toShortValue(formatAddress(wallet.address))} +  ·  + {toFormattedTonBalance(wallet.tonBalance)} TON + {wallet.hasJettons && + t('wallet_version_and_tokens')} + + + {isWalletAdded ? ( + + ) : ( + + )} + + + ); + })} + + )} {isLedger && {t('ledger_operation_not_supported')}} {isKeystone && {t('operation_not_supported')}} + ); }; + +const UpdateWalletNameNotification: FC<{ + isOpen: boolean; + onClose: (isAdded: { name: string; emoji: string; id: string }) => void; + wallet: StandardTonWalletState | undefined; +}> = ({ isOpen, onClose, wallet }) => { + return ( + + onClose({ name: wallet!.name, emoji: wallet!.emoji, id: wallet!.id }) + } + > + {() => ( + onClose({ ...val, id: wallet!.id })} + walletEmoji={wallet?.emoji || ''} + /> + )} + + ); +}; diff --git a/packages/uikit/src/state/asset.ts b/packages/uikit/src/state/asset.ts index f4b224a90..754a9e588 100644 --- a/packages/uikit/src/state/asset.ts +++ b/packages/uikit/src/state/asset.ts @@ -1,17 +1,33 @@ import { Address } from '@ton/core'; -import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; +import { BLOCKCHAIN_NAME, CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; import { Asset } from '@tonkeeper/core/dist/entries/crypto/asset/asset'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { AssetIdentification } from '@tonkeeper/core/dist/entries/crypto/asset/asset-identification'; import { isBasicAsset, packAssetId } from '@tonkeeper/core/dist/entries/crypto/asset/basic-asset'; -import { TON_ASSET, TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; -import { TonAsset, legacyTonAssetId } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; +import { + KNOWN_TON_ASSETS, + TON_ASSET, + TRON_USDT_ASSET +} from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { legacyTonAssetId, TonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; import BigNumber from 'bignumber.js'; import { useJettonList } from './jetton'; -import { useRate } from './rates'; +import { + getJettonsFiatAmount, + getTonFiatAmount, + tokenRate as getTokenRate, + useRate +} from './rates'; import { useTronBalances } from './tron/tron'; import { useWalletAccountInfo } from './wallet'; +import { JettonBalance } from '@tonkeeper/core/dist/tonApiV2'; +import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; +import { useAssets } from './home'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { QueryKey } from '../libs/queryKey'; +import { useAppContext } from '../hooks/appContext'; +import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance'; export function useUserAssetBalance< T extends AssetIdentification = AssetIdentification, @@ -96,3 +112,183 @@ export function useAssetAmountFiatEquivalent(assetAmount: AssetAmount): { : undefined }; } + +function tokenColor(tokenAddress: string) { + if (tokenAddress === 'TON') { + return '#0098EA'; + } + + const address = Address.parse(tokenAddress); + + if (address.equals(KNOWN_TON_ASSETS.jUSDT)) { + return '#2AAF86'; + } + + const addressId = Number('0x' + address.toRawString().slice(-10)); + + const restColors = [ + '#FF8585', + '#FFA970', + '#FFC95C', + '#85CC7A', + '#70A0FF', + '#6CCCF5', + '#AD89F5', + '#F57FF5', + '#F576B1', + '#293342' + ]; + + return restColors[addressId % restColors.length]; +} + +export const useWalletTotalBalance = (fiat: FiatCurrencies) => { + const [assets] = useAssets(); + const { data: tonRate } = useRate(CryptoCurrency.TON); + + const client = useQueryClient(); + return useQuery( + [QueryKey.total, fiat, assets, tonRate], + () => { + if (!assets) { + return new BigNumber(0); + } + return ( + getTonFiatAmount(client, fiat, assets) + // .plus(getTRC20FiatAmount(client, fiat, assets)) + .plus(getJettonsFiatAmount(client, fiat, assets)) + ); + }, + { enabled: !!assets && !!tonRate } + ); +}; + +export interface TokenMeta { + address: string; + name: string; + symbol: string; + color: string; + image: string; + balance: BigNumber; + price: number; +} + +export interface TokenDistribution { + percent: number; + fiatBalance: BigNumber; + meta: + | TokenMeta + | { + type: 'others'; + color: string; + tokens: TokenMeta[]; + }; +} + +export function useAssetsDistribution(maxGropusNumber = 10) { + const [assets] = useAssets(); + const { fiat } = useAppContext(); + const { data: tonRate } = useRate(CryptoCurrency.TON); + + const client = useQueryClient(); + return useQuery( + [QueryKey.distribution, fiat, assets, tonRate, maxGropusNumber], + () => { + if (!assets) { + return []; + } + + const ton: Omit = { + fiatBalance: getTonFiatAmount(client, fiat, assets), + meta: convertJettonToTokenMeta( + { isNative: true, balance: assets.ton.info.balance }, + getTokenRate(client, fiat, CryptoCurrency.TON)?.prices || 0 + ) + }; + + const tokensOmited: Omit[] = [ton].concat( + assets.ton.jettons.balances.map(b => { + const price = + getTokenRate(client, fiat, Address.parse(b.jetton.address).toString()) + ?.prices || 0; + const fiatBalance = shiftedDecimals(b.balance, b.jetton.decimals).multipliedBy( + price + ); + + return { + fiatBalance, + meta: convertJettonToTokenMeta(b, price) + }; + }) + ); + + const total = tokensOmited.reduce( + (acc, t) => t.fiatBalance.plus(acc), + new BigNumber(0) + ); + + tokensOmited.sort((a, b) => b.fiatBalance.minus(a.fiatBalance).toNumber()); + + const tokens: TokenDistribution[] = tokensOmited + .slice(0, maxGropusNumber - 1) + .map(t => ({ + ...t, + percent: t.fiatBalance + .dividedBy(total) + .multipliedBy(100) + .decimalPlaces(2) + .toNumber() + })); + + const includedPercent = tokens.reduce((acc, t) => t.percent + acc, 0); + const includedBalance = tokens.reduce( + (acc, t) => t.fiatBalance.plus(acc), + new BigNumber(0) + ); + + if (tokensOmited.length > maxGropusNumber) { + tokens.push({ + percent: new BigNumber(100 - includedPercent).decimalPlaces(2).toNumber(), + fiatBalance: total.minus(includedBalance), + meta: { + type: 'others', + color: '#9DA2A4', + tokens: tokensOmited + .slice(maxGropusNumber - 1) + .map(t => t.meta) as TokenMeta[] + } + }); + } + + return tokens; + }, + { enabled: !!assets && !!tonRate } + ); +} + +function convertJettonToTokenMeta( + asset: JettonBalance | { isNative: true; balance: number }, + price: number +): TokenMeta { + if ('isNative' in asset) { + return { + address: 'TON', + name: 'TON', + symbol: 'TON', + color: tokenColor('TON'), + image: 'https://wallet.tonkeeper.com/img/toncoin.svg', + price, + balance: new BigNumber(asset.balance) + }; + } + + return { + address: asset.jetton.address, + name: asset.jetton.name, + symbol: asset.jetton.symbol, + color: tokenColor(asset.jetton.address), + image: asset.jetton.image, + balance: new BigNumber(asset.balance), + price + }; +} diff --git a/packages/uikit/src/state/fiat.ts b/packages/uikit/src/state/fiat.ts index 88327905b..b14f2a353 100644 --- a/packages/uikit/src/state/fiat.ts +++ b/packages/uikit/src/state/fiat.ts @@ -5,11 +5,18 @@ import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; export const useUserFiat = () => { const sdk = useAppSdk(); - return useQuery([AppKey.FIAT], async () => { - return ( - (await sdk.storage.get(AppKey.FIAT)) || FiatCurrencies.USD - ); - }); + return useQuery( + [AppKey.FIAT], + async () => { + return ( + (await sdk.storage.get(AppKey.FIAT)) || + FiatCurrencies.USD + ); + }, + { + keepPreviousData: true + } + ); }; export const useMutateUserFiat = () => { diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index d78efaed2..ff2164324 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -150,6 +150,15 @@ export const getMnemonic = async ( walletId: string, checkTouchId: () => Promise ): Promise => { + const { mnemonic } = await getMnemonicAndPassword(sdk, walletId, checkTouchId); + return mnemonic; +}; + +export const getMnemonicAndPassword = async ( + sdk: IAppSdk, + walletId: string, + checkTouchId: () => Promise +): Promise<{ mnemonic: string[]; password?: string }> => { const wallet = await walletsStorage(sdk.storage).getWallet(walletId); if (!wallet || !('auth' in wallet)) { throw new Error('Unexpected auth method for wallet'); @@ -158,7 +167,14 @@ export const getMnemonic = async ( switch (wallet.auth.kind) { case 'password': { const password = await getPasswordByNotification(sdk); - return decryptWalletMnemonic(wallet as WalletState & { auth: AuthPassword }, password); + const mnemonic = await decryptWalletMnemonic( + wallet as WalletState & { auth: AuthPassword }, + password + ); + return { + password, + mnemonic + }; } case 'keychain': { if (!sdk.keychain) { @@ -170,8 +186,8 @@ export const getMnemonic = async ( throw new Error('Unexpected auth method for wallet, keychain'); } - const mnemonic = await sdk.keychain.getPassword(wallet.publicKey); - return mnemonic.split(' '); + const mnemonic = await sdk.keychain.getPassword(wallet.auth.keychainStoreKey); + return { mnemonic: mnemonic.split(' ') }; } default: throw new Error('Unexpected auth method'); diff --git a/packages/uikit/src/state/nft.ts b/packages/uikit/src/state/nft.ts index f938d30b8..d8f974c77 100644 --- a/packages/uikit/src/state/nft.ts +++ b/packages/uikit/src/state/nft.ts @@ -1,12 +1,24 @@ import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { getActiveWalletConfig } from '@tonkeeper/core/dist/service/wallet/configService'; import { useActiveWallet, useActiveWalletConfig, useMutateActiveWalletConfig } from './wallet'; import { NFT } from '@tonkeeper/core/dist/entries/nft'; -import { useTonenpointConfig } from './tonendpoint'; +import { DefaultRefetchInterval, useTonenpointConfig } from './tonendpoint'; import { ActiveWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; import { useTranslation } from '../hooks/translation'; +import { + AccountsApi, + BlockchainApi, + DNSApi, + DnsRecord, + NFTApi, + NftCollection, + NftItem +} from '@tonkeeper/core/dist/tonApiV2'; +import { QueryKey } from '../libs/queryKey'; +import { isTONDNSDomain } from '@tonkeeper/core/dist/utils/nft'; +import { useMemo } from 'react'; type NftWithCollectionId = Pick & { collection?: Pick['collection'], 'address'>; @@ -152,3 +164,135 @@ export const isUnverifiedNft = ( !config?.trustedNfts.includes(nft.collection?.address || nft.address) ); }; +export const useWalletNftList = () => { + const wallet = useActiveWallet(); + const { + api: { tonApiV2 } + } = useAppContext(); + + return useQuery( + [wallet.rawAddress, QueryKey.nft], + async () => { + const { nftItems } = await new AccountsApi(tonApiV2).getAccountNftItems({ + accountId: wallet.rawAddress, + offset: 0, + limit: 1000, + indirectOwnership: true + }); + return nftItems; + }, + { + refetchInterval: DefaultRefetchInterval, + refetchIntervalInBackground: true, + refetchOnWindowFocus: true, + keepPreviousData: true + } + ); +}; +export const useWalletFilteredNftList = () => { + const { data: nfts, ...rest } = useWalletNftList(); + const { data: walletConfig } = useActiveWalletConfig(); + + const filtered = useMemo(() => { + if (!nfts || !walletConfig) return undefined; + + return nfts.filter(item => { + const address = item.collection ? item.collection.address : item.address; + + if (isSpamNft(item, walletConfig)) { + return false; + } + + return !walletConfig?.hiddenNfts.includes(address); + }); + }, [nfts, walletConfig?.trustedNfts, walletConfig?.spamNfts, walletConfig?.hiddenNfts]); + + return { + data: filtered, + ...rest + }; +}; +export const useNftDNSLinkData = (nft: NFT) => { + const { + api: { tonApiV2 } + } = useAppContext(); + + return useQuery( + ['dns_link', nft?.address], + async () => { + const { dns: domainName } = nft; + if (!domainName) return null; + + try { + return await new DNSApi(tonApiV2).dnsResolve({ domainName }); + } catch (e) { + return null; + } + }, + { enabled: nft.dns != null } + ); +}; +const MINUTES_IN_YEAR = 60 * 60 * 24 * 366; +export const useNftDNSExpirationDate = (nft: NFT) => { + const { + api: { tonApiV2 } + } = useAppContext(); + + return useQuery(['dns_expiring', nft.address], async () => { + if (!nft.owner?.address || !nft.dns || !isTONDNSDomain(nft.dns)) { + return null; + } + + try { + const result = await new BlockchainApi(tonApiV2).execGetMethodForBlockchainAccount({ + accountId: nft.address, + methodName: 'get_last_fill_up_time' + }); + + const lastRefill = result?.decoded?.last_fill_up_time; + if (lastRefill && typeof lastRefill === 'number' && isFinite(lastRefill)) { + return new Date((lastRefill + MINUTES_IN_YEAR) * 1000); + } + + return null; + } catch (e) { + return null; + } + }); +}; +export const useNftCollectionData = (nftOrCollection: NftItem | string) => { + const { + api: { tonApiV2 } + } = useAppContext(); + + const collectionAddress = + typeof nftOrCollection === 'string' ? nftOrCollection : nftOrCollection.collection?.address; + + return useQuery( + [collectionAddress, QueryKey.nftCollection], + async () => { + if (!collectionAddress) return null; + + return new NFTApi(tonApiV2).getNftCollection({ + accountId: collectionAddress + }); + }, + { enabled: !!collectionAddress } + ); +}; +export const useNftItemData = (address?: string) => { + const { + api: { tonApiV2 } + } = useAppContext(); + + return useQuery( + [address, QueryKey.nft], + async () => { + const result = await new NFTApi(tonApiV2).getNftItemByAddress({ + accountId: address! + }); + return result; + }, + { enabled: address !== undefined } + ); +}; diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index 16598e9ef..e896247c2 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -28,7 +28,6 @@ import { signTonConnectOver } from './mnemonic'; import { getServerTime } from '@tonkeeper/core/dist/service/transfer/common'; import { isStandardTonWallet, StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; import { IStorage } from '@tonkeeper/core/dist/Storage'; -import { Network } from '@tonkeeper/core/dist/entries/network'; import { useActiveWallet, useWalletsState } from './wallet'; export const useAppTonConnectConnections = () => { @@ -174,12 +173,9 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => } else { connectionsToDisconnect = ( await Promise.all( - wallets.filter(isStandardTonWallet).map(w => - disconnectFromWallet(sdk.storage, connection, { - publicKey: w.publicKey, - network: Network.MAINNET - }) - ) + wallets + .filter(isStandardTonWallet) + .map(w => disconnectFromWallet(sdk.storage, connection, w)) ) ).flat(); } @@ -203,7 +199,7 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => const disconnectFromWallet = async ( storage: IStorage, connection: AccountConnection | 'all', - wallet: Pick + wallet: Pick ) => { let connections = await getAccountConnection(storage, wallet); const connectionsToDisconnect = connection === 'all' ? connections : [connection]; diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index ed6e2ad67..4e79eb409 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -1,51 +1,37 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { Address } from '@ton/core'; -import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { KNOWN_TON_ASSETS } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; -import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; -import { NFT } from '@tonkeeper/core/dist/entries/nft'; import { ActiveWalletConfig, isPasswordAuthWallet, isStandardTonWallet, + StandardTonWalletState, WalletId, WalletsState, - WalletState + WalletState, + WalletVersion, + WalletVersions } from '@tonkeeper/core/dist/entries/wallet'; -import { updateWalletProperty } from '@tonkeeper/core/dist/service/walletService'; import { - Account, - AccountsApi, - BlockchainApi, - DNSApi, - DnsRecord, - JettonBalance, - NFTApi, - NftCollection, - NftItem -} from '@tonkeeper/core/dist/tonApiV2'; -import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance'; -import { isTONDNSDomain } from '@tonkeeper/core/dist/utils/nft'; -import BigNumber from 'bignumber.js'; + createStandardTonWalletStateByMnemonic, + getWalletAddress, + updateWalletProperty +} from '@tonkeeper/core/dist/service/walletService'; +import { Account, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; -import { useAssets } from './home'; -import { - getJettonsFiatAmount, - getTonFiatAmount, - tokenRate as getTokenRate, - useRate -} from './rates'; import { DefaultRefetchInterval } from './tonendpoint'; import { getActiveWalletConfig, setActiveWalletConfig } from '@tonkeeper/core/dist/service/wallet/configService'; import { useMemo } from 'react'; -import { isSpamNft } from './nft'; import { useWalletsStorage } from '../hooks/useStorage'; import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; +import { AuthKeychain } from '@tonkeeper/core/dist/entries/password'; +import { mnemonicValidate } from '@ton/crypto'; +import { getPasswordByNotification } from './mnemonic'; +import { encrypt } from '@tonkeeper/core/dist/service/cryptoService'; +import { Network } from '@tonkeeper/core/dist/entries/network'; export const useActiveWalletQuery = () => { const storage = useWalletsStorage(); @@ -114,8 +100,96 @@ export const useMutateWalletsState = () => { }); }; +export const useCreateStandardTonWalletsByMnemonic = () => { + const sdk = useAppSdk(); + const { api } = useAppContext(); + const { mutateAsync: addWalletsToState } = useAddWalletsToStateMutation(); + const { mutateAsync: selectWallet } = useMutateActiveWallet(); + + return useMutation< + StandardTonWalletState[], + Error, + { + mnemonic: string[]; + password?: string; + versions: WalletVersion[]; + activateFirstWallet?: boolean; + } + >(async ({ mnemonic, password, versions, activateFirstWallet }) => { + const valid = await mnemonicValidate(mnemonic); + if (!valid) { + throw new Error('Mnemonic is not valid.'); + } + + if (sdk.keychain) { + const states = await Promise.all( + versions.map(version => + createStandardTonWalletStateByMnemonic(api, mnemonic, { + auth: { + kind: 'keychain' + }, + version + }) + ) + ); + + await sdk.keychain.setPassword( + (states[0].auth as AuthKeychain).keychainStoreKey, + mnemonic.join(' ') + ); + + await addWalletsToState(states); + if (activateFirstWallet) { + await selectWallet(states[0].id); + } + return states; + } + + if (!password) { + password = await getPasswordByNotification(sdk); + } + + const encryptedMnemonic = await encrypt(mnemonic.join(' '), password); + const states = await Promise.all( + versions.map(version => + createStandardTonWalletStateByMnemonic(api, mnemonic, { + auth: { + kind: 'password', + encryptedMnemonic + }, + version + }) + ) + ); + + await addWalletsToState(states); + if (activateFirstWallet) { + await selectWallet(states[0].id); + } + return states; + }); +}; + +export const useAddWalletToStateMutation = () => { + const ws = useWalletsStorage(); + const client = useQueryClient(); + return useMutation(async state => { + await ws.addWalletToState(state); + await client.invalidateQueries([QueryKey.account]); + }); +}; + +export const useAddWalletsToStateMutation = () => { + const ws = useWalletsStorage(); + const client = useQueryClient(); + return useMutation(async states => { + await ws.addWalletsToState(states); + await client.invalidateQueries([QueryKey.account]); + }); +}; + export const useWalletsState = () => { - return useWalletsStateQuery().data; + return useWalletsStateQuery().data!; }; export const useMutateDeleteAll = () => { @@ -139,11 +213,11 @@ export const useMutateLogOut = () => { }); }; -export const useMutateRenameWallet = (wallet: WalletState) => { +export const useMutateRenameWallet = () => { const sdk = useAppSdk(); const client = useQueryClient(); - return useMutation(async form => { + return useMutation(async form => { if (form.name !== undefined && form.name.length <= 0) { throw new Error('Missing name'); } @@ -153,7 +227,7 @@ export const useMutateRenameWallet = (wallet: WalletState) => { ...(form.name && { name: form.name }) }; - await updateWalletProperty(sdk.storage, wallet, formToUpdate); + await updateWalletProperty(sdk.storage, form.id, formToUpdate); await client.invalidateQueries([QueryKey.account]); }); }; @@ -164,7 +238,7 @@ export const useMutateWalletProperty = (clearWallet = false) => { const sdk = useAppSdk(); return useMutation>>(async props => { - await updateWalletProperty(sdk.storage, wallet, props); + await updateWalletProperty(sdk.storage, wallet.id, props); await client.invalidateQueries([QueryKey.account]); if (clearWallet) { await client.invalidateQueries([wallet.id]); @@ -218,319 +292,39 @@ export const useMutateActiveWalletConfig = () => { }); }; -export const useWalletNftList = () => { - const wallet = useActiveWallet(); - const { - api: { tonApiV2 } - } = useAppContext(); - - return useQuery( - [wallet.rawAddress, QueryKey.nft], +export const useStandardTonWalletVersions = (publicKey?: string, network = Network.MAINNET) => { + const { api, fiat } = useAppContext(); + return useQuery( + [QueryKey.walletVersions, publicKey, network], async () => { - const { nftItems } = await new AccountsApi(tonApiV2).getAccountNftItems({ - accountId: wallet.rawAddress, - offset: 0, - limit: 1000, - indirectOwnership: true - }); - return nftItems; - }, - { - refetchInterval: DefaultRefetchInterval, - refetchIntervalInBackground: true, - refetchOnWindowFocus: true, - keepPreviousData: true - } - ); -}; - -export const useWalletFilteredNftList = () => { - const { data: nfts, ...rest } = useWalletNftList(); - const { data: walletConfig } = useActiveWalletConfig(); - - const filtered = useMemo(() => { - if (!nfts || !walletConfig) return undefined; - - return nfts.filter(item => { - const address = item.collection ? item.collection.address : item.address; - - if (isSpamNft(item, walletConfig)) { - return false; + if (!publicKey) { + return undefined; } + const versions = WalletVersions.map(v => getWalletAddress(publicKey, v, network)); - return !walletConfig?.hiddenNfts.includes(address); - }); - }, [nfts, walletConfig?.trustedNfts, walletConfig?.spamNfts, walletConfig?.hiddenNfts]); - - return { - data: filtered, - ...rest - }; -}; - -export const useNftDNSLinkData = (nft: NFT) => { - const { - api: { tonApiV2 } - } = useAppContext(); - - return useQuery( - ['dns_link', nft?.address], - async () => { - const { dns: domainName } = nft; - if (!domainName) return null; - - try { - return await new DNSApi(tonApiV2).dnsResolve({ domainName }); - } catch (e) { - return null; - } - }, - { enabled: nft.dns != null } - ); -}; - -const MINUTES_IN_YEAR = 60 * 60 * 24 * 366; -export const useNftDNSExpirationDate = (nft: NFT) => { - const { - api: { tonApiV2 } - } = useAppContext(); - - return useQuery(['dns_expiring', nft.address], async () => { - if (!nft.owner?.address || !nft.dns || !isTONDNSDomain(nft.dns)) { - return null; - } - - try { - const result = await new BlockchainApi(tonApiV2).execGetMethodForBlockchainAccount({ - accountId: nft.address, - methodName: 'get_last_fill_up_time' - }); - - const lastRefill = result?.decoded?.last_fill_up_time; - if (lastRefill && typeof lastRefill === 'number' && isFinite(lastRefill)) { - return new Date((lastRefill + MINUTES_IN_YEAR) * 1000); - } - - return null; - } catch (e) { - return null; - } - }); -}; - -export const useNftCollectionData = (nftOrCollection: NftItem | string) => { - const { - api: { tonApiV2 } - } = useAppContext(); - - const collectionAddress = - typeof nftOrCollection === 'string' ? nftOrCollection : nftOrCollection.collection?.address; - - return useQuery( - [collectionAddress, QueryKey.nftCollection], - async () => { - if (!collectionAddress) return null; - - return new NFTApi(tonApiV2).getNftCollection({ - accountId: collectionAddress - }); - }, - { enabled: !!collectionAddress } - ); -}; - -export const useNftItemData = (address?: string) => { - const { - api: { tonApiV2 } - } = useAppContext(); - - return useQuery( - [address, QueryKey.nft], - async () => { - const result = await new NFTApi(tonApiV2).getNftItemByAddress({ - accountId: address! + const response = await new AccountsApi(api.tonApiV2).getAccounts({ + getAccountsRequest: { accountIds: versions.map(v => v.address.toRawString()) } }); - return result; - }, - { enabled: address !== undefined } - ); -}; - -export const useWalletTotalBalance = (fiat: FiatCurrencies) => { - const [assets] = useAssets(); - const { data: tonRate } = useRate(CryptoCurrency.TON); - - const client = useQueryClient(); - return useQuery( - [QueryKey.total, fiat, assets, tonRate], - () => { - if (!assets) { - return new BigNumber(0); - } - return ( - getTonFiatAmount(client, fiat, assets) - // .plus(getTRC20FiatAmount(client, fiat, assets)) // TODO: ENABLE TRON - .plus(getJettonsFiatAmount(client, fiat, assets)) - ); - }, - { enabled: !!assets && !!tonRate } - ); -}; - -export interface TokenMeta { - address: string; - name: string; - symbol: string; - color: string; - image: string; - balance: BigNumber; - price: number; -} - -export interface TokenDistribution { - percent: number; - fiatBalance: BigNumber; - meta: - | TokenMeta - | { - type: 'others'; - color: string; - tokens: TokenMeta[]; - }; -} - -export function useAssetsDistribution(maxGropusNumber = 10) { - const [assets] = useAssets(); - const { fiat } = useAppContext(); - const { data: tonRate } = useRate(CryptoCurrency.TON); - const client = useQueryClient(); - return useQuery( - [QueryKey.distribution, fiat, assets, tonRate, maxGropusNumber], - () => { - if (!assets) { - return []; - } - - const ton: Omit = { - fiatBalance: getTonFiatAmount(client, fiat, assets), - meta: convertJettonToTokenMeta( - { isNative: true, balance: assets.ton.info.balance }, - getTokenRate(client, fiat, CryptoCurrency.TON)?.prices || 0 + const walletsJettonsBalances = await Promise.all( + versions.map(v => + new AccountsApi(api.tonApiV2).getAccountJettonsBalances({ + accountId: v.address.toRawString(), + currencies: [fiat] + }) ) - }; - - const tokensOmited: Omit[] = [ton].concat( - assets.ton.jettons.balances.map(b => { - const price = - getTokenRate(client, fiat, Address.parse(b.jetton.address).toString()) - ?.prices || 0; - const fiatBalance = shiftedDecimals(b.balance, b.jetton.decimals).multipliedBy( - price - ); - - return { - fiatBalance, - meta: convertJettonToTokenMeta(b, price) - }; - }) - ); - - const total = tokensOmited.reduce( - (acc, t) => t.fiatBalance.plus(acc), - new BigNumber(0) - ); - - tokensOmited.sort((a, b) => b.fiatBalance.minus(a.fiatBalance).toNumber()); - - const tokens: TokenDistribution[] = tokensOmited - .slice(0, maxGropusNumber - 1) - .map(t => ({ - ...t, - percent: t.fiatBalance - .dividedBy(total) - .multipliedBy(100) - .decimalPlaces(2) - .toNumber() - })); - - const includedPercent = tokens.reduce((acc, t) => t.percent + acc, 0); - const includedBalance = tokens.reduce( - (acc, t) => t.fiatBalance.plus(acc), - new BigNumber(0) ); - if (tokensOmited.length > maxGropusNumber) { - tokens.push({ - percent: new BigNumber(100 - includedPercent).decimalPlaces(2).toNumber(), - fiatBalance: total.minus(includedBalance), - meta: { - type: 'others', - color: '#9DA2A4', - tokens: tokensOmited - .slice(maxGropusNumber - 1) - .map(t => t.meta) as TokenMeta[] - } - }); - } - - return tokens; + return versions.map((v, index) => ({ + ...v, + tonBalance: response.accounts[index].balance, + hasJettons: walletsJettonsBalances[index].balances.some( + b => b.price?.prices && Number(b.balance) > 0 + ) + })); }, - { enabled: !!assets && !!tonRate } + { + keepPreviousData: true + } ); -} -function tokenColor(tokenAddress: string) { - if (tokenAddress === 'TON') { - return '#0098EA'; - } - - const address = Address.parse(tokenAddress); - - if (address.equals(KNOWN_TON_ASSETS.jUSDT)) { - return '#2AAF86'; - } - - const addressId = Number('0x' + address.toRawString().slice(-10)); - - const restColors = [ - '#FF8585', - '#FFA970', - '#FFC95C', - '#85CC7A', - '#70A0FF', - '#6CCCF5', - '#AD89F5', - '#F57FF5', - '#F576B1', - '#293342' - ]; - - return restColors[addressId % restColors.length]; -} - -function convertJettonToTokenMeta( - asset: JettonBalance | { isNative: true; balance: number }, - price: number -): TokenMeta { - if ('isNative' in asset) { - return { - address: 'TON', - name: 'TON', - symbol: 'TON', - color: tokenColor('TON'), - image: 'https://wallet.tonkeeper.com/img/toncoin.svg', - price, - balance: new BigNumber(asset.balance) - }; - } - - return { - address: asset.jetton.address, - name: asset.jetton.name, - symbol: asset.jetton.symbol, - color: tokenColor(asset.jetton.address), - image: asset.jetton.image, - balance: new BigNumber(asset.balance), - price - }; -} +}; From 83f0e2e45ccfd95b18262f24686648327e4c1389 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 11 Jul 2024 15:49:56 +0200 Subject: [PATCH 03/53] chore: pro subscription minor changes --- packages/core/src/service/proService.ts | 1 + packages/uikit/src/components/settings/ProSettings.tsx | 4 ++-- packages/uikit/src/state/pro.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index bc26afc60..0fc197478 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -53,6 +53,7 @@ export const getProState = async ( try { return await loadProState(storage, wallet); } catch (e) { + console.error(e); return { subscription: toEmptySubscription(), hasWalletAuthCookie: false, diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index b36be43ad..d9e47dd3b 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -15,7 +15,7 @@ import { useProLogout, useProPlans, useProState, - useSelectWalletMutation, + useSelectWalletForProMutation, useWaitInvoiceMutation } from '../../state/pro'; import { useWalletsState, useWalletState } from '../../state/wallet'; @@ -78,7 +78,7 @@ const SelectLabel = styled(Label1)` const SelectWallet: FC<{ onClose: () => void }> = ({ onClose }) => { const { t } = useTranslation(); - const { mutateAsync, error } = useSelectWalletMutation(); + const { mutateAsync, error } = useSelectWalletForProMutation(); useNotifyError(error); const wallets = useWalletsState(); diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index 4d92f4ab9..c6fdd80d0 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -52,7 +52,7 @@ export const useProState = () => { }); }; -export const useSelectWalletMutation = () => { +export const useSelectWalletForProMutation = () => { const sdk = useAppSdk(); const client = useQueryClient(); const { api } = useAppContext(); From 9ded9c031cac6eebdff0145b626f362bf43b1679 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 11 Jul 2024 18:13:40 +0200 Subject: [PATCH 04/53] chore: merge v5r1 --- packages/core/src/entries/wallet.ts | 28 ++++++------------- packages/core/src/service/proService.ts | 25 +++++++++++++---- packages/core/src/service/signerService.ts | 9 ++---- packages/core/src/service/transfer/common.ts | 4 +-- .../src/service/transfer/multiSendService.ts | 2 +- .../src/service/wallet/contractService.ts | 2 +- packages/core/src/service/walletService.ts | 8 +++--- .../desktop/multi-send/MultiSendTable.tsx | 9 ++---- packages/uikit/src/pages/settings/Dev.tsx | 4 --- 9 files changed, 39 insertions(+), 52 deletions(-) diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index e4046be4c..5bfd80809 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -9,19 +9,23 @@ export enum WalletVersion { V3R2 = 1, V4R1 = 2, V4R2 = 3, - V5beta = 4, + V5_BETA = 4, V5R1 = 5 } +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 defaultWalletVersion = WalletVersion.V4R2; +export const defaultWalletVersion = WalletVersion.V5R1; export const walletVersionText = (version: WalletVersion) => { switch (version) { @@ -31,7 +35,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'; @@ -40,22 +44,6 @@ export const walletVersionText = (version: WalletVersion) => { } }; -export const walletVersionFromText = (value: string) => { - switch (value.toUpperCase()) { - case 'V3R1': - return WalletVersion.V3R1; - case 'V3R2': - return WalletVersion.V3R2; - case 'V4R2': - return WalletVersion.V4R2; - case 'W5': - case 'W5R1': - return WalletVersion.W5; - default: - throw new Error('Unsupported version'); - } -}; - /** * @deprecated */ diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 0fc197478..aeb08ad31 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -11,11 +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 { - isStandardTonWallet, - StandardTonWalletState, - walletVersionFromText -} from '../entries/wallet'; +import { isStandardTonWallet, StandardTonWalletState, WalletVersion } from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; import { FiatCurrencies as FiatCurrenciesGenerated, @@ -73,6 +69,23 @@ 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: StandardTonWalletState @@ -91,7 +104,7 @@ export const loadProState = async ( w => w.publicKey === user.pub_key && user.version && - w.version === walletVersionFromText(user.version) + w.version === walletVersionFromProServiceDTO(user.version) ); if (!actualWallet) { throw new Error('Unknown wallet'); diff --git a/packages/core/src/service/signerService.ts b/packages/core/src/service/signerService.ts index 56b8f1f31..0290ea438 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 { StandardTonWalletState, WalletVersion } from '../entries/wallet'; +import { isW5Version, StandardTonWalletState, 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'; @@ -79,10 +79,7 @@ export const publishSignerMessage = async ( const message = Cell.fromBase64(messageBase64).asBuilder(); const transfer = beginCell(); - if ( - walletState.active.version === WalletVersion.V5beta || - walletState.active.version === WalletVersion.V5R1 - ) { + if (isW5Version(walletState.version)) { transfer.storeBuilder(message).storeBuffer(signature); } else { transfer.storeBuffer(signature).storeBuilder(message); diff --git a/packages/core/src/service/transfer/common.ts b/packages/core/src/service/transfer/common.ts index 670c80941..2bbd03839 100644 --- a/packages/core/src/service/transfer/common.ts +++ b/packages/core/src/service/transfer/common.ts @@ -17,10 +17,8 @@ import { BaseSigner } from '../../entries/signer'; import { StandardTonWalletState } from '../../entries/wallet'; import { Account, AccountsApi, LiteServerApi, WalletApi } from '../../tonApiV2'; import { walletContractFromState } from '../wallet/contractService'; -import { WalletState } from '../../entries/wallet'; import { NotEnoughBalanceError } from '../../errors/NotEnoughBalanceError'; -import { Account, AccountsApi, LiteServerApi, WalletApi } from '../../tonApiV2'; -import { WalletContract, walletContractFromState } from '../wallet/contractService'; +import { WalletContract } from '../wallet/contractService'; export enum SendMode { CARRY_ALL_REMAINING_BALANCE = 128, diff --git a/packages/core/src/service/transfer/multiSendService.ts b/packages/core/src/service/transfer/multiSendService.ts index ccb80a976..83af94f96 100644 --- a/packages/core/src/service/transfer/multiSendService.ts +++ b/packages/core/src/service/transfer/multiSendService.ts @@ -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, diff --git a/packages/core/src/service/wallet/contractService.ts b/packages/core/src/service/wallet/contractService.ts index c03ebb593..a559d943c 100644 --- a/packages/core/src/service/wallet/contractService.ts +++ b/packages/core/src/service/wallet/contractService.ts @@ -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 diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index cbdc7786f..09deb20ff 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -10,13 +10,14 @@ import { APIConfig } from '../entries/apis'; import { Network } from '../entries/network'; import { AuthKeychain, AuthPassword } from '../entries/password'; import { + defaultWalletVersion, StandardTonWalletState, TonWalletState, WalletId, WalletState, WalletVersion, WalletVersions -} from '../entries/wallet'; +} from "../entries/wallet"; import { WalletApi } from '../tonApiV2'; import { walletContract } from './wallet/contractService'; import { BLOCKCHAIN_NAME } from '../entries/crypto'; @@ -70,7 +71,7 @@ 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 }; @@ -131,8 +132,7 @@ const findWalletAddress = async ( }); return { rawAddress: contact.address.toRawString(), - friendlyAddress: contact.address.toString(), - version: WalletVersion.V5R1 + version: defaultWalletVersion }; }; diff --git a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx index 30dd22096..25f3f0137 100644 --- a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx +++ b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx @@ -2,12 +2,10 @@ 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 { StandardTonWalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { isW5Version, StandardTonWalletState } 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'; @@ -342,10 +340,7 @@ const MultiSendAddMore: FC<{ ); } - if ( - wallet.active.version !== WalletVersion.V5beta && - wallet.active.version !== WalletVersion.V5R1 - ) { + if (isW5Version(wallet.version)) { return ( {t('multi_send_maximum_reached')} diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index 8fdc36b6e..c41ce5caf 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -3,12 +3,8 @@ import React, { useMemo } from 'react'; import { InnerBody } from '../../components/Body'; import { SubHeader } from '../../components/SubHeader'; import { SettingsItem, SettingsList } from '../../components/settings/SettingsList'; -import { useAppContext } from '../../hooks/appContext'; -import { useWalletContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; -import { useEnableW5, useEnableW5Mutation } from '../../state/experemental'; import { useActiveWallet, useMutateWalletProperty } from '../../state/wallet'; -import { useMutateWalletProperty } from '../../state/wallet'; export const DevSettings = React.memo(() => { const { t } = useTranslation(); From fd3afbc45adad39737afed7a217e11310f771a66 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 12 Jul 2024 11:24:09 +0200 Subject: [PATCH 05/53] chore: eslint fixes --- apps/extension/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index ae65279fb..86a7017ac 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -41,7 +41,7 @@ import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/imp import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; -import { useActiveWallet, useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; +import { useActiveWalletQuery, useWalletsStateQuery } 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 { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; @@ -52,7 +52,7 @@ import { TonConnectSubscription } from './components/TonConnectSubscription'; import { connectToBackground } from './event'; import { ExtensionAppSdk } from './libs/appSdk'; import { useAnalytics, useAppWidth } from './libs/hooks'; -import { useMutateUserLanguage, useUserLanguage } from "@tonkeeper/uikit/dist/state/language"; +import { useMutateUserLanguage } from "@tonkeeper/uikit/dist/state/language"; const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import')); const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings')); From 6eb90cf5bc5890fef921618f992a3d5686219c55 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 12 Jul 2024 18:29:33 +0200 Subject: [PATCH 06/53] fix: resolve merge conflicts --- apps/desktop/src/app/App.tsx | 18 +++++++---- apps/extension/src/App.tsx | 7 ++-- apps/twa/src/App.tsx | 7 ++-- apps/web/src/App.tsx | 7 ++-- packages/core/src/entries/wallet.ts | 2 -- .../src/service/wallet/contractService.ts | 2 +- packages/core/src/service/walletService.ts | 32 +++++++++---------- .../components/create/ChoseWalletVersions.tsx | 3 +- packages/uikit/src/hooks/appContext.ts | 9 ++++-- packages/uikit/src/pages/import/Create.tsx | 6 ++-- packages/uikit/src/pages/signer/LinkPage.tsx | 4 +-- packages/uikit/src/state/signer.ts | 4 +-- packages/uikit/src/state/tonendpoint.ts | 4 +++ packages/uikit/src/state/wallet.ts | 18 +++++++---- 14 files changed, 70 insertions(+), 53 deletions(-) diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 7b06391a2..5f8e15a10 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -1,8 +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 { AuthState } from '@tonkeeper/core/dist/entries/password'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { getApiConfig, Network } from '@tonkeeper/core/dist/entries/network'; +import { WalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; import ConnectLedgerNotification from '@tonkeeper/uikit/dist/components/ConnectLedgerNotification'; import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; @@ -49,7 +48,7 @@ import { useRecommendations } from '@tonkeeper/uikit/dist/hooks/browser/useRecom import { useLock } from '@tonkeeper/uikit/dist/hooks/lock'; import { StorageContext } from '@tonkeeper/uikit/dist/hooks/storage'; import { I18nContext, TranslationContext } from '@tonkeeper/uikit/dist/hooks/translation'; -import { AppProRoute, AppRoute, any } from '@tonkeeper/uikit/dist/libs/routes'; +import { any, AppProRoute, AppRoute } from '@tonkeeper/uikit/dist/libs/routes'; import { Unlock } from '@tonkeeper/uikit/dist/pages/home/Unlock'; import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotification'; import ImportRouter from '@tonkeeper/uikit/dist/pages/import'; @@ -58,17 +57,21 @@ import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProv import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; import { useCanPromptTouchId } from '@tonkeeper/uikit/dist/state/password'; import { useProBackupState } from '@tonkeeper/uikit/dist/state/pro'; -import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { + isV5R1Enabled, + useTonendpoint, + useTonenpointConfig +} from '@tonkeeper/uikit/dist/state/tonendpoint'; import { useActiveWalletQuery, useWalletsStateQuery } from '@tonkeeper/uikit/dist/state/wallet'; import { Container, GlobalStyleCss } from '@tonkeeper/uikit/dist/styles/globalStyle'; import { FC, Suspense, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { + createMemoryRouter, Outlet, Route, RouterProvider, Routes, - createMemoryRouter, useLocation, useNavigate } from 'react-router-dom'; @@ -313,7 +316,8 @@ export const Loader: FC = () => { env: { tgAuthBotId: REACT_APP_TG_BOT_ID, stonfiReferralAddress: REACT_APP_STONFI_REFERRAL_ADDRESS - } + }, + defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 86a7017ac..d95934bde 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { localizationFrom } from '@tonkeeper/core/dist/entries/language'; import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network'; -import { WalletState } from "@tonkeeper/core/dist/entries/wallet"; +import { WalletState, 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'; @@ -40,7 +40,7 @@ import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotif import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/import/Initialize'; import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; -import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; import React, { FC, PropsWithChildren, Suspense, useEffect, useMemo } from 'react'; @@ -213,7 +213,8 @@ export const Loader: FC = React.memo(() => { extension: true, proFeatures: false, hideQrScanner: true, - hideSigner: true + hideSigner: true, + defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index e499cc425..018e27b0c 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -1,6 +1,6 @@ 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 { WalletState, 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'; @@ -36,7 +36,7 @@ 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 { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; import { defaultTheme } from '@tonkeeper/uikit/dist/styles/defaultTheme'; import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; @@ -254,7 +254,8 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { hideBrowser: true, hideSigner: !showQrScan, hideKeystone: !showQrScan, - hideQrScanner: !showQrScan + hideQrScanner: !showQrScan, + defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 743b7d476..0bb749107 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 { WalletState, 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'; @@ -38,7 +38,7 @@ import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/imp import { useKeyboardHeight } from '@tonkeeper/uikit/dist/pages/import/hooks'; import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; -import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; import React, { FC, PropsWithChildren, Suspense, useEffect, useMemo } from 'react'; @@ -234,7 +234,8 @@ export const Loader: FC = () => { standalone, extension: false, proFeatures: false, - ios + ios, + defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index 5bfd80809..c4616ef93 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -25,8 +25,6 @@ export const WalletVersions = [ WalletVersion.V5R1 ]; -export const defaultWalletVersion = WalletVersion.V5R1; - export const walletVersionText = (version: WalletVersion) => { switch (version) { case WalletVersion.V3R1: diff --git a/packages/core/src/service/wallet/contractService.ts b/packages/core/src/service/wallet/contractService.ts index a559d943c..20dbe634a 100644 --- a/packages/core/src/service/wallet/contractService.ts +++ b/packages/core/src/service/wallet/contractService.ts @@ -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: diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index e26fda7bb..3b01a010d 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -3,21 +3,19 @@ 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 { IStorage } from '../Storage'; import { APIConfig } from '../entries/apis'; import { Network } from '../entries/network'; import { AuthKeychain, AuthPassword } from '../entries/password'; import { - defaultWalletVersion, StandardTonWalletState, TonWalletState, WalletId, WalletState, WalletVersion, WalletVersions -} from "../entries/wallet"; +} from '../entries/wallet'; import { WalletApi } from '../tonApiV2'; import { walletContract } from './wallet/contractService'; import { BLOCKCHAIN_NAME } from '../entries/crypto'; @@ -26,7 +24,7 @@ import { emojis } from '../utils/emojis'; import { formatAddress } from '../utils/common'; export const createStandardTonWalletStateByMnemonic = async ( - api: APIConfig, + appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, mnemonic: string[], options: { version?: WalletVersion; @@ -39,7 +37,12 @@ export const createStandardTonWalletStateByMnemonic = async ( const publicKey = keyPair.publicKey.toString('hex'); - const address = await findWalletAddress(api, publicKey, options.version, options.network); + const address = await findWalletAddress( + appContext, + publicKey, + options.version, + options.network + ); let walletAuth: AuthPassword | AuthKeychain; if (options.auth.kind === 'keychain') { @@ -88,7 +91,7 @@ const findWalletVersion = (interfaces?: string[]): WalletVersion => { }; const findWalletAddress = async ( - api: APIConfig, + appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, publicKey: string, version?: WalletVersion, network?: Network @@ -102,7 +105,7 @@ const findWalletAddress = async ( } try { - const result = await new WalletApi(api.tonApiV2).getWalletsByPublicKey({ + const result = await new WalletApi(appContext.api.tonApiV2).getWalletsByPublicKey({ publicKey: publicKey }); @@ -126,13 +129,10 @@ const findWalletAddress = async ( console.warn(e); } - const contact = WalletContractV5R1.create({ - workChain: 0, - publicKey: Buffer.from(publicKey, 'hex') - }); + const contact = walletContract(Buffer.from(publicKey, 'hex'), appContext.defaultWalletVersion); return { rawAddress: contact.address.toRawString(), - version: defaultWalletVersion + version: appContext.defaultWalletVersion }; }; @@ -181,7 +181,7 @@ export const updateWalletProperty = async ( }; export const walletStateFromSignerQr = async ( - api: APIConfig, + appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, qrCode: string ): Promise => { if (!qrCode.startsWith('tonkeeper://signer')) { @@ -201,7 +201,7 @@ export const walletStateFromSignerQr = async ( const publicKey = pk; - const active = await findWalletAddress(api, publicKey, config.flags?.disable_v5r1 ?? true); + const active = await findWalletAddress(appContext, publicKey); return { type: 'standard', @@ -218,11 +218,11 @@ export const walletStateFromSignerQr = async ( }; export const walletStateFromSignerDeepLink = async ( - api: APIConfig, + appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, publicKey: string, name: string | null ): Promise => { - const active = await findWalletAddress(api, publicKey); + const active = await findWalletAddress(appContext, publicKey); return { publicKey, diff --git a/packages/uikit/src/components/create/ChoseWalletVersions.tsx b/packages/uikit/src/components/create/ChoseWalletVersions.tsx index fc80ab551..5e9a9e395 100644 --- a/packages/uikit/src/components/create/ChoseWalletVersions.tsx +++ b/packages/uikit/src/components/create/ChoseWalletVersions.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { Body1, Body2, H2, Label1 } from '../Text'; import { useTranslation } from '../../hooks/translation'; import { - defaultWalletVersion, WalletVersion, WalletVersions, walletVersionText @@ -18,6 +17,7 @@ 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; @@ -71,6 +71,7 @@ export const ChoseWalletVersions: FC<{ isLoading?: boolean; }> = ({ mnemonic, onSubmit, onBack, isLoading }) => { const { t } = useTranslation(); + const { defaultWalletVersion } = useAppContext(); const [publicKey, setPublicKey] = useState(undefined); const { data: wallets } = useStandardTonWalletVersions(publicKey); diff --git a/packages/uikit/src/hooks/appContext.ts b/packages/uikit/src/hooks/appContext.ts index 5716b6afa..9a02fd994 100644 --- a/packages/uikit/src/hooks/appContext.ts +++ b/packages/uikit/src/hooks/appContext.ts @@ -2,12 +2,13 @@ import { APIConfig } from '@tonkeeper/core/dist/entries/apis'; import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; 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; @@ -28,6 +29,7 @@ export interface IAppContext { tgAuthBotId: string; stonfiReferralAddress: string; }; + defaultWalletVersion: WalletVersion; } export const AppContext = React.createContext({ @@ -42,7 +44,8 @@ export const AppContext = React.createContext({ extension: false, ios: false, proFeatures: false, - hideQrScanner: false + hideQrScanner: false, + defaultWalletVersion: WalletVersion.V5R1 }); export const useAppContext = () => { diff --git a/packages/uikit/src/pages/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index 0714da21e..c2c17f12b 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -15,13 +15,13 @@ import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { defaultWalletVersion, StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; import { useCreateStandardTonWalletsByMnemonic, useWalletsState } from '../../state/wallet'; const Create = () => { const sdk = useAppSdk(); const { t } = useTranslation(); - const { config } = useAppContext(); + const { defaultWalletVersion } = useAppContext(); const { mutateAsync: createWalletsAsync, isLoading: isCreateWalletLoading, @@ -69,7 +69,7 @@ const Create = () => { } return resetCreateWallets; - }, [authExists, createdPassword, mnemonic, checked, createWalletsAsync]); + }, [authExists, createdPassword, mnemonic, checked, createWalletsAsync, defaultWalletVersion]); if (!mnemonic) { return } title={t('create_wallet_generating')} />; diff --git a/packages/uikit/src/pages/signer/LinkPage.tsx b/packages/uikit/src/pages/signer/LinkPage.tsx index fa5d654ec..8e497233e 100644 --- a/packages/uikit/src/pages/signer/LinkPage.tsx +++ b/packages/uikit/src/pages/signer/LinkPage.tsx @@ -13,7 +13,7 @@ const useAddWalletMutation = () => { const sdk = useAppSdk(); const walletsStorage = useWalletsStorage(); const client = useQueryClient(); - const { api, config } = useAppContext(); + const context = useAppContext(); const navigate = useNavigate(); return useMutation( @@ -21,7 +21,7 @@ const useAddWalletMutation = () => { if (publicKey === null) { sdk.topMessage('Missing public key'); } else { - const state = await walletStateFromSignerDeepLink(api, publicKey, name, config); + const state = await walletStateFromSignerDeepLink(context, publicKey, name); await walletsStorage.addWalletToState(state); await client.invalidateQueries([QueryKey.account]); } diff --git a/packages/uikit/src/state/signer.ts b/packages/uikit/src/state/signer.ts index 280313117..b43052797 100644 --- a/packages/uikit/src/state/signer.ts +++ b/packages/uikit/src/state/signer.ts @@ -10,12 +10,12 @@ import { useWalletsStorage } from '../hooks/useStorage'; export const usePairSignerMutation = () => { const sdk = useAppSdk(); const walletsStorage = useWalletsStorage(); - const { api, config } = useAppContext(); + const context = useAppContext(); const client = useQueryClient(); const navigate = useNavigate(); return useMutation(async qrCode => { try { - const state = await walletStateFromSignerQr(api, qrCode, config); + const state = await walletStateFromSignerQr(context, qrCode); await walletsStorage.addWalletToState(state); diff --git a/packages/uikit/src/state/tonendpoint.ts b/packages/uikit/src/state/tonendpoint.ts index 068466849..0ff24d16d 100644 --- a/packages/uikit/src/state/tonendpoint.ts +++ b/packages/uikit/src/state/tonendpoint.ts @@ -45,6 +45,10 @@ export const useTonenpointConfig = (tonendpoint: Tonendpoint) => { ); }; +export function isV5R1Enabled(config: TonendpointConfig) { + return config.flags?.disable_v5r1 === false; +} + export const DefaultRefetchInterval = 60000; // 60 sec export const useTonendpointBuyMethods = () => { diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 4e79eb409..518ae31cd 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -3,6 +3,7 @@ import { ActiveWalletConfig, isPasswordAuthWallet, isStandardTonWallet, + isW5Version, StandardTonWalletState, WalletId, WalletsState, @@ -19,7 +20,7 @@ import { Account, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; -import { DefaultRefetchInterval } from './tonendpoint'; +import { DefaultRefetchInterval, isV5R1Enabled } from './tonendpoint'; import { getActiveWalletConfig, setActiveWalletConfig @@ -102,7 +103,7 @@ export const useMutateWalletsState = () => { export const useCreateStandardTonWalletsByMnemonic = () => { const sdk = useAppSdk(); - const { api } = useAppContext(); + const context = useAppContext(); const { mutateAsync: addWalletsToState } = useAddWalletsToStateMutation(); const { mutateAsync: selectWallet } = useMutateActiveWallet(); @@ -124,7 +125,7 @@ export const useCreateStandardTonWalletsByMnemonic = () => { if (sdk.keychain) { const states = await Promise.all( versions.map(version => - createStandardTonWalletStateByMnemonic(api, mnemonic, { + createStandardTonWalletStateByMnemonic(context, mnemonic, { auth: { kind: 'keychain' }, @@ -152,7 +153,7 @@ export const useCreateStandardTonWalletsByMnemonic = () => { const encryptedMnemonic = await encrypt(mnemonic.join(' '), password); const states = await Promise.all( versions.map(version => - createStandardTonWalletStateByMnemonic(api, mnemonic, { + createStandardTonWalletStateByMnemonic(context, mnemonic, { auth: { kind: 'password', encryptedMnemonic @@ -293,14 +294,17 @@ export const useMutateActiveWalletConfig = () => { }; export const useStandardTonWalletVersions = (publicKey?: string, network = Network.MAINNET) => { - const { api, fiat } = useAppContext(); + const { api, fiat, config } = useAppContext(); + const isV5Enabled = isV5R1Enabled(config); return useQuery( - [QueryKey.walletVersions, publicKey, network], + [QueryKey.walletVersions, publicKey, network, isV5Enabled], async () => { if (!publicKey) { return undefined; } - const versions = WalletVersions.map(v => getWalletAddress(publicKey, v, network)); + const versions = WalletVersions.filter(v => isV5Enabled || !isW5Version(v)).map(v => + getWalletAddress(publicKey, v, network) + ); const response = await new AccountsApi(api.tonApiV2).getAccounts({ getAccountsRequest: { accountIds: versions.map(v => v.address.toRawString()) } From 0412ea3967e3c07f20103348277dfd49344fab63 Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 16 Jul 2024 14:25:36 +0200 Subject: [PATCH 07/53] fix: added enable v5 dev flag --- apps/desktop/src/app/App.tsx | 8 +++- apps/extension/src/App.tsx | 6 ++- apps/twa/src/App.tsx | 6 ++- apps/web/src/App.tsx | 7 +++- packages/core/src/Keys.ts | 1 + packages/uikit/src/pages/settings/Dev.tsx | 10 ++++- packages/uikit/src/state/dev.ts | 39 +++++++++++++++++++ .../uikit/src/state/swap/useSwapsConfig.ts | 2 +- packages/uikit/src/state/wallet.ts | 4 +- 9 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 packages/uikit/src/state/dev.ts diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 5f8e15a10..8690424d2 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -84,6 +84,7 @@ import { DesktopDns } from '@tonkeeper/uikit/dist/desktop-pages/nft/DesktopDns'; import { DesktopCollectables } from '@tonkeeper/uikit/dist/desktop-pages/nft/DesktopCollectables'; import { useUserLanguage } from '@tonkeeper/uikit/dist/state/language'; import { useDebuggingTools } from '@tonkeeper/uikit/dist/hooks/useDebuggingTools'; +import { useDevSettings } from '@tonkeeper/uikit/dist/state/dev'; const queryClient = new QueryClient({ defaultOptions: { @@ -260,6 +261,7 @@ export const Loader: FC = () => { const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); const { data: lang, isLoading: isLangLoading } = useUserLanguage(); + const { data: devSettings } = useDevSettings(); const lock = useLock(sdk); const { i18n } = useTranslation(); @@ -297,7 +299,8 @@ export const Loader: FC = () => { isWalletsLoading || config === undefined || lock === undefined || - fiat === undefined + fiat === undefined || + !devSettings ) { return ; } @@ -317,7 +320,8 @@ export const Loader: FC = () => { tgAuthBotId: REACT_APP_TG_BOT_ID, stonfiReferralAddress: REACT_APP_STONFI_REFERRAL_ADDRESS }, - defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 + defaultWalletVersion: + isV5R1Enabled(config) || devSettings.enableV5 ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index d95934bde..1eb69e675 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -53,6 +53,7 @@ import { connectToBackground } from './event'; import { ExtensionAppSdk } from './libs/appSdk'; import { useAnalytics, useAppWidth } from './libs/hooks'; import { useMutateUserLanguage } from "@tonkeeper/uikit/dist/state/language"; +import { useDevSettings } from "@tonkeeper/uikit/dist/state/dev"; const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import')); const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings')); @@ -177,6 +178,7 @@ export const Loader: FC = React.memo(() => { const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); const { data: fiat } = useUserFiat(); const { mutate: setLang } = useMutateUserLanguage(); + const { data: devSettings } = useDevSettings(); useEffect(() => { setLang(localizationFrom(browser.i18n.getUILanguage())) @@ -193,7 +195,7 @@ export const Loader: FC = React.memo(() => { const { data: tracker } = useAnalytics(sdk.storage, activeWallet || undefined, wallets, sdk.version); - if (activeWalletLoading || isWalletsLoading || !config || lock === undefined || fiat === undefined) { + if (activeWalletLoading || isWalletsLoading || !config || lock === undefined || fiat === undefined || !devSettings) { return ( @@ -214,7 +216,7 @@ export const Loader: FC = React.memo(() => { proFeatures: false, hideQrScanner: true, hideSigner: true, - defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 + defaultWalletVersion: (isV5R1Enabled(config) || devSettings.enableV5) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index 018e27b0c..733a649b6 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -57,6 +57,7 @@ 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"; const Initialize = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import/Initialize')); const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import')); @@ -219,6 +220,7 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); const { data: lang, isLoading: isLangLoading } = useUserLanguage(); const { data: fiat } = useUserFiat(); + const { data: devSettings } = useDevSettings(); const lock = useLock(sdk); @@ -234,7 +236,7 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { const navigate = useNavigate(); const { data: tracker } = useAnalytics(activeWallet || undefined,wallets, sdk.version); - if (isWalletsLoading || activeWalletLoading || isLangLoading || config === undefined || lock === undefined || fiat === undefined) { + if (isWalletsLoading || activeWalletLoading || isLangLoading || config === undefined || lock === undefined || fiat === undefined || !devSettings) { return ; } @@ -255,7 +257,7 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { hideSigner: !showQrScan, hideKeystone: !showQrScan, hideQrScanner: !showQrScan, - defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 + defaultWalletVersion: (isV5R1Enabled(config) || devSettings.enableV5) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 0bb749107..2f585685a 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -48,6 +48,7 @@ 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"; const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import')); const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings')); @@ -181,6 +182,7 @@ export const Loader: FC = () => { const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); 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; @@ -220,7 +222,8 @@ export const Loader: FC = () => { isLangLoading || config === undefined || lock === undefined || - fiat === undefined + fiat === undefined || + !devSettings ) { return ; } @@ -235,7 +238,7 @@ export const Loader: FC = () => { extension: false, proFeatures: false, ios, - defaultWalletVersion: isV5R1Enabled(config) ? WalletVersion.V5R1 : WalletVersion.V4R2 + defaultWalletVersion: (isV5R1Enabled(config) || devSettings.enableV5) ? WalletVersion.V5R1 : WalletVersion.V4R2 }; return ( diff --git a/packages/core/src/Keys.ts b/packages/core/src/Keys.ts index baccd4e09..949af0c81 100644 --- a/packages/core/src/Keys.ts +++ b/packages/core/src/Keys.ts @@ -11,6 +11,7 @@ export enum AppKey { MULTI_SEND_LISTS = 'multi_send_lists', FIAT = 'fiat', LANGUAGE = 'language', + DEV_SETTINGS = 'dev_settings', DEPRECATED_GLOBAL_AUTH_STATE = 'password', LOCK = 'lock', diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index c41ce5caf..dff46a702 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -5,12 +5,15 @@ import { SubHeader } from '../../components/SubHeader'; import { SettingsItem, SettingsList } from '../../components/settings/SettingsList'; import { useTranslation } from '../../hooks/translation'; import { useActiveWallet, useMutateWalletProperty } from '../../state/wallet'; +import { useDevSettings, useMutateDevSettings } from '../../state/dev'; export const DevSettings = React.memo(() => { const { t } = useTranslation(); const wallet = useActiveWallet(); const { mutate } = useMutateWalletProperty(true); + const { mutate: mutateDevSettings } = useMutateDevSettings(); + const { data: devSettings } = useDevSettings(); const items = useMemo(() => { const network = wallet.network ?? Network.MAINNET; @@ -19,9 +22,14 @@ export const DevSettings = React.memo(() => { name: t('settings_network_alert_title'), icon: network === Network.MAINNET ? 'Mainnet' : 'Testnet', action: () => mutate({ network: switchNetwork(network) }) + }, + { + name: t('Enable wallet V5'), + icon: devSettings?.enableV5 ? 'Enabled' : 'Disabled', + action: () => mutateDevSettings({ enableV5: !devSettings?.enableV5 }) } ]; - }, [t, wallet]); + }, [t, wallet, devSettings]); return ( <> diff --git a/packages/uikit/src/state/dev.ts b/packages/uikit/src/state/dev.ts new file mode 100644 index 000000000..dd605599c --- /dev/null +++ b/packages/uikit/src/state/dev.ts @@ -0,0 +1,39 @@ +import { useAppSdk } from '../hooks/appSdk'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { AppKey } from '@tonkeeper/core/dist/Keys'; +import { UIPreferences } from './theme'; + +export interface DevSettings { + enableV5: boolean; +} + +const defaultDevSettings: DevSettings = { + enableV5: false +}; + +export const useDevSettings = () => { + const sdk = useAppSdk(); + return useQuery( + [AppKey.DEV_SETTINGS], + async () => { + const settings = await sdk.storage.get(AppKey.DEV_SETTINGS); + return { + ...defaultDevSettings, + ...settings + }; + }, + { + keepPreviousData: true + } + ); +}; + +export const useMutateDevSettings = () => { + const sdk = useAppSdk(); + const client = useQueryClient(); + return useMutation>(async devSettings => { + const current = await sdk.storage.get(AppKey.DEV_SETTINGS); + await sdk.storage.set(AppKey.DEV_SETTINGS, { ...devSettings, ...current, ...devSettings }); + await client.invalidateQueries([AppKey.DEV_SETTINGS]); + }); +}; diff --git a/packages/uikit/src/state/swap/useSwapsConfig.ts b/packages/uikit/src/state/swap/useSwapsConfig.ts index 0f4f05d78..7d1d75b99 100644 --- a/packages/uikit/src/state/swap/useSwapsConfig.ts +++ b/packages/uikit/src/state/swap/useSwapsConfig.ts @@ -4,7 +4,7 @@ import { OpenAPI, SwapService } from '@tonkeeper/core/dist/swapsApi'; export const useSwapsConfig = () => { const { config } = useAppContext(); - OpenAPI.BASE = config.web_swaps_url!; + OpenAPI.BASE = 'http://localhost:8080'; //config.web_swaps_url!; return { swapService: SwapService, referralAddress: config.web_swaps_referral_address, diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 518ae31cd..e28c891f7 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -33,6 +33,7 @@ import { mnemonicValidate } from '@ton/crypto'; import { getPasswordByNotification } from './mnemonic'; import { encrypt } from '@tonkeeper/core/dist/service/cryptoService'; import { Network } from '@tonkeeper/core/dist/entries/network'; +import { useDevSettings } from './dev'; export const useActiveWalletQuery = () => { const storage = useWalletsStorage(); @@ -295,7 +296,8 @@ export const useMutateActiveWalletConfig = () => { export const useStandardTonWalletVersions = (publicKey?: string, network = Network.MAINNET) => { const { api, fiat, config } = useAppContext(); - const isV5Enabled = isV5R1Enabled(config); + const { data: devSettings } = useDevSettings(); + const isV5Enabled = isV5R1Enabled(config) || devSettings?.enableV5; return useQuery( [QueryKey.walletVersions, publicKey, network, isV5Enabled], async () => { From 186347c0c421eb15687016214b806407a111a55d Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 16 Jul 2024 14:57:10 +0200 Subject: [PATCH 08/53] fix: rename wallet while creating or importing --- packages/uikit/src/components/create/WalletName.tsx | 13 +++++++++++-- packages/uikit/src/pages/import/Create.tsx | 13 +++++++++++-- packages/uikit/src/pages/import/Import.tsx | 13 +++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/uikit/src/components/create/WalletName.tsx b/packages/uikit/src/components/create/WalletName.tsx index a134c1d45..708c0db45 100644 --- a/packages/uikit/src/components/create/WalletName.tsx +++ b/packages/uikit/src/components/create/WalletName.tsx @@ -24,7 +24,8 @@ export const UpdateWalletName: FC<{ walletEmoji: string; name?: string; submitHandler: ({ name, emoji }: { name: string; emoji: string }) => void; -}> = ({ walletEmoji, submitHandler, name: nameProp }) => { + isLoading?: boolean; +}> = ({ walletEmoji, submitHandler, name: nameProp, isLoading }) => { const { t } = useTranslation(); const ref = useRef(null); @@ -67,7 +68,15 @@ export const UpdateWalletName: FC<{ /> -
diff --git a/packages/uikit/src/pages/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index c2c17f12b..12ba3eb4b 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -16,7 +16,11 @@ import { useTranslation } from '../../hooks/translation'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; -import { useCreateStandardTonWalletsByMnemonic, useWalletsState } from '../../state/wallet'; +import { + useCreateStandardTonWalletsByMnemonic, + useMutateRenameWallet, + useWalletsState +} from '../../state/wallet'; const Create = () => { const sdk = useAppSdk(); @@ -27,6 +31,7 @@ const Create = () => { isLoading: isCreateWalletLoading, reset: resetCreateWallets } = useCreateStandardTonWalletsByMnemonic(); + const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameWallet(); const existingWallets = useWalletsState(); const [mnemonic, setMnemonic] = useState(); @@ -157,9 +162,13 @@ const Create = () => { ...w!, ...val })); - setPassName(true); + renameWallet({ + id: wallet.id, + ...val + }).then(() => setPassName(true)); }} walletEmoji={wallet.emoji} + isLoading={renameLoading} /> ); } diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx index 5157f32ee..8232d9fb0 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -5,7 +5,11 @@ import { ImportWords } from '../../components/create/Words'; import { useAppSdk } from '../../hooks/appSdk'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { useCreateStandardTonWalletsByMnemonic, useWalletsState } from '../../state/wallet'; +import { + useCreateStandardTonWalletsByMnemonic, + useMutateRenameWallet, + useWalletsState +} from '../../state/wallet'; import { StandardTonWalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions'; @@ -22,6 +26,7 @@ const Import = () => { const [passName, setPassName] = useState(false); const [passNotifications, setPassNotification] = useState(false); const existingWallets = useWalletsState(); + const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameWallet(); const { mutateAsync: createWalletsAsync, @@ -89,9 +94,13 @@ const Import = () => { name={wallets[0].name} submitHandler={val => { setWallets(w => [{ ...w![0], ...val }]); - setPassName(true); + renameWallet({ + id: wallets![0].id, + ...val + }).then(() => setPassName(true)); }} walletEmoji={wallets[0].emoji} + isLoading={renameLoading} /> ); } From bb1a105c2aa56bfe843024747806f3fff149fe0d Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 24 Jul 2024 14:05:27 +0200 Subject: [PATCH 09/53] feat: new accounts storage structure --- apps/desktop/src/app/App.tsx | 28 +- apps/desktop/src/electron/sseEvetns.ts | 41 ++- apps/desktop/src/libs/aptabaseElectron.ts | 31 +- apps/desktop/src/libs/hooks.ts | 27 +- apps/extension/src/App.tsx | 6 +- apps/twa/src/App.tsx | 6 +- apps/web/src/App.tsx | 6 +- packages/core/src/AppSdk.ts | 4 +- packages/core/src/Keys.ts | 17 +- packages/core/src/entries/network.ts | 1 + packages/core/src/entries/password.ts | 9 - packages/core/src/entries/wallet.ts | 283 ++++++++++++++-- packages/core/src/service/accountsStorage.ts | 268 +++++++++++++++ packages/core/src/service/devStorage.ts | 26 ++ packages/core/src/service/ledger/transfer.ts | 22 +- packages/core/src/service/mnemonicService.ts | 6 +- packages/core/src/service/passwordService.ts | 42 +-- packages/core/src/service/proService.ts | 19 +- packages/core/src/service/signerService.ts | 4 +- .../core/src/service/suggestionService.ts | 6 +- .../src/service/tonConnect/connectService.ts | 45 ++- .../service/tonConnect/connectionService.ts | 35 +- packages/core/src/service/transfer/common.ts | 8 +- .../src/service/transfer/jettonService.ts | 44 ++- .../src/service/transfer/multiSendService.ts | 18 +- .../core/src/service/transfer/nftService.ts | 63 ++-- .../core/src/service/transfer/tonService.ts | 45 ++- .../core/src/service/wallet/configService.ts | 10 +- .../src/service/wallet/contractService.ts | 8 +- packages/core/src/service/walletService.ts | 255 ++++++++------ packages/core/src/service/walletsService.ts | 203 ----------- packages/uikit/src/components/Header.tsx | 20 +- .../activity/NotificationCommon.tsx | 26 +- .../activity/ton/ContractDeployAction.tsx | 6 +- .../activity/ton/JettonActivity.tsx | 19 +- .../components/activity/ton/NftActivity.tsx | 18 +- .../components/activity/ton/StakeActivity.tsx | 16 +- .../activity/ton/SubscribeAction.tsx | 10 +- .../activity/ton/TonActivityAction.tsx | 16 +- .../connect/TonConnectNotification.tsx | 5 +- .../connect/TonConnectSubscription.tsx | 4 +- .../connect/TonTransactionNotification.tsx | 4 +- .../components/dashboard/DashboardTable.tsx | 7 +- .../components/desktop/aside/AsideMenu.tsx | 12 +- .../desktop/aside/PreferencesAsideMenu.tsx | 4 +- .../desktop/history/ton/HistoryCell.tsx | 8 +- .../desktop/multi-send/MultiSendTable.tsx | 7 +- .../uikit/src/components/home/AccountView.tsx | 9 +- .../uikit/src/components/home/Balance.tsx | 19 +- .../components/home/BuyItemNotification.tsx | 6 +- packages/uikit/src/components/nft/LinkNft.tsx | 14 +- .../uikit/src/components/nft/NftDetails.tsx | 14 +- .../uikit/src/components/nft/NftHeader.tsx | 4 +- packages/uikit/src/components/nft/NftView.tsx | 4 +- .../components/settings/AccountSettings.tsx | 25 +- .../src/components/settings/ClearSettings.tsx | 4 +- .../settings/LogOutNotification.tsx | 51 +-- .../src/components/settings/ProSettings.tsx | 13 +- .../settings/nft/NFTSettingsContent.tsx | 4 +- .../wallet-name/WalletNameNotification.tsx | 38 ++- .../components/transfer/ConfirmListItem.tsx | 6 +- .../src/components/transfer/RecipientView.tsx | 10 +- .../src/components/transfer/ShowAddress.tsx | 8 +- .../components/transfer/SuggestionAddress.tsx | 8 +- .../transfer/amountView/AmountViewUI.tsx | 12 +- .../src/components/transfer/nft/Common.tsx | 16 +- .../transfer/nft/ConfirmNftView.tsx | 16 +- .../src/desktop-pages/notcoin/NotcoinPage.tsx | 6 +- .../settings/DesktopWalletSettingsPage.tsx | 25 +- packages/uikit/src/hooks/accountUtils.ts | 14 + .../uikit/src/hooks/analytics/amplitude.ts | 31 +- .../uikit/src/hooks/analytics/aptabase-web.ts | 31 +- packages/uikit/src/hooks/analytics/google.ts | 31 +- packages/uikit/src/hooks/analytics/gtag.ts | 31 +- packages/uikit/src/hooks/analytics/index.ts | 47 +-- .../src/hooks/blockchain/useEstimateTonFee.ts | 8 +- .../hooks/blockchain/useEstimateTransfer.ts | 4 +- .../hooks/blockchain/useExecuteTonContract.ts | 15 +- .../src/hooks/blockchain/useSendTransfer.ts | 15 +- .../src/hooks/blockchain/useTonRecipient.ts | 8 +- packages/uikit/src/hooks/useDebuggingTools.ts | 6 +- packages/uikit/src/hooks/useStorage.ts | 6 +- packages/uikit/src/libs/queryKey.ts | 1 + packages/uikit/src/pages/home/Unlock.tsx | 4 +- packages/uikit/src/pages/import/Create.tsx | 43 ++- packages/uikit/src/pages/import/Import.tsx | 53 +-- packages/uikit/src/pages/import/Ledger.tsx | 4 +- packages/uikit/src/pages/import/Subscribe.tsx | 5 +- packages/uikit/src/pages/settings/Account.tsx | 58 ++-- packages/uikit/src/pages/settings/Dev.tsx | 7 +- packages/uikit/src/pages/settings/Jettons.tsx | 12 +- .../uikit/src/pages/settings/Notification.tsx | 12 +- packages/uikit/src/pages/settings/Version.tsx | 52 ++- packages/uikit/src/pages/signer/LinkPage.tsx | 10 +- .../src/state/dashboard/useDashboardData.ts | 11 +- packages/uikit/src/state/dev.ts | 23 +- packages/uikit/src/state/jetton.ts | 39 +-- packages/uikit/src/state/keystone.ts | 19 +- packages/uikit/src/state/ledger.ts | 28 +- packages/uikit/src/state/mnemonic.ts | 123 ++++--- packages/uikit/src/state/nft.ts | 39 ++- packages/uikit/src/state/pro.ts | 25 +- packages/uikit/src/state/signer.ts | 10 +- packages/uikit/src/state/subscribe.ts | 4 +- packages/uikit/src/state/tonConnect.ts | 30 +- packages/uikit/src/state/wallet.ts | 319 +++++++++++------- 106 files changed, 1898 insertions(+), 1360 deletions(-) create mode 100644 packages/core/src/service/accountsStorage.ts create mode 100644 packages/core/src/service/devStorage.ts delete mode 100644 packages/core/src/service/walletsService.ts create mode 100644 packages/uikit/src/hooks/accountUtils.ts diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 8690424d2..64110cdcc 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { localizationText } from '@tonkeeper/core/dist/entries/language'; -import { getApiConfig, Network } from '@tonkeeper/core/dist/entries/network'; -import { WalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { getApiConfig } from '@tonkeeper/core/dist/entries/network'; +import { Account, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; import ConnectLedgerNotification from '@tonkeeper/uikit/dist/components/ConnectLedgerNotification'; import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; @@ -62,7 +62,11 @@ import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; -import { useActiveWalletQuery, useWalletsStateQuery } from '@tonkeeper/uikit/dist/state/wallet'; +import { + useActiveAccountQuery, + useAccountsStateQuery, + useActiveTonNetwork +} from '@tonkeeper/uikit/dist/state/wallet'; import { Container, GlobalStyleCss } from '@tonkeeper/uikit/dist/styles/globalStyle'; import { FC, Suspense, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -258,8 +262,9 @@ const FullSizeWrapperBounded = styled(FullSizeWrapper)` `; export const Loader: FC = () => { - const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); - const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); + const network = useActiveTonNetwork(); + const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery(); + const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery(); const { data: lang, isLoading: isLangLoading } = useUserLanguage(); const { data: devSettings } = useDevSettings(); @@ -270,7 +275,7 @@ export const Loader: FC = () => { const tonendpoint = useTonendpoint({ targetEnv: TARGET_ENV, build: sdk.version, - network: activeWallet?.network, + network, lang, platform: 'desktop' }); @@ -279,7 +284,7 @@ export const Loader: FC = () => { const navigate = useNavigate(); useAppHeight(); - const { data: tracker } = useAnalytics(sdk.version, activeWallet, wallets); + const { data: tracker } = useAnalytics(sdk.version, activeAccount, accounts); useEffect(() => { if (lang && i18n.language !== localizationText(lang)) { @@ -305,7 +310,6 @@ export const Loader: FC = () => { return ; } - const network = activeWallet?.network ?? Network.MAINNET; const context: IAppContext = { api: getApiConfig(config, network, REACT_APP_TONCONSOLE_API), fiat, @@ -331,7 +335,7 @@ export const Loader: FC = () => { value={() => navigate(AppRoute.home, { replace: true })} > - + @@ -347,9 +351,9 @@ const usePrefetch = () => { }; export const Content: FC<{ - activeWallet?: WalletState | null; + activeAccount?: Account | null; lock: boolean; -}> = ({ activeWallet, lock }) => { +}> = ({ activeAccount, lock }) => { const location = useLocation(); useWindowsScroll(); useAppWidth(); @@ -365,7 +369,7 @@ export const Content: FC<{ ); } - if (!activeWallet || location.pathname.startsWith(AppRoute.import)) { + if (!activeAccount || location.pathname.startsWith(AppRoute.import)) { return ( diff --git a/apps/desktop/src/electron/sseEvetns.ts b/apps/desktop/src/electron/sseEvetns.ts index 3549d04f2..1e7b3f400 100644 --- a/apps/desktop/src/electron/sseEvetns.ts +++ b/apps/desktop/src/electron/sseEvetns.ts @@ -6,20 +6,27 @@ import { import { AccountConnection, disconnectAppConnection, - getAccountConnection + getTonWalletConnections } from '@tonkeeper/core/dist/service/tonConnect/connectionService'; import { getLastEventId, subscribeTonConnect } from '@tonkeeper/core/dist/service/tonConnect/httpBridge'; -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; import { delay } from '@tonkeeper/core/dist/utils/common'; import { Buffer as BufferPolyfill } from 'buffer'; import log from 'electron-log/main'; import EventSourcePolyfill from 'eventsource'; import { MainWindow } from './mainWindow'; import { mainStorage } from './storageService'; -import { isStandardTonWallet, WalletId } from '@tonkeeper/core/dist/entries/wallet'; +import { + accountWithUpdatedActiveTonWalletId, + getAccountActiveTonWallet, + getAccountAllTonWallets, + getWalletById, + isStandardTonWallet, + WalletId +} from '@tonkeeper/core/dist/entries/wallet'; +import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; globalThis.Buffer = BufferPolyfill; @@ -48,15 +55,15 @@ export class TonConnectSSE { public async init() { this.lastEventId = await getLastEventId(mainStorage); - const walletsState = (await walletsStorage(mainStorage).getWallets()).filter( - isStandardTonWallet + const walletsState = (await accountsStorage(mainStorage).getAccounts()).flatMap( + getAccountAllTonWallets ); this.connections = []; this.dist = {}; for (const wallet of walletsState) { - const walletConnections = await getAccountConnection(mainStorage, wallet); + const walletConnections = await getTonWalletConnections(mainStorage, wallet); this.connections = this.connections.concat(walletConnections); walletConnections.forEach(item => { @@ -79,9 +86,8 @@ export class TonConnectSSE { }; private onDisconnect = async ({ connection, request }: TonConnectAppRequest) => { - const wallet = await walletsStorage(mainStorage).getWallet( - this.dist[connection.clientSessionId] - ); + const accounts = await accountsStorage(mainStorage).getAccounts(); + const wallet = getWalletById(accounts, this.dist[connection.clientSessionId]); if (!wallet || !isStandardTonWallet(wallet)) { return; @@ -111,12 +117,23 @@ export class TonConnectSSE { const walletId = this.dist[params.connection.clientSessionId]; - const activeWalletId = await walletsStorage(mainStorage).getActiveWalletId(); + const activeAccount = await accountsStorage(mainStorage).getActiveAccount(); + const activeWallet = getAccountActiveTonWallet(activeAccount); const window = await MainWindow.bringToFront(); - if (activeWalletId !== walletId) { - await walletsStorage(mainStorage).setActiveWalletId(walletId); + if (activeWallet.id !== walletId) { + let accountToActivate = (await accountsStorage(mainStorage).getAccounts()).find( + a => getAccountAllTonWallets(a).some(w => w.id === walletId) + ); + + accountToActivate = accountWithUpdatedActiveTonWalletId( + accountToActivate, + walletId + ); + + await accountsStorage(mainStorage).updateAccountInState(accountToActivate); + await accountsStorage(mainStorage).setActiveAccountId(accountToActivate.id); window.webContents.send('refresh'); await delay(500); } diff --git a/apps/desktop/src/libs/aptabaseElectron.ts b/apps/desktop/src/libs/aptabaseElectron.ts index 7ee616e87..55deaea77 100644 --- a/apps/desktop/src/libs/aptabaseElectron.ts +++ b/apps/desktop/src/libs/aptabaseElectron.ts @@ -1,26 +1,27 @@ import { trackEvent } from '@aptabase/electron/renderer'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { Analytics } from '@tonkeeper/uikit/dist/hooks/analytics'; -import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; export class AptabaseElectron implements Analytics { private user_properties: Record = {}; - init = ( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - version?: string | undefined, - platform?: string | undefined - ) => { - this.user_properties['application'] = application; - this.user_properties['walletType'] = walletType; + 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'] = - activeWallet?.network === Network.TESTNET ? 'testnet' : 'mainnet'; - this.user_properties['accounts'] = wallets?.length ?? 0; - this.user_properties['version'] = version; - this.user_properties['platform'] = platform; + 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/apps/desktop/src/libs/hooks.ts b/apps/desktop/src/libs/hooks.ts index f51aedc58..30a210514 100644 --- a/apps/desktop/src/libs/hooks.ts +++ b/apps/desktop/src/libs/hooks.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import { AppKey } from '@tonkeeper/core/dist/Keys'; -import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account, getAccountActiveTonWallet } 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'; @@ -9,6 +9,7 @@ import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey'; import { useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { AptabaseElectron } from './aptabaseElectron'; +import { useActiveTonNetwork } from '@tonkeeper/uikit/dist/state/wallet'; export const useAppHeight = () => { useEffect(() => { @@ -47,12 +48,9 @@ export const useAppWidth = () => { declare const REACT_APP_AMPLITUDE: string; -export const useAnalytics = ( - version: string, - activeWallet?: WalletState, - wallets?: WalletsState -) => { +export const useAnalytics = (version: string, activeAccount?: Account, accounts?: Account[]) => { const sdk = useAppSdk(); + const network = useActiveTonNetwork(); return useQuery( [QueryKey.analytics], @@ -74,17 +72,18 @@ export const useAnalytics = ( new Amplitude(REACT_APP_AMPLITUDE, userId) ); - tracker.init( - 'Desktop', - toWalletType(activeWallet), - activeWallet, - wallets, + tracker.init({ + application: 'Desktop', + walletType: toWalletType(getAccountActiveTonWallet(activeAccount)), + activeAccount, + accounts, + network, version, - `${window.backgroundApi.platform()}-${window.backgroundApi.arch()}` - ); + platform: `${window.backgroundApi.platform()}-${window.backgroundApi.arch()}` + }); return tracker; }, - { enabled: wallets != null && activeWallet !== undefined } + { enabled: accounts != null && activeAccount !== undefined } ); }; diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 1eb69e675..0adf3089e 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -41,7 +41,7 @@ import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/imp import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; -import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; +import { useActiveAccountQuery, useAccountsStateQuery } 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 { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; @@ -174,8 +174,8 @@ const Wrapper = styled(FullSizeWrapper)<{ `; export const Loader: FC = React.memo(() => { - const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); - const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); + const { data: activeWallet, isLoading: activeWalletLoading } = useActiveAccountQuery(); + const { data: wallets, isLoading: isWalletsLoading } = useAccountsStateQuery(); const { data: fiat } = useUserFiat(); const { mutate: setLang } = useMutateUserLanguage(); const { data: devSettings } = useDevSettings(); diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index 733a649b6..d0f5708d9 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -37,7 +37,7 @@ import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/di import { useLock } from '@tonkeeper/uikit/dist/hooks/lock'; import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotification'; import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; -import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; +import { useActiveAccountQuery, useAccountsStateQuery } 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'; @@ -216,8 +216,8 @@ const seeIfShowQrScanner = (platform: TwaPlatform): boolean => { }; export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { - const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); - const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); + const { data: activeWallet, isLoading: activeWalletLoading } = useActiveAccountQuery(); + const { data: wallets, isLoading: isWalletsLoading } = useAccountsStateQuery(); const { data: lang, isLoading: isLangLoading } = useUserLanguage(); const { data: fiat } = useUserFiat(); const { data: devSettings } = useDevSettings(); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 2f585685a..483a47a52 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -39,7 +39,7 @@ import { useKeyboardHeight } from '@tonkeeper/uikit/dist/pages/import/hooks'; import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; import { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; -import { useActiveWalletQuery, useWalletsStateQuery } from "@tonkeeper/uikit/dist/state/wallet"; +import { useActiveAccountQuery, useAccountsStateQuery } 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'; @@ -178,8 +178,8 @@ const Wrapper = styled(FullSizeWrapper)<{ standalone: boolean }>` `; export const Loader: FC = () => { - const { data: activeWallet, isLoading: activeWalletLoading } = useActiveWalletQuery(); - const { data: wallets, isLoading: isWalletsLoading } = useWalletsStateQuery(); + const { data: activeWallet, isLoading: activeWalletLoading } = useActiveAccountQuery(); + const { data: wallets, isLoading: isWalletsLoading } = useAccountsStateQuery(); const { data: lang, isLoading: isLangLoading } = useUserLanguage(); const { data: fiat } = useUserFiat(); const { data: devSettings } = useDevSettings(); diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index af6c768b7..83afedfde 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -4,10 +4,10 @@ import { BLOCKCHAIN_NAME } from './entries/crypto'; import { EventEmitter, IEventEmitter } from './entries/eventEmitter'; import { NFT } from './entries/nft'; import { FavoriteSuggestion, LatestSuggestion } from './entries/suggestion'; -import { StandardTonWalletState } 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'; @@ -68,7 +68,7 @@ export interface TouchId { export interface NotificationService { subscribe: ( api: APIConfig, - wallet: StandardTonWalletState, + 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 949af0c81..53502a0dd 100644 --- a/packages/core/src/Keys.ts +++ b/packages/core/src/Keys.ts @@ -1,11 +1,22 @@ export enum AppKey { + /** + * @deprecated + */ DEPRECATED_ACCOUNT = 'account', + /** + * @deprecated + */ DEPRECATED_WALLET = 'wallet', - WALLETS = 'wallets', - ACTIVE_WALLET_ID = 'active_wallet_id', - WALLET_CONFIG = 'wallet_config', + /** + * @deprecated + */ DEPRECATED_MNEMONIC = 'mnemonic', + ACCOUNTS = 'accounts', + ACTIVE_ACCOUNT_ID = 'active_account_id', + WALLET_CONFIG = 'wallet_config', + GLOBAL_PREFERENCES_CONFIG = 'global_preferences_config', + THEME = 'theme', UI_PREFERENCES = 'ui_preferences', MULTI_SEND_LISTS = 'multi_send_lists', 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 01712dd60..0ee8f96e3 100644 --- a/packages/core/src/entries/password.ts +++ b/packages/core/src/entries/password.ts @@ -2,7 +2,6 @@ import { KeystonePathInfo } from '../service/keystone/types'; export type AuthState = | AuthPassword - | WebAuthn | AuthKeychain | AuthSigner | AuthSignerDeepLink @@ -39,20 +38,12 @@ export interface AuthKeystone { info?: KeystonePathInfo; } -export interface WebAuthn { - kind: 'webauthn'; - type: 'largeBlob' | 'credBlob' | 'userHandle'; - credentialId: string; - transports?: AuthenticatorTransport[]; -} - /** * @deprecated */ export type DeprecatedAuthState = | DeprecatedAuthNone | DeprecatedAuthPassword - | WebAuthn | DeprecatedKeychainPassword | AuthSigner | AuthSignerDeepLink diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index c4616ef93..760b9163c 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -1,8 +1,15 @@ import { Language } from './language'; import { Network } from './network'; -import { AuthKeychain, AuthPassword, AuthState, DeprecatedAuthState } from './password'; +import { + AuthKeychain, + AuthPassword, + AuthSigner, + AuthSignerDeepLink, + DeprecatedAuthState +} from './password'; import { WalletProxy } from './proxy'; -import { BLOCKCHAIN_NAME } from './crypto'; +import { assertUnreachable } from '../utils/types'; +import { KeystonePathInfo } from '../service/keystone/types'; export enum WalletVersion { V3R1 = 0, @@ -86,62 +93,259 @@ export interface DeprecatedWalletState { } export type WalletId = string; +export type AccountId = string; -export interface WalletBasic { - blockchain: BLOCKCHAIN_NAME; +export type TonContract = { id: WalletId; -} + rawAddress: string; // rawAddress +}; -export interface TonWalletStateBasic extends WalletBasic { - blockchain: BLOCKCHAIN_NAME.TON; - rawAddress: string; +export type TonWalletStandard = TonContract & { name: string; emoji: string; - network: Network; -} - -export interface StandardTonWalletState extends TonWalletStateBasic { - type: 'standard'; publicKey: string; version: WalletVersion; - auth: AuthState; +}; + +export type DerivationItem = { + index: number; + name: string; + emoji: string; + activeTonWalletId: WalletId; + tonWallets: TonWalletStandard[]; + // tronWallets: never; +}; + +export interface AccountBasic { + emoji: string; + name: string; } -export interface MultisigTonWalletState extends TonWalletStateBasic { +export type AccountTonMnemonic = AccountBasic & { + id: AccountId; // ton public key + type: 'mnemonic'; + auth: AuthPassword | AuthKeychain; + + activeTonWalletId: WalletId; + tonWallets: TonWalletStandard[]; + // tronWallet: never; +}; + +export type AccountLedger = AccountBasic & { + id: AccountId; // first acc public key + type: 'ledger'; + + activeDerivationIndex: number; + derivations: DerivationItem[]; +}; + +export type AccountKeystone = AccountBasic & { + id: AccountId; // ton wallet id + type: 'keystone'; + pathInfo?: KeystonePathInfo; + + tonWallet: TonWalletStandard; +}; + +/** + * temporary, will be removed when signer supports tron + */ +export type AccountTonOnly = AccountBasic & { + id: AccountId; // ton wallet id + type: 'ton-only'; + auth: AuthSigner | AuthSignerDeepLink; + + activeTonWalletId: WalletId; + tonWallets: TonWalletStandard[]; +}; + +export type AccountTonMultisig = AccountBasic & { + id: AccountId; type: 'multisig'; + + // tonWallet: TonContract; + //... +}; + +export type AccountKeeperMnemonic = AccountBasic & { + id: AccountId; + type: 'root-mnemonic'; + auth: AuthPassword | AuthKeychain; + + derivations: DerivationItem[]; +}; + +export type Account = AccountTonMnemonic | AccountLedger | AccountTonOnly | AccountKeystone; //| AccountTonMultisig; // | AccountKeeperMnemonic; + +export type AccountsState = Account[]; + +export const defaultAccountState = []; + +export function isAccountTonMnemonic(account: Account): account is AccountTonMnemonic { + return account.type === 'mnemonic'; +} + +export function isAccountLedger(account: Account): account is AccountLedger { + return account.type === 'ledger'; +} + +export function isAccountTonOnly(account: Account): account is AccountTonOnly { + return account.type === 'ton-only'; +} + +export function isStandardTonWallet(wallet: TonContract): wallet is TonWalletStandard { + return 'version' in wallet && 'publicKey' in wallet; +} + +export function getWalletById( + accounts: Account[], + walletId: WalletId +): TonWalletStandard | undefined { + for (const account of accounts || []) { + const wallet = getAccountAllTonWallets(account).find(w => w.id === walletId); + if (wallet) { + return wallet; + } + } } -export type TonWalletState = StandardTonWalletState | MultisigTonWalletState; +export function getAccountAllTonWallets(account: Account): TonWalletStandard[] { + if (account.type === 'mnemonic') { + return account.tonWallets; + } + + if (account.type === 'ledger') { + return account.derivations.flatMap(d => d.tonWallets); + } -export type WalletState = TonWalletState; -export type WalletsState = WalletState[]; + if (account.type === 'ton-only') { + return account.tonWallets; + } -export const defaultWalletsState = []; + if (account.type === 'keystone') { + return [account.tonWallet]; + } -export function isTonWallet(state: WalletState): state is TonWalletState { - return state.blockchain === BLOCKCHAIN_NAME.TON; + assertUnreachable(account); } -export function isStandardTonWallet(state: WalletState): state is StandardTonWalletState { - return state.blockchain === BLOCKCHAIN_NAME.TON && state.type === 'standard'; +export function getAccountActiveDerivationTonWallets(account: Account): TonWalletStandard[] { + if (account.type === 'mnemonic') { + return account.tonWallets; + } + + if (account.type === 'ledger') { + return account.derivations.find(d => account.activeDerivationIndex === d.index)!.tonWallets; + } + + if (account.type === 'ton-only') { + return account.tonWallets; + } + + if (account.type === 'keystone') { + return [account.tonWallet]; + } + + assertUnreachable(account); } -export function isPasswordAuthWallet( - state: WalletState -): state is WalletState & { auth: AuthPassword } { - return isStandardTonWallet(state) && state.auth.kind === 'password'; +export function getAccountActiveTonWallet(account: Account): TonWalletStandard { + if (account.type === 'mnemonic' || account.type === 'ton-only') { + return account.tonWallets.find(w => w.id === account.activeTonWalletId)!; + } + + if (account.type === 'ledger') { + const derivation = account.derivations.find( + d => d.index === account.activeDerivationIndex + )!; + return derivation.tonWallets.find(w => w.id === derivation.activeTonWalletId)!; + } + + if (account.type === 'keystone') { + return account.tonWallet; + } + + assertUnreachable(account); } -export function isMnemonicAuthWallet( - state: WalletState -): state is WalletState & { auth: AuthPassword | AuthKeychain } { - return ( - isStandardTonWallet(state) && - (state.auth.kind === 'password' || state.auth.kind === 'keychain') - ); +export function accountWithUpdatedTonWallet( + account: Account, + tonWallet: TonWalletStandard +): Account { + const newAcc: Account = JSON.parse(JSON.stringify(account)); + if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { + const index = newAcc.tonWallets.findIndex(w => w.id === tonWallet.id)!; + newAcc.tonWallets[index] = tonWallet; + return newAcc; + } + + if (newAcc.type === 'ledger') { + for (const derivation of newAcc.derivations) { + const index = derivation.tonWallets.findIndex(w => w.id === tonWallet.id)!; + if (index !== -1) { + derivation.tonWallets[index] = tonWallet; + return newAcc; + } + } + + throw new Error('Derivation not found'); + } + + if (newAcc.type === 'keystone') { + newAcc.tonWallet = tonWallet; + return newAcc; + } + + assertUnreachable(newAcc); +} + +export function accountWithUpdatedActiveTonWalletId(account: Account, walletId: WalletId): Account { + const newAcc: Account = JSON.parse(JSON.stringify(account)); + if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { + newAcc.activeTonWalletId = walletId; + return newAcc; + } + + if (newAcc.type === 'ledger') { + for (const derivation of newAcc.derivations) { + const index = derivation.tonWallets.findIndex(w => w.id === walletId)!; + if (index !== -1) { + derivation.activeTonWalletId = walletId; + return newAcc; + } + } + + throw new Error('Derivation not found'); + } + + if (newAcc.type === 'keystone') { + return newAcc; + } + + assertUnreachable(newAcc); } -export interface ActiveWalletConfig { +export function accountWithAddedTonWallet(account: Account, tonWallet: TonWalletStandard): Account { + const newAcc: Account = JSON.parse(JSON.stringify(account)); + if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { + newAcc.tonWallets.push(tonWallet); + return newAcc; + } + + if (newAcc.type === 'ledger') { + const derivation = newAcc.derivations.find(d => d.index === newAcc.activeDerivationIndex)!; + derivation.tonWallets.push(tonWallet); + return newAcc; + } + + if (newAcc.type === 'keystone') { + throw new Error('Cannot add ton wallet to keystone account'); + } + + assertUnreachable(newAcc); +} + +export interface TonWalletConfig { pinnedTokens: string[]; hiddenTokens: string[]; pinnedNfts: string[]; @@ -150,6 +354,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/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts new file mode 100644 index 000000000..7b81c771b --- /dev/null +++ b/packages/core/src/service/accountsStorage.ts @@ -0,0 +1,268 @@ +import { AppKey } from '../Keys'; +import { IStorage } from '../Storage'; +import { + Account, + AccountId, + AccountKeystone, + AccountLedger, + AccountsState, + AccountTonMnemonic, + AccountTonOnly, + defaultAccountState, + DeprecatedWalletState, + getAccountAllTonWallets, + TonWalletStandard, + WalletId +} from '../entries/wallet'; +import { DeprecatedAccountState } from '../entries/account'; +import { AuthState, DeprecatedAuthState } from '../entries/password'; +import { assertUnreachable, notNullish } from '../utils/types'; +import { + getFallbackDerivationItemEmoji, + getFallbackTonStandardWalletEmoji, + getWalletNameAddress +} 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); + } + } + 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) => { + 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(); + await this.setAccounts( + state.concat(accounts.filter(acc => state.every(a => a.id !== acc.id))) + ); + }; + + /** + * 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]); + }; + + removeAccountFromState = async (id: AccountId) => { + const state = await this.getAccounts(); + const activeAccountId = await this.getActiveAccountId(); + + const newState = state.filter(w => w.id !== id); + + if (activeAccountId === id) { + await this.setActiveAccountId(newState[0]?.id || null); + } + + await this.setAccounts(state.filter(w => w.id !== id)); + }; + + 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 => + getAccountAllTonWallets(a).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 => { + 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 ' + w.active.friendlyAddress.slice(-4); + const emoji = w.emoji; + + const tonWallet: TonWalletStandard = { + id: w.active.rawAddress, + publicKey: w.publicKey, + version: w.active.version, + rawAddress: w.active.rawAddress, + name: getWalletNameAddress(w.active.rawAddress), + emoji: getFallbackTonStandardWalletEmoji(w.publicKey, w.active.version) + }; + + const authKind = auth.kind; + switch (authKind) { + case 'password': + case 'keychain': + return { + id: w.publicKey, + name, + emoji, + auth, + type: 'mnemonic', + activeTonWalletId: w.active.rawAddress, + tonWallets: [tonWallet] + } satisfies AccountTonMnemonic; + + case 'signer': + case 'signer-deeplink': + return { + id: w.publicKey, + name, + emoji, + auth, + type: 'ton-only', + activeTonWalletId: w.active.rawAddress, + tonWallets: [tonWallet] + } satisfies AccountTonOnly; + + case 'keystone': + return { + id: w.publicKey, + name, + emoji, + pathInfo: auth.info, + type: 'keystone', + tonWallet + } satisfies AccountKeystone; + + case 'ledger': + return { + id: w.publicKey, + name, + emoji, + type: 'ledger', + activeDerivationIndex: auth.accountIndex, + derivations: [ + { + index: auth.accountIndex, + activeTonWalletId: tonWallet.rawAddress, + name, + emoji: getFallbackDerivationItemEmoji( + w.publicKey, + auth.accountIndex + ), + tonWallets: [tonWallet] + } + ] + } satisfies AccountLedger; + 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..5a7eff0e0 --- /dev/null +++ b/packages/core/src/service/devStorage.ts @@ -0,0 +1,26 @@ +import { IStorage } from '../Storage'; +import { Network } from '../entries/network'; +import { AppKey } from '../Keys'; + +export interface DevSettings { + enableV5: boolean; + tonNetwork: Network; +} + +const defaultDevSettings: DevSettings = { + enableV5: false, + 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 70ce925be..bd8d87ee0 100644 --- a/packages/core/src/service/ledger/transfer.ts +++ b/packages/core/src/service/ledger/transfer.ts @@ -1,4 +1,4 @@ -import { StandardTonWalletState } from '../../entries/wallet'; +import { AccountLedger, getAccountActiveTonWallet } from '../../entries/wallet'; 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: StandardTonWalletState, + 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 = getAccountActiveTonWallet(account); 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: StandardTonWalletState, + 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 = getAccountActiveTonWallet(account); + const contract = walletContractFromState(getAccountActiveTonWallet(account)); const transfer = await signer(path, { to: Address.parse(jettonWalletAddress), @@ -71,7 +72,7 @@ export const createLedgerJettonTransfer = async ( queryId: getTonkeeperQueryId(), amount: jettonAmount, destination: Address.parse(recipientAddress), - responseDestination: Address.parse(walletState.rawAddress), + responseDestination: Address.parse(wallet.rawAddress), forwardAmount: jettonTransferForwardAmount, forwardPayload, customPayload: null @@ -84,14 +85,15 @@ export const createLedgerJettonTransfer = async ( export const createLedgerNftTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + 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 = getAccountActiveTonWallet(account); const contract = walletContractFromState(walletState); const transfer = await signer(path, { diff --git a/packages/core/src/service/mnemonicService.ts b/packages/core/src/service/mnemonicService.ts index 75db4fe0f..a7a078f58 100644 --- a/packages/core/src/service/mnemonicService.ts +++ b/packages/core/src/service/mnemonicService.ts @@ -1,12 +1,8 @@ import { mnemonicValidate } from '@ton/crypto'; import { decrypt } from './cryptoService'; -import { WalletState } from '../entries/wallet'; import { AuthPassword } from '../entries/password'; -export const decryptWalletMnemonic = async ( - state: WalletState & { auth: AuthPassword }, - password: string -) => { +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) { diff --git a/packages/core/src/service/passwordService.ts b/packages/core/src/service/passwordService.ts index 1309de9bc..5f4535f83 100644 --- a/packages/core/src/service/passwordService.ts +++ b/packages/core/src/service/passwordService.ts @@ -1,32 +1,33 @@ import { IStorage } from '../Storage'; -import { WalletsStorage } from './walletsService'; -import { isPasswordAuthWallet } from '../entries/wallet'; +import { isAccountTonMnemonic } from '../entries/wallet'; import { decrypt, encrypt } from './cryptoService'; import { mnemonicValidate } from '@ton/crypto'; import { decryptWalletMnemonic } from './mnemonicService'; +import { AccountsStorage } from './accountsStorage'; +import { AuthPassword } from '../entries/password'; export class PasswordStorage { - private readonly walletStorage: WalletsStorage; + private readonly accountsStorage: AccountsStorage; constructor(storage: IStorage) { - this.walletStorage = new WalletsStorage(storage); + this.accountsStorage = new AccountsStorage(storage); } async getIsPasswordSet() { - const wallets = await this.getPasswordAuthWallets(); + const wallets = await this.getPasswordAuthAccounts(); return wallets.length > 0; } async isPasswordValid(password: string): Promise { try { - const walletToCheck = (await this.getPasswordAuthWallets())[0]; - if (!walletToCheck) { + const accToCheck = (await this.getPasswordAuthAccounts())[0]; + if (!accToCheck) { throw new Error('None wallet has a password auth'); } - const mnemonic = (await decrypt(walletToCheck.auth.encryptedMnemonic, password)).split( - ' ' - ); + const mnemonic = ( + await decrypt((accToCheck.auth as AuthPassword).encryptedMnemonic, password) + ).split(' '); return await mnemonicValidate(mnemonic); } catch (e) { console.error(e); @@ -42,25 +43,28 @@ export class PasswordStorage { } async updatePassword(oldPassword: string, newPassword: string): Promise { - const wallets = await this.getPasswordAuthWallets(); + const accounts = await this.getPasswordAuthAccounts(); const updatedWallets = await Promise.all( - wallets.map(async wallet => { - const mnemonic = await decryptWalletMnemonic(wallet, oldPassword); + accounts.map(async acc => { + const mnemonic = await decryptWalletMnemonic( + acc as { auth: AuthPassword }, + oldPassword + ); const newEncrypted = await encrypt(mnemonic.join(' '), newPassword); return { - ...wallet, - auth: { ...wallet.auth, encryptedMnemonic: newEncrypted } + ...acc, + auth: { ...acc.auth, encryptedMnemonic: newEncrypted } }; }) ); - await this.walletStorage.updateWalletsInState(updatedWallets); + await this.accountsStorage.updateAccountsInState(updatedWallets); } - private async getPasswordAuthWallets() { - const wallets = await this.walletStorage.getWallets(); - return wallets.filter(isPasswordAuthWallet); + private async getPasswordAuthAccounts() { + const wallets = await this.accountsStorage.getAccounts(); + return wallets.filter(isAccountTonMnemonic).filter(a => a.auth.kind === 'password'); } } diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index aeb08ad31..84a8d5269 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -11,7 +11,12 @@ 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 { isStandardTonWallet, StandardTonWalletState, WalletVersion } from '../entries/wallet'; +import { + getAccountAllTonWallets, + isStandardTonWallet, + TonWalletStandard, + WalletVersion +} from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; import { FiatCurrencies as FiatCurrenciesGenerated, @@ -31,7 +36,7 @@ import { loginViaTG } from './telegramOauth'; import { createTonProofItem, tonConnectProofPayload } from './tonConnect/connectService'; import { getServerTime } from './transfer/common'; import { walletStateInitFromState } from './wallet/contractService'; -import { walletsStorage } from './walletsService'; +import { accountsStorage } from './accountsStorage'; export const setBackupState = async (storage: IStorage, state: ProSubscription) => { await storage.set(AppKey.PRO_BACKUP, state); @@ -44,7 +49,7 @@ export const getBackupState = async (storage: IStorage) => { export const getProState = async ( storage: IStorage, - wallet: StandardTonWalletState + wallet: TonWalletStandard ): Promise => { try { return await loadProState(storage, wallet); @@ -88,7 +93,7 @@ export const walletVersionFromProServiceDTO = (value: string) => { export const loadProState = async ( storage: IStorage, - fallbackWallet: StandardTonWalletState + fallbackWallet: TonWalletStandard ): Promise => { const user = await ProServiceService.proServiceGetUserInfo(); @@ -97,7 +102,9 @@ export const loadProState = async ( rawAddress: fallbackWallet.rawAddress }; if (user.pub_key && user.version) { - const wallets = await walletsStorage(storage).getWallets(); + const wallets = (await accountsStorage(storage).getAccounts()).flatMap( + getAccountAllTonWallets + ); const actualWallet = wallets .filter(isStandardTonWallet) .find( @@ -160,7 +167,7 @@ export const checkAuthCookie = async () => { export const authViaTonConnect = async ( api: APIConfig, - wallet: StandardTonWalletState, + wallet: TonWalletStandard, signProof: (bufferToSing: Buffer) => Promise ) => { const domain = 'https://tonkeeper.com/'; diff --git a/packages/core/src/service/signerService.ts b/packages/core/src/service/signerService.ts index 108455dbf..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 { isW5Version, StandardTonWalletState, 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'; @@ -66,7 +66,7 @@ export const storeTransactionAndCreateDeepLink = async ( export const publishSignerMessage = async ( sdk: IAppSdk, api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, signatureHex: string ) => { const messageBase64 = await sdk.storage.get(AppKey.SIGNER_MESSAGE); diff --git a/packages/core/src/service/suggestionService.ts b/packages/core/src/service/suggestionService.ts index 32c15419d..60e11b241 100644 --- a/packages/core/src/service/suggestionService.ts +++ b/packages/core/src/service/suggestionService.ts @@ -2,7 +2,7 @@ import { IAppSdk } from '../AppSdk'; import { APIConfig } from '../entries/apis'; import { BLOCKCHAIN_NAME } from '../entries/crypto'; import { FavoriteSuggestion, LatestSuggestion } from '../entries/suggestion'; -import { StandardTonWalletState, TonWalletState } from '../entries/wallet'; +import { TonWalletStandard } from '../entries/wallet'; import { AppKey } from '../Keys'; import { IStorage } from '../Storage'; import { AccountsApi } from '../tonApiV2'; @@ -73,7 +73,7 @@ export const deleteFavoriteSuggestion = async ( const getTonSuggestionsList = async ( api: APIConfig, - wallet: TonWalletState, + wallet: TonWalletStandard, seeIfAddressIsAdded: (list: LatestSuggestion[], address: string) => boolean ) => { const list = [] as LatestSuggestion[]; @@ -111,7 +111,7 @@ const getTonSuggestionsList = async ( export const getSuggestionsList = async ( sdk: IAppSdk, api: APIConfig, - wallet: StandardTonWalletState, + wallet: TonWalletStandard, acceptBlockchains: BLOCKCHAIN_NAME[] = [BLOCKCHAIN_NAME.TON, BLOCKCHAIN_NAME.TRON] ) => { const favorites = await getFavoriteSuggestions(sdk.storage, wallet.publicKey); diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index 7e70977ef..a01827970 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -20,17 +20,18 @@ import { TonConnectAccount, TonProofItemReplySuccess } from '../../entries/tonConnect'; -import { isStandardTonWallet, StandardTonWalletState } from '../../entries/wallet'; +import { Account, getAccountAllTonWallets, TonWalletStandard } from '../../entries/wallet'; import { walletContractFromState } from '../wallet/contractService'; import { AccountConnection, - TonConnectParams, disconnectAccountConnection, - getAccountConnection, - saveAccountConnection + getTonWalletConnections, + saveAccountConnection, + TonConnectParams } from './connectionService'; import { SessionCrypto } from './protocol'; -import { walletsStorage } from '../walletsService'; +import { accountsStorage } from '../accountsStorage'; +import { getDevSettings } from '../devStorage'; export function parseTonConnect(options: { url: string }): TonConnectParams | string { try { @@ -169,7 +170,7 @@ export const getDappConnection = async ( storage: IStorage, origin: string, account?: TonConnectAccount -): Promise<{ wallet: StandardTonWalletState; connection: AccountConnection } | undefined> => { +): Promise<{ wallet: TonWalletStandard; connection: AccountConnection } | undefined> => { const appConnections = await getAppConnections(storage); if (account) { const walletState = appConnections.find(c => c.wallet.rawAddress === account?.address); @@ -194,9 +195,9 @@ export const getDappConnection = async ( export const getAppConnections = async ( storage: IStorage -): Promise<{ wallet: StandardTonWalletState; connections: AccountConnection[] }[]> => { - const wallets = (await walletsStorage(storage).getWallets()).filter(isStandardTonWallet); - if (!wallets.length) { +): 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 @@ -204,8 +205,8 @@ export const getAppConnections = async ( } return Promise.all( - wallets.map(async wallet => { - const walletConnections = await getAccountConnection(storage, wallet); + accounts.flatMap(getAccountAllTonWallets).map(async wallet => { + const walletConnections = await getTonWalletConnections(storage, wallet); return { wallet, connections: walletConnections }; }) ); @@ -213,10 +214,10 @@ export const getAppConnections = async ( export const checkWalletConnectionOrDie = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + wallet: TonWalletStandard; webViewUrl: string; }) => { - const connections = await getAccountConnection(options.storage, options.wallet); + const connections = await getTonWalletConnections(options.storage, options.wallet); console.log(connections); @@ -240,15 +241,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: StandardTonWalletState) => { +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() @@ -317,19 +318,15 @@ export const tonConnectProofPayload = ( export const toTonProofItemReply = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + 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 = ( @@ -372,7 +369,7 @@ export const tonDisconnectRequest = async (options: { storage: IStorage; webView export const saveWalletTonConnect = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + 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 9d235da1e..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 { StandardTonWalletState } 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,12 +18,14 @@ export interface AccountConnection { webViewUrl?: string; } -export const getAccountConnection = async ( +export const getTonWalletConnections = async ( storage: IStorage, - wallet: Pick + wallet: Pick ) => { + const network = (await getDevSettings(storage)).tonNetwork; + let result = await storage.get( - `${AppKey.CONNECTIONS}_${wallet.id}_${wallet.network}` + `${AppKey.CONNECTIONS}_${wallet.id}_${network}` ); if (!result) { @@ -36,20 +38,22 @@ export const getAccountConnection = async ( export const setAccountConnection = async ( storage: IStorage, - wallet: Pick, + wallet: Pick, items: AccountConnection[] ) => { - await storage.set(`${AppKey.CONNECTIONS}_${wallet.id}_${wallet.network}`, items); + const network = (await getDevSettings(storage)).tonNetwork; + + await storage.set(`${AppKey.CONNECTIONS}_${wallet.id}_${network}`, items); }; export const saveAccountConnection = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + 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) { @@ -71,10 +75,10 @@ export const saveAccountConnection = async (options: { */ export const disconnectAccountConnection = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + 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); @@ -86,10 +90,10 @@ export const disconnectAccountConnection = async (options: { */ export const disconnectAppConnection = async (options: { storage: IStorage; - wallet: StandardTonWalletState; + 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); @@ -98,10 +102,11 @@ export const disconnectAppConnection = async (options: { async function migrateAccountConnections( storage: IStorage, - wallet: Pick + wallet: Pick ) { + const network = (await getDevSettings(storage)).tonNetwork; const oldConnections = await storage.get( - `${AppKey.CONNECTIONS}_${wallet.publicKey}_${wallet.network ?? Network.MAINNET}` + `${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 2bbd03839..67ca4ae81 100644 --- a/packages/core/src/service/transfer/common.ts +++ b/packages/core/src/service/transfer/common.ts @@ -14,7 +14,7 @@ import nacl from 'tweetnacl'; import { APIConfig } from '../../entries/apis'; import { TonRecipient, TransferEstimationEvent } from '../../entries/send'; import { BaseSigner } from '../../entries/signer'; -import { StandardTonWalletState } from '../../entries/wallet'; +import { TonWalletStandard } from '../../entries/wallet'; import { Account, AccountsApi, LiteServerApi, WalletApi } from '../../tonApiV2'; import { walletContractFromState } from '../wallet/contractService'; import { NotEnoughBalanceError } from '../../errors/NotEnoughBalanceError'; @@ -77,7 +77,7 @@ export const getWalletSeqNo = async (api: APIConfig, accountId: string) => { return seqno; }; -export const getWalletBalance = async (api: APIConfig, walletState: StandardTonWalletState) => { +export const getWalletBalance = async (api: APIConfig, walletState: TonWalletStandard) => { const wallet = await new AccountsApi(api.tonApiV2).getAccount({ accountId: walletState.rawAddress }); @@ -98,7 +98,7 @@ export const seeIfTimeError = (e: unknown): e is Error => { export const createTransferMessage = async ( wallet: { seqno: number; - state: StandardTonWalletState; + state: TonWalletStandard; signer: BaseSigner; timestamp: number; }, @@ -137,7 +137,7 @@ signEstimateMessage.type = 'cell' as const; export async function getKeyPairAndSeqno(options: { api: APIConfig; - walletState: StandardTonWalletState; + 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 a3aa82a69..371e313f3 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 { StandardTonWalletState } from '../../entries/wallet'; +import { Account, getAccountActiveTonWallet, TonWalletStandard } from '../../entries/wallet'; import { BlockchainApi, EmulationApi } from '../../tonApiV2'; import { createLedgerJettonTransfer } from '../ledger/transfer'; import { walletContractFromState } from '../wallet/contractService'; @@ -47,7 +47,7 @@ export const jettonTransferBody = (params: { const createJettonTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipientAddress: string, amount: AssetAmount, jettonWalletAddress: string, @@ -86,7 +86,7 @@ const createJettonTransfer = async ( export const estimateJettonTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipient: TonRecipientData, amount: AssetAmount, jettonWalletAddress: string @@ -117,7 +117,7 @@ export const estimateJettonTransfer = async ( export const sendJettonTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + account: Account, recipient: TonRecipientData, amount: AssetAmount, jettonWalletAddress: string, @@ -130,23 +130,37 @@ export const sendJettonTransfer = async ( .multipliedBy(-1) .plus(jettonTransferAmount.toString()); + const walletState = getAccountActiveTonWallet(account); 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 83af94f96..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 { StandardTonWalletState, 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'; @@ -51,7 +51,7 @@ const checkMaxAllowedMessagesInMultiTransferOrDie = ( export const estimateTonMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, transferMessages: TransferMessage[] ) => { const timestamp = await getServerTime(api); @@ -81,7 +81,7 @@ export const estimateTonMultiTransfer = async ( export const sendTonMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, transferMessages: TransferMessage[], feeEstimate: BigNumber, signer: CellSigner @@ -112,7 +112,7 @@ export const sendTonMultiTransfer = async ( const createTonMultiTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, transferMessages: TransferMessage[], signer: CellSigner ) => { @@ -138,7 +138,7 @@ const createTonMultiTransfer = async ( export const estimateJettonMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, jettonWalletAddress: string, transferMessages: TransferMessage[] ) => { @@ -173,7 +173,7 @@ export const estimateJettonMultiTransfer = async ( export const sendJettonMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, jettonWalletAddress: string, transferMessages: TransferMessage[], feeEstimate: BigNumber, @@ -231,7 +231,7 @@ export const sendJettonMultiTransfer = async ( const createJettonMultiTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, jettonWalletAddress: string, transferMessages: TransferMessage[], attachValue: BigNumber, @@ -273,7 +273,7 @@ export type NftTransferMessage = { export const createNftMultiTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, transferMessages: NftTransferMessage[], attachValue: BigNumber, signer: CellSigner @@ -306,7 +306,7 @@ export const createNftMultiTransfer = async ( export const sendNftMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, transferMessages: NftTransferMessage[], feeEstimate: BigNumber, signer: CellSigner diff --git a/packages/core/src/service/transfer/nftService.ts b/packages/core/src/service/transfer/nftService.ts index 5c131d836..1e3a9be3f 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 { StandardTonWalletState } from '../../entries/wallet'; +import { Account, getAccountActiveTonWallet, TonWalletStandard } from '../../entries/wallet'; import { BlockchainApi, EmulationApi, NftItem } from '../../tonApiV2'; import { createLedgerNftTransfer } from '../ledger/transfer'; import { @@ -68,7 +68,7 @@ const nftLinkBody = (params: { queryId: bigint; linkToAddress: string }) => { const createNftTransfer = ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipientAddress: string, nftAddress: string, nftTransferAmount: bigint, @@ -91,7 +91,7 @@ const createNftTransfer = ( export const estimateNftTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipient: TonRecipientData, nftItem: NftItem ): Promise => { @@ -120,7 +120,7 @@ export const estimateNftTransfer = async ( export const sendNftTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + account: Account, recipient: TonRecipientData, nftItem: NftItem, fee: TransferEstimationEvent, @@ -139,27 +139,39 @@ export const sendNftTransfer = async ( throw new Error(`Unexpected nft transfer amount: ${nftTransferAmount.toString()}`); } + const walletState = getAccountActiveTonWallet(account); 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 +183,16 @@ export const sendNftTransfer = async ( export const sendNftRenew = async (options: { api: APIConfig; - walletState: StandardTonWalletState; + account: Account; nftAddress: string; fee: TransferEstimationEvent; signer: CellSigner; amount: BigNumber; }) => { + const walletState = getAccountActiveTonWallet(options.account); + const timestamp = await getServerTime(options.api); - const { seqno } = await getKeyPairAndSeqno(options); + const { seqno } = await getKeyPairAndSeqno({ ...options, walletState }); const body = nftRenewBody({ queryId: getTonkeeperQueryId() }); @@ -186,7 +200,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 +213,7 @@ export const sendNftRenew = async (options: { export const estimateNftRenew = async (options: { api: APIConfig; - walletState: StandardTonWalletState; + walletState: TonWalletStandard; nftAddress: string; amount: BigNumber; }) => { @@ -224,15 +238,16 @@ export const estimateNftRenew = async (options: { export const sendNftLink = async (options: { api: APIConfig; - walletState: StandardTonWalletState; + account: Account; nftAddress: string; linkToAddress: string; fee: TransferEstimationEvent; signer: CellSigner; amount: BigNumber; }) => { + const walletState = getAccountActiveTonWallet(options.account); 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 +255,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 +268,7 @@ export const sendNftLink = async (options: { export const estimateNftLink = async (options: { api: APIConfig; - walletState: StandardTonWalletState; + 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 3b15afdd2..91de10815 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 { StandardTonWalletState } from '../../entries/wallet'; +import { Account, getAccountActiveTonWallet, TonWalletStandard } from '../../entries/wallet'; import { AccountsApi, BlockchainApi, EmulationApi } from '../../tonApiV2'; import { createLedgerTonTransfer } from '../ledger/transfer'; import { walletContractFromState } from '../wallet/contractService'; @@ -44,7 +44,7 @@ export const toStateInit = ( const createTonTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipient: TonRecipientData, weiAmount: BigNumber, isMax: boolean, @@ -73,7 +73,7 @@ const createTonTransfer = async ( const createTonConnectTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, params: TonConnectTransactionPayload, signer: CellSigner ) => { @@ -99,7 +99,7 @@ const createTonConnectTransfer = async ( export const estimateTonTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, recipient: TonRecipientData, weiAmount: BigNumber, isMax: boolean @@ -133,7 +133,7 @@ export type ConnectTransferError = { kind: 'not-enough-balance' } | { kind: unde export const tonConnectTransferError = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, params: TonConnectTransactionPayload ): Promise => { const wallet = await new AccountsApi(api.tonApiV2).getAccount({ @@ -154,7 +154,7 @@ export const tonConnectTransferError = async ( export const estimateTonConnectTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, params: TonConnectTransactionPayload ): Promise => { const timestamp = await getServerTime(api); @@ -180,7 +180,7 @@ export const estimateTonConnectTransfer = async ( export const sendTonConnectTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, params: TonConnectTransactionPayload, signer: CellSigner ) => { @@ -200,7 +200,7 @@ export const sendTonConnectTransfer = async ( export const sendTonTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + account: Account, recipient: TonRecipientData, amount: AssetAmount, isMax: boolean, @@ -211,17 +211,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 = getAccountActiveTonWallet(account); + 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/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 20dbe634a..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 { StandardTonWalletState, WalletVersion } from '../../entries/wallet'; +import { TonWalletStandard, WalletVersion } from '../../entries/wallet'; -export const walletContractFromState = (wallet: StandardTonWalletState) => { +export const walletContractFromState = (wallet: TonWalletStandard) => { const publicKey = Buffer.from(wallet.publicKey, 'hex'); - return walletContract(publicKey, wallet.version, wallet.network); + return walletContract(publicKey, wallet.version); }; const workchain = 0; @@ -42,7 +42,7 @@ export const walletContract = ( } }; -export const walletStateInitFromState = (wallet: StandardTonWalletState) => { +export const walletStateInitFromState = (wallet: TonWalletStandard) => { const contract = walletContractFromState(wallet); return beginCell() diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 3b01a010d..3e968ba04 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -9,40 +9,45 @@ import { APIConfig } from '../entries/apis'; import { Network } from '../entries/network'; import { AuthKeychain, AuthPassword } from '../entries/password'; import { - StandardTonWalletState, - TonWalletState, - WalletId, - WalletState, + Account, + AccountId, + AccountKeystone, + AccountLedger, + AccountTonMnemonic, + AccountTonOnly, WalletVersion, WalletVersions } from '../entries/wallet'; import { WalletApi } from '../tonApiV2'; import { walletContract } from './wallet/contractService'; -import { BLOCKCHAIN_NAME } from '../entries/crypto'; -import { walletsStorage } from './walletsService'; import { emojis } from '../utils/emojis'; import { formatAddress } from '../utils/common'; +import { accountsStorage } from './accountsStorage'; -export const createStandardTonWalletStateByMnemonic = async ( +export const createStandardTonAccountByMnemonic = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, mnemonic: string[], options: { - version?: WalletVersion; + versions?: WalletVersion[]; network?: Network; auth: AuthPassword | Omit; - name?: string; } ) => { const keyPair = await mnemonicToPrivateKey(mnemonic); const publicKey = keyPair.publicKey.toString('hex'); - const address = await findWalletAddress( - appContext, - publicKey, - options.version, - options.network - ); + 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)]; + } let walletAuth: AuthPassword | AuthKeychain; if (options.auth.kind === 'keychain') { @@ -54,20 +59,22 @@ export const createStandardTonWalletStateByMnemonic = async ( walletAuth = options.auth; } - const state: TonWalletState = { - blockchain: BLOCKCHAIN_NAME.TON, - id: address.rawAddress, - type: 'standard', - publicKey, - rawAddress: address.rawAddress, - version: address.version, - name: options.name || getFallbackWalletName(Address.parse(address.rawAddress)), - emoji: getFallbackWalletEmoji(publicKey), + return { + type: 'mnemonic', + id: publicKey, auth: walletAuth, - network: Network.MAINNET - }; - - return state; + activeTonWalletId: tonWallets[0].rawAddress, + emoji: getFallbackAccountEmoji(publicKey), + name: getFallbackAccountName(publicKey), + tonWallets: tonWallets.map(w => ({ + id: w.rawAddress, + publicKey, + version: w.version, + rawAddress: w.rawAddress, + name: getFallbackWalletName(w.rawAddress), + emoji: getFallbackTonStandardWalletEmoji(publicKey, w.version) + })) + } satisfies AccountTonMnemonic; }; const versionMap: Record = { @@ -92,18 +99,8 @@ const findWalletVersion = (interfaces?: string[]): WalletVersion => { const findWalletAddress = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, - publicKey: string, - version?: WalletVersion, - network?: Network + publicKey: string ): Promise<{ rawAddress: string; version: WalletVersion }> => { - if (version !== undefined) { - const parsed = getWalletAddress(publicKey, version, network); - return { - rawAddress: parsed.address.toRawString(), - version - }; - } - try { const result = await new WalletApi(appContext.api.tonApiV2).getWalletsByPublicKey({ publicKey: publicKey @@ -167,23 +164,24 @@ export const getWalletsAddresses = ( ) as Record<(typeof WalletVersions)[number], { address: Address; version: WalletVersion }>; }; -export const updateWalletProperty = async ( +export const updateAccountProperty = async ( storage: IStorage, - walletId: WalletId, - props: Partial> + accountId: AccountId, + props: Partial> ) => { - const wallet = (await walletsStorage(storage).getWallet(walletId))!; - const updated: WalletState = { + const wallet = (await accountsStorage(storage).getAccount(accountId))!; + const updated: Account = { ...wallet, ...props }; - await walletsStorage(storage).updateWalletInState(updated); + await accountsStorage(storage).updateAccountInState(updated); + return updated; }; -export const walletStateFromSignerQr = async ( +export const accountBySignerQr = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, qrCode: string -): Promise => { +): Promise => { if (!qrCode.startsWith('tonkeeper://signer')) { throw new Error('Unexpected QR code'); } @@ -201,66 +199,98 @@ export const walletStateFromSignerQr = async ( const publicKey = pk; + // TODO support multiple wallets versions configuration const active = await findWalletAddress(appContext, publicKey); return { - type: 'standard', - blockchain: BLOCKCHAIN_NAME.TON, - id: active.rawAddress, - network: Network.MAINNET, - version: active.version, - rawAddress: active.rawAddress, - publicKey, - name: name || getFallbackWalletName(Address.parse(active.rawAddress)), + type: 'ton-only', + id: publicKey, auth: { kind: 'signer' }, - emoji: getFallbackWalletEmoji(publicKey) + activeTonWalletId: active.rawAddress, + emoji: getFallbackAccountEmoji(publicKey), + name: name || getFallbackAccountName(publicKey), + tonWallets: [ + { + id: active.rawAddress, + publicKey, + version: active.version, + rawAddress: active.rawAddress, + name: getFallbackWalletName(active.rawAddress), + emoji: getFallbackTonStandardWalletEmoji(publicKey, active.version) + } + ] }; }; -export const walletStateFromSignerDeepLink = async ( +export const accountBySignerDeepLink = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, publicKey: string, name: string | null -): Promise => { +): Promise => { const active = await findWalletAddress(appContext, publicKey); return { - publicKey, - rawAddress: active.rawAddress, - version: active.version, - type: 'standard', - blockchain: BLOCKCHAIN_NAME.TON, - id: active.rawAddress, - network: Network.MAINNET, - name: name || getFallbackWalletName(Address.parse(active.rawAddress)), - auth: { kind: 'signer-deeplink' }, - emoji: getFallbackWalletEmoji(publicKey) + type: 'ton-only', + id: publicKey, + auth: { kind: 'signer' }, + activeTonWalletId: active.rawAddress, + emoji: getFallbackAccountEmoji(publicKey), + name: name || getFallbackAccountName(publicKey), + tonWallets: [ + { + id: active.rawAddress, + publicKey, + version: active.version, + rawAddress: active.rawAddress, + name: getFallbackWalletName(active.rawAddress), + emoji: getFallbackTonStandardWalletEmoji(publicKey, active.version) + } + ] }; }; -export const walletStateFromLedger = (walletInfo: { - address: string; - publicKey: Buffer; - accountIndex: number; -}): StandardTonWalletState => { - const address = Address.parse(walletInfo.address); - const publicKey = walletInfo.publicKey.toString('hex'); - +export const accountByLedger = ( + walletsInfo: { + address: string; + publicKey: Buffer; + accountIndex: number; + }[], + name: string, + emoji: string +): AccountLedger => { + const zeroAccPublicKey = walletsInfo[0].publicKey.toString('hex'); return { - network: Network.MAINNET, - blockchain: BLOCKCHAIN_NAME.TON, - type: 'standard', - publicKey, - id: address.toRawString(), - rawAddress: address.toRawString(), - version: WalletVersion.V4R2, - name: `Ledger ${walletInfo.accountIndex + 1}`, - auth: { kind: 'ledger', accountIndex: walletInfo.accountIndex }, - emoji: getFallbackWalletEmoji(publicKey) + type: 'ledger', + id: zeroAccPublicKey, + emoji, + name, + activeDerivationIndex: walletsInfo[0].accountIndex, + derivations: walletsInfo.map(item => ({ + index: item.accountIndex, + name: getFallbackWalletName(item.address), + emoji: getFallbackDerivationItemEmoji( + item.publicKey.toString('hex'), + item.accountIndex + ), + activeTonWalletId: item.address, + tonWallets: [ + { + id: item.address, + publicKey: item.publicKey.toString('hex'), + version: WalletVersion.V4R2, + rawAddress: item.address, + name: getFallbackWalletName(item.address), + emoji: getFallbackTonStandardWalletEmoji( + item.publicKey.toString('hex'), + WalletVersion.V4R2 + ) + } + ] + })) }; }; -export const walletStateFromKeystone = (ur: UR) => { +export const accountByKeystone = (ur: UR): AccountKeystone => { const account = parseTonAccount(ur); const contact = WalletContractV4.create({ workchain: 0, @@ -270,27 +300,48 @@ export const walletStateFromKeystone = (ur: UR) => { const pathInfo = account.path && account.xfp ? { path: account.path, mfp: account.xfp } : undefined; - const state: StandardTonWalletState = { - publicKey: account.publicKey, - rawAddress: contact.address.toRawString(), - version: WalletVersion.V4R2, - type: 'standard', - blockchain: BLOCKCHAIN_NAME.TON, - id: contact.address.toRawString(), - network: Network.MAINNET, - name: account.name ?? 'Keystone', - auth: { kind: 'keystone', info: pathInfo }, - emoji: getFallbackWalletEmoji(account.publicKey) + return { + type: 'keystone', + id: account.publicKey, + emoji: getFallbackAccountEmoji(account.publicKey), + name: getFallbackAccountName(account.publicKey), + pathInfo, + tonWallet: { + id: contact.address.toRawString(), + publicKey: account.publicKey, + version: WalletVersion.V4R2, + rawAddress: contact.address.toRawString(), + name: getFallbackWalletName(contact.address.toRawString()), + emoji: getFallbackTonStandardWalletEmoji(account.publicKey, WalletVersion.V4R2) + } }; - - return state; }; -export function getFallbackWalletEmoji(publicKey: string) { +export function getFallbackAccountEmoji(publicKey: string) { const index = Number('0x' + publicKey.slice(-6)) % emojis.length; return emojis[index]; } -export function getFallbackWalletName(address: Address) { +export function getFallbackTonStandardWalletEmoji(publicKey: string, version: WalletVersion) { + const index = Number('0x' + publicKey.slice(-6) + version.toString()) % emojis.length; + return emojis[index]; +} + +export function getFallbackDerivationItemEmoji(publicKey: string, derivationIndex: number) { + const index = + Number('0x' + derivationIndex.toString() + publicKey.slice(-6).toString()) % emojis.length; + return emojis[index]; +} + +export function getFallbackWalletName(address: Address | string) { return 'Wallet ' + formatAddress(address).slice(-4); } + +export function getFallbackAccountName(id: string) { + return 'Account ' + id.slice(0, 4); +} + +export function getWalletNameAddress(address: Address | string) { + const friendly = formatAddress(address); + return friendly.slice(0, 4) + '…' + friendly.slice(-4); +} diff --git a/packages/core/src/service/walletsService.ts b/packages/core/src/service/walletsService.ts deleted file mode 100644 index 66a0e105b..000000000 --- a/packages/core/src/service/walletsService.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { AppKey } from '../Keys'; -import { IStorage } from '../Storage'; -import { - defaultWalletsState, - DeprecatedWalletState, - isStandardTonWallet, - WalletId, - WalletsState, - WalletState -} from '../entries/wallet'; -import { DeprecatedAccountState } from '../entries/account'; -import { Network } from '../entries/network'; -import { BLOCKCHAIN_NAME } from '../entries/crypto'; -import { AuthState, DeprecatedAuthState } from '../entries/password'; -import { notNullish } from '../utils/types'; - -export class WalletsStorage { - constructor(private storage: IStorage) {} - - getWallets = async () => { - let state = await this.storage.get(AppKey.WALLETS); - if (!state) { - state = await migrateToWalletState(this.storage); - if (state) { - await this.setWallets(state); - } - } - return state ?? defaultWalletsState; - }; - - setWallets = async (state: WalletsState) => { - await this.storage.set(AppKey.WALLETS, state); - }; - - getActiveWalletId = async () => { - let state = await this.storage.get(AppKey.ACTIVE_WALLET_ID); - if (!state) { - state = await this.migrateToActiveWalletIdState(); - if (state !== null) { - await this.setActiveWalletId(state); - } - } - return state ?? null; - }; - - getActiveWallet = async (): Promise => { - const id = await this.getActiveWalletId(); - if (id !== null) { - const state = await this.getWallets(); - return state.find(w => w.id === id) || null; - } - return null; - }; - - getWallet = async (id: WalletId): Promise => { - const state = await this.getWallets(); - return state.find(w => w.id === id) || null; - }; - - setActiveWalletId = async (activeWalletId: WalletId | null) => { - await this.storage.set(AppKey.ACTIVE_WALLET_ID, activeWalletId); - }; - - addWalletToState = async (wallet: WalletState) => { - await this.addWalletsToState([wallet]); - }; - - addWalletsToState = async (wallets: WalletState[]) => { - const state = await this.getWallets(); - await this.storage.set( - AppKey.WALLETS, - state.concat(wallets.filter(wallet => state.every(w => w.id !== wallet.id))) - ); - }; - - /** - * Replace found wallets with same id in state and replace them with new ones with no array order changes - * @param wallets - */ - updateWalletsInState = async (wallets: WalletState[]) => { - const state = await this.getWallets(); - - for (let i = 0; i < state.length; i++) { - const wallet = wallets.find(w => w.id === state[i].id); - if (!wallet) { - continue; - } - - state[i] = wallet; - } - - await this.storage.set(AppKey.WALLETS, state); - }; - - /** - * Replace found wallet with same id in state and replace it with new one with no array order changes - * @param wallet - */ - updateWalletInState = async (wallet: WalletState) => { - return this.updateWalletsInState([wallet]); - }; - - removeWalletFromState = async (id: WalletId) => { - const state = await this.getWallets(); - const activeWalletId = await this.getActiveWalletId(); - - const newState = state.filter(w => w.id !== id); - - if (activeWalletId === id) { - await this.setActiveWalletId(newState[0]?.id || null); - } - - await this.storage.set( - AppKey.WALLETS, - state.filter(w => w.id !== id) - ); - }; - - private migrateToActiveWalletIdState = async (): Promise => { - const state = await this.storage.get(AppKey.DEPRECATED_ACCOUNT); - if (!state || !state.activePublicKey) { - return null; - } - - const wallets = await this.getWallets(); - return wallets.find(w => isStandardTonWallet(w) && w.publicKey === state.activePublicKey)! - .id; - }; -} - -export const walletsStorage = (storage: IStorage): WalletsStorage => new WalletsStorage(storage); - -async function migrateToWalletState(storage: IStorage): Promise { - const state = await storage.get(AppKey.DEPRECATED_ACCOUNT); - if (!state) { - return null; - } - - const wallets: (WalletState | null)[] = await Promise.all( - state.publicKeys.map(async pk => { - 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; - } - - return { - blockchain: BLOCKCHAIN_NAME.TON, - name: w.name || 'Wallet ' + w.active.friendlyAddress.slice(-4), - emoji: w.emoji, - type: 'standard' as const, - network: w.network || Network.MAINNET, - id: w.active.rawAddress, - version: w.active.version, - publicKey: w.publicKey, - rawAddress: w.active.rawAddress, - auth - }; - }) - ); - - return wallets.filter(notNullish); -} diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index 3673e1978..3c6828c5f 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -5,7 +5,12 @@ import styled, { createGlobalStyle, css } from 'styled-components'; import { useTranslation } from '../hooks/translation'; import { AppRoute, SettingsRoute } from '../libs/routes'; import { useUserCountry } from '../state/country'; -import { useActiveWallet, useMutateActiveWallet, useWalletsState } from '../state/wallet'; +import { + useActiveWallet, + useAccountsState, + useMutateActiveTonWallet, + useActiveTonNetwork +} from '../state/wallet'; import { DropDown } from './DropDown'; import { DoneIcon, DownIcon, PlusIcon, SettingsIcon } from './Icon'; import { ColumnText, Divider } from './Layout'; @@ -15,7 +20,7 @@ import { ScanButton } from './connect/ScanButton'; import { ImportNotification } from './create/ImportNotification'; import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; -import { TonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div<{ center?: boolean; @@ -114,11 +119,12 @@ const Row = styled.div` `; const WalletRow: FC<{ - walletState: TonWalletState; + walletState: TonWalletStandard; onClose: () => void; }> = ({ walletState, onClose }) => { - const { mutate } = useMutateActiveWallet(); - const address = toShortValue(formatAddress(walletState.rawAddress, walletState.network)); + const network = useActiveTonNetwork(); + const { mutate } = useMutateActiveTonWallet(); + const address = toShortValue(formatAddress(walletState.rawAddress, network)); const activeWallet = useActiveWallet(); return ( void; onCreate: () => void }> = ({ }) => { const navigate = useNavigate(); const { t } = useTranslation(); - const wallets = useWalletsState(); + const wallets = useAccountsState().flatMap(getAccountAllTonWallets); if (!wallets) { return null; @@ -195,7 +201,7 @@ export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => { const wallet = useActiveWallet(); const [isOpen, setOpen] = useState(false); - const wallets = useWalletsState(); + const wallets = useAccountsState(); const shouldShowIcon = wallets.length > 1; return ( diff --git a/packages/uikit/src/components/activity/NotificationCommon.tsx b/packages/uikit/src/components/activity/NotificationCommon.tsx index f459317d7..ad6a11718 100644 --- a/packages/uikit/src/components/activity/NotificationCommon.tsx +++ b/packages/uikit/src/components/activity/NotificationCommon.tsx @@ -25,7 +25,7 @@ import { ListItem, ListItemPayload } from '../List'; import { Body1, H2, Label1 } from '../Text'; import { Button } from '../fields/Button'; import { hexToRGBA } from '../../libs/css'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork } from '../../state/wallet'; export const Title = styled(H2)<{ secondary?: boolean; tertiary?: boolean }>` display: flex; @@ -164,7 +164,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b }) => { const { t } = useTranslation(); const sdk = useAppSdk(); - const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); return ( <> @@ -177,7 +177,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b )} @@ -186,7 +186,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b export const ActionPoolDetails: FC<{ pool: AccountAddress }> = ({ pool }) => { const { t } = useTranslation(); - const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); return ( <> @@ -196,7 +196,7 @@ export const ActionPoolDetails: FC<{ pool: AccountAddress }> = ({ pool }) => {
@@ -223,7 +223,7 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean }) => { const { t } = useTranslation(); const sdk = useAppSdk(); - const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); return ( <> @@ -236,7 +236,7 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean )} @@ -246,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 = useActiveWallet(); + const network = useActiveTonNetwork(); - const address = formatAddress(beneficiary.address, wallet.network, true); + const address = formatAddress(beneficiary.address, network, true); return ( <> {beneficiary.name && ( @@ -303,8 +303,8 @@ export const ActionDeployerAddress: FC<{ address?: string }> = ({ address }) => }; export const ActionDeployerDetails: FC<{ deployer: string }> = ({ deployer }) => { - const wallet = useActiveWallet(); - return ; + const network = useActiveTonNetwork(); + return ; }; export const ActionFeeDetails: FC<{ @@ -436,9 +436,9 @@ export const TronActionDetailsBlock: FC> event, children }) => { - const wallet = useActiveWallet(); + 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/ContractDeployAction.tsx b/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx index 39e4add40..80debbcf3 100644 --- a/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx +++ b/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx @@ -18,7 +18,7 @@ import { } from '../NotificationCommon'; import { ActionData } from './ActivityNotification'; import { NftComment } from './NftActivity'; -import { useActiveWallet } from '../../../state/wallet'; +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 = useActiveWallet(); + 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 fd67018e0..c769abfbb 100644 --- a/packages/uikit/src/components/activity/ton/JettonActivity.tsx +++ b/packages/uikit/src/components/activity/ton/JettonActivity.tsx @@ -18,7 +18,7 @@ import { } from '../CommonAction'; import { toDexName } from '../NotificationCommon'; import { useSwapValue } from './JettonNotifications'; -import { useActiveWallet } from '../../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet'; export interface JettonActionProps { action: Action; @@ -27,6 +27,7 @@ export interface JettonActionProps { export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ action, date }) => { const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); const { jettonTransfer } = action; const format = useFormatCoinValue(); @@ -47,7 +48,7 @@ export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ 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 = useActiveWallet(); + 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 = useActiveWallet(); + 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/NftActivity.tsx b/packages/uikit/src/components/activity/ton/NftActivity.tsx index 0bc95faa9..14bd7a0d5 100644 --- a/packages/uikit/src/components/activity/ton/NftActivity.tsx +++ b/packages/uikit/src/components/activity/ton/NftActivity.tsx @@ -5,7 +5,7 @@ import React, { FC, useState } from 'react'; import styled, { css } from 'styled-components'; import { useAppSdk } from '../../../hooks/appSdk'; import { useTranslation } from '../../../hooks/translation'; -import { useActiveWallet } from '../../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet'; import { InfoCircleIcon, VerificationIcon } from '../../Icon'; import { ListBlock } from '../../List'; import { Body1, Body2 } from '../../Text'; @@ -40,11 +40,12 @@ import { ActionData } from './ActivityNotification'; import { useDisclosure } from '../../../hooks/useDisclosure'; import { UnverifiedNftNotification } from '../../nft/UnverifiedNftNotification'; import { - useIsSpamNft, - useIsUnverifiedNft, - useMarkNftAsSpam, - useMarkNftAsTrusted, useNftItemData -} from "../../../state/nft"; + useIsSpamNft, + useIsUnverifiedNft, + useMarkNftAsSpam, + useMarkNftAsTrusted, + useNftItemData +} from '../../../state/nft'; const NftBlock = styled.div` background: ${props => props.theme.backgroundContentTint}; @@ -141,6 +142,7 @@ export const NftItemTransferAction: FC<{ }> = ({ action, date }) => { const { t } = useTranslation(); const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); const { nftItemTransfer } = action; if (!nftItemTransfer) { return ; @@ -160,7 +162,7 @@ export const NftItemTransferAction: FC<{ toShortValue( formatAddress( nftItemTransfer.sender?.address ?? nftItemTransfer.nft, - wallet.network, + network, !nftItemTransfer.sender?.address ) ) @@ -188,7 +190,7 @@ export const NftItemTransferAction: FC<{ toShortValue( formatAddress( nftItemTransfer.recipient?.address ?? nftItemTransfer.nft, - wallet.network, + network, !nftItemTransfer.recipient?.address ) ) diff --git a/packages/uikit/src/components/activity/ton/StakeActivity.tsx b/packages/uikit/src/components/activity/ton/StakeActivity.tsx index f8c05ac13..de6e78900 100644 --- a/packages/uikit/src/components/activity/ton/StakeActivity.tsx +++ b/packages/uikit/src/components/activity/ton/StakeActivity.tsx @@ -7,7 +7,7 @@ import { useTranslation } from '../../../hooks/translation'; import { FailedNote } from '../ActivityActionLayout'; import { ActivityIcon, ReceiveIcon, SentIcon } from '../ActivityIcons'; import { ColumnLayout, ErrorAction, ListItemGrid } from '../CommonAction'; -import { useActiveWallet } from '../../../state/wallet'; +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 = useActiveWallet(); + 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 = useActiveWallet(); + 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 = useActiveWallet(); + 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 f323d13bd..3cbc29efe 100644 --- a/packages/uikit/src/components/activity/ton/SubscribeAction.tsx +++ b/packages/uikit/src/components/activity/ton/SubscribeAction.tsx @@ -16,7 +16,7 @@ import { Title } from '../NotificationCommon'; import { ActionData } from './ActivityNotification'; -import { useActiveWallet } from '../../../state/wallet'; +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 = useActiveWallet(); + 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 = useActiveWallet(); + 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 0a6312aa2..3c8799ea6 100644 --- a/packages/uikit/src/components/activity/ton/TonActivityAction.tsx +++ b/packages/uikit/src/components/activity/ton/TonActivityAction.tsx @@ -36,7 +36,7 @@ import { WithdrawStakeAction } from './StakeActivity'; import { SubscribeAction, UnSubscribeAction } from './SubscribeAction'; -import { useActiveWallet } from '../../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet'; const TonTransferAction: FC<{ action: Action; @@ -45,6 +45,7 @@ const TonTransferAction: FC<{ }> = ({ action, date, isScam }) => { const wallet = useActiveWallet(); const { tonTransfer } = action; + const network = useActiveTonNetwork(); const format = useFormatCoinValue(); @@ -58,7 +59,7 @@ const TonTransferAction: FC<{ amount={format(tonTransfer.amount)} sender={ tonTransfer.sender.name ?? - toShortValue(formatAddress(tonTransfer.sender.address, wallet.network)) + toShortValue(formatAddress(tonTransfer.sender.address, network)) } symbol={CryptoCurrency.TON} date={date} @@ -74,7 +75,7 @@ const TonTransferAction: FC<{ symbol={CryptoCurrency.TON} recipient={ tonTransfer.recipient.name ?? - toShortValue(formatAddress(tonTransfer.recipient.address, wallet.network)) + toShortValue(formatAddress(tonTransfer.recipient.address, network)) } date={date} isScam={isScam} @@ -92,6 +93,7 @@ export const SmartContractExecAction: FC<{ const { smartContractExec } = action; const wallet = useActiveWallet(); const format = useFormatCoinValue(); + const network = useActiveTonNetwork(); if (!smartContractExec) { 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 = useActiveWallet(); + 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} diff --git a/packages/uikit/src/components/connect/TonConnectNotification.tsx b/packages/uikit/src/components/connect/TonConnectNotification.tsx index c4da68dc2..07f52d0d4 100644 --- a/packages/uikit/src/components/connect/TonConnectNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx @@ -20,7 +20,7 @@ import { Body2, Body3, H2, Label2 } from '../Text'; import { Button } from '../fields/Button'; import { ResultButton } from '../transfer/common'; import { useConnectTonConnectAppMutation } from '../../state/tonConnect'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; const Title = styled(H2)` text-align: center; @@ -109,7 +109,8 @@ const ConnectContent: FC<{ } }; - const address = formatAddress(wallet.rawAddress, wallet.network); + const network = useActiveTonNetwork(); + const address = formatAddress(wallet.rawAddress, network); let shortUrl = manifest.url; try { diff --git a/packages/uikit/src/components/connect/TonConnectSubscription.tsx b/packages/uikit/src/components/connect/TonConnectSubscription.tsx index 6b2087fae..6a0ffd817 100644 --- a/packages/uikit/src/components/connect/TonConnectSubscription.tsx +++ b/packages/uikit/src/components/connect/TonConnectSubscription.tsx @@ -17,7 +17,7 @@ import { import { TonTransactionNotification } from './TonTransactionNotification'; import { SendTransactionAppRequest, useResponseSendMutation } from './connectHook'; -import { useActiveWallet, useMutateActiveWallet } from '../../state/wallet'; +import { useActiveWallet, useMutateActiveTonWallet } from '../../state/wallet'; const useUnSupportMethodMutation = () => { return useMutation(replyBadRequestResponse); @@ -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) => { diff --git a/packages/uikit/src/components/connect/TonTransactionNotification.tsx b/packages/uikit/src/components/connect/TonTransactionNotification.tsx index d9d7fca61..5fa01f1d8 100644 --- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx +++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx @@ -33,7 +33,7 @@ import { Button } from '../fields/Button'; import { WalletEmoji } from '../shared/emoji/WalletEmoji'; import { ResultButton } from '../transfer/common'; import { EmulationList } from './EstimationLayout'; -import { useActiveStandardTonWallet, useActiveWallet, useWalletsState } from '../../state/wallet'; +import { useActiveStandardTonWallet, useActiveWallet, useAccountsState } from '../../state/wallet'; const ButtonGap = styled.div` ${props => @@ -305,7 +305,7 @@ export const TonTransactionNotification: FC<{ waitInvalidation?: boolean; }> = ({ params, handleClose, waitInvalidation }) => { const { t } = useTranslation(); - const wallets = useWalletsState(); + const wallets = useAccountsState(); const Content = useCallback(() => { if (!params) return undefined; return ( diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index 5689b156a..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,8 +100,8 @@ const isNumericColumn = (columnType: DashboardColumnType): boolean => { export const DashboardTable: FC<{ className?: string }> = ({ className }) => { const { data: columns } = useDashboardColumnsAsForm(); const { data: dashboardData } = useDashboardData(); - const wallets = useWalletsState(); - const mainnetIds = wallets?.filter(w => w && w.network !== Network.TESTNET).map(w => w!.id); + const wallets = useAccountsState(); + const mainnetIds = wallets?.map(w => w!.id); const [isResizing, setIsResizing] = useState(false); const [hoverOnColumn, setHoverOnColumn] = useState(undefined); diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 04673518d..d10f2aa68 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -9,7 +9,7 @@ import { useIsScrolled } from '../../../hooks/useIsScrolled'; import { scrollToTop } from '../../../libs/common'; import { AppProRoute, AppRoute } from '../../../libs/routes'; import { useMutateUserUIPreferences, useUserUIPreferences } from '../../../state/theme'; -import { useActiveWallet, useMutateActiveWallet, useWalletsState } from "../../../state/wallet"; +import { useActiveWallet, useAccountsState, useMutateActiveTonWallet } from '../../../state/wallet'; import { fallbackRenderOver } from '../../Error'; import { GlobeIcon, PlusIcon, SlidersIcon, StatsIcon } from '../../Icon'; import { Label2 } from '../../Text'; @@ -18,7 +18,7 @@ import { AsideMenuItem } from '../../shared/AsideItem'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; import { AsideHeader } from './AsideHeader'; import { SubscriptionInfo } from './SubscriptionInfo'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -92,16 +92,16 @@ const SubscriptionInfoStyled = styled(SubscriptionInfo)` padding: 6px 16px 6px 8px; `; -export const AsideMenuAccount: FC<{ wallet: WalletState; isSelected: boolean }> = ({ +export const AsideMenuAccount: FC<{ wallet: TonWalletStandard; isSelected: boolean }> = ({ wallet, isSelected }) => { const { t } = useTranslation(); - const { mutateAsync } = useMutateActiveWallet(); + const { mutateAsync } = useMutateActiveTonWallet(); const navigate = useNavigate(); const location = useLocation(); - const wallets = useWalletsState(); + const wallets = useAccountsState(); const shouldShowIcon = wallets.length > 1; const handleNavigateHome = useCallback(() => { @@ -137,7 +137,7 @@ const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation(); const [isOpenImport, setIsOpenImport] = useState(false); const { proFeatures } = useAppContext(); - const wallets = useWalletsState(); + const wallets = useAccountsState().flatMap(getAccountAllTonWallets); const activeWallet = useActiveWallet(); const navigate = useNavigate(); const location = useLocation(); diff --git a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx index 983b9c0c4..bc5b73704 100644 --- a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx @@ -29,7 +29,7 @@ import { Skeleton } from '../../shared/Skeleton'; import { useProState } from '../../../state/pro'; import { availableThemes, useUserUIPreferences } from '../../../state/theme'; import { hexToRGBA } from '../../../libs/css'; -import { useWalletsState } from '../../../state/wallet'; +import { useAccountsState } from '../../../state/wallet'; const PreferencesAsideContainer = styled.div` width: fit-content; @@ -91,7 +91,7 @@ export const PreferencesAsideMenu = () => { const { data: proState } = useProState(); const { data: uiPreferences } = useUserUIPreferences(); const { fiat } = useAppContext(); - const wallets = useWalletsState(); + const wallets = useAccountsState(); return ( diff --git a/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx b/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx index 0975de3fe..0bfb2e753 100644 --- a/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx +++ b/packages/uikit/src/components/desktop/history/ton/HistoryCell.tsx @@ -7,7 +7,7 @@ import { Body2, Body2Class } from '../../../Text'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { useFormatCoinValue } from '../../../../hooks/balance'; import { HistoryGridCell, HistoryGridCellFillRow } from './HistoryGrid'; -import { useActiveWallet } from '../../../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../../../state/wallet'; export const HistoryCellAction = styled(HistoryGridCell)` display: flex; @@ -103,7 +103,7 @@ export const HistoryCellAccount: FC<{ account?: { address?: string; name?: string }; fallbackAddress?: string; }> = ({ account, fallbackAddress }) => { - const wallet = useActiveWallet(); + 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/multi-send/MultiSendTable.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx index 25f3f0137..f26226c30 100644 --- a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx +++ b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx @@ -2,7 +2,7 @@ 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 { isW5Version, StandardTonWalletState } 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'; @@ -319,7 +319,7 @@ const MultiSendAddMore: FC<{ }> = ({ onAdd, fieldsNumber }) => { const { t } = useTranslation(); - const wallet = useActiveWallet() as StandardTonWalletState; + const wallet = useActiveWallet(); if (fieldsNumber < MAX_ALLOWED_WALLET_MSGS[wallet.version]) { return ( @@ -485,8 +485,7 @@ const MultiSendFooter: FC<{ const wallet = useActiveWallet(); - const maxMsgsNumberExceeded = - watch('rows').length > MAX_ALLOWED_WALLET_MSGS[(wallet as StandardTonWalletState).version]; + const maxMsgsNumberExceeded = watch('rows').length > MAX_ALLOWED_WALLET_MSGS[wallet.version]; const isLedger = useIsActiveWalletLedger(); diff --git a/packages/uikit/src/components/home/AccountView.tsx b/packages/uikit/src/components/home/AccountView.tsx index b63ea31e6..f9b96ff22 100644 --- a/packages/uikit/src/components/home/AccountView.tsx +++ b/packages/uikit/src/components/home/AccountView.tsx @@ -21,7 +21,7 @@ import { Body1, H3 } from '../Text'; import { Button } from '../fields/Button'; import { Wrapper, childFactoryCreator, duration } from '../transfer/common'; import { QrWrapper } from './qrCodeView'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; const CopyBlock = styled.div` display: flex; @@ -98,10 +98,10 @@ const Description = styled(Body1)` color: ${props => props.theme.textSecondary}; `; -const values = [ +/*const values = [ { name: BLOCKCHAIN_NAME.TON, id: BLOCKCHAIN_NAME.TON }, { name: 'TRC20', id: BLOCKCHAIN_NAME.TRON } -]; +];*/ export const HeaderBlock: FC<{ title?: string; description: string }> = ({ title, @@ -141,8 +141,9 @@ const ReceiveTon: FC<{ jetton?: string }> = ({ jetton }) => { const { extension } = useAppContext(); const wallet = useActiveWallet(); const { t } = useTranslation(); + const network = useActiveTonNetwork(); - const address = formatAddress(wallet.rawAddress, wallet.network); + const address = formatAddress(wallet.rawAddress, network); return ( diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index 64d786d66..5ea5c86b3 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -6,13 +6,12 @@ import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { formatFiatCurrency } from '../../hooks/balance'; import { QueryKey } from '../../libs/queryKey'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveAccount, useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; import { Body3, Label2, Num2 } from '../Text'; import { Badge } from '../shared'; import { SkeletonText } from '../shared/Skeleton'; import { AssetData } from './Jettons'; -import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet'; -import { useWalletTotalBalance } from "../../state/asset"; +import { useWalletTotalBalance } from '../../state/asset'; const Block = styled.div` display: flex; @@ -69,15 +68,10 @@ export const BalanceSkeleton = () => { }; const Label = () => { - const wallet = useActiveWallet(); - - if (!isStandardTonWallet(wallet)) { - return <>; - } + const account = useActiveAccount(); - switch (wallet.auth.kind) { - case 'signer': - case 'signer-deeplink': + switch (account.type) { + case 'ton-only': return ( <> {' '} @@ -118,8 +112,9 @@ export const Balance: FC<{ const { fiat } = useAppContext(); const wallet = useActiveWallet(); const client = useQueryClient(); + const network = useActiveTonNetwork(); - const address = formatAddress(wallet.rawAddress, wallet.network); + const address = formatAddress(wallet.rawAddress, network); const { data: total } = useWalletTotalBalance(fiat); diff --git a/packages/uikit/src/components/home/BuyItemNotification.tsx b/packages/uikit/src/components/home/BuyItemNotification.tsx index c75894e94..90cc52621 100644 --- a/packages/uikit/src/components/home/BuyItemNotification.tsx +++ b/packages/uikit/src/components/home/BuyItemNotification.tsx @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { sha512_sync } from '@ton/crypto'; import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; -import { TonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { TonendpoinFiatButton, TonendpoinFiatItem, @@ -159,12 +159,12 @@ const useShowDisclaimer = (title: string, kind: 'buy' | 'sell') => { const replacePlaceholders = ( url: string, config: TonendpointConfig, - wallet: TonWalletState, + wallet: TonWalletStandard, fiat: FiatCurrencies, kind: 'buy' | 'sell' ) => { const [CUR_FROM, CUR_TO] = kind === 'buy' ? [fiat, 'TON'] : ['TON', fiat]; - const address = formatAddress(wallet.rawAddress, wallet.network); + const address = formatAddress(wallet.rawAddress); url = url .replace('{ADDRESS}', address) .replace('{CUR_FROM}', CUR_FROM) diff --git a/packages/uikit/src/components/nft/LinkNft.tsx b/packages/uikit/src/components/nft/LinkNft.tsx index 085dbe835..1020e4de2 100644 --- a/packages/uikit/src/components/nft/LinkNft.tsx +++ b/packages/uikit/src/components/nft/LinkNft.tsx @@ -19,7 +19,7 @@ import { useTonRecipient } from '../../hooks/blockchain/useTonRecipient'; import { useTranslation } from '../../hooks/translation'; import { useNotification } from '../../hooks/useNotification'; import { useQueryChangeWait } from '../../hooks/useQueryChangeWait'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; import { ColumnText, Gap } from '../Layout'; import { ListItem, ListItemPayload } from '../List'; import { Notification, NotificationBlock } from '../Notification'; @@ -38,7 +38,7 @@ import { ConfirmViewTitleSlot } from '../transfer/ConfirmView'; import { ConfirmAndCancelMainButton } from '../transfer/common'; -import { useNftDNSLinkData } from "../../state/nft"; +import { useNftDNSLinkData } from '../../state/nft'; export const LinkNft: FC<{ nft: NFTDNS }> = ({ nft }) => { const toast = useToast(); @@ -143,6 +143,7 @@ const LinkNftUnlinked: FC<{ }); const isSelectedCurrentAddress = areEqAddresses(linkToAddress, walletState.rawAddress); + const network = useActiveTonNetwork(); const confirmChild = () => ( ( - isStandardTonWallet(walletState) - ? getWalletsAddresses(walletState.publicKey, walletState.network) - : {} + isStandardTonWallet(walletState) ? getWalletsAddresses(walletState.publicKey, network) : {} ).every(({ address }) => !areEqAddresses(address.toRawString(), linkedAddress)); return ( @@ -388,7 +388,7 @@ const LinkNftLinked: FC<{ > {t('nft_unlink_domain_button').replace( '{{address}}', - toShortValue(formatAddress(linkedAddress, walletState.network)) + toShortValue(formatAddress(linkedAddress, network)) )} {isLinkedWithAnotherWallet && !isLoading && ( diff --git a/packages/uikit/src/components/nft/NftDetails.tsx b/packages/uikit/src/components/nft/NftDetails.tsx index f0fea685a..4e6575d81 100644 --- a/packages/uikit/src/components/nft/NftDetails.tsx +++ b/packages/uikit/src/components/nft/NftDetails.tsx @@ -7,12 +7,12 @@ import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { useDateFormat } from '../../hooks/dateFormat'; import { useTranslation } from '../../hooks/translation'; -import { useActiveWallet } 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"; +import { useNftDNSExpirationDate, useNftItemData } from '../../state/nft'; const Block = styled.div` width: 100%; @@ -35,7 +35,6 @@ const RightText = styled(Body1)` `; export const NftDetails: FC<{ nftItem: NftItem; kind: NFTKind }> = React.memo(({ nftItem }) => { - const wallet = useActiveWallet(); const { t } = useTranslation(); const { data } = useNftItemData(nftItem.address); const { data: expirationDate, isLoading: isExpirationDateLoading } = @@ -53,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 ( @@ -66,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 c04368f82..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 } from '../../state/wallet'; +import { useActiveTonWalletConfig } from '../../state/wallet'; import { BlockIcon, ChevronDownIcon, @@ -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/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index f3ed9574f..e68eeeb4a 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -1,11 +1,15 @@ -import { isStandardTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { + getAccountActiveTonWallet, + isStandardTonWallet, + walletVersionText +} from '@tonkeeper/core/dist/entries/wallet'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; 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 { LogOutAccountNotification } from './LogOutNotification'; import { AppsIcon, ListOfTokensIcon, @@ -16,14 +20,15 @@ import { WalletsIcon } from './SettingsIcons'; import { SettingsItem, SettingsList } from './SettingsList'; -import { useActiveWallet, useWalletsState } from "../../state/wallet"; -import { useWalletNftList } from "../../state/nft"; +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 = useActiveWallet(); + const account = useActiveAccount(); + const wallet = getAccountActiveTonWallet(account); const { data: jettons } = useJettonList(); const { data: nft } = useWalletNftList(); const { proFeatures } = useAppContext(); @@ -44,7 +49,7 @@ const SingleAccountSettings = () => { }); }*/ - if (isStandardTonWallet(wallet)) { + if (account.type === 'mnemonic') { items.push({ name: t('settings_wallet_version'), icon: walletVersionText(wallet.version), @@ -93,13 +98,13 @@ const SingleAccountSettings = () => { }); return items; - }, [t, navigate, wallet, jettons, nft]); + }, [t, navigate, account, jettons, nft]); return ( <> - setLogout(false)} /> @@ -197,7 +202,7 @@ const MultipleAccountSettings = () => { }; export const AccountSettings = () => { - const wallets = useWalletsState(); + const wallets = useAccountsState(); if (wallets.length > 1) { return ; diff --git a/packages/uikit/src/components/settings/ClearSettings.tsx b/packages/uikit/src/components/settings/ClearSettings.tsx index e99e87d08..4d9a4d51d 100644 --- a/packages/uikit/src/components/settings/ClearSettings.tsx +++ b/packages/uikit/src/components/settings/ClearSettings.tsx @@ -3,12 +3,12 @@ import { useTranslation } from '../../hooks/translation'; import { DeleteAllNotification } from './LogOutNotification'; import { DeleteAccountIcon } from './SettingsIcons'; import { SettingsList } from './SettingsList'; -import { useWalletsState } from '../../state/wallet'; +import { useAccountsState } from '../../state/wallet'; export const ClearSettings = () => { const { t } = useTranslation(); - const wallets = useWalletsState(); + const wallets = useAccountsState(); const [open, setOpen] = useState(false); const deleteItems = useMemo(() => { return [ diff --git a/packages/uikit/src/components/settings/LogOutNotification.tsx b/packages/uikit/src/components/settings/LogOutNotification.tsx index fa2a636da..339db5394 100644 --- a/packages/uikit/src/components/settings/LogOutNotification.tsx +++ b/packages/uikit/src/components/settings/LogOutNotification.tsx @@ -1,5 +1,5 @@ import { useQueryClient } from '@tanstack/react-query'; -import { isStandardTonWallet, TonWalletState, WalletId } from '@tonkeeper/core/dist/entries/wallet'; +import { AccountId } from '@tonkeeper/core/dist/entries/wallet'; import { FC, useCallback, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -12,6 +12,7 @@ import { Body1, H2, Label1, Label2 } from '../Text'; import { Button } from '../fields/Button'; import { Checkbox } from '../fields/Checkbox'; import { DisclaimerBlock } from '../home/BuyItemNotification'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; const NotificationBlock = styled.form` display: flex; @@ -38,9 +39,9 @@ const DisclaimerLink = styled(Label1)` const LotOutContent: FC<{ onClose: (action: () => void) => void; - walletId: WalletId; + accountId: AccountId; isKeystone: boolean; -}> = ({ onClose, walletId, isKeystone }) => { +}> = ({ onClose, accountId, isKeystone }) => { const navigate = useNavigate(); const { t } = useTranslation(); const [checked, setChecked] = useState(false); @@ -70,7 +71,7 @@ const LotOutContent: FC<{ onClick={() => onClose(() => navigate( - AppRoute.settings + SettingsRoute.recovery + '/' + walletId + AppRoute.settings + SettingsRoute.recovery + '/' + accountId ) ) } @@ -86,7 +87,7 @@ const LotOutContent: FC<{ fullWidth loading={isLoading} onClick={async () => { - await mutateAsync(walletId); + await mutateAsync(accountId); onClose(() => navigate(AppRoute.home)); }} > @@ -96,26 +97,26 @@ const LotOutContent: FC<{ ); }; -export const LogOutWalletNotification: FC<{ - wallet?: TonWalletState; +export const LogOutAccountNotification: 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} ); @@ -123,16 +124,16 @@ export const LogOutWalletNotification: FC<{ const DeleteContent: FC<{ onClose: (action: () => void) => void; - walletId: WalletId; + accountId: AccountId; isKeystone: boolean; -}> = ({ onClose, walletId, isKeystone }) => { +}> = ({ onClose, accountId, isKeystone }) => { const navigate = useNavigate(); const { t } = useTranslation(); const [checked, setChecked] = useState(false); const { mutateAsync, isLoading } = useMutateLogOut(); const onDelete = async () => { - await mutateAsync(walletId); + await mutateAsync(accountId); onClose(() => navigate(AppRoute.home)); }; @@ -160,7 +161,7 @@ const DeleteContent: FC<{ onClick={() => onClose(() => navigate( - AppRoute.settings + SettingsRoute.recovery + '/' + walletId + AppRoute.settings + SettingsRoute.recovery + '/' + accountId ) ) } @@ -183,26 +184,26 @@ const DeleteContent: FC<{ ); }; -export const DeleteWalletNotification: FC<{ - wallet?: TonWalletState; +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} ); diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index d9e47dd3b..19723d238 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -18,7 +18,7 @@ import { useSelectWalletForProMutation, useWaitInvoiceMutation } from '../../state/pro'; -import { useWalletsState, useWalletState } from '../../state/wallet'; +import { useAccountsState, useActiveTonNetwork, useWalletState } from '../../state/wallet'; import { InnerBody } from '../Body'; import { SubscriptionStatus } from '../desktop/aside/SubscriptionInfo'; import { Button } from '../fields/Button'; @@ -31,7 +31,7 @@ import { Notification } from '../Notification'; import { SubHeader } from '../SubHeader'; import { Body1, Label1, Title } from '../Text'; import { ConfirmView } from '../transfer/ConfirmView'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div` display: flex; @@ -55,12 +55,11 @@ const Description = styled(Body1)` margin-bottom: 16px; `; -const WalletItem: FC<{ wallet: WalletState }> = ({ wallet }) => { +const WalletItem: FC<{ wallet: TonWalletStandard }> = ({ wallet }) => { const { t } = useTranslation(); + const network = useActiveTonNetwork(); - const address = wallet - ? toShortValue(formatAddress(wallet.rawAddress, wallet.network)) - : undefined; + const address = wallet ? toShortValue(formatAddress(wallet.rawAddress, network)) : undefined; return ( void }> = ({ onClose }) => { const { t } = useTranslation(); const { mutateAsync, error } = useSelectWalletForProMutation(); useNotifyError(error); - const wallets = useWalletsState(); + const wallets = useAccountsState().flatMap(getAccountAllTonWallets); return ( <> diff --git a/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx b/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx index 22080dc3f..03d4f41be 100644 --- a/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx +++ b/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx @@ -5,7 +5,7 @@ import { ListBlock, ListItemElement, ListItemPayload } from '../../../components import { SkeletonListWithImages } from '../../../components/Skeleton'; import { Body2, H3, Label1 } from '../../../components/Text'; import { useTranslation } from '../../../hooks/translation'; -import { useActiveWalletConfig } 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, useWalletNftList } from "../../../state/nft"; @@ -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 []; diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx index 3f80e50cc..7d0c4b043 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/wallet'; 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(); + 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({ id: wallet.id, name, emoji }); + await mutateAsync({ id: account.id, name, emoji }); afterClose(() => null); }; - const address = formatAddress(wallet.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 ( { 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 = useActiveWallet(); - 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 78e1cc35a..e00cb301b 100644 --- a/packages/uikit/src/components/transfer/RecipientView.tsx +++ b/packages/uikit/src/components/transfer/RecipientView.tsx @@ -32,7 +32,7 @@ import { TextArea } from '../fields/Input'; import { InputWithScanner } from '../fields/InputWithScanner'; import { ShowAddress, useShowAddress } from './ShowAddress'; import { SuggestionList } from './SuggestionList'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork } from '../../state/wallet'; const Warning = styled(Body2)` user-select: none; @@ -136,7 +136,7 @@ export const RecipientView: FC<{ }) => { const sdk = useAppSdk(); const [submitted, setSubmit] = useState(false); - const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); const { t } = useTranslation(); const { standalone, ios } = useAppContext(); const ref = useRef(null); @@ -243,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); } } @@ -252,7 +252,7 @@ export const RecipientView: FC<{ } return recipient.address; - }, [recipient]); + }, [recipient, network]); const showAddress = useShowAddress(ref, formatted, toAccount); @@ -294,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 c4058cb92..3d2594d06 100644 --- a/packages/uikit/src/components/transfer/ShowAddress.tsx +++ b/packages/uikit/src/components/transfer/ShowAddress.tsx @@ -4,7 +4,7 @@ import React, { FC, PropsWithChildren, useEffect, useState } from 'react'; import styled from 'styled-components'; import useTextWidth from '../../hooks/textWidth'; import { Body1 } from '../Text'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; interface ShowAddressProps { inputTextWidth: number; @@ -16,7 +16,7 @@ export const useShowAddress = ( value: string, toAccount?: Account ) => { - const wallet = useActiveWallet(); + 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 5fda8ee62..cc085c88b 100644 --- a/packages/uikit/src/components/transfer/SuggestionAddress.tsx +++ b/packages/uikit/src/components/transfer/SuggestionAddress.tsx @@ -7,16 +7,16 @@ import { useTranslation } from '../../hooks/translation'; import { ListBlock, ListItem, ListItemPayload } from '../List'; import { Label1 } from '../Text'; import { Label } from './common'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork } from '../../state/wallet'; export const useSuggestionAddress = (item: Suggestion) => { - const wallet = useActiveWallet(); + 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/amountView/AmountViewUI.tsx b/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx index be6ba9f70..26b68497e 100644 --- a/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx +++ b/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx @@ -1,5 +1,5 @@ import { RecipientData, isTonRecipientData } from '@tonkeeper/core/dist/entries/send'; -import { TonWalletState } 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'; @@ -11,7 +11,7 @@ import { formatter } from '../../../hooks/balance'; import { Body1, Body2, H3, Label2, Num2 } from '../../Text'; import { cropName } from '../ConfirmListItem'; import { AmountState } from './amountState'; -import { useActiveWallet } from '../../../state/wallet'; +import { useActiveTonNetwork } from '../../../state/wallet'; export const Center = styled.div` text-align: center; @@ -160,18 +160,18 @@ export const RecipientName: FC<{ recipient: RecipientData }> = ({ recipient }) = return <>; }; -export const getRecipientAddress = (recipient: RecipientData, wallet: TonWalletState) => { +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 = useActiveWallet(); - const address = getRecipientAddress(recipient, wallet); + const network = useActiveTonNetwork(); + const address = getRecipientAddress(recipient, network); return
{toShortValue(address)}
; }; diff --git a/packages/uikit/src/components/transfer/nft/Common.tsx b/packages/uikit/src/components/transfer/nft/Common.tsx index 81a15b9dd..f7326a746 100644 --- a/packages/uikit/src/components/transfer/nft/Common.tsx +++ b/packages/uikit/src/components/transfer/nft/Common.tsx @@ -9,7 +9,7 @@ import { NotificationCancelButton, NotificationTitleBlock } from '../../Notifica import { Label1 } from '../../Text'; import { RoundedButton } from '../../fields/RoundedButton'; import { Label } from '../common'; -import { useActiveWallet } from '../../../state/wallet'; +import { useActiveTonNetwork } from '../../../state/wallet'; export type ConfirmHeaderBlockComponent = (props: { onBack: () => void; @@ -30,7 +30,7 @@ export const ConfirmHeaderBlock: ConfirmHeaderBlockComponent = ({ onBack, onClos export const NftDetailsBlock: FC<{ nftItem: NFT }> = ({ nftItem }) => { const { t } = useTranslation(); const sdk = useAppSdk(); - const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); return ( @@ -38,7 +38,7 @@ export const NftDetailsBlock: FC<{ nftItem: NFT }> = ({ nftItem }) => { sdk.copyToClipboard( - formatAddress(nftItem.collection!.address, wallet.network, true) + formatAddress(nftItem.collection!.address, network, true) ) } > @@ -46,22 +46,18 @@ export const NftDetailsBlock: FC<{ nftItem: NFT }> = ({ nftItem }) => { {toShortValue( - formatAddress(nftItem.collection!.address, wallet.network, true) + formatAddress(nftItem.collection!.address, network, true) )} )} - 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 5128beeeb..8c5dee132 100644 --- a/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx +++ b/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx @@ -34,7 +34,11 @@ import { ConfirmViewDetailsRecipient } from '../ConfirmView'; import { NftDetailsBlock } from './Common'; -import { useActiveStandardTonWallet } from '../../../state/wallet'; +import { + useActiveAccount, + useActiveStandardTonWallet, + useInvalidateActiveWalletQueries +} from '../../../state/wallet'; const assetAmount = new AssetAmount({ asset: TON_ASSET, @@ -75,15 +79,16 @@ const useSendNft = ( const { t } = useTranslation(); const sdk = useAppSdk(); const { api } = useAppContext(); - const wallet = useActiveStandardTonWallet(); + 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.id, 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')); } @@ -91,13 +96,12 @@ const useSendNft = ( 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.id]); - await client.invalidateQueries(); + await invalidateAccountQueries(); return true; }); }; diff --git a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx b/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx index 3dd264042..baf724cb8 100644 --- a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx +++ b/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx @@ -2,7 +2,7 @@ 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 { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { checkWalletBalanceOrDie, externalMessage, @@ -118,7 +118,7 @@ const checkBurnDate = async (api: APIConfig, config: TonendpointConfig) => { const createNftMultiTransfer = async ( timestamp: number, seqno: number, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, chunk: NftItem[], config: TonendpointConfig, signer: CellSigner @@ -155,7 +155,7 @@ const createNftMultiTransfer = async ( const sendNftMultiTransfer = async ( api: APIConfig, - walletState: StandardTonWalletState, + walletState: TonWalletStandard, chunk: NftItem[], config: TonendpointConfig, signer: CellSigner diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index c484fe556..69da8d02a 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -1,4 +1,4 @@ -import { isStandardTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { getAccountActiveTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { @@ -15,13 +15,13 @@ import { DesktopViewHeader, DesktopViewPageLayout } from '../../components/desktop/DesktopViewLayout'; -import { LogOutWalletNotification } from '../../components/settings/LogOutNotification'; +import { LogOutAccountNotification } from '../../components/settings/LogOutNotification'; import { RenameWalletNotification } from '../../components/settings/wallet-name/WalletNameNotification'; import { WalletEmoji } from '../../components/shared/emoji/WalletEmoji'; import { useTranslation } from '../../hooks/translation'; import { useDisclosure } from '../../hooks/useDisclosure'; import { AppRoute, WalletSettingsRoute } from '../../libs/routes'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveAccount } from '../../state/wallet'; const SettingsListBlock = styled.div` padding: 0.5rem 0; @@ -55,10 +55,13 @@ const LinkStyled = styled(Link)` export const DesktopWalletSettingsPage = () => { const { t } = useTranslation(); - const wallet = useActiveWallet(); + const account = useActiveAccount(); const { isOpen: isRenameOpen, onClose: onRenameClose, onOpen: onRenameOpen } = useDisclosure(); const { isOpen: isLogoutOpen, onClose: onLogoutClose, onOpen: onLogoutOpen } = useDisclosure(); + const canChangeVersion = account.type === 'mnemonic'; + const activeWallet = getAccountActiveTonWallet(account); + return ( @@ -66,9 +69,9 @@ export const DesktopWalletSettingsPage = () => { - + - {wallet.name || t('wallet_title')} + {account.name || t('wallet_title')} {t('customize')} @@ -81,13 +84,13 @@ export const DesktopWalletSettingsPage = () => { {t('settings_backup_seed')} - {isStandardTonWallet(wallet) && ( + {canChangeVersion && ( {t('settings_wallet_version')} - {walletVersionText(wallet.version)} + {walletVersionText(activeWallet.version)} @@ -121,11 +124,11 @@ export const DesktopWalletSettingsPage = () => { - diff --git a/packages/uikit/src/hooks/accountUtils.ts b/packages/uikit/src/hooks/accountUtils.ts new file mode 100644 index 000000000..7249c2174 --- /dev/null +++ b/packages/uikit/src/hooks/accountUtils.ts @@ -0,0 +1,14 @@ +import { Account, getAccountAllTonWallets } from '@tonkeeper/core/dist/entries/wallet'; +import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import { useActiveTonNetwork } from '../state/wallet'; +import { useTranslation } from './translation'; + +export function useAccountLabel(account: Account) { + const tonWallets = getAccountAllTonWallets(account); + const network = useActiveTonNetwork(); + const { t } = useTranslation(); + + return tonWallets.length === 1 + ? toShortValue(formatAddress(tonWallets[0].rawAddress, network)) + : tonWallets.length + ' ' + t('wallets'); +} diff --git a/packages/uikit/src/hooks/analytics/amplitude.ts b/packages/uikit/src/hooks/analytics/amplitude.ts index 07993a08c..94c203b12 100644 --- a/packages/uikit/src/hooks/analytics/amplitude.ts +++ b/packages/uikit/src/hooks/analytics/amplitude.ts @@ -1,19 +1,20 @@ import * as amplitude from '@amplitude/analytics-browser'; import { Network } from '@tonkeeper/core/dist/entries/network'; -import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; import { Analytics } from '.'; export class Amplitude implements Analytics { constructor(private key: string, private userId?: string) {} - init( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - version?: string, - platform?: string - ) { + init(params: { + application: string; + walletType: string; + activeAccount: Account; + accounts: Account[]; + network?: Network; + version?: string; + platform?: string; + }) { amplitude.init(this.key, this.userId, { defaultTracking: { sessions: true, @@ -24,12 +25,12 @@ export class Amplitude implements Analytics { }); const event = new amplitude.Identify(); - event.set('application', application ?? 'Unknown'); - event.set('walletType', walletType); - event.set('network', activeWallet?.network === Network.TESTNET ? 'testnet' : 'mainnet'); - event.set('accounts', wallets?.length ?? 0); - event.set('version', version ?? 'Unknown'); - event.set('platform', platform ?? 'Unknown'); + event.set('application', params.application ?? 'Unknown'); + event.set('walletType', params.walletType); + event.set('network', params.network === Network.TESTNET ? 'testnet' : 'mainnet'); + event.set('accounts', params.accounts?.length ?? 0); + event.set('version', params.version ?? 'Unknown'); + event.set('platform', params.platform ?? 'Unknown'); amplitude.identify(event); } diff --git a/packages/uikit/src/hooks/analytics/aptabase-web.ts b/packages/uikit/src/hooks/analytics/aptabase-web.ts index 20f354f98..a0befcb54 100644 --- a/packages/uikit/src/hooks/analytics/aptabase-web.ts +++ b/packages/uikit/src/hooks/analytics/aptabase-web.ts @@ -1,6 +1,7 @@ import { init, trackEvent } from '@aptabase/web'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { Analytics } from '.'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; export class AptabaseWeb implements Analytics { private user_properties: Record = {}; @@ -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 135ab6901..02c5847ac 100644 --- a/packages/uikit/src/hooks/analytics/google.ts +++ b/packages/uikit/src/hooks/analytics/google.ts @@ -1,7 +1,7 @@ import { AppKey } from '@tonkeeper/core/dist/Keys'; import { IStorage } from '@tonkeeper/core/dist/Storage'; import { Network } from '@tonkeeper/core/dist/entries/network'; -import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; import { v4 as uuidv4 } from 'uuid'; import { Analytics } from '.'; @@ -30,22 +30,23 @@ export class GoogleAnalytics4 implements Analytics { } } - init( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - version?: string, - platform?: string - ) { - this.user_properties.application = { value: application }; - this.user_properties.walletType = { value: walletType }; + 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: activeWallet?.network === Network.TESTNET ? 'testnet' : 'mainnet' + value: params.network === Network.TESTNET ? 'testnet' : 'mainnet' }; - this.user_properties.accounts = { value: wallets?.length || 0 }; - 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 e6dd38119..a9d052dd0 100644 --- a/packages/uikit/src/hooks/analytics/gtag.ts +++ b/packages/uikit/src/hooks/analytics/gtag.ts @@ -1,5 +1,5 @@ import { Network } from '@tonkeeper/core/dist/entries/network'; -import { WalletsState, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; import ReactGA from 'react-ga4'; import { Analytics } from '.'; @@ -8,21 +8,22 @@ export class Gtag implements Analytics { ReactGA.initialize(this.measurementId); } - init( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - 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: activeWallet?.network === Network.TESTNET ? 'testnet' : 'mainnet', - accounts: wallets?.length || 0, - 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 b59c896ae..603df9716 100644 --- a/packages/uikit/src/hooks/analytics/index.ts +++ b/packages/uikit/src/hooks/analytics/index.ts @@ -1,21 +1,23 @@ import { isStandardTonWallet, - WalletsState, - WalletState, - walletVersionText + walletVersionText, + Account, + TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { Network } from '@tonkeeper/core/dist/entries/network'; export interface Analytics { pageView: (location: string) => void; - init: ( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - 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; } @@ -30,17 +32,16 @@ export class AnalyticsGroup implements Analytics { this.analytics.forEach(c => c.pageView(location)); } - init( - application: string, - walletType: string, - activeWallet?: WalletState, - wallets?: WalletsState, - version?: string, - platform?: string - ) { - this.analytics.forEach(c => - c.init(application, walletType, activeWallet, wallets, 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) { @@ -48,7 +49,7 @@ export class AnalyticsGroup implements Analytics { } } -export const toWalletType = (wallet?: WalletState | null): string => { +export const toWalletType = (wallet?: TonWalletStandard | null): string => { if (!wallet) return 'new-user'; if (!isStandardTonWallet(wallet)) { return 'multisend'; diff --git a/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts b/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts index 5d671ef30..7cf382c36 100644 --- a/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts +++ b/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts @@ -4,15 +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 { StandardTonWalletState } 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 } from '../appContext'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveStandardTonWallet } from '../../state/wallet'; export type ContractCallerParams = { api: APIConfig; - walletState: StandardTonWalletState; + walletState: TonWalletStandard; }; export function useEstimateTonFee( @@ -28,7 +28,7 @@ export function useEstimateTonFee( args: Omit ) { const { api } = useAppContext(); - const walletState = useActiveWallet() as StandardTonWalletState; + const walletState = useActiveStandardTonWallet(); return useQuery, Error>( queryKey, diff --git a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts index e7b416484..b9fa194bd 100644 --- a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts +++ b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts @@ -11,7 +11,7 @@ import { TransferEstimation, TransferEstimationEvent } from '@tonkeeper/core/dist/entries/send'; -import { StandardTonWalletState } 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 { JettonsBalances } from '@tonkeeper/core/dist/tonApiV2'; @@ -36,7 +36,7 @@ async function estimateTon({ amount: AssetAmount; isMax: boolean; api: APIConfig; - wallet: StandardTonWalletState; + wallet: TonWalletStandard; jettons: JettonsBalances | undefined; }): Promise> { let payload: TransferEstimationEvent; diff --git a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts index dc9c6386f..5f940f405 100644 --- a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts +++ b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts @@ -2,7 +2,7 @@ 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 { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/wallet'; import { Omit } from 'react-beautiful-dnd'; import { notifyError } from '../../components/transfer/common'; import { getSigner } from '../../state/mnemonic'; @@ -12,11 +12,11 @@ import { useAppSdk } from '../appSdk'; import { useTranslation } from '../translation'; import { TxConfirmationCustomError } from '../../libs/errors/TxConfirmationCustomError'; import { useCheckTouchId } from '../../state/password'; -import { useActiveStandardTonWallet } from '../../state/wallet'; +import { useActiveAccount, useInvalidateActiveWalletQueries } from '../../state/wallet'; export type ContractExecutorParams = { api: APIConfig; - walletState: StandardTonWalletState; + account: Account; signer: CellSigner; fee: TransferEstimationEvent; }; @@ -34,17 +34,18 @@ export function useExecuteTonContract( const { t } = useTranslation(); const sdk = useAppSdk(); const { api } = useAppContext(); - const walletState = useActiveStandardTonWallet(); + 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')); } @@ -55,7 +56,7 @@ export function useExecuteTonContract( try { await executor({ api, - walletState, + account, signer, ...args } as Args); @@ -63,7 +64,7 @@ export function useExecuteTonContract( await notifyError(client, sdk, t, e); } - await client.invalidateQueries([walletState.id]); + await invalidateAccountQueries(); await client.invalidateQueries(); return true; }); diff --git a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts index c0362560b..e8c4e56c3 100644 --- a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts +++ b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts @@ -20,7 +20,7 @@ import { useTransactionAnalytics } from '../amplitude'; import { useAppContext } from '../appContext'; import { useAppSdk } from '../appSdk'; import { useTranslation } from '../translation'; -import { useActiveStandardTonWallet } from '../../state/wallet'; +import { useActiveAccount, useInvalidateActiveWalletQueries } from '../../state/wallet'; export function useSendTransfer( recipient: T extends TonAsset ? TonRecipientData : TronRecipientData, @@ -31,14 +31,15 @@ export function useSendTransfer( const { t } = useTranslation(); const sdk = useAppSdk(); const { api } = useAppContext(); - const wallet = useActiveStandardTonWallet(); + 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.id, checkTouchId).catch(() => null); + const signer = await getSigner(sdk, account.id, checkTouchId).catch(() => null); if (signer === null) return false; try { if (isTonAsset(amount.asset)) { @@ -46,7 +47,7 @@ export function useSendTransfer( track2('send-ton'); await sendTonTransfer( api, - wallet, + account, recipient as TonRecipientData, amount, isMax, @@ -62,7 +63,7 @@ export function useSendTransfer( )!; await sendJettonTransfer( api, - wallet, + account, recipient as TonRecipientData, amount as AssetAmount, jettonInfo!.walletAddress.address, @@ -88,9 +89,7 @@ export function useSendTransfer( await notifyError(client, sdk, t, e); } - await client.invalidateQueries({ - predicate: query => query.queryKey.includes(wallet.id) - }); + await invalidateAccountQueries(); return true; }); } diff --git a/packages/uikit/src/hooks/blockchain/useTonRecipient.ts b/packages/uikit/src/hooks/blockchain/useTonRecipient.ts index 8fecd5f76..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 { useActiveWallet } from '../../state/wallet'; +import { useActiveTonNetwork } from '../../state/wallet'; export function useTonRecipient(address: string): { recipient: TonRecipientData; isLoading: boolean; } { - const wallet = useActiveWallet(); + 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 index 14b572f93..53f0c6d5a 100644 --- a/packages/uikit/src/hooks/useDebuggingTools.ts +++ b/packages/uikit/src/hooks/useDebuggingTools.ts @@ -1,6 +1,6 @@ import { useAppSdk } from './appSdk'; -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; -import { WalletsState } from '@tonkeeper/core/dist/entries/wallet'; +import { AccountsState } from '@tonkeeper/core/dist/entries/wallet'; +import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; export const useDebuggingTools = () => { const sdk = useAppSdk(); @@ -18,7 +18,7 @@ export const useDebuggingTools = () => { console.error('ERR: method is not supported'); return; } - walletsStorage(sdk.storage).setWallets(null as unknown as WalletsState); + accountsStorage(sdk.storage).setAccounts(null as unknown as AccountsState); } }; } diff --git a/packages/uikit/src/hooks/useStorage.ts b/packages/uikit/src/hooks/useStorage.ts index 0027ae5ba..da047076c 100644 --- a/packages/uikit/src/hooks/useStorage.ts +++ b/packages/uikit/src/hooks/useStorage.ts @@ -1,10 +1,10 @@ -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; import { useAppSdk } from './appSdk'; import { passwordStorage } from '@tonkeeper/core/dist/service/passwordService'; +import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; -export const useWalletsStorage = () => { +export const useAccountsStorage = () => { const sdk = useAppSdk(); - return walletsStorage(sdk.storage); + return accountsStorage(sdk.storage); }; export const usePasswordStorage = () => { diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index d579f5d34..3d3d8bbed 100644 --- a/packages/uikit/src/libs/queryKey.ts +++ b/packages/uikit/src/libs/queryKey.ts @@ -22,6 +22,7 @@ export enum QueryKey { analytics = 'analytics', language = 'language', walletVersions = 'walletVersions', + globalPreferencesConfig = 'globalPreferencesConfig', tonConnectConnection = 'tonConnectConnection', tonConnectLastEventId = 'tonConnectLastEventId', diff --git a/packages/uikit/src/pages/home/Unlock.tsx b/packages/uikit/src/pages/home/Unlock.tsx index b07f47929..d0e4f5607 100644 --- a/packages/uikit/src/pages/home/Unlock.tsx +++ b/packages/uikit/src/pages/home/Unlock.tsx @@ -6,7 +6,7 @@ import { Button, ButtonRow } from '../../components/fields/Button'; import { Input } from '../../components/fields/Input'; import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; -import { useIsPasswordSet, useMutateDeleteAll, useWalletsState } from '../../state/wallet'; +import { useIsPasswordSet, useMutateDeleteAll, useAccountsState } from '../../state/wallet'; import { passwordStorage } from '@tonkeeper/core/dist/service/passwordService'; const Block = styled.form<{ minHeight?: string }>` @@ -53,7 +53,7 @@ const useMutateUnlock = () => { export const PasswordUnlock: FC<{ minHeight?: string }> = ({ minHeight }) => { const sdk = useAppSdk(); const { t } = useTranslation(); - const wallets = useWalletsState(); + const wallets = useAccountsState(); const ref = useRef(null); const { mutate: mutateLogOut, isLoading: isLogOutLoading } = useMutateDeleteAll(); diff --git a/packages/uikit/src/pages/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index 12ba3eb4b..64e3ca82f 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -15,11 +15,11 @@ import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Account, getAccountActiveTonWallet } from '@tonkeeper/core/dist/entries/wallet'; import { - useCreateStandardTonWalletsByMnemonic, - useMutateRenameWallet, - useWalletsState + useCreateAccountMnemonic, + useMutateRenameAccount, + useAccountsState } from '../../state/wallet'; const Create = () => { @@ -30,12 +30,12 @@ const Create = () => { mutateAsync: createWalletsAsync, isLoading: isCreateWalletLoading, reset: resetCreateWallets - } = useCreateStandardTonWalletsByMnemonic(); - const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameWallet(); + } = useCreateAccountMnemonic(); + const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameAccount(); - const existingWallets = useWalletsState(); + const existingWallets = useAccountsState(); const [mnemonic, setMnemonic] = useState(); - const [wallet, setWallet] = useState(undefined); + const [account, setAccount] = useState(undefined); const [create, setCreate] = useState(false); const [open, setOpen] = useState(false); @@ -67,10 +67,8 @@ const Create = () => { mnemonic, password: createdPassword, versions: [defaultWalletVersion], - activateFirstWallet: true - }).then(result => { - setWallet(result[0]); - }); + selectAccount: true + }).then(setAccount); } return resetCreateWallets; @@ -122,7 +120,7 @@ const Create = () => { } if (authExists) { - if (!wallet) { + if (!account) { return ( { ); } - if (!wallet) { + if (!account) { return ( { if (existingWallets.length > 1 && !passName) { return ( { - setWallet(w => ({ - ...w!, - ...val - })); renameWallet({ - id: wallet.id, + id: account.id, ...val - }).then(() => setPassName(true)); + }).then(newAcc => { + setPassName(true); + setAccount(newAcc); + }); }} - walletEmoji={wallet.emoji} + walletEmoji={account.emoji} isLoading={renameLoading} /> ); @@ -176,7 +173,7 @@ const Create = () => { if (sdk.notifications && !passNotifications) { return ( setPassNotification(true)} /> diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx index 8232d9fb0..d65a41e5e 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -6,18 +6,22 @@ import { useAppSdk } from '../../hooks/appSdk'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; import { - useCreateStandardTonWalletsByMnemonic, - useMutateRenameWallet, - useWalletsState + useCreateAccountMnemonic, + useMutateRenameAccount, + useAccountsState } from '../../state/wallet'; -import { StandardTonWalletState, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { + AccountTonMnemonic, + getAccountActiveTonWallet, + WalletVersion +} from '@tonkeeper/core/dist/entries/wallet'; import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions'; const Import = () => { const sdk = useAppSdk(); const [mnemonic, setMnemonic] = useState(); - const [wallets, setWallets] = useState(undefined); + const [account, setAccount] = useState(undefined); const [selectedVersions, setSelectedVersions] = useState( undefined ); @@ -25,14 +29,15 @@ const Import = () => { const [createdPassword, setCreatedPassword] = useState(undefined); const [passName, setPassName] = useState(false); const [passNotifications, setPassNotification] = useState(false); - const existingWallets = useWalletsState(); - const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameWallet(); + const existingWallets = useAccountsState(); + const { mutateAsync: renameAccount, isLoading: renameLoading } = + useMutateRenameAccount(); const { mutateAsync: createWalletsAsync, isLoading: isCreatingWallets, reset: resetCreateWallets - } = useCreateStandardTonWalletsByMnemonic(); + } = useCreateAccountMnemonic(); const authExists = createdPassword || existingWallets.length >= 1; @@ -42,8 +47,8 @@ const Import = () => { mnemonic, password: createdPassword, versions: selectedVersions, - activateFirstWallet: true - }).then(setWallets); + selectAccount: true + }).then(setAccount); } return resetCreateWallets; @@ -54,13 +59,13 @@ const Import = () => { } if (authExists) { - if (!wallets) { + if (!account) { return ( { - setWallets(undefined); + setAccount(undefined); setMnemonic(undefined); }} isLoading={isCreatingWallets} @@ -74,41 +79,43 @@ const Import = () => { mnemonic={mnemonic} onSubmit={setSelectedVersions} onBack={() => { - setWallets(undefined); + setAccount(undefined); setMnemonic(undefined); }} /> ); } - if (!wallets) { + if (!account) { return ( ); } } - if (existingWallets.length > 1 && wallets.length === 1 && !passName) { + if (existingWallets.length > 1 && !passName) { return ( { - setWallets(w => [{ ...w![0], ...val }]); - renameWallet({ - id: wallets![0].id, + renameAccount({ + id: account.id, ...val - }).then(() => setPassName(true)); + }).then(newAcc => { + setPassName(true); + setAccount(newAcc); + }); }} - walletEmoji={wallets[0].emoji} + walletEmoji={account.emoji} isLoading={renameLoading} /> ); } - if (sdk.notifications && !passNotifications && wallets.length === 1) { + if (sdk.notifications && !passNotifications) { return ( setPassNotification(true)} /> diff --git a/packages/uikit/src/pages/import/Ledger.tsx b/packages/uikit/src/pages/import/Ledger.tsx index 75b72edd7..35b7347b4 100644 --- a/packages/uikit/src/pages/import/Ledger.tsx +++ b/packages/uikit/src/pages/import/Ledger.tsx @@ -20,7 +20,7 @@ import { formatAddress } from '@tonkeeper/core/dist/utils/common'; import { Checkbox } from '../../components/fields/Checkbox'; import { LedgerConnectionSteps } from '../../components/ledger/LedgerConnectionSteps'; import { UpdateWalletName } from '../../components/create/WalletName'; -import { getFallbackWalletEmoji } from '@tonkeeper/core/dist/service/walletService'; +import { getFallbackAccountEmoji } from '@tonkeeper/core/dist/service/walletService'; import { toFormattedTonBalance } from "../../hooks/balance"; const ConnectLedgerWrapper = styled.div` @@ -183,7 +183,7 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: () }; if (accountsToAdd) { - const fallbackEmoji = getFallbackWalletEmoji(accountsToAdd[0].publicKey.toString('hex')); + const fallbackEmoji = getFallbackAccountEmoji(accountsToAdd[0].publicKey.toString('hex')); return ( void; }> = ({ wallet, mnemonic, onDone }) => { diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index 9418f8a4e..f547fc2cf 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -1,4 +1,3 @@ -import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { FC, useCallback, useMemo, useState } from 'react'; import { DragDropContext, @@ -19,8 +18,8 @@ import { SubHeader } from '../../components/SubHeader'; import { Label1 } from '../../components/Text'; import { ImportNotification } from '../../components/create/ImportNotification'; import { - DeleteWalletNotification, - LogOutWalletNotification + DeleteAccountNotification, + LogOutAccountNotification } from '../../components/settings/LogOutNotification'; import { SetUpWalletIcon } from '../../components/settings/SettingsIcons'; import { SettingsList } from '../../components/settings/SettingsList'; @@ -28,8 +27,9 @@ import { RenameWalletNotification } from '../../components/settings/wallet-name/ import { WalletEmoji } from '../../components/shared/emoji/WalletEmoji'; import { useTranslation } from '../../hooks/translation'; import { AppRoute, SettingsRoute } from '../../libs/routes'; -import { useMutateWalletsState, useWalletsState } from '../../state/wallet'; -import { isMnemonicAuthWallet, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { useMutateAccountsState, useAccountsState } from '../../state/wallet'; +import { Account as AccountType } from '@tonkeeper/core/dist/entries/wallet'; +import { useAccountLabel } from '../../hooks/accountUtils'; const Row = styled.div` display: flex; @@ -45,9 +45,9 @@ const Icon = styled.span` `; const WalletRow: FC<{ - wallet: WalletState; + account: AccountType; dragHandleProps: DraggableProvidedDragHandleProps | null | undefined; -}> = ({ wallet, dragHandleProps }) => { +}> = ({ account, dragHandleProps }) => { const navigate = useNavigate(); const { t } = useTranslation(); @@ -55,12 +55,12 @@ const WalletRow: FC<{ const [logout, setLogout] = useState(false); const [remove, setRemove] = useState(false); - if (!wallet) { + const secondary = useAccountLabel(account); + + if (!account) { return ; } - const address = formatAddress(wallet.rawAddress, wallet.network); - return ( <> @@ -68,12 +68,8 @@ const WalletRow: FC<{ - - + + ( @@ -88,14 +84,14 @@ const WalletRow: FC<{ {t('Rename')}
- {isMnemonicAuthWallet(wallet) && ( + {account.type === 'mnemonic' && ( { navigate( AppRoute.settings + SettingsRoute.recovery + - `/${wallet.id}` + `/${account.id}` ); }} > @@ -137,15 +133,15 @@ const WalletRow: FC<{ setRename(false)} /> - setLogout(false)} /> - setRemove(false)} /> @@ -156,8 +152,8 @@ export const Account = () => { const [isOpen, setOpen] = useState(false); const { t } = useTranslation(); - const wallets = useWalletsState(); - const { mutate } = useMutateWalletsState(); + const accounts = useAccountsState(); + const { mutate } = useMutateAccountsState(); const createItems = useMemo(() => { return [ @@ -172,12 +168,12 @@ export const Account = () => { const handleDrop: OnDragEndResponder = useCallback( droppedItem => { if (!droppedItem.destination) return; - const updatedList = [...wallets]; + const updatedList = [...accounts]; const [reorderedItem] = updatedList.splice(droppedItem.source.index, 1); updatedList.splice(droppedItem.destination.index, 0, reorderedItem); mutate(updatedList); }, - [wallets, mutate] + [accounts, mutate] ); return ( @@ -188,10 +184,10 @@ export const Account = () => { {provided => ( - {wallets.map((wallet, index) => ( + {accounts.map((account, index) => ( {p => ( @@ -203,7 +199,7 @@ export const Account = () => { > )} diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index dff46a702..6c75cdaed 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -4,24 +4,23 @@ import { InnerBody } from '../../components/Body'; import { SubHeader } from '../../components/SubHeader'; import { SettingsItem, SettingsList } from '../../components/settings/SettingsList'; import { useTranslation } from '../../hooks/translation'; -import { useActiveWallet, useMutateWalletProperty } from '../../state/wallet'; +import { useActiveWallet } from '../../state/wallet'; import { useDevSettings, useMutateDevSettings } from '../../state/dev'; export const DevSettings = React.memo(() => { const { t } = useTranslation(); const wallet = useActiveWallet(); - const { mutate } = useMutateWalletProperty(true); const { mutate: mutateDevSettings } = useMutateDevSettings(); const { data: devSettings } = useDevSettings(); const items = useMemo(() => { - const network = wallet.network ?? Network.MAINNET; + const network = devSettings?.tonNetwork ?? Network.MAINNET; return [ { name: t('settings_network_alert_title'), icon: network === Network.MAINNET ? 'Mainnet' : 'Testnet', - action: () => mutate({ network: switchNetwork(network) }) + action: () => mutateDevSettings({ tonNetwork: switchNetwork(network) }) }, { name: t('Enable wallet V5'), diff --git a/packages/uikit/src/pages/settings/Jettons.tsx b/packages/uikit/src/pages/settings/Jettons.tsx index 26d803fa1..fd2378761 100644 --- a/packages/uikit/src/pages/settings/Jettons.tsx +++ b/packages/uikit/src/pages/settings/Jettons.tsx @@ -1,4 +1,4 @@ -import { ActiveWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; import { JettonBalance } from '@tonkeeper/core/dist/tonApiV2'; import { FC, useCallback, useMemo } from 'react'; import { @@ -24,7 +24,7 @@ import { useToggleHideJettonMutation, useTogglePinJettonMutation } from '../../state/jetton'; -import { useActiveWalletConfig } from '../../state/wallet'; +import { useActiveTonWalletConfig } from '../../state/wallet'; const TurnOnIcon = styled.span` color: ${props => props.theme.accentBlue}; @@ -57,7 +57,7 @@ const RadioWrapper = styled.span` cursor: pointer; `; -const SampleJettonRow: FC<{ jetton: JettonBalance; config: ActiveWalletConfig }> = ({ +const SampleJettonRow: FC<{ jetton: JettonBalance; config: TonWalletConfig }> = ({ jetton, config }) => { @@ -116,7 +116,7 @@ const SampleJettonRow: FC<{ jetton: JettonBalance; config: ActiveWalletConfig }> }; export const PinnedJettonList: FC<{ - config: ActiveWalletConfig; + config: TonWalletConfig; jettons: JettonBalance[]; }> = ({ config, jettons }) => { const { mutate } = useSavePinnedJettonOrderMutation(); @@ -183,7 +183,7 @@ export const PinnedJettonList: FC<{ const JettonRow: FC<{ jetton: JettonBalance; - config: ActiveWalletConfig; + config: TonWalletConfig; dragHandleProps: DraggableProvidedDragHandleProps | null | undefined; }> = ({ jetton, config, dragHandleProps }) => { const { t } = useTranslation(); @@ -236,7 +236,7 @@ export const JettonsSettings = () => { const { t } = useTranslation(); const { data: jettons } = useJettonRawList(); - const { data: config } = useActiveWalletConfig(); + const { data: config } = useActiveTonWalletConfig(); if (!jettons || !config) { return ; diff --git a/packages/uikit/src/pages/settings/Notification.tsx b/packages/uikit/src/pages/settings/Notification.tsx index 9043c1eab..a4716638d 100644 --- a/packages/uikit/src/pages/settings/Notification.tsx +++ b/packages/uikit/src/pages/settings/Notification.tsx @@ -10,14 +10,19 @@ import { useTranslation } from '../../hooks/translation'; import { QueryKey } from '../../libs/queryKey'; import { signTonConnectOver } from '../../state/mnemonic'; import { useCheckTouchId } from '../../state/password'; -import { useActiveStandardTonWallet, useActiveWallet } from '../../state/wallet'; +import { + useActiveStandardTonWallet, + useActiveTonNetwork, + useActiveWallet +} from '../../state/wallet'; import { useAppContext } from '../../hooks/appContext'; const useSubscribed = () => { const sdk = useAppSdk(); const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); - return useQuery([wallet.id, wallet.network, QueryKey.subscribed], async () => { + return useQuery([wallet.id, network, QueryKey.subscribed], async () => { const { notifications } = sdk; if (!notifications) { throw new Error('Missing notifications'); @@ -33,6 +38,7 @@ const useToggleSubscribe = () => { const client = useQueryClient(); const { mutateAsync: checkTouchId } = useCheckTouchId(); const { api } = useAppContext(); + const network = useActiveTonNetwork(); return useMutation(async checked => { const { notifications } = sdk; @@ -59,7 +65,7 @@ const useToggleSubscribe = () => { } } - await client.invalidateQueries([wallet.id, wallet.network, QueryKey.subscribed]); + await client.invalidateQueries([wallet.id, network, QueryKey.subscribed]); }); }; diff --git a/packages/uikit/src/pages/settings/Version.tsx b/packages/uikit/src/pages/settings/Version.tsx index 5614ede3d..c5f584195 100644 --- a/packages/uikit/src/pages/settings/Version.tsx +++ b/packages/uikit/src/pages/settings/Version.tsx @@ -1,5 +1,6 @@ import { - StandardTonWalletState, + getAccountActiveDerivationTonWallets, + TonWalletStandard, WalletVersion as WalletVersionType, WalletVersions, walletVersionText @@ -15,11 +16,11 @@ import { useIsActiveWalletKeystone } from '../../state/keystone'; import { useIsActiveWalletLedger } from '../../state/ledger'; import { useActiveStandardTonWallet, - useCreateStandardTonWalletsByMnemonic, - useMutateActiveWallet, - useMutateRenameWallet, useStandardTonWalletVersions, - useWalletsState + useActiveAccount, + useAddTonWalletVersionToActiveAccount, + useRenameTonWallet, + useMutateActiveTonWallet } from '../../state/wallet'; import { ListBlock, ListItem, ListItemPayload } from '../../components/List'; import { toFormattedTonBalance } from '../../hooks/balance'; @@ -29,9 +30,6 @@ import { useNavigate } from 'react-router-dom'; import { AppRoute } from '../../libs/routes'; import { Notification } from '../../components/Notification'; import { UpdateWalletName } from '../../components/create/WalletName'; -import { useCheckTouchId } from '../../state/password'; -import { getMnemonicAndPassword } from '../../state/mnemonic'; -import { useAppSdk } from '../../hooks/appSdk'; import { SkeletonList } from '../../components/Skeleton'; const LedgerError = styled(Body2)` @@ -51,23 +49,21 @@ const Body2Secondary = styled(Body2)` export const WalletVersion = () => { const { t } = useTranslation(); - const sdk = useAppSdk(); const isLedger = useIsActiveWalletLedger(); const isKeystone = useIsActiveWalletKeystone(); const currentWallet = useActiveStandardTonWallet(); - const connectedWallets = useWalletsState(); - const { mutateAsync: selectWallet, isLoading: isSelectWalletLoading } = useMutateActiveWallet(); + const currentAccount = useActiveAccount(); + const currentAccountWalletsVersions = getAccountActiveDerivationTonWallets(currentAccount); + + const { mutateAsync: selectWallet, isLoading: isSelectWalletLoading } = + useMutateActiveTonWallet(); const navigate = useNavigate(); - const { mutateAsync: checkTouchId } = useCheckTouchId(); - const { data: wallets } = useStandardTonWalletVersions( - currentWallet.publicKey, - currentWallet.network - ); + const { data: wallets } = useStandardTonWalletVersions(currentWallet.publicKey); const { mutateAsync: createWalletAsync, isLoading: isCreateWalletLoading } = - useCreateStandardTonWalletsByMnemonic(); - const { mutateAsync: renameWallet, isLoading: isRenameWalletLoading } = useMutateRenameWallet(); + useAddTonWalletVersionToActiveAccount(); + const { mutateAsync: renameWallet, isLoading: isRenameWalletLoading } = useRenameTonWallet(); const onOpenWallet = async (address: Address) => { if (address.toRawString() !== currentWallet.rawAddress) { @@ -77,24 +73,14 @@ export const WalletVersion = () => { }; const [editWalletNameNotificationPayload, setEditWalletNameNotificationPayload] = useState< - StandardTonWalletState | undefined + TonWalletStandard | undefined >(); const onAddWallet = async (w: { version: WalletVersionType; address: Address }) => { - const { mnemonic, password } = await getMnemonicAndPassword( - sdk, - currentWallet.id, - checkTouchId - ); const newWallet = await createWalletAsync({ - mnemonic, - versions: [w.version], - password + version: w.version }); - if (!newWallet) { - return; - } - setEditWalletNameNotificationPayload(newWallet[0]); + setEditWalletNameNotificationPayload(newWallet); }; const onChangeName = async (args: { name: string; emoji: string; id: string }) => { @@ -122,7 +108,7 @@ export const WalletVersion = () => { {!isLedger && !isKeystone && ( {wallets.map(wallet => { - const isWalletAdded = connectedWallets.some( + const isWalletAdded = currentAccountWalletsVersions.some( w => w.rawAddress === wallet.address.toRawString() ); @@ -176,7 +162,7 @@ export const WalletVersion = () => { const UpdateWalletNameNotification: FC<{ isOpen: boolean; onClose: (isAdded: { name: string; emoji: string; id: string }) => void; - wallet: StandardTonWalletState | undefined; + wallet: TonWalletStandard | undefined; }> = ({ isOpen, onClose, wallet }) => { return ( { const sdk = useAppSdk(); - const walletsStorage = useWalletsStorage(); + const accountsStorage = useAccountsStorage(); const client = useQueryClient(); const context = useAppContext(); const navigate = useNavigate(); @@ -21,8 +21,8 @@ const useAddWalletMutation = () => { if (publicKey === null) { sdk.topMessage('Missing public key'); } else { - const state = await walletStateFromSignerDeepLink(context, publicKey, name); - await walletsStorage.addWalletToState(state); + const state = await accountBySignerDeepLink(context, publicKey, name); + await accountsStorage.addAccountToState(state); await client.invalidateQueries([QueryKey.account]); } navigate(AppRoute.home); diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index 4587bc90d..9cd384371 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -1,13 +1,12 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Address } from '@ton/core'; import { DashboardCell } from '@tonkeeper/core/dist/entries/dashboard'; -import { Network } from '@tonkeeper/core/dist/entries/network'; -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; import { QueryKey } from '../../libs/queryKey'; -import { useWalletsState } from '../wallet'; +import { useAccountsState } from '../wallet'; import { ClientColumns, useDashboardColumnsAsForm } from './useDashboardColumns'; import { formatAddress } from '@tonkeeper/core/dist/utils/common'; @@ -22,8 +21,8 @@ export function useDashboardData() { const selectedColIds = selectedColumns?.map(c => c.id); const client = useQueryClient(); - const walletsState = useWalletsState(); - const mainnetWallets = walletsState.filter(w => w && w.network !== Network.TESTNET); + const accountsState = useAccountsState(); + const mainnetWallets = accountsState.flatMap(getAccountAllTonWallets); const idsMainnet = mainnetWallets.map(w => w!.id); return useQuery( @@ -88,7 +87,7 @@ export function useDashboardData() { /* cache */ if (pastQueries?.length) { - const walletsToQuerySet = new Set(); + const walletsToQuerySet = new Set(); const columnsToQuerySet = new Set(); const result: (DashboardCell | null)[][] = idsMainnet.map(() => []); diff --git a/packages/uikit/src/state/dev.ts b/packages/uikit/src/state/dev.ts index dd605599c..ca418fd4f 100644 --- a/packages/uikit/src/state/dev.ts +++ b/packages/uikit/src/state/dev.ts @@ -1,26 +1,18 @@ import { useAppSdk } from '../hooks/appSdk'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AppKey } from '@tonkeeper/core/dist/Keys'; -import { UIPreferences } from './theme'; - -export interface DevSettings { - enableV5: boolean; -} - -const defaultDevSettings: DevSettings = { - enableV5: false -}; +import { + DevSettings, + getDevSettings, + setDevSettings +} from '@tonkeeper/core/dist/service/devStorage'; export const useDevSettings = () => { const sdk = useAppSdk(); return useQuery( [AppKey.DEV_SETTINGS], async () => { - const settings = await sdk.storage.get(AppKey.DEV_SETTINGS); - return { - ...defaultDevSettings, - ...settings - }; + return getDevSettings(sdk.storage); }, { keepPreviousData: true @@ -32,8 +24,7 @@ export const useMutateDevSettings = () => { const sdk = useAppSdk(); const client = useQueryClient(); return useMutation>(async devSettings => { - const current = await sdk.storage.get(AppKey.DEV_SETTINGS); - await sdk.storage.set(AppKey.DEV_SETTINGS, { ...devSettings, ...current, ...devSettings }); + await setDevSettings(sdk.storage, devSettings); await client.invalidateQueries([AppKey.DEV_SETTINGS]); }); }; diff --git a/packages/uikit/src/state/jetton.ts b/packages/uikit/src/state/jetton.ts index 35c8d7ecb..eba948274 100644 --- a/packages/uikit/src/state/jetton.ts +++ b/packages/uikit/src/state/jetton.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Address } from '@ton/core'; import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; -import { ActiveWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; import { getActiveWalletConfig, setActiveWalletConfig @@ -20,7 +20,7 @@ import { useAppSdk } from '../hooks/appSdk'; import { JettonKey, QueryKey } from '../libs/queryKey'; import { getRateKey, toTokenRate } from './rates'; import { DefaultRefetchInterval } from './tonendpoint'; -import { useActiveWallet } from './wallet'; +import { useActiveTonNetwork, useActiveWallet } from './wallet'; export const useJettonInfo = (jettonAddress: string) => { const wallet = useActiveWallet(); @@ -61,10 +61,11 @@ const compareTokensOver = (fiat: FiatCurrencies) => { export const useJettonRawList = () => { const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); const { api, fiat } = useAppContext(); return useQuery( - [wallet.id, JettonKey.raw, QueryKey.jettons, fiat, wallet.network], + [wallet.id, JettonKey.raw, QueryKey.jettons, fiat, network], async () => { const result = await new AccountsApi(api.tonApiV2).getAccountJettonsBalances({ accountId: wallet.rawAddress, @@ -78,23 +79,20 @@ export const useJettonRawList = () => { export const useJettonList = () => { const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); const { api, fiat } = useAppContext(); const client = useQueryClient(); const sdk = useAppSdk(); return useQuery( - [wallet.id, QueryKey.jettons, fiat, wallet.network], + [wallet.id, QueryKey.jettons, fiat, network], async () => { const result = await new AccountsApi(api.tonApiV2).getAccountJettonsBalances({ accountId: wallet.rawAddress, currencies: [fiat] }); - const config = await getActiveWalletConfig( - sdk.storage, - wallet.rawAddress, - wallet.network - ); + const config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); const balances = filterTokens(result.balances, config.hiddenTokens).sort( compareTokensOver(fiat) @@ -164,8 +162,9 @@ export const useTogglePinJettonMutation = () => { const sdk = useAppSdk(); const client = useQueryClient(); const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); - return useMutation( + return useMutation( async ({ config, jetton }) => { const pinnedTokens = config.pinnedTokens.includes(jetton.jetton.address) ? config.pinnedTokens.filter(item => item !== jetton.jetton.address) @@ -176,9 +175,9 @@ export const useTogglePinJettonMutation = () => { pinnedTokens }; - client.setQueryData([wallet.id, wallet.network, QueryKey.walletConfig], newConfig); + client.setQueryData([wallet.id, network, QueryKey.walletConfig], newConfig); - await setActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network, newConfig); + await setActiveWalletConfig(sdk.storage, wallet.rawAddress, network, newConfig); await client.invalidateQueries([wallet.id, QueryKey.jettons]); } @@ -189,13 +188,14 @@ export const useSavePinnedJettonOrderMutation = () => { const sdk = useAppSdk(); const client = useQueryClient(); const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); - return useMutation( + return useMutation( async ({ config, pinnedTokens }) => { const newConfig = { ...config, pinnedTokens }; - client.setQueryData([wallet.id, wallet.network, QueryKey.walletConfig], newConfig); + client.setQueryData([wallet.id, network, QueryKey.walletConfig], newConfig); - await setActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network, newConfig); + await setActiveWalletConfig(sdk.storage, wallet.rawAddress, network, newConfig); await client.invalidateQueries([wallet.id, QueryKey.jettons]); } ); @@ -205,10 +205,11 @@ export const useToggleHideJettonMutation = () => { const sdk = useAppSdk(); const client = useQueryClient(); const wallet = useActiveWallet(); + const network = useActiveTonNetwork(); - return useMutation( + return useMutation( async ({ config, jetton }) => { - let newConfig: ActiveWalletConfig; + let newConfig: TonWalletConfig; if (config.hiddenTokens.includes(jetton.jetton.address)) { const hiddenTokens = config.hiddenTokens.filter( @@ -230,9 +231,9 @@ export const useToggleHideJettonMutation = () => { }; } - client.setQueryData([wallet.id, wallet.network, QueryKey.walletConfig], newConfig); + client.setQueryData([wallet.id, network, QueryKey.walletConfig], newConfig); - await setActiveWalletConfig(sdk.storage, wallet.id, wallet.network, newConfig); + await setActiveWalletConfig(sdk.storage, wallet.id, network, newConfig); await client.invalidateQueries([wallet.id, QueryKey.jettons]); } diff --git a/packages/uikit/src/state/keystone.ts b/packages/uikit/src/state/keystone.ts index b2ad7d865..0f6e33fb5 100644 --- a/packages/uikit/src/state/keystone.ts +++ b/packages/uikit/src/state/keystone.ts @@ -1,29 +1,28 @@ import UR from '@ngraveio/bc-ur/dist/ur'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { walletStateFromKeystone } from '@tonkeeper/core/dist/service/walletService'; +import { accountByKeystone } from '@tonkeeper/core/dist/service/walletService'; import { useNavigate } from 'react-router-dom'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; import { AppRoute } from '../libs/routes'; -import { useWalletsStorage } from '../hooks/useStorage'; -import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet'; -import { useActiveWallet } from './wallet'; +import { useAccountsStorage } from '../hooks/useStorage'; +import { useActiveAccount } from './wallet'; export const usePairKeystoneMutation = () => { const sdk = useAppSdk(); - const walletsStorage = useWalletsStorage(); + const accountsStorage = useAccountsStorage(); const navigate = useNavigate(); const client = useQueryClient(); return useMutation(async ur => { try { - const state = await walletStateFromKeystone(ur); - const duplicatedWallet = await walletsStorage.getWallet(state.id); + const state = await accountByKeystone(ur); + const duplicatedWallet = await accountsStorage.getAccount(state.id); if (duplicatedWallet) { throw new Error('Wallet already exist'); } - await walletsStorage.addWalletToState(state); + await accountsStorage.addAccountToState(state); await client.invalidateQueries([QueryKey.account]); @@ -36,6 +35,6 @@ export const usePairKeystoneMutation = () => { }; export const useIsActiveWalletKeystone = () => { - const wallet = useActiveWallet(); - return isStandardTonWallet(wallet) && wallet.auth.kind === 'keystone'; + const account = useActiveAccount(); + return account.type === 'keystone'; }; diff --git a/packages/uikit/src/state/ledger.ts b/packages/uikit/src/state/ledger.ts index 7a9cb798b..75930da3b 100644 --- a/packages/uikit/src/state/ledger.ts +++ b/packages/uikit/src/state/ledger.ts @@ -11,14 +11,12 @@ import { AccountsApi, Account } from '@tonkeeper/core/dist/tonApiV2'; import { Address } from '@ton/core'; import { useAppSdk } from '../hooks/appSdk'; import { useNavigate } from 'react-router-dom'; -import { walletStateFromLedger } from '@tonkeeper/core/dist/service/walletService'; import { QueryKey } from '../libs/queryKey'; import { AppRoute } from '../libs/routes'; import { useCallback, useState } from 'react'; -import { AuthLedger } from '@tonkeeper/core/dist/entries/password'; -import { useWalletsStorage } from '../hooks/useStorage'; -import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet'; -import { useActiveWallet } from './wallet'; +import { useAccountsStorage } from '../hooks/useStorage'; +import { useActiveAccount } from './wallet'; +import { accountByLedger } from '@tonkeeper/core/dist/service/walletService'; export type LedgerAccount = { accountIndex: number; @@ -96,22 +94,14 @@ export const useAddLedgerAccountsMutation = () => { const sdk = useAppSdk(); const client = useQueryClient(); const navigate = useNavigate(); - const walletsStorage = useWalletsStorage(); + const accStorage = useAccountsStorage(); return useMutation( async form => { try { - let states = form.accounts.map(walletStateFromLedger); - if (form.name) { - const suffix = (index: number) => (states.length > 1 ? ' ' + (index + 1) : ''); - states = states.map((s, i) => ({ - ...s, - name: form.name + suffix((s.auth as AuthLedger).accountIndex), - ...(i === 0 && !!form.emoji && { emoji: form.emoji }) - })); - } - - await walletsStorage.addWalletsToState(states); + const states = accountByLedger(form.accounts, form.name, form.emoji); + + await accStorage.addAccountToState(states); await client.invalidateQueries([QueryKey.account]); @@ -125,6 +115,6 @@ export const useAddLedgerAccountsMutation = () => { }; export const useIsActiveWalletLedger = () => { - const wallet = useActiveWallet(); - return isStandardTonWallet(wallet) && wallet.auth.kind === 'ledger'; + const wallet = useActiveAccount(); + return wallet.type === 'ledger'; }; diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index b80e4504b..b87333b62 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -13,34 +13,29 @@ import { import { delay } from '@tonkeeper/core/dist/utils/common'; import nacl from 'tweetnacl'; import { TxConfirmationCustomError } from '../libs/errors/TxConfirmationCustomError'; -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; -import { isStandardTonWallet, WalletId, WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { AccountId, getAccountActiveTonWallet } from '@tonkeeper/core/dist/entries/wallet'; +import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; +import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; export const signTonConnectOver = ( sdk: IAppSdk, - walletId: WalletId, + accountId: AccountId, t: (text: string) => string, checkTouchId: () => Promise ) => { return async (bufferToSign: Buffer) => { - const wallet = await walletsStorage(sdk.storage).getWallet(walletId); + const account = await accountsStorage(sdk.storage).getAccount(accountId); - if (!wallet || !isStandardTonWallet(wallet)) { + if (!account) { throw new Error("Can't use tonconnect over non standard ton wallet"); } - const auth = wallet.auth; - switch (auth.kind) { - case 'signer': { + switch (account.type) { + case 'ton-only': { throw new TxConfirmationCustomError( 'Signer linked by QR is not support sign buffer.' ); } - case 'signer-deeplink': { - throw new TxConfirmationCustomError( - 'Signer linked by deep link is not support sign buffer.' - ); - } case 'ledger': { throw new TxConfirmationCustomError(t('ledger_operation_not_supported')); } @@ -49,12 +44,12 @@ export const signTonConnectOver = ( sdk, bufferToSign, 'signProof', - auth.info + account.pathInfo ); return Buffer.from(result, 'hex'); } default: { - const mnemonic = await getMnemonic(sdk, walletId, checkTouchId); + const mnemonic = await getMnemonic(sdk, accountId, checkTouchId); const keyPair = await mnemonicToPrivateKey(mnemonic); const signature = nacl.sign.detached( Buffer.from(sha256_sync(bufferToSign)), @@ -79,28 +74,51 @@ export const signTonConnectMnemonicOver = (mnemonic: string[]) => { export const getSigner = async ( sdk: IAppSdk, - walletId: WalletId, + accountId: AccountId, checkTouchId: () => Promise ): Promise => { try { - const wallet = await walletsStorage(sdk.storage).getWallet(walletId); - if (!wallet || !isStandardTonWallet(wallet)) { + const account = await accountsStorage(sdk.storage).getAccount(accountId); + if (!account) { throw new Error('Wallet not found'); } - const auth = wallet.auth; + switch (account.type) { + case 'ton-only': { + if (account.auth.kind === 'signer') { + const callback = async (message: Cell) => { + const result = await pairSignerByNotification( + sdk, + message.toBoc({ idx: false }).toString('base64') + ); + return parseSignerSignature(result); + }; + callback.type = 'cell' as const; + return callback; + } - switch (auth.kind) { - case 'signer': { - const callback = async (message: Cell) => { - const result = await pairSignerByNotification( - sdk, - message.toBoc({ idx: false }).toString('base64') - ); - return parseSignerSignature(result); - }; - callback.type = 'cell' as const; - return callback; + if (account.auth.kind === 'signer-deeplink') { + const wallet = getAccountActiveTonWallet(account); + const callback = async (message: Cell) => { + const deeplink = await storeTransactionAndCreateDeepLink( + sdk, + wallet.publicKey, + wallet.version, + message.toBoc({ idx: false }).toString('base64') + ); + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + window.location = deeplink as any; + + await delay(2000); + + throw new Error('Navigate to deeplink'); + }; + callback.type = 'cell' as const; + return callback as CellSigner; + } + + return assertUnreachable(account.auth); } case 'ledger': { const callback = async (path: number[], transaction: LedgerTransaction) => @@ -108,32 +126,13 @@ export const getSigner = async ( callback.type = 'ledger' as const; return callback; } - case 'signer-deeplink': { - const callback = async (message: Cell) => { - const deeplink = await storeTransactionAndCreateDeepLink( - sdk, - wallet.publicKey, - wallet.version, - message.toBoc({ idx: false }).toString('base64') - ); - - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - window.location = deeplink as any; - - await delay(2000); - - throw new Error('Navigate to deeplink'); - }; - callback.type = 'cell' as const; - return callback as CellSigner; - } case 'keystone': { const callback = async (message: Cell) => { const result = await pairKeystoneByNotification( sdk, message.toBoc({ idx: false }), 'transaction', - auth.info + account.pathInfo ); return Buffer.from(result, 'hex'); }; @@ -141,7 +140,7 @@ export const getSigner = async ( return callback; } default: { - const mnemonic = await getMnemonic(sdk, wallet.id, checkTouchId); + const mnemonic = await getMnemonic(sdk, account.id, checkTouchId); const callback = async (message: Cell) => { const keyPair = await mnemonicToPrivateKey(mnemonic); return sign(message.hash(), keyPair.secretKey); @@ -158,28 +157,28 @@ export const getSigner = async ( export const getMnemonic = async ( sdk: IAppSdk, - walletId: string, + accountId: AccountId, checkTouchId: () => Promise ): Promise => { - const { mnemonic } = await getMnemonicAndPassword(sdk, walletId, checkTouchId); + const { mnemonic } = await getMnemonicAndPassword(sdk, accountId, checkTouchId); return mnemonic; }; export const getMnemonicAndPassword = async ( sdk: IAppSdk, - walletId: string, + accountId: AccountId, checkTouchId: () => Promise ): Promise<{ mnemonic: string[]; password?: string }> => { - const wallet = await walletsStorage(sdk.storage).getWallet(walletId); - if (!wallet || !('auth' in wallet)) { - throw new Error('Unexpected auth method for wallet'); + const account = await accountsStorage(sdk.storage).getAccount(accountId); + if (!account || account.type !== 'mnemonic' || !('auth' in account)) { + throw new Error('Unexpected auth method for account'); } - switch (wallet.auth.kind) { + switch (account.auth.kind) { case 'password': { const password = await getPasswordByNotification(sdk); const mnemonic = await decryptWalletMnemonic( - wallet as WalletState & { auth: AuthPassword }, + account as { auth: AuthPassword }, password ); return { @@ -193,11 +192,7 @@ export const getMnemonicAndPassword = async ( } await checkTouchId(); - if (!('publicKey' in wallet)) { - throw new Error('Unexpected auth method for wallet, keychain'); - } - - const mnemonic = await sdk.keychain.getPassword(wallet.auth.keychainStoreKey); + const mnemonic = await sdk.keychain.getPassword(account.auth.keychainStoreKey); return { mnemonic: mnemonic.split(' ') }; } default: diff --git a/packages/uikit/src/state/nft.ts b/packages/uikit/src/state/nft.ts index d8f974c77..46aee1394 100644 --- a/packages/uikit/src/state/nft.ts +++ b/packages/uikit/src/state/nft.ts @@ -2,10 +2,15 @@ import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { useMutation, useQuery } from '@tanstack/react-query'; import { getActiveWalletConfig } from '@tonkeeper/core/dist/service/wallet/configService'; -import { useActiveWallet, useActiveWalletConfig, useMutateActiveWalletConfig } from './wallet'; +import { + useActiveWallet, + useActiveTonWalletConfig, + useMutateActiveTonWalletConfig, + useActiveTonNetwork +} from './wallet'; import { NFT } from '@tonkeeper/core/dist/entries/nft'; import { DefaultRefetchInterval, useTonenpointConfig } from './tonendpoint'; -import { ActiveWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; import { useTranslation } from '../hooks/translation'; import { AccountsApi, @@ -27,12 +32,13 @@ type NftWithCollectionId = Pick & { export const useMarkNftAsSpam = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); - const { mutateAsync } = useMutateActiveWalletConfig(); + const { mutateAsync } = useMutateActiveTonWalletConfig(); const { tonendpoint } = useAppContext(); const { data: tonendpointConfig } = useTonenpointConfig(tonendpoint); const { t } = useTranslation(); + const network = useActiveTonNetwork(); return useMutation(async nft => { - let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network); + let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); const address = nft.collection?.address || nft.address; @@ -68,9 +74,10 @@ export const useMarkNftAsSpam = () => { export const useMarkNftAsTrusted = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); - const { mutateAsync } = useMutateActiveWalletConfig(); + const { mutateAsync } = useMutateActiveTonWalletConfig(); + const network = useActiveTonNetwork(); return useMutation(async nft => { - let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network); + let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); const address = typeof nft === 'string' ? nft : nft.collection?.address || nft.address; @@ -87,10 +94,11 @@ export const useMarkNftAsTrusted = () => { export const useHideNft = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); - const { mutateAsync } = useMutateActiveWalletConfig(); + const { mutateAsync } = useMutateActiveTonWalletConfig(); const { t } = useTranslation(); + const network = useActiveTonNetwork(); return useMutation(async nft => { - let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network); + let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); const address = nft.collection?.address || nft.address; @@ -115,9 +123,10 @@ export const useHideNft = () => { export const useMakeNftVisible = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); - const { mutateAsync } = useMutateActiveWalletConfig(); + const { mutateAsync } = useMutateActiveTonWalletConfig(); + const network = useActiveTonNetwork(); return useMutation(async nft => { - let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network); + let config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); const address = typeof nft === 'string' ? nft : nft.collection?.address || nft.address; @@ -131,20 +140,20 @@ export const useMakeNftVisible = () => { }; export function useIsSpamNft(nft: (NftWithCollectionId & { trust: NFT['trust'] }) | undefined) { - const { data: config } = useActiveWalletConfig(); + const { data: config } = useActiveTonWalletConfig(); return isSpamNft(nft, config); } export function useIsUnverifiedNft( nft: (NftWithCollectionId & { trust: NFT['trust'] }) | undefined ) { - const { data: config } = useActiveWalletConfig(); + const { data: config } = useActiveTonWalletConfig(); return isUnverifiedNft(nft, config); } export const isSpamNft = ( nft: (NftWithCollectionId & { trust: NFT['trust'] }) | undefined, - config: ActiveWalletConfig | undefined + config: TonWalletConfig | undefined ) => { return Boolean( nft && @@ -156,7 +165,7 @@ export const isSpamNft = ( export const isUnverifiedNft = ( nft: (NftWithCollectionId & { trust: NFT['trust'] }) | undefined, - config: ActiveWalletConfig | undefined + config: TonWalletConfig | undefined ) => { return Boolean( nft && @@ -191,7 +200,7 @@ export const useWalletNftList = () => { }; export const useWalletFilteredNftList = () => { const { data: nfts, ...rest } = useWalletNftList(); - const { data: walletConfig } = useActiveWalletConfig(); + const { data: walletConfig } = useActiveTonWalletConfig(); const filtered = useMemo(() => { if (!nfts || !walletConfig) return undefined; diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index c6fdd80d0..c96c97971 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -2,7 +2,12 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { ProState, ProSubscription } from '@tonkeeper/core/dist/entries/pro'; import { RecipientData } from '@tonkeeper/core/dist/entries/send'; -import { isStandardTonWallet, StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { + getAccountAllTonWallets, + getWalletById, + isStandardTonWallet, + TonWalletStandard +} from '@tonkeeper/core/dist/entries/wallet'; import { authViaTonConnect, createProServiceInvoice, @@ -24,10 +29,9 @@ import { useTranslation } from '../hooks/translation'; import { QueryKey } from '../libs/queryKey'; import { signTonConnectOver } from './mnemonic'; import { useCheckTouchId } from './password'; -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; import { useActiveWallet } from './wallet'; import { useUserLanguage } from './language'; -import { useWalletsStorage } from '../hooks/useStorage'; +import { useAccountsStorage } from '../hooks/useStorage'; export const useProBackupState = () => { const sdk = useAppSdk(); @@ -45,7 +49,7 @@ export const useProState = () => { return useQuery([QueryKey.pro], async () => { // TODO а что если активный кошелек не стандартный? // TODO сделать флоу подписки - const state = await getProState(sdk.storage, wallet as StandardTonWalletState); + const state = await getProState(sdk.storage, wallet); await setBackupState(sdk.storage, state.subscription); await client.invalidateQueries([QueryKey.proBackup]); return state; @@ -58,9 +62,12 @@ export const useSelectWalletForProMutation = () => { const { api } = useAppContext(); const { t } = useTranslation(); const { mutateAsync: checkTouchId } = useCheckTouchId(); + const accountsStorage = useAccountsStorage(); return useMutation(async walletId => { - const state = await walletsStorage(sdk.storage).getWallet(walletId); + const accounts = await accountsStorage.getAccounts(); + const state = getWalletById(accounts, walletId); + if (!state) { throw new Error('Missing wallet state'); } @@ -109,11 +116,11 @@ export interface ConfirmState { invoice: InvoicesInvoice; recipient: RecipientData; assetAmount: AssetAmount; - wallet: StandardTonWalletState; + wallet: TonWalletStandard; } export const useCreateInvoiceMutation = () => { - const ws = useWalletsStorage(); + const ws = useAccountsStorage(); const { api } = useAppContext(); return useMutation< ConfirmState, @@ -124,7 +131,9 @@ export const useCreateInvoiceMutation = () => { throw new Error('missing tier'); } - const wallet = await ws.getWallet(data.state.wallet.rawAddress); + const wallet = (await ws.getAccounts()) + .flatMap(getAccountAllTonWallets) + .find(w => w.id === data.state.wallet.rawAddress); if (!wallet || !isStandardTonWallet(wallet)) { throw new Error('Missing wallet'); } diff --git a/packages/uikit/src/state/signer.ts b/packages/uikit/src/state/signer.ts index b43052797..ade492292 100644 --- a/packages/uikit/src/state/signer.ts +++ b/packages/uikit/src/state/signer.ts @@ -1,23 +1,23 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { walletStateFromSignerQr } from '@tonkeeper/core/dist/service/walletService'; +import { accountBySignerQr } from '@tonkeeper/core/dist/service/walletService'; import { useNavigate } from 'react-router-dom'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; import { AppRoute } from '../libs/routes'; -import { useWalletsStorage } from '../hooks/useStorage'; +import { useAccountsStorage } from '../hooks/useStorage'; export const usePairSignerMutation = () => { const sdk = useAppSdk(); - const walletsStorage = useWalletsStorage(); + const accountsStorage = useAccountsStorage(); const context = useAppContext(); const client = useQueryClient(); const navigate = useNavigate(); return useMutation(async qrCode => { try { - const state = await walletStateFromSignerQr(context, qrCode); + const state = await accountBySignerQr(context, qrCode); - await walletsStorage.addWalletToState(state); + await accountsStorage.addAccountToState(state); await client.invalidateQueries([QueryKey.account]); diff --git a/packages/uikit/src/state/subscribe.ts b/packages/uikit/src/state/subscribe.ts index 38e028817..d0387b8b3 100644 --- a/packages/uikit/src/state/subscribe.ts +++ b/packages/uikit/src/state/subscribe.ts @@ -1,10 +1,10 @@ import { useMutation } from '@tanstack/react-query'; -import { StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; export const useSubscribeMutation = ( - wallet: StandardTonWalletState, + wallet: TonWalletStandard, signTonConnect: (bufferToSign: Buffer) => Promise, onDone: () => void ) => { diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index e896247c2..49e05df37 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -3,7 +3,7 @@ import { useAppSdk } from '../hooks/appSdk'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AccountConnection, - getAccountConnection, + getTonWalletConnections, saveAccountConnection, setAccountConnection } from '@tonkeeper/core/dist/service/tonConnect/connectionService'; @@ -26,15 +26,19 @@ import { } from '@tonkeeper/core/dist/service/tonConnect/connectService'; import { signTonConnectOver } from './mnemonic'; import { getServerTime } from '@tonkeeper/core/dist/service/transfer/common'; -import { isStandardTonWallet, StandardTonWalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { + getAccountAllTonWallets, + isStandardTonWallet, + TonWalletStandard +} from '@tonkeeper/core/dist/entries/wallet'; import { IStorage } from '@tonkeeper/core/dist/Storage'; -import { useActiveWallet, useWalletsState } from './wallet'; +import { useActiveWallet, useAccountsState, useActiveAccount, useActiveTonNetwork } from './wallet'; export const useAppTonConnectConnections = () => { const sdk = useAppSdk(); - const wallets = useWalletsState().filter(isStandardTonWallet); + const wallets = useAccountsState().flatMap(getAccountAllTonWallets); - return useQuery<{ wallet: StandardTonWalletState; connections: AccountConnection[] }[]>( + return useQuery<{ wallet: TonWalletStandard; connections: AccountConnection[] }[]>( [QueryKey.tonConnectConnection, wallets.map(i => i.id)], async () => { return getAppConnections(sdk.storage); @@ -78,11 +82,13 @@ export const useActiveWalletTonConnectConnections = () => { export const useConnectTonConnectAppMutation = () => { const wallet = useActiveWallet(); + const account = useActiveAccount(); const sdk = useAppSdk(); const client = useQueryClient(); const { api } = useAppContext(); const { t } = useTranslation(); const { mutateAsync: checkTouchId } = useCheckTouchId(); + const network = useActiveTonNetwork(); return useMutation< ConnectItemReply[], @@ -102,7 +108,7 @@ export const useConnectTonConnectAppMutation = () => { for (const item of request.items) { if (item.name === 'ton_addr') { - result.push(toTonAddressItemReply(wallet)); + result.push(toTonAddressItemReply(wallet, network)); } if (item.name === 'ton_proof') { const signTonConnect = signTonConnectOver(sdk, wallet.publicKey, t, checkTouchId); @@ -116,7 +122,7 @@ export const useConnectTonConnectAppMutation = () => { result.push( await toTonProofItemReply({ storage: sdk.storage, - wallet, + account, signTonConnect, proof }) @@ -161,7 +167,7 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => const sdk = useAppSdk(); const wallet = useActiveWallet(); const client = useQueryClient(); - const wallets = useWalletsState(); + const accounts = useAccountsState(); return useMutation(async (connection: AccountConnection | 'all') => { if (!isStandardTonWallet(wallet)) { @@ -173,8 +179,8 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => } else { connectionsToDisconnect = ( await Promise.all( - wallets - .filter(isStandardTonWallet) + accounts + .flatMap(getAccountAllTonWallets) .map(w => disconnectFromWallet(sdk.storage, connection, w)) ) ).flat(); @@ -199,9 +205,9 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => const disconnectFromWallet = async ( storage: IStorage, connection: AccountConnection | 'all', - wallet: Pick + wallet: Pick ) => { - let connections = await getAccountConnection(storage, wallet); + let connections = await getTonWalletConnections(storage, wallet); const connectionsToDisconnect = connection === 'all' ? connections : [connection]; connections = diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index e28c891f7..139218da8 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -1,46 +1,55 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { - ActiveWalletConfig, - isPasswordAuthWallet, + Account, + AccountId, + AccountsState, + AccountTonMnemonic, + accountWithAddedTonWallet, + accountWithUpdatedActiveTonWalletId, + accountWithUpdatedTonWallet, + getAccountActiveTonWallet, + getAccountAllTonWallets, + getWalletById, + isAccountTonMnemonic, isStandardTonWallet, isW5Version, - StandardTonWalletState, + TonWalletConfig, + TonWalletStandard, WalletId, - WalletsState, - WalletState, WalletVersion, WalletVersions } from '@tonkeeper/core/dist/entries/wallet'; import { - createStandardTonWalletStateByMnemonic, + createStandardTonAccountByMnemonic, + getFallbackTonStandardWalletEmoji, + getFallbackWalletName, getWalletAddress, - updateWalletProperty + updateAccountProperty } from '@tonkeeper/core/dist/service/walletService'; -import { Account, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; +import { Account as TonapiAccount, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; import { DefaultRefetchInterval, isV5R1Enabled } from './tonendpoint'; -import { - getActiveWalletConfig, - setActiveWalletConfig -} from '@tonkeeper/core/dist/service/wallet/configService'; import { useMemo } from 'react'; -import { useWalletsStorage } from '../hooks/useStorage'; -import { walletsStorage } from '@tonkeeper/core/dist/service/walletsService'; -import { AuthKeychain } from '@tonkeeper/core/dist/entries/password'; +import { useAccountsStorage } from '../hooks/useStorage'; import { mnemonicValidate } from '@ton/crypto'; import { getPasswordByNotification } from './mnemonic'; import { encrypt } from '@tonkeeper/core/dist/service/cryptoService'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { useDevSettings } from './dev'; +import { AuthKeychain } from '@tonkeeper/core/dist/entries/password'; +import { + getActiveWalletConfig, + setActiveWalletConfig +} from '@tonkeeper/core/dist/service/wallet/configService'; -export const useActiveWalletQuery = () => { - const storage = useWalletsStorage(); - return useQuery( +export const useActiveAccountQuery = () => { + const storage = useAccountsStorage(); + return useQuery( [QueryKey.account, QueryKey.wallet], () => { - return storage.getActiveWallet(); + return storage.getActiveAccount(); }, { keepPreviousData: true @@ -48,103 +57,124 @@ export const useActiveWalletQuery = () => { ); }; -export const useActiveWallet = () => { - const { data } = useActiveWalletQuery(); +export const useActiveAccount = () => { + const { data } = useActiveAccountQuery(); if (!data) { - throw new Error('No active wallet'); + throw new Error('No active account'); } return data; }; +export const useActiveWallet = () => { + const account = useActiveAccount(); + return getAccountActiveTonWallet(account); +}; + export const useActiveStandardTonWallet = () => { const wallet = useActiveWallet(); - if (!isStandardTonWallet(wallet)) { - throw new Error('Active wallet is not standard TON'); + throw new Error('Wallet is not standard'); } - return wallet; }; -export const useMutateActiveWallet = () => { - const storage = useWalletsStorage(); +export const useMutateActiveAccount = () => { + const storage = useAccountsStorage(); + const client = useQueryClient(); + return useMutation(async accountId => { + await storage.setActiveAccountId(accountId); + await client.invalidateQueries([QueryKey.account]); + await client.invalidateQueries([accountId]); + }); +}; + +export const useMutateActiveTonWallet = () => { + const storage = useAccountsStorage(); const client = useQueryClient(); return useMutation(async walletId => { - await storage.setActiveWalletId(walletId); + const accounts = await storage.getAccounts(); + const account = accounts.find(a => getAccountAllTonWallets(a).some(w => w.id === walletId)); + + if (!account) { + throw new Error('Account not found'); + } + await storage.updateAccountInState(accountWithUpdatedActiveTonWalletId(account, walletId)); + await storage.setActiveAccountId(account.id); await client.invalidateQueries([QueryKey.account]); await client.invalidateQueries([walletId]); }); }; -export const useWalletState = (id: WalletId) => { - const wallets = useWalletsState(); - return useMemo(() => (wallets || []).find(w => w.id === id), [wallets]); +export const useAccountState = (id: AccountId) => { + const accounts = useAccountsState(); + return useMemo(() => (accounts || []).find(w => w.id === id), [accounts]); }; -export const useWalletsStateQuery = () => { - const storage = useWalletsStorage(); - return useQuery( +export const useWalletState = (id: WalletId): TonWalletStandard | undefined => { + const accounts = useAccountsState(); + return useMemo(() => getWalletById(accounts, id), [accounts]); +}; + +export const useAccountsStateQuery = () => { + const storage = useAccountsStorage(); + return useQuery( [QueryKey.account, QueryKey.wallets], - () => storage.getWallets(), + () => storage.getAccounts(), { keepPreviousData: true } ); }; -export const useMutateWalletsState = () => { - const sdk = useAppSdk(); +export const useMutateAccountsState = () => { const client = useQueryClient(); - return useMutation(async state => { - await walletsStorage(sdk.storage).setWallets(state); + const storage = useAccountsStorage(); + return useMutation(async state => { + await storage.setAccounts(state); await client.invalidateQueries([QueryKey.account]); }); }; -export const useCreateStandardTonWalletsByMnemonic = () => { +export const useCreateAccountMnemonic = () => { const sdk = useAppSdk(); const context = useAppContext(); - const { mutateAsync: addWalletsToState } = useAddWalletsToStateMutation(); - const { mutateAsync: selectWallet } = useMutateActiveWallet(); + const { mutateAsync: addAccountToState } = useAddAccountToStateMutation(); + const { mutateAsync: selectAccountMutation } = useMutateActiveAccount(); return useMutation< - StandardTonWalletState[], + AccountTonMnemonic, Error, { mnemonic: string[]; password?: string; versions: WalletVersion[]; - activateFirstWallet?: boolean; + selectAccount?: boolean; } - >(async ({ mnemonic, password, versions, activateFirstWallet }) => { + >(async ({ mnemonic, password, versions, selectAccount }) => { const valid = await mnemonicValidate(mnemonic); if (!valid) { throw new Error('Mnemonic is not valid.'); } if (sdk.keychain) { - const states = await Promise.all( - versions.map(version => - createStandardTonWalletStateByMnemonic(context, mnemonic, { - auth: { - kind: 'keychain' - }, - version - }) - ) - ); + const account = await createStandardTonAccountByMnemonic(context, mnemonic, { + auth: { + kind: 'keychain' + }, + versions + }); await sdk.keychain.setPassword( - (states[0].auth as AuthKeychain).keychainStoreKey, + (account.auth as AuthKeychain).keychainStoreKey, mnemonic.join(' ') ); - await addWalletsToState(states); - if (activateFirstWallet) { - await selectWallet(states[0].id); + await addAccountToState(account); + if (selectAccount) { + await selectAccountMutation(account.id); } - return states; + return account; } if (!password) { @@ -152,46 +182,89 @@ export const useCreateStandardTonWalletsByMnemonic = () => { } const encryptedMnemonic = await encrypt(mnemonic.join(' '), password); - const states = await Promise.all( - versions.map(version => - createStandardTonWalletStateByMnemonic(context, mnemonic, { - auth: { - kind: 'password', - encryptedMnemonic - }, - version - }) - ) - ); - - await addWalletsToState(states); - if (activateFirstWallet) { - await selectWallet(states[0].id); + const account = await createStandardTonAccountByMnemonic(context, mnemonic, { + auth: { + kind: 'password', + encryptedMnemonic + }, + versions + }); + + await addAccountToState(account); + if (selectAccount) { + await selectAccountMutation(account.id); } - return states; + return account; }); }; -export const useAddWalletToStateMutation = () => { - const ws = useWalletsStorage(); - const client = useQueryClient(); - return useMutation(async state => { - await ws.addWalletToState(state); - await client.invalidateQueries([QueryKey.account]); +export const useAddTonWalletVersionToActiveAccount = () => { + const accountsStore = useAccountsStorage(); + const account = useActiveAccount(); + + return useMutation< + TonWalletStandard, + Error, + { + version: WalletVersion; + } + >(async ({ version }) => { + const publicKey = getAccountActiveTonWallet(account).publicKey; + const w = getWalletAddress(publicKey, version); + const wallet: TonWalletStandard = { + id: w.address.toRawString(), + rawAddress: w.address.toRawString(), + version, + publicKey, + name: getFallbackWalletName(w.address), + emoji: getFallbackTonStandardWalletEmoji(publicKey, version) + }; + + await accountsStore.updateAccountInState(accountWithAddedTonWallet(account, wallet)); + return wallet; + }); +}; + +export const useRenameTonWallet = () => { + const accountsStore = useAccountsStorage(); + const account = useActiveAccount(); + + return useMutation< + TonWalletStandard, + Error, + { + id: WalletId; + name?: string; + emoji?: string; + } + >(async ({ id, name, emoji }) => { + const wallet = getAccountAllTonWallets(account).find(w => w.id === id); + if (!wallet) { + throw new Error('Wallet to rename not found'); + } + + const newWallet = { + ...wallet, + name: name || wallet.name, + emoji: emoji || wallet.emoji + }; + + await accountsStore.updateAccountInState(accountWithUpdatedTonWallet(account, newWallet)); + return wallet; }); }; -export const useAddWalletsToStateMutation = () => { - const ws = useWalletsStorage(); +export const useAddAccountToStateMutation = () => { + const storage = useAccountsStorage(); const client = useQueryClient(); - return useMutation(async states => { - await ws.addWalletsToState(states); + return useMutation(async account => { + await storage.addAccountToState(account); await client.invalidateQueries([QueryKey.account]); }); }; -export const useWalletsState = () => { - return useWalletsStateQuery().data!; +export const useAccountsState = () => { + return useAccountsStateQuery().data!; }; export const useMutateDeleteAll = () => { @@ -202,24 +275,24 @@ export const useMutateDeleteAll = () => { }; export const useIsPasswordSet = () => { - const wallets = useWalletsState(); - return (wallets || []).some(isPasswordAuthWallet); + const wallets = useAccountsState(); + return (wallets || []).some(acc => isAccountTonMnemonic(acc) && acc.auth.kind === 'password'); }; export const useMutateLogOut = () => { - const storage = useWalletsStorage(); + const storage = useAccountsStorage(); const client = useQueryClient(); - return useMutation(async walletId => { - await storage.removeWalletFromState(walletId); + return useMutation(async accountId => { + await storage.removeAccountFromState(accountId); await client.invalidateQueries([QueryKey.account]); }); }; -export const useMutateRenameWallet = () => { +export const useMutateRenameAccount = () => { const sdk = useAppSdk(); const client = useQueryClient(); - return useMutation(async form => { + return useMutation(async form => { if (form.name !== undefined && form.name.length <= 0) { throw new Error('Missing name'); } @@ -229,29 +302,17 @@ export const useMutateRenameWallet = () => { ...(form.name && { name: form.name }) }; - await updateWalletProperty(sdk.storage, form.id, formToUpdate); + const newAccount = await updateAccountProperty(sdk.storage, form.id, formToUpdate); await client.invalidateQueries([QueryKey.account]); - }); -}; -export const useMutateWalletProperty = (clearWallet = false) => { - const wallet = useActiveWallet(); - const client = useQueryClient(); - const sdk = useAppSdk(); - - return useMutation>>(async props => { - await updateWalletProperty(sdk.storage, wallet.id, props); - await client.invalidateQueries([QueryKey.account]); - if (clearWallet) { - await client.invalidateQueries([wallet.id]); - } + return newAccount as T; }); }; export const useWalletAccountInfo = () => { const wallet = useActiveWallet(); const { api } = useAppContext(); - return useQuery( + return useQuery( [wallet.rawAddress, QueryKey.info], async () => { return new AccountsApi(api.tonApiV2).getAccount({ @@ -267,23 +328,29 @@ export const useWalletAccountInfo = () => { ); }; -export const useActiveWalletConfig = () => { +export const useActiveTonNetwork = () => { + return useDevSettings().data?.tonNetwork || Network.MAINNET; +}; + +export const useActiveTonWalletConfig = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); - return useQuery( - [wallet.rawAddress, wallet.network, QueryKey.walletConfig], - async () => getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network) + const network = useActiveTonNetwork(); + return useQuery( + [wallet.rawAddress, network, QueryKey.walletConfig], + async () => getActiveWalletConfig(sdk.storage, wallet.rawAddress, network) ); }; -export const useMutateActiveWalletConfig = () => { +export const useMutateActiveTonWalletConfig = () => { const wallet = useActiveWallet(); const sdk = useAppSdk(); const client = useQueryClient(); - return useMutation>(async newConfig => { - const config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network); + const network = useActiveTonNetwork(); + return useMutation>(async newConfig => { + const config = await getActiveWalletConfig(sdk.storage, wallet.rawAddress, network); - await setActiveWalletConfig(sdk.storage, wallet.rawAddress, wallet.network, { + await setActiveWalletConfig(sdk.storage, wallet.rawAddress, network, { ...config, ...newConfig }); @@ -294,10 +361,12 @@ export const useMutateActiveWalletConfig = () => { }); }; -export const useStandardTonWalletVersions = (publicKey?: string, network = Network.MAINNET) => { +export const useStandardTonWalletVersions = (publicKey?: string) => { const { api, fiat, config } = useAppContext(); const { data: devSettings } = useDevSettings(); const isV5Enabled = isV5R1Enabled(config) || devSettings?.enableV5; + const network = useActiveTonNetwork(); + return useQuery( [QueryKey.walletVersions, publicKey, network, isV5Enabled], async () => { @@ -334,3 +403,15 @@ export const useStandardTonWalletVersions = (publicKey?: string, network = Netwo } ); }; + +export function useInvalidateActiveWalletQueries() { + const account = useActiveAccount(); + const client = useQueryClient(); + return useMutation(async () => { + const activeTonWallet = getAccountActiveTonWallet(account); + await client.invalidateQueries({ + predicate: query => + query.queryKey.includes(activeTonWallet.id) || query.queryKey.includes(account.id) + }); + }); +} From 2622546072a4bd028a89fb910281ac375ef15254 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 25 Jul 2024 12:45:03 +0200 Subject: [PATCH 10/53] feat: accounts maid as classes --- apps/desktop/src/electron/sseEvetns.ts | 26 +- apps/desktop/src/libs/aptabaseElectron.ts | 2 +- apps/desktop/src/libs/hooks.ts | 4 +- apps/desktop/src/libs/scroll.ts | 36 +-- apps/desktop/src/libs/storage.ts | 30 +- packages/core/src/entries/account.ts | 266 ++++++++++++++++++ packages/core/src/entries/wallet.ts | 236 +--------------- packages/core/src/service/accountsStorage.ts | 78 ++--- packages/core/src/service/ledger/transfer.ts | 10 +- packages/core/src/service/passwordService.ts | 24 +- packages/core/src/service/proService.ts | 9 +- .../src/service/tonConnect/connectService.ts | 13 +- .../src/service/transfer/jettonService.ts | 5 +- .../core/src/service/transfer/nftService.ts | 9 +- .../core/src/service/transfer/tonService.ts | 5 +- packages/core/src/service/walletService.ts | 110 +++----- packages/uikit/src/components/Header.tsx | 4 +- .../components/desktop/aside/AsideMenu.tsx | 4 +- .../components/settings/AccountSettings.tsx | 8 +- .../settings/LogOutNotification.tsx | 3 +- .../src/components/settings/ProSettings.tsx | 4 +- .../wallet-name/WalletNameNotification.tsx | 2 +- .../settings/DesktopWalletSettingsPage.tsx | 4 +- packages/uikit/src/hooks/accountUtils.ts | 4 +- .../uikit/src/hooks/analytics/amplitude.ts | 2 +- .../uikit/src/hooks/analytics/aptabase-web.ts | 2 +- packages/uikit/src/hooks/analytics/google.ts | 2 +- packages/uikit/src/hooks/analytics/gtag.ts | 2 +- packages/uikit/src/hooks/analytics/index.ts | 2 +- .../hooks/blockchain/useExecuteTonContract.ts | 2 +- packages/uikit/src/hooks/useDebuggingTools.ts | 2 +- packages/uikit/src/libs/queryKey.ts | 8 + packages/uikit/src/pages/import/Create.tsx | 4 +- packages/uikit/src/pages/import/Import.tsx | 9 +- packages/uikit/src/pages/settings/Account.tsx | 2 +- packages/uikit/src/pages/settings/Version.tsx | 3 +- .../src/state/dashboard/useDashboardData.ts | 4 +- packages/uikit/src/state/mnemonic.ts | 4 +- packages/uikit/src/state/pro.ts | 10 +- .../uikit/src/state/swap/useSwapsConfig.ts | 2 +- packages/uikit/src/state/tonConnect.ts | 10 +- packages/uikit/src/state/wallet.ts | 79 +++--- 42 files changed, 508 insertions(+), 537 deletions(-) diff --git a/apps/desktop/src/electron/sseEvetns.ts b/apps/desktop/src/electron/sseEvetns.ts index 1e7b3f400..82abdab1c 100644 --- a/apps/desktop/src/electron/sseEvetns.ts +++ b/apps/desktop/src/electron/sseEvetns.ts @@ -18,15 +18,9 @@ import log from 'electron-log/main'; import EventSourcePolyfill from 'eventsource'; import { MainWindow } from './mainWindow'; import { mainStorage } from './storageService'; -import { - accountWithUpdatedActiveTonWalletId, - getAccountActiveTonWallet, - getAccountAllTonWallets, - getWalletById, - isStandardTonWallet, - WalletId -} from '@tonkeeper/core/dist/entries/wallet'; +import { isStandardTonWallet, WalletId } from '@tonkeeper/core/dist/entries/wallet'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; +import { getWalletById } from '@tonkeeper/core/dist/entries/account'; globalThis.Buffer = BufferPolyfill; @@ -56,7 +50,7 @@ export class TonConnectSSE { this.lastEventId = await getLastEventId(mainStorage); const walletsState = (await accountsStorage(mainStorage).getAccounts()).flatMap( - getAccountAllTonWallets + a => a.allTonWallets ); this.connections = []; @@ -118,20 +112,16 @@ export class TonConnectSSE { const walletId = this.dist[params.connection.clientSessionId]; const activeAccount = await accountsStorage(mainStorage).getActiveAccount(); - const activeWallet = getAccountActiveTonWallet(activeAccount); + const activeWallet = activeAccount.activeTonWallet; const window = await MainWindow.bringToFront(); if (activeWallet.id !== walletId) { - let accountToActivate = (await accountsStorage(mainStorage).getAccounts()).find( - a => getAccountAllTonWallets(a).some(w => w.id === walletId) - ); - - accountToActivate = accountWithUpdatedActiveTonWalletId( - accountToActivate, - walletId - ); + const accountToActivate = ( + await accountsStorage(mainStorage).getAccounts() + ).find(a => a.getTonWallet(walletId) !== undefined); + accountToActivate.setActiveTonWallet(walletId); await accountsStorage(mainStorage).updateAccountInState(accountToActivate); await accountsStorage(mainStorage).setActiveAccountId(accountToActivate.id); window.webContents.send('refresh'); diff --git a/apps/desktop/src/libs/aptabaseElectron.ts b/apps/desktop/src/libs/aptabaseElectron.ts index 55deaea77..e21fbe44b 100644 --- a/apps/desktop/src/libs/aptabaseElectron.ts +++ b/apps/desktop/src/libs/aptabaseElectron.ts @@ -1,7 +1,7 @@ import { trackEvent } from '@aptabase/electron/renderer'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { Analytics } from '@tonkeeper/uikit/dist/hooks/analytics'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; export class AptabaseElectron implements Analytics { private user_properties: Record = {}; diff --git a/apps/desktop/src/libs/hooks.ts b/apps/desktop/src/libs/hooks.ts index 30a210514..6f92d3cd1 100644 --- a/apps/desktop/src/libs/hooks.ts +++ b/apps/desktop/src/libs/hooks.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import { AppKey } from '@tonkeeper/core/dist/Keys'; -import { Account, getAccountActiveTonWallet } 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 { Amplitude } from '@tonkeeper/uikit/dist/hooks/analytics/amplitude'; @@ -74,7 +74,7 @@ export const useAnalytics = (version: string, activeAccount?: Account, accounts? tracker.init({ application: 'Desktop', - walletType: toWalletType(getAccountActiveTonWallet(activeAccount)), + walletType: toWalletType(activeAccount.activeTonWallet), activeAccount, accounts, network, diff --git a/apps/desktop/src/libs/scroll.ts b/apps/desktop/src/libs/scroll.ts index 4a5e3463a..d8d76b46d 100644 --- a/apps/desktop/src/libs/scroll.ts +++ b/apps/desktop/src/libs/scroll.ts @@ -1,30 +1,30 @@ export const disableScroll = () => { - document.documentElement.classList.add('is-locked'); - window.document.body.style.paddingRight = `${getScrollbarWidth()}px`; + document.documentElement.classList.add('is-locked'); + window.document.body.style.paddingRight = `${getScrollbarWidth()}px`; }; export const enableScroll = () => { - document.documentElement.classList.remove('is-locked'); - window.document.body.style.paddingRight = '0px'; + document.documentElement.classList.remove('is-locked'); + window.document.body.style.paddingRight = '0px'; }; export const getScrollbarWidth = () => { - // Creating invisible container - const outer = document.createElement('div'); - outer.style.visibility = 'hidden'; - outer.style.overflow = 'scroll'; // forcing scrollbar to appear - (outer.style as any).msOverflowStyle = 'scrollbar'; // needed for WinJS apps - document.body.appendChild(outer); + // Creating invisible container + const outer = document.createElement('div'); + outer.style.visibility = 'hidden'; + outer.style.overflow = 'scroll'; // forcing scrollbar to appear + (outer.style as any).msOverflowStyle = 'scrollbar'; // needed for WinJS apps + document.body.appendChild(outer); - // Creating inner element and placing it in the container - const inner = document.createElement('div'); - outer.appendChild(inner); + // Creating inner element and placing it in the container + const inner = document.createElement('div'); + outer.appendChild(inner); - // Calculating difference between container's full width and the child width - const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; + // Calculating difference between container's full width and the child width + const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; - // Removing temporary elements from the DOM - outer.parentNode!.removeChild(outer); + // Removing temporary elements from the DOM + outer.parentNode!.removeChild(outer); - return scrollbarWidth; + return scrollbarWidth; }; diff --git a/apps/desktop/src/libs/storage.ts b/apps/desktop/src/libs/storage.ts index 8c71fd507..721b04415 100644 --- a/apps/desktop/src/libs/storage.ts +++ b/apps/desktop/src/libs/storage.ts @@ -2,23 +2,23 @@ import { IStorage } from '@tonkeeper/core/dist/Storage'; import { sendBackground } from './backgroudService'; export class DesktopStorage implements IStorage { - get = async (key: string) => { - return sendBackground({ king: 'storage-get', key }); - }; + get = async (key: string) => { + return sendBackground({ king: 'storage-get', key }); + }; - set = async (key: string, value: R) => { - return sendBackground({ king: 'storage-set', key, value }); - }; + set = async (key: string, value: R) => { + return sendBackground({ king: 'storage-set', key, value }); + }; - setBatch = async >(value: V) => { - return sendBackground({ king: 'storage-set-batch', value }); - }; + setBatch = async >(value: V) => { + return sendBackground({ king: 'storage-set-batch', value }); + }; - delete = async (key: string) => { - return sendBackground({ king: 'storage-delete', key }); - }; + delete = async (key: string) => { + return sendBackground({ king: 'storage-delete', key }); + }; - clear = async () => { - await sendBackground({ king: 'storage-clear' }); - }; + clear = async () => { + await sendBackground({ king: 'storage-clear' }); + }; } diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index 8b49db093..23a47449c 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -1,3 +1,8 @@ +// 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 */ @@ -5,3 +10,264 @@ export interface DeprecatedAccountState { publicKeys: string[]; activePublicKey?: string; } + +export type AccountId = string; + +export interface IAccount { + 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; + 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)!; + } + + constructor( + public readonly id: AccountId, + public name: string, + public emoji: string, + public auth: AuthPassword | AuthKeychain, + public activeTonWalletId: WalletId, + public readonly 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) { + this.tonWallets.push(wallet); + } + + setActiveTonWallet(walletId: WalletId) { + 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 + )!; + } + + constructor( + public readonly id: AccountId, + public name: string, + public emoji: string, + public activeDerivationIndex: number, + public readonly derivations: DerivationItem[] + ) { + super(); + } + + 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) { + this.activeDerivation.tonWallets.push(wallet); + } + + setActiveTonWallet(walletId: WalletId) { + for (const derivation of this.derivations) { + const index = derivation.tonWallets.findIndex(w => w.id === walletId); + if (index !== -1) { + derivation.activeTonWalletId = walletId; + this.activeDerivationIndex = derivation.index; + return; + } + } + + throw new Error('Derivation not found'); + } +} + +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; + } + + 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'); + } + + 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)!; + } + + constructor( + public readonly id: AccountId, + public name: string, + public emoji: string, + public readonly auth: AuthSigner | AuthSignerDeepLink, + public activeTonWalletId: WalletId, + public readonly 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) { + this.tonWallets.push(wallet); + } + + setActiveTonWallet(walletId: WalletId) { + 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; + } + } +} diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index 760b9163c..d0afeb8a4 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -1,15 +1,7 @@ import { Language } from './language'; import { Network } from './network'; -import { - AuthKeychain, - AuthPassword, - AuthSigner, - AuthSignerDeepLink, - DeprecatedAuthState -} from './password'; +import { DeprecatedAuthState } from './password'; import { WalletProxy } from './proxy'; -import { assertUnreachable } from '../utils/types'; -import { KeystonePathInfo } from '../service/keystone/types'; export enum WalletVersion { V3R1 = 0, @@ -93,7 +85,6 @@ export interface DeprecatedWalletState { } export type WalletId = string; -export type AccountId = string; export type TonContract = { id: WalletId; @@ -116,235 +107,10 @@ export type DerivationItem = { // tronWallets: never; }; -export interface AccountBasic { - emoji: string; - name: string; -} - -export type AccountTonMnemonic = AccountBasic & { - id: AccountId; // ton public key - type: 'mnemonic'; - auth: AuthPassword | AuthKeychain; - - activeTonWalletId: WalletId; - tonWallets: TonWalletStandard[]; - // tronWallet: never; -}; - -export type AccountLedger = AccountBasic & { - id: AccountId; // first acc public key - type: 'ledger'; - - activeDerivationIndex: number; - derivations: DerivationItem[]; -}; - -export type AccountKeystone = AccountBasic & { - id: AccountId; // ton wallet id - type: 'keystone'; - pathInfo?: KeystonePathInfo; - - tonWallet: TonWalletStandard; -}; - -/** - * temporary, will be removed when signer supports tron - */ -export type AccountTonOnly = AccountBasic & { - id: AccountId; // ton wallet id - type: 'ton-only'; - auth: AuthSigner | AuthSignerDeepLink; - - activeTonWalletId: WalletId; - tonWallets: TonWalletStandard[]; -}; - -export type AccountTonMultisig = AccountBasic & { - id: AccountId; - type: 'multisig'; - - // tonWallet: TonContract; - //... -}; - -export type AccountKeeperMnemonic = AccountBasic & { - id: AccountId; - type: 'root-mnemonic'; - auth: AuthPassword | AuthKeychain; - - derivations: DerivationItem[]; -}; - -export type Account = AccountTonMnemonic | AccountLedger | AccountTonOnly | AccountKeystone; //| AccountTonMultisig; // | AccountKeeperMnemonic; - -export type AccountsState = Account[]; - -export const defaultAccountState = []; - -export function isAccountTonMnemonic(account: Account): account is AccountTonMnemonic { - return account.type === 'mnemonic'; -} - -export function isAccountLedger(account: Account): account is AccountLedger { - return account.type === 'ledger'; -} - -export function isAccountTonOnly(account: Account): account is AccountTonOnly { - return account.type === 'ton-only'; -} - export function isStandardTonWallet(wallet: TonContract): wallet is TonWalletStandard { return 'version' in wallet && 'publicKey' in wallet; } -export function getWalletById( - accounts: Account[], - walletId: WalletId -): TonWalletStandard | undefined { - for (const account of accounts || []) { - const wallet = getAccountAllTonWallets(account).find(w => w.id === walletId); - if (wallet) { - return wallet; - } - } -} - -export function getAccountAllTonWallets(account: Account): TonWalletStandard[] { - if (account.type === 'mnemonic') { - return account.tonWallets; - } - - if (account.type === 'ledger') { - return account.derivations.flatMap(d => d.tonWallets); - } - - if (account.type === 'ton-only') { - return account.tonWallets; - } - - if (account.type === 'keystone') { - return [account.tonWallet]; - } - - assertUnreachable(account); -} - -export function getAccountActiveDerivationTonWallets(account: Account): TonWalletStandard[] { - if (account.type === 'mnemonic') { - return account.tonWallets; - } - - if (account.type === 'ledger') { - return account.derivations.find(d => account.activeDerivationIndex === d.index)!.tonWallets; - } - - if (account.type === 'ton-only') { - return account.tonWallets; - } - - if (account.type === 'keystone') { - return [account.tonWallet]; - } - - assertUnreachable(account); -} - -export function getAccountActiveTonWallet(account: Account): TonWalletStandard { - if (account.type === 'mnemonic' || account.type === 'ton-only') { - return account.tonWallets.find(w => w.id === account.activeTonWalletId)!; - } - - if (account.type === 'ledger') { - const derivation = account.derivations.find( - d => d.index === account.activeDerivationIndex - )!; - return derivation.tonWallets.find(w => w.id === derivation.activeTonWalletId)!; - } - - if (account.type === 'keystone') { - return account.tonWallet; - } - - assertUnreachable(account); -} - -export function accountWithUpdatedTonWallet( - account: Account, - tonWallet: TonWalletStandard -): Account { - const newAcc: Account = JSON.parse(JSON.stringify(account)); - if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { - const index = newAcc.tonWallets.findIndex(w => w.id === tonWallet.id)!; - newAcc.tonWallets[index] = tonWallet; - return newAcc; - } - - if (newAcc.type === 'ledger') { - for (const derivation of newAcc.derivations) { - const index = derivation.tonWallets.findIndex(w => w.id === tonWallet.id)!; - if (index !== -1) { - derivation.tonWallets[index] = tonWallet; - return newAcc; - } - } - - throw new Error('Derivation not found'); - } - - if (newAcc.type === 'keystone') { - newAcc.tonWallet = tonWallet; - return newAcc; - } - - assertUnreachable(newAcc); -} - -export function accountWithUpdatedActiveTonWalletId(account: Account, walletId: WalletId): Account { - const newAcc: Account = JSON.parse(JSON.stringify(account)); - if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { - newAcc.activeTonWalletId = walletId; - return newAcc; - } - - if (newAcc.type === 'ledger') { - for (const derivation of newAcc.derivations) { - const index = derivation.tonWallets.findIndex(w => w.id === walletId)!; - if (index !== -1) { - derivation.activeTonWalletId = walletId; - return newAcc; - } - } - - throw new Error('Derivation not found'); - } - - if (newAcc.type === 'keystone') { - return newAcc; - } - - assertUnreachable(newAcc); -} - -export function accountWithAddedTonWallet(account: Account, tonWallet: TonWalletStandard): Account { - const newAcc: Account = JSON.parse(JSON.stringify(account)); - if (newAcc.type === 'mnemonic' || newAcc.type === 'ton-only') { - newAcc.tonWallets.push(tonWallet); - return newAcc; - } - - if (newAcc.type === 'ledger') { - const derivation = newAcc.derivations.find(d => d.index === newAcc.activeDerivationIndex)!; - derivation.tonWallets.push(tonWallet); - return newAcc; - } - - if (newAcc.type === 'keystone') { - throw new Error('Cannot add ton wallet to keystone account'); - } - - assertUnreachable(newAcc); -} - export interface TonWalletConfig { pinnedTokens: string[]; hiddenTokens: string[]; diff --git a/packages/core/src/service/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts index 7b81c771b..54c6e7f1b 100644 --- a/packages/core/src/service/accountsStorage.ts +++ b/packages/core/src/service/accountsStorage.ts @@ -1,5 +1,7 @@ import { AppKey } from '../Keys'; import { IStorage } from '../Storage'; +import { DeprecatedWalletState, TonWalletStandard, WalletId } from '../entries/wallet'; + import { Account, AccountId, @@ -9,11 +11,9 @@ import { AccountTonMnemonic, AccountTonOnly, defaultAccountState, - DeprecatedWalletState, - getAccountAllTonWallets, - TonWalletStandard, - WalletId -} from '../entries/wallet'; + bindAccountToClass +} from '../entries/account'; + import { DeprecatedAccountState } from '../entries/account'; import { AuthState, DeprecatedAuthState } from '../entries/password'; import { assertUnreachable, notNullish } from '../utils/types'; @@ -33,6 +33,8 @@ export class AccountsStorage { if (state) { await this.setAccounts(state); } + } else { + state.forEach(bindAccountToClass); } return state ?? defaultAccountState; }; @@ -127,9 +129,8 @@ export class AccountsStorage { const accounts = await this.getAccounts(); return ( - accounts.find(a => - getAccountAllTonWallets(a).some(w => w.publicKey === state.activePublicKey) - )?.id || null + accounts.find(a => a.allTonWallets.some(w => w.publicKey === state.activePublicKey)) + ?.id || null ); }; } @@ -206,58 +207,33 @@ async function migrateToAccountsState(storage: IStorage): Promise { const path = getLedgerAccountPathByIndex(account.activeDerivationIndex); - const walletState = getAccountActiveTonWallet(account); + const walletState = account.activeTonWallet; const contract = walletContractFromState(walletState); const transfer = await signer(path, { @@ -57,8 +57,8 @@ export const createLedgerJettonTransfer = async ( ) => { const jettonAmount = BigInt(amount.stringWeiAmount); const path = getLedgerAccountPathByIndex(account.activeDerivationIndex); - const wallet = getAccountActiveTonWallet(account); - const contract = walletContractFromState(getAccountActiveTonWallet(account)); + const wallet = account.activeTonWallet; + const contract = walletContractFromState(wallet); const transfer = await signer(path, { to: Address.parse(jettonWalletAddress), @@ -93,7 +93,7 @@ export const createLedgerNftTransfer = async ( signer: LedgerSigner ) => { const path = getLedgerAccountPathByIndex(account.activeDerivationIndex); - const walletState = getAccountActiveTonWallet(account); + const walletState = account.activeTonWallet; const contract = walletContractFromState(walletState); const transfer = await signer(path, { diff --git a/packages/core/src/service/passwordService.ts b/packages/core/src/service/passwordService.ts index 5f4535f83..5d74ec787 100644 --- a/packages/core/src/service/passwordService.ts +++ b/packages/core/src/service/passwordService.ts @@ -1,10 +1,10 @@ import { IStorage } from '../Storage'; -import { isAccountTonMnemonic } from '../entries/wallet'; 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; @@ -45,26 +45,28 @@ export class PasswordStorage { async updatePassword(oldPassword: string, newPassword: string): Promise { const accounts = await this.getPasswordAuthAccounts(); - const updatedWallets = await Promise.all( + const updatedAccounts = await Promise.all( accounts.map(async acc => { const mnemonic = await decryptWalletMnemonic( acc as { auth: AuthPassword }, oldPassword ); - const newEncrypted = await encrypt(mnemonic.join(' '), newPassword); - return { - ...acc, - auth: { ...acc.auth, encryptedMnemonic: newEncrypted } - }; + (acc.auth as AuthPassword).encryptedMnemonic = await encrypt( + mnemonic.join(' '), + newPassword + ); + return acc.clone(); }) ); - await this.accountsStorage.updateAccountsInState(updatedWallets); + await this.accountsStorage.updateAccountsInState(updatedAccounts); } - private async getPasswordAuthAccounts() { - const wallets = await this.accountsStorage.getAccounts(); - return wallets.filter(isAccountTonMnemonic).filter(a => a.auth.kind === 'password'); + private async getPasswordAuthAccounts(): Promise { + const accounts = await this.accountsStorage.getAccounts(); + return accounts.filter( + a => a.type === 'mnemonic' && a.auth.kind === 'password' + ) as AccountTonMnemonic[]; } } diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 84a8d5269..2352bd5f2 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -11,12 +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 { - getAccountAllTonWallets, - isStandardTonWallet, - TonWalletStandard, - WalletVersion -} from '../entries/wallet'; +import { isStandardTonWallet, TonWalletStandard, WalletVersion } from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; import { FiatCurrencies as FiatCurrenciesGenerated, @@ -103,7 +98,7 @@ export const loadProState = async ( }; if (user.pub_key && user.version) { const wallets = (await accountsStorage(storage).getAccounts()).flatMap( - getAccountAllTonWallets + a => a.allTonWallets ); const actualWallet = wallets .filter(isStandardTonWallet) diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index a01827970..d9ceb4c42 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -20,7 +20,7 @@ import { TonConnectAccount, TonProofItemReplySuccess } from '../../entries/tonConnect'; -import { Account, getAccountAllTonWallets, TonWalletStandard } from '../../entries/wallet'; +import { TonWalletStandard } from '../../entries/wallet'; import { walletContractFromState } from '../wallet/contractService'; import { AccountConnection, @@ -32,6 +32,7 @@ import { 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 { @@ -205,10 +206,12 @@ export const getAppConnections = async ( } return Promise.all( - accounts.flatMap(getAccountAllTonWallets).map(async wallet => { - const walletConnections = await getTonWalletConnections(storage, wallet); - return { wallet, connections: walletConnections }; - }) + accounts + .flatMap(a => a.allTonWallets) + .map(async wallet => { + const walletConnections = await getTonWalletConnections(storage, wallet); + return { wallet, connections: walletConnections }; + }) ); }; diff --git a/packages/core/src/service/transfer/jettonService.ts b/packages/core/src/service/transfer/jettonService.ts index 371e313f3..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 { Account, getAccountActiveTonWallet, TonWalletStandard } 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); @@ -130,7 +131,7 @@ export const sendJettonTransfer = async ( .multipliedBy(-1) .plus(jettonTransferAmount.toString()); - const walletState = getAccountActiveTonWallet(account); + const walletState = account.activeTonWallet; const [wallet, seqno] = await getWalletBalance(api, walletState); checkWalletBalanceOrDie(total, wallet); diff --git a/packages/core/src/service/transfer/nftService.ts b/packages/core/src/service/transfer/nftService.ts index 1e3a9be3f..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 { Account, getAccountActiveTonWallet, TonWalletStandard } 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'); @@ -139,7 +140,7 @@ export const sendNftTransfer = async ( throw new Error(`Unexpected nft transfer amount: ${nftTransferAmount.toString()}`); } - const walletState = getAccountActiveTonWallet(account); + const walletState = account.activeTonWallet; const [wallet, seqno] = await getWalletBalance(api, walletState); checkWalletBalanceOrDie(total, wallet); @@ -189,7 +190,7 @@ export const sendNftRenew = async (options: { signer: CellSigner; amount: BigNumber; }) => { - const walletState = getAccountActiveTonWallet(options.account); + const walletState = options.account.activeTonWallet; const timestamp = await getServerTime(options.api); const { seqno } = await getKeyPairAndSeqno({ ...options, walletState }); @@ -245,7 +246,7 @@ export const sendNftLink = async (options: { signer: CellSigner; amount: BigNumber; }) => { - const walletState = getAccountActiveTonWallet(options.account); + const walletState = options.account.activeTonWallet; const timestamp = await getServerTime(options.api); const { seqno } = await getKeyPairAndSeqno({ ...options, walletState }); diff --git a/packages/core/src/service/transfer/tonService.ts b/packages/core/src/service/transfer/tonService.ts index 91de10815..f06be62fd 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 { Account, getAccountActiveTonWallet, TonWalletStandard } from '../../entries/wallet'; +import { TonWalletStandard } from '../../entries/wallet'; import { AccountsApi, BlockchainApi, EmulationApi } from '../../tonApiV2'; import { createLedgerTonTransfer } from '../ledger/transfer'; import { walletContractFromState } from '../wallet/contractService'; @@ -23,6 +23,7 @@ import { seeIfTransferBounceable, signEstimateMessage } from './common'; +import { Account } from '../../entries/account'; export type EstimateData = { accountEvent: TransferEstimationEvent; @@ -211,7 +212,7 @@ export const sendTonTransfer = async ( const total = new BigNumber(fee.event.extra).multipliedBy(-1).plus(amount.weiAmount); - const wallet = getAccountActiveTonWallet(account); + const wallet = account.activeTonWallet; const [tonapiWallet, seqno] = await getWalletBalance(api, wallet); if (!isMax) { checkWalletBalanceOrDie(total, tonapiWallet); diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 3e968ba04..710e76e89 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -4,25 +4,20 @@ import { Address } from '@ton/core'; import { mnemonicToPrivateKey } from '@ton/crypto'; import { WalletContractV4 } from '@ton/ton/dist/wallets/WalletContractV4'; import queryString from 'query-string'; -import { IStorage } from '../Storage'; import { APIConfig } from '../entries/apis'; import { Network } from '../entries/network'; import { AuthKeychain, AuthPassword } from '../entries/password'; -import { - Account, - AccountId, - AccountKeystone, - AccountLedger, - AccountTonMnemonic, - AccountTonOnly, - WalletVersion, - WalletVersions -} from '../entries/wallet'; +import { WalletVersion, WalletVersions } from '../entries/wallet'; import { WalletApi } from '../tonApiV2'; import { walletContract } from './wallet/contractService'; import { emojis } from '../utils/emojis'; import { formatAddress } from '../utils/common'; -import { accountsStorage } from './accountsStorage'; +import { + AccountKeystone, + AccountLedger, + AccountTonMnemonic, + AccountTonOnly +} from '../entries/account'; export const createStandardTonAccountByMnemonic = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, @@ -59,14 +54,13 @@ export const createStandardTonAccountByMnemonic = async ( walletAuth = options.auth; } - return { - type: 'mnemonic', - id: publicKey, - auth: walletAuth, - activeTonWalletId: tonWallets[0].rawAddress, - emoji: getFallbackAccountEmoji(publicKey), - name: getFallbackAccountName(publicKey), - tonWallets: tonWallets.map(w => ({ + return new AccountTonMnemonic( + publicKey, + getFallbackAccountName(publicKey), + getFallbackAccountEmoji(publicKey), + walletAuth, + tonWallets[0].rawAddress, + tonWallets.map(w => ({ id: w.rawAddress, publicKey, version: w.version, @@ -74,7 +68,7 @@ export const createStandardTonAccountByMnemonic = async ( name: getFallbackWalletName(w.rawAddress), emoji: getFallbackTonStandardWalletEmoji(publicKey, w.version) })) - } satisfies AccountTonMnemonic; + ); }; const versionMap: Record = { @@ -164,20 +158,6 @@ export const getWalletsAddresses = ( ) as Record<(typeof WalletVersions)[number], { address: Address; version: WalletVersion }>; }; -export const updateAccountProperty = async ( - storage: IStorage, - accountId: AccountId, - props: Partial> -) => { - const wallet = (await accountsStorage(storage).getAccount(accountId))!; - const updated: Account = { - ...wallet, - ...props - }; - await accountsStorage(storage).updateAccountInState(updated); - return updated; -}; - export const accountBySignerQr = async ( appContext: { api: APIConfig; defaultWalletVersion: WalletVersion }, qrCode: string @@ -202,14 +182,13 @@ export const accountBySignerQr = async ( // TODO support multiple wallets versions configuration const active = await findWalletAddress(appContext, publicKey); - return { - type: 'ton-only', - id: publicKey, - auth: { kind: 'signer' }, - activeTonWalletId: active.rawAddress, - emoji: getFallbackAccountEmoji(publicKey), - name: name || getFallbackAccountName(publicKey), - tonWallets: [ + return new AccountTonOnly( + publicKey, + name || getFallbackAccountName(publicKey), + getFallbackAccountEmoji(publicKey), + { kind: 'signer' }, + active.rawAddress, + [ { id: active.rawAddress, publicKey, @@ -219,7 +198,7 @@ export const accountBySignerQr = async ( emoji: getFallbackTonStandardWalletEmoji(publicKey, active.version) } ] - }; + ); }; export const accountBySignerDeepLink = async ( @@ -229,14 +208,13 @@ export const accountBySignerDeepLink = async ( ): Promise => { const active = await findWalletAddress(appContext, publicKey); - return { - type: 'ton-only', - id: publicKey, - auth: { kind: 'signer' }, - activeTonWalletId: active.rawAddress, - emoji: getFallbackAccountEmoji(publicKey), - name: name || getFallbackAccountName(publicKey), - tonWallets: [ + return new AccountTonOnly( + publicKey, + name || getFallbackAccountName(publicKey), + getFallbackAccountEmoji(publicKey), + { kind: 'signer-deeplink' }, + active.rawAddress, + [ { id: active.rawAddress, publicKey, @@ -246,7 +224,7 @@ export const accountBySignerDeepLink = async ( emoji: getFallbackTonStandardWalletEmoji(publicKey, active.version) } ] - }; + ); }; export const accountByLedger = ( @@ -259,13 +237,12 @@ export const accountByLedger = ( emoji: string ): AccountLedger => { const zeroAccPublicKey = walletsInfo[0].publicKey.toString('hex'); - return { - type: 'ledger', - id: zeroAccPublicKey, - emoji, + return new AccountLedger( + zeroAccPublicKey, name, - activeDerivationIndex: walletsInfo[0].accountIndex, - derivations: walletsInfo.map(item => ({ + emoji, + walletsInfo[0].accountIndex, + walletsInfo.map(item => ({ index: item.accountIndex, name: getFallbackWalletName(item.address), emoji: getFallbackDerivationItemEmoji( @@ -287,7 +264,7 @@ export const accountByLedger = ( } ] })) - }; + ); }; export const accountByKeystone = (ur: UR): AccountKeystone => { @@ -300,13 +277,12 @@ export const accountByKeystone = (ur: UR): AccountKeystone => { const pathInfo = account.path && account.xfp ? { path: account.path, mfp: account.xfp } : undefined; - return { - type: 'keystone', - id: account.publicKey, - emoji: getFallbackAccountEmoji(account.publicKey), - name: getFallbackAccountName(account.publicKey), + return new AccountKeystone( + account.publicKey, + getFallbackAccountName(account.publicKey), + getFallbackAccountEmoji(account.publicKey), pathInfo, - tonWallet: { + { id: contact.address.toRawString(), publicKey: account.publicKey, version: WalletVersion.V4R2, @@ -314,7 +290,7 @@ export const accountByKeystone = (ur: UR): AccountKeystone => { name: getFallbackWalletName(contact.address.toRawString()), emoji: getFallbackTonStandardWalletEmoji(account.publicKey, WalletVersion.V4R2) } - }; + ); }; export function getFallbackAccountEmoji(publicKey: string) { diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index 3c6828c5f..8ddba6556 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -20,7 +20,7 @@ import { ScanButton } from './connect/ScanButton'; import { ImportNotification } from './create/ImportNotification'; import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; -import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div<{ center?: boolean; @@ -153,7 +153,7 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ }) => { const navigate = useNavigate(); const { t } = useTranslation(); - const wallets = useAccountsState().flatMap(getAccountAllTonWallets); + const wallets = useAccountsState().flatMap(a => a.allTonWallets); if (!wallets) { return null; diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index d10f2aa68..610d48349 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -18,7 +18,7 @@ import { AsideMenuItem } from '../../shared/AsideItem'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; import { AsideHeader } from './AsideHeader'; import { SubscriptionInfo } from './SubscriptionInfo'; -import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -137,7 +137,7 @@ const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation(); const [isOpenImport, setIsOpenImport] = useState(false); const { proFeatures } = useAppContext(); - const wallets = useAccountsState().flatMap(getAccountAllTonWallets); + const wallets = useAccountsState().flatMap(a => a.allTonWallets); const activeWallet = useActiveWallet(); const navigate = useNavigate(); const location = useLocation(); diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index e68eeeb4a..ef7c3fa07 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -1,8 +1,4 @@ -import { - getAccountActiveTonWallet, - isStandardTonWallet, - walletVersionText -} from '@tonkeeper/core/dist/entries/wallet'; +import { isStandardTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAppContext } from '../../hooks/appContext'; @@ -28,7 +24,7 @@ const SingleAccountSettings = () => { const { t } = useTranslation(); const navigate = useNavigate(); const account = useActiveAccount(); - const wallet = getAccountActiveTonWallet(account); + const wallet = account.activeTonWallet; const { data: jettons } = useJettonList(); const { data: nft } = useWalletNftList(); const { proFeatures } = useAppContext(); diff --git a/packages/uikit/src/components/settings/LogOutNotification.tsx b/packages/uikit/src/components/settings/LogOutNotification.tsx index 339db5394..4ebdd4362 100644 --- a/packages/uikit/src/components/settings/LogOutNotification.tsx +++ b/packages/uikit/src/components/settings/LogOutNotification.tsx @@ -1,5 +1,4 @@ import { useQueryClient } from '@tanstack/react-query'; -import { AccountId } from '@tonkeeper/core/dist/entries/wallet'; import { FC, useCallback, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -12,7 +11,7 @@ import { Body1, H2, Label1, Label2 } from '../Text'; import { Button } from '../fields/Button'; import { Checkbox } from '../fields/Checkbox'; import { DisclaimerBlock } from '../home/BuyItemNotification'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account, AccountId } from '@tonkeeper/core/dist/entries/account'; const NotificationBlock = styled.form` display: flex; diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 19723d238..6061733a9 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -31,7 +31,7 @@ import { Notification } from '../Notification'; import { SubHeader } from '../SubHeader'; import { Body1, Label1, Title } from '../Text'; import { ConfirmView } from '../transfer/ConfirmView'; -import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div` display: flex; @@ -79,7 +79,7 @@ const SelectWallet: FC<{ onClose: () => void }> = ({ onClose }) => { const { t } = useTranslation(); const { mutateAsync, error } = useSelectWalletForProMutation(); useNotifyError(error); - const wallets = useAccountsState().flatMap(getAccountAllTonWallets); + const wallets = useAccountsState().flatMap(a => a.allTonWallets); return ( <> diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx index 7d0c4b043..a285fab1a 100644 --- a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx +++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx @@ -1,4 +1,4 @@ -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import React, { FC, useCallback, useState } from 'react'; import { useTranslation } from '../../../hooks/translation'; import { useMutateRenameAccount } from '../../../state/wallet'; diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index 69da8d02a..56cb37f8f 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -1,4 +1,4 @@ -import { getAccountActiveTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { @@ -60,7 +60,7 @@ export const DesktopWalletSettingsPage = () => { const { isOpen: isLogoutOpen, onClose: onLogoutClose, onOpen: onLogoutOpen } = useDisclosure(); const canChangeVersion = account.type === 'mnemonic'; - const activeWallet = getAccountActiveTonWallet(account); + const activeWallet = account.activeTonWallet; return ( diff --git a/packages/uikit/src/hooks/accountUtils.ts b/packages/uikit/src/hooks/accountUtils.ts index 7249c2174..9be5b9ca8 100644 --- a/packages/uikit/src/hooks/accountUtils.ts +++ b/packages/uikit/src/hooks/accountUtils.ts @@ -1,10 +1,10 @@ -import { Account, getAccountAllTonWallets } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { useActiveTonNetwork } from '../state/wallet'; import { useTranslation } from './translation'; export function useAccountLabel(account: Account) { - const tonWallets = getAccountAllTonWallets(account); + const tonWallets = account.allTonWallets; const network = useActiveTonNetwork(); const { t } = useTranslation(); diff --git a/packages/uikit/src/hooks/analytics/amplitude.ts b/packages/uikit/src/hooks/analytics/amplitude.ts index 94c203b12..953aeeb9d 100644 --- a/packages/uikit/src/hooks/analytics/amplitude.ts +++ b/packages/uikit/src/hooks/analytics/amplitude.ts @@ -1,6 +1,6 @@ import * as amplitude from '@amplitude/analytics-browser'; import { Network } from '@tonkeeper/core/dist/entries/network'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import { Analytics } from '.'; export class Amplitude implements Analytics { diff --git a/packages/uikit/src/hooks/analytics/aptabase-web.ts b/packages/uikit/src/hooks/analytics/aptabase-web.ts index a0befcb54..196c9d0e6 100644 --- a/packages/uikit/src/hooks/analytics/aptabase-web.ts +++ b/packages/uikit/src/hooks/analytics/aptabase-web.ts @@ -1,7 +1,7 @@ import { init, trackEvent } from '@aptabase/web'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { Analytics } from '.'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; export class AptabaseWeb implements Analytics { private user_properties: Record = {}; diff --git a/packages/uikit/src/hooks/analytics/google.ts b/packages/uikit/src/hooks/analytics/google.ts index 02c5847ac..eca575587 100644 --- a/packages/uikit/src/hooks/analytics/google.ts +++ b/packages/uikit/src/hooks/analytics/google.ts @@ -1,7 +1,7 @@ import { AppKey } from '@tonkeeper/core/dist/Keys'; import { IStorage } from '@tonkeeper/core/dist/Storage'; import { Network } from '@tonkeeper/core/dist/entries/network'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import { v4 as uuidv4 } from 'uuid'; import { Analytics } from '.'; diff --git a/packages/uikit/src/hooks/analytics/gtag.ts b/packages/uikit/src/hooks/analytics/gtag.ts index a9d052dd0..c2f6ba771 100644 --- a/packages/uikit/src/hooks/analytics/gtag.ts +++ b/packages/uikit/src/hooks/analytics/gtag.ts @@ -1,5 +1,5 @@ import { Network } from '@tonkeeper/core/dist/entries/network'; -import { Account } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import ReactGA from 'react-ga4'; import { Analytics } from '.'; diff --git a/packages/uikit/src/hooks/analytics/index.ts b/packages/uikit/src/hooks/analytics/index.ts index 603df9716..7e7ce6c62 100644 --- a/packages/uikit/src/hooks/analytics/index.ts +++ b/packages/uikit/src/hooks/analytics/index.ts @@ -1,9 +1,9 @@ import { isStandardTonWallet, walletVersionText, - Account, 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 { diff --git a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts index 5f940f405..ac84ddc41 100644 --- a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts +++ b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts @@ -2,7 +2,7 @@ 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 { Account } 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'; diff --git a/packages/uikit/src/hooks/useDebuggingTools.ts b/packages/uikit/src/hooks/useDebuggingTools.ts index 53f0c6d5a..bfa30c244 100644 --- a/packages/uikit/src/hooks/useDebuggingTools.ts +++ b/packages/uikit/src/hooks/useDebuggingTools.ts @@ -1,5 +1,5 @@ import { useAppSdk } from './appSdk'; -import { AccountsState } from '@tonkeeper/core/dist/entries/wallet'; +import { AccountsState } from '@tonkeeper/core/dist/entries/account'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; export const useDebuggingTools = () => { diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index 3d3d8bbed..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', @@ -63,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/pages/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index 64e3ca82f..89ef40fd4 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -15,7 +15,7 @@ import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { Account, getAccountActiveTonWallet } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import { useCreateAccountMnemonic, useMutateRenameAccount, @@ -173,7 +173,7 @@ const Create = () => { if (sdk.notifications && !passNotifications) { return ( setPassNotification(true)} /> diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx index d65a41e5e..7d809dc59 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -10,12 +10,9 @@ import { useMutateRenameAccount, useAccountsState } from '../../state/wallet'; -import { - AccountTonMnemonic, - getAccountActiveTonWallet, - WalletVersion -} from '@tonkeeper/core/dist/entries/wallet'; +import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions'; +import { AccountTonMnemonic } from '@tonkeeper/core/dist/entries/account'; const Import = () => { const sdk = useAppSdk(); @@ -115,7 +112,7 @@ const Import = () => { if (sdk.notifications && !passNotifications) { return ( setPassNotification(true)} /> diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index f547fc2cf..0b1c48acb 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -28,7 +28,7 @@ import { WalletEmoji } from '../../components/shared/emoji/WalletEmoji'; import { useTranslation } from '../../hooks/translation'; import { AppRoute, SettingsRoute } from '../../libs/routes'; import { useMutateAccountsState, useAccountsState } from '../../state/wallet'; -import { Account as AccountType } from '@tonkeeper/core/dist/entries/wallet'; +import { Account as AccountType } from '@tonkeeper/core/dist/entries/account'; import { useAccountLabel } from '../../hooks/accountUtils'; const Row = styled.div` diff --git a/packages/uikit/src/pages/settings/Version.tsx b/packages/uikit/src/pages/settings/Version.tsx index c5f584195..2aefc9838 100644 --- a/packages/uikit/src/pages/settings/Version.tsx +++ b/packages/uikit/src/pages/settings/Version.tsx @@ -1,5 +1,4 @@ import { - getAccountActiveDerivationTonWallets, TonWalletStandard, WalletVersion as WalletVersionType, WalletVersions, @@ -53,7 +52,7 @@ export const WalletVersion = () => { const isKeystone = useIsActiveWalletKeystone(); const currentWallet = useActiveStandardTonWallet(); const currentAccount = useActiveAccount(); - const currentAccountWalletsVersions = getAccountActiveDerivationTonWallets(currentAccount); + const currentAccountWalletsVersions = currentAccount.activeDerivationTonWallets; const { mutateAsync: selectWallet, isLoading: isSelectWalletLoading } = useMutateActiveTonWallet(); diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index 9cd384371..3f95d2885 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -1,7 +1,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Address } from '@ton/core'; import { DashboardCell } from '@tonkeeper/core/dist/entries/dashboard'; -import { getAccountAllTonWallets, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; @@ -22,7 +22,7 @@ export function useDashboardData() { const client = useQueryClient(); const accountsState = useAccountsState(); - const mainnetWallets = accountsState.flatMap(getAccountAllTonWallets); + const mainnetWallets = accountsState.flatMap(a => a.allTonWallets); const idsMainnet = mainnetWallets.map(w => w!.id); return useQuery( diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index b87333b62..360675d0e 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -13,9 +13,9 @@ import { import { delay } from '@tonkeeper/core/dist/utils/common'; import nacl from 'tweetnacl'; import { TxConfirmationCustomError } from '../libs/errors/TxConfirmationCustomError'; -import { AccountId, getAccountActiveTonWallet } from '@tonkeeper/core/dist/entries/wallet'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; +import { AccountId } from '@tonkeeper/core/dist/entries/account'; export const signTonConnectOver = ( sdk: IAppSdk, @@ -98,7 +98,7 @@ export const getSigner = async ( } if (account.auth.kind === 'signer-deeplink') { - const wallet = getAccountActiveTonWallet(account); + const wallet = account.activeTonWallet; const callback = async (message: Cell) => { const deeplink = await storeTransactionAndCreateDeepLink( sdk, diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index c96c97971..106a7a0db 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -2,12 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { ProState, ProSubscription } from '@tonkeeper/core/dist/entries/pro'; import { RecipientData } from '@tonkeeper/core/dist/entries/send'; -import { - getAccountAllTonWallets, - getWalletById, - isStandardTonWallet, - TonWalletStandard -} from '@tonkeeper/core/dist/entries/wallet'; +import { isStandardTonWallet, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { authViaTonConnect, createProServiceInvoice, @@ -32,6 +27,7 @@ import { useCheckTouchId } from './password'; import { useActiveWallet } from './wallet'; import { useUserLanguage } from './language'; import { useAccountsStorage } from '../hooks/useStorage'; +import { getWalletById } from '@tonkeeper/core/dist/entries/account'; export const useProBackupState = () => { const sdk = useAppSdk(); @@ -132,7 +128,7 @@ export const useCreateInvoiceMutation = () => { } const wallet = (await ws.getAccounts()) - .flatMap(getAccountAllTonWallets) + .flatMap(a => a.allTonWallets) .find(w => w.id === data.state.wallet.rawAddress); if (!wallet || !isStandardTonWallet(wallet)) { throw new Error('Missing wallet'); diff --git a/packages/uikit/src/state/swap/useSwapsConfig.ts b/packages/uikit/src/state/swap/useSwapsConfig.ts index 7d1d75b99..0f4f05d78 100644 --- a/packages/uikit/src/state/swap/useSwapsConfig.ts +++ b/packages/uikit/src/state/swap/useSwapsConfig.ts @@ -4,7 +4,7 @@ import { OpenAPI, SwapService } from '@tonkeeper/core/dist/swapsApi'; export const useSwapsConfig = () => { const { config } = useAppContext(); - OpenAPI.BASE = 'http://localhost:8080'; //config.web_swaps_url!; + OpenAPI.BASE = config.web_swaps_url!; return { swapService: SwapService, referralAddress: config.web_swaps_referral_address, diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index 49e05df37..73aaa8880 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -26,17 +26,13 @@ import { } from '@tonkeeper/core/dist/service/tonConnect/connectService'; import { signTonConnectOver } from './mnemonic'; import { getServerTime } from '@tonkeeper/core/dist/service/transfer/common'; -import { - getAccountAllTonWallets, - isStandardTonWallet, - TonWalletStandard -} from '@tonkeeper/core/dist/entries/wallet'; +import { isStandardTonWallet, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { IStorage } from '@tonkeeper/core/dist/Storage'; import { useActiveWallet, useAccountsState, useActiveAccount, useActiveTonNetwork } from './wallet'; export const useAppTonConnectConnections = () => { const sdk = useAppSdk(); - const wallets = useAccountsState().flatMap(getAccountAllTonWallets); + const wallets = useAccountsState().flatMap(a => a.allTonWallets); return useQuery<{ wallet: TonWalletStandard; connections: AccountConnection[] }[]>( [QueryKey.tonConnectConnection, wallets.map(i => i.id)], @@ -180,7 +176,7 @@ export const useDisconnectTonConnectApp = (options?: { skipEmit?: boolean }) => connectionsToDisconnect = ( await Promise.all( accounts - .flatMap(getAccountAllTonWallets) + .flatMap(a => a.allTonWallets) .map(w => disconnectFromWallet(sdk.storage, connection, w)) ) ).flat(); diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 139218da8..779bf1a7d 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -1,35 +1,23 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { - Account, - AccountId, - AccountsState, - AccountTonMnemonic, - accountWithAddedTonWallet, - accountWithUpdatedActiveTonWalletId, - accountWithUpdatedTonWallet, - getAccountActiveTonWallet, - getAccountAllTonWallets, - getWalletById, - isAccountTonMnemonic, isStandardTonWallet, isW5Version, - TonWalletConfig, - TonWalletStandard, - WalletId, WalletVersion, - WalletVersions + WalletVersions, + WalletId, + TonWalletStandard, + TonWalletConfig } from '@tonkeeper/core/dist/entries/wallet'; import { createStandardTonAccountByMnemonic, getFallbackTonStandardWalletEmoji, getFallbackWalletName, - getWalletAddress, - updateAccountProperty + getWalletAddress } from '@tonkeeper/core/dist/service/walletService'; import { Account as TonapiAccount, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; -import { QueryKey } from '../libs/queryKey'; +import { anyOfKeysParts, QueryKey } from '../libs/queryKey'; import { DefaultRefetchInterval, isV5R1Enabled } from './tonendpoint'; import { useMemo } from 'react'; import { useAccountsStorage } from '../hooks/useStorage'; @@ -43,6 +31,13 @@ import { getActiveWalletConfig, setActiveWalletConfig } from '@tonkeeper/core/dist/service/wallet/configService'; +import { + Account, + AccountId, + AccountsState, + AccountTonMnemonic, + getWalletById +} from '@tonkeeper/core/dist/entries/account'; export const useActiveAccountQuery = () => { const storage = useAccountsStorage(); @@ -68,7 +63,7 @@ export const useActiveAccount = () => { export const useActiveWallet = () => { const account = useActiveAccount(); - return getAccountActiveTonWallet(account); + return account.activeTonWallet; }; export const useActiveStandardTonWallet = () => { @@ -84,8 +79,7 @@ export const useMutateActiveAccount = () => { const client = useQueryClient(); return useMutation(async accountId => { await storage.setActiveAccountId(accountId); - await client.invalidateQueries([QueryKey.account]); - await client.invalidateQueries([accountId]); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, accountId)); }); }; @@ -94,15 +88,15 @@ export const useMutateActiveTonWallet = () => { const client = useQueryClient(); return useMutation(async walletId => { const accounts = await storage.getAccounts(); - const account = accounts.find(a => getAccountAllTonWallets(a).some(w => w.id === walletId)); + const account = accounts.find(a => !!a.getTonWallet(walletId)); if (!account) { throw new Error('Account not found'); } - await storage.updateAccountInState(accountWithUpdatedActiveTonWalletId(account, walletId)); + account.setActiveTonWallet(walletId); + await storage.updateAccountInState(account); await storage.setActiveAccountId(account.id); - await client.invalidateQueries([QueryKey.account]); - await client.invalidateQueries([walletId]); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, walletId)); }); }; @@ -209,7 +203,7 @@ export const useAddTonWalletVersionToActiveAccount = () => { version: WalletVersion; } >(async ({ version }) => { - const publicKey = getAccountActiveTonWallet(account).publicKey; + const publicKey = account.activeTonWallet.publicKey; const w = getWalletAddress(publicKey, version); const wallet: TonWalletStandard = { id: w.address.toRawString(), @@ -220,7 +214,8 @@ export const useAddTonWalletVersionToActiveAccount = () => { emoji: getFallbackTonStandardWalletEmoji(publicKey, version) }; - await accountsStore.updateAccountInState(accountWithAddedTonWallet(account, wallet)); + account.addTonWalletToActiveDerivation(wallet); + await accountsStore.updateAccountInState(account); return wallet; }); }; @@ -228,6 +223,7 @@ export const useAddTonWalletVersionToActiveAccount = () => { export const useRenameTonWallet = () => { const accountsStore = useAccountsStorage(); const account = useActiveAccount(); + const client = useQueryClient(); return useMutation< TonWalletStandard, @@ -238,7 +234,7 @@ export const useRenameTonWallet = () => { emoji?: string; } >(async ({ id, name, emoji }) => { - const wallet = getAccountAllTonWallets(account).find(w => w.id === id); + const wallet = account.getTonWallet(id); if (!wallet) { throw new Error('Wallet to rename not found'); } @@ -249,7 +245,9 @@ export const useRenameTonWallet = () => { emoji: emoji || wallet.emoji }; - await accountsStore.updateAccountInState(accountWithUpdatedTonWallet(account, newWallet)); + account.updateTonWallet(newWallet); + await accountsStore.updateAccountInState(account); + await client.invalidateQueries([QueryKey.account]); return wallet; }); }; @@ -276,7 +274,7 @@ export const useMutateDeleteAll = () => { export const useIsPasswordSet = () => { const wallets = useAccountsState(); - return (wallets || []).some(acc => isAccountTonMnemonic(acc) && acc.auth.kind === 'password'); + return (wallets || []).some(acc => acc.type === 'mnemonic' && acc.auth.kind === 'password'); }; export const useMutateLogOut = () => { @@ -289,23 +287,28 @@ export const useMutateLogOut = () => { }; export const useMutateRenameAccount = () => { - const sdk = useAppSdk(); const client = useQueryClient(); + const storage = useAccountsStorage(); return useMutation(async form => { if (form.name !== undefined && form.name.length <= 0) { throw new Error('Missing name'); } - const formToUpdate = { - ...(form.emoji && { emoji: form.emoji }), - ...(form.name && { name: form.name }) - }; + const account = (await storage.getAccount(form.id))!; + if (form.emoji) { + account.emoji = form.emoji; + } + + if (form.name) { + account.name = form.name; + } + + await storage.updateAccountInState(account); - const newAccount = await updateAccountProperty(sdk.storage, form.id, formToUpdate); await client.invalidateQueries([QueryKey.account]); - return newAccount as T; + return account.clone() as T; }); }; @@ -408,7 +411,7 @@ export function useInvalidateActiveWalletQueries() { const account = useActiveAccount(); const client = useQueryClient(); return useMutation(async () => { - const activeTonWallet = getAccountActiveTonWallet(account); + const activeTonWallet = account.activeTonWallet; await client.invalidateQueries({ predicate: query => query.queryKey.includes(activeTonWallet.id) || query.queryKey.includes(account.id) From 15249bedc456eabf5a473dfc3d7cf503e734d7ba Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 25 Jul 2024 16:50:16 +0200 Subject: [PATCH 11/53] feat: two-layers desktop aside --- apps/desktop/src/app/App.tsx | 5 +- packages/core/src/entries/account.ts | 44 +++- packages/locales/src/tonkeeper-web/en.json | 1 + packages/locales/src/tonkeeper-web/ru-RU.json | 1 + packages/uikit/src/components/Icon.tsx | 26 +++ packages/uikit/src/components/ModalsRoot.tsx | 9 + .../components/desktop/aside/AsideMenu.tsx | 216 +++++++++++++++--- .../WalletVersionSettingsNotification.tsx | 28 +++ .../settings/DesktopWalletSettingsRouting.tsx | 4 +- packages/uikit/src/pages/settings/Version.tsx | 168 ++++++-------- packages/uikit/src/pages/settings/index.tsx | 4 +- packages/uikit/src/state/wallet.ts | 20 ++ 12 files changed, 399 insertions(+), 127 deletions(-) create mode 100644 packages/uikit/src/components/ModalsRoot.tsx create mode 100644 packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 64110cdcc..6499b5728 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { localizationText } from '@tonkeeper/core/dist/entries/language'; import { getApiConfig } from '@tonkeeper/core/dist/entries/network'; -import { Account, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; import ConnectLedgerNotification from '@tonkeeper/uikit/dist/components/ConnectLedgerNotification'; import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; @@ -89,6 +89,8 @@ import { DesktopCollectables } from '@tonkeeper/uikit/dist/desktop-pages/nft/Des import { useUserLanguage } from '@tonkeeper/uikit/dist/state/language'; import { useDebuggingTools } from '@tonkeeper/uikit/dist/hooks/useDebuggingTools'; import { useDevSettings } from '@tonkeeper/uikit/dist/state/dev'; +import { ModalsRoot } from '@tonkeeper/uikit/dist/components/ModalsRoot'; +import { Account } from '@tonkeeper/core/dist/entries/account'; const queryClient = new QueryClient({ defaultOptions: { @@ -338,6 +340,7 @@ export const Loader: FC = () => { + diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index 23a47449c..d0d1adeab 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -24,6 +24,7 @@ export interface IAccount { getTonWallet(id: WalletId): TonWalletStandard | undefined; updateTonWallet(wallet: TonWalletStandard): void; addTonWalletToActiveDerivation(wallet: TonWalletStandard): void; + removeTonWalletFromActiveDerivation(walletId: WalletId): void; setActiveTonWallet(walletId: WalletId): void; } @@ -56,7 +57,7 @@ export class AccountTonMnemonic extends Clonable implements IAccount { public emoji: string, public auth: AuthPassword | AuthKeychain, public activeTonWalletId: WalletId, - public readonly tonWallets: TonWalletStandard[] + public tonWallets: TonWalletStandard[] ) { super(); } @@ -77,6 +78,17 @@ export class AccountTonMnemonic extends Clonable implements IAccount { this.tonWallets.push(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) { this.activeTonWalletId = walletId; } @@ -134,6 +146,19 @@ export class AccountLedger extends Clonable implements IAccount { this.activeDerivation.tonWallets.push(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 index = derivation.tonWallets.findIndex(w => w.id === walletId); @@ -185,6 +210,10 @@ export class AccountKeystone extends Clonable implements IAccount { 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'); @@ -213,7 +242,7 @@ export class AccountTonOnly extends Clonable implements IAccount { public emoji: string, public readonly auth: AuthSigner | AuthSignerDeepLink, public activeTonWalletId: WalletId, - public readonly tonWallets: TonWalletStandard[] + public tonWallets: TonWalletStandard[] ) { super(); } @@ -234,6 +263,17 @@ export class AccountTonOnly extends Clonable implements IAccount { this.tonWallets.push(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) { this.activeTonWalletId = walletId; } diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index ffebd80e8..c9586e3d5 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -46,6 +46,7 @@ "Enable_storing_config" : "Enable storing config", "enter_password" : "Enter password", "export_dot_csv" : "Export .CSV", + "hide" : "Hide", "history_spam_nft" : "Spam NFT", "I_have_a_backup_copy_of_recovery_phrase" : "I have a backup copy of recovery phrase", "import_csv" : "Import CSV", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 73e3336d7..783959f4b 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -44,6 +44,7 @@ "Enable_storing_config" : "Cохранение конфигурации", "enter_password" : "Введите пароль", "export_dot_csv" : "Экспорт в .CSV", + "hide" : "Скрыть", "history_spam_nft" : "Спам NFT", "I_have_a_backup_copy_of_recovery_phrase" : "У меня есть резервная копия фразы восстановления", "import_csv" : "Импорт CSV", 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..bdb5c279f --- /dev/null +++ b/packages/uikit/src/components/ModalsRoot.tsx @@ -0,0 +1,9 @@ +import { WalletVersionSettingsNotification } from './modals/WalletVersionSettingsNotification'; + +export const ModalsRoot = () => { + return ( + <> + + + ); +}; diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 610d48349..6b4e49019 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -9,16 +9,26 @@ import { useIsScrolled } from '../../../hooks/useIsScrolled'; import { scrollToTop } from '../../../libs/common'; import { AppProRoute, AppRoute } from '../../../libs/routes'; import { useMutateUserUIPreferences, useUserUIPreferences } from '../../../state/theme'; -import { useActiveWallet, useAccountsState, useMutateActiveTonWallet } 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 { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { Account } from '@tonkeeper/core/dist/entries/account'; +import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import { WalletId, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; +import { IconButtonTransparentBackground } from '../../fields/IconButton'; +import { useWalletVersionSettingsNotification } from '../../modals/WalletVersionSettingsNotification'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -92,17 +102,55 @@ const SubscriptionInfoStyled = styled(SubscriptionInfo)` padding: 6px 16px 6px 8px; `; -export const AsideMenuAccount: FC<{ wallet: TonWalletStandard; isSelected: boolean }> = ({ - wallet, +const AsideMenuSubItem = styled(AsideMenuItem)` + padding-left: 36px; +`; + +const Badge = styled.div` + padding: 2px 4px; + margin-left: -4px; + background: ${p => p.theme.backgroundContentAttention}; + border-radius: 3px; + color: ${p => p.theme.textSecondary}; + font-size: 9px; + font-style: normal; + font-weight: 510; + line-height: 12px; +`; + +const GearIconButtonStyled = styled(IconButtonTransparentBackground)` + margin-left: auto; + margin-right: -10px; +`; + +const AccountBadge: FC<{ account: Account }> = ({ account }) => { + if (account.type === 'ledger') { + return LEDGER; + } + + if (account.type === 'ton-only') { + return SIGNER; + } + + if (account.type === 'keystone') { + return KEYSTONE; + } + + return null; +}; + +export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ({ + account, isSelected }) => { - const { t } = useTranslation(); - const { mutateAsync } = useMutateActiveTonWallet(); + const { onOpen: openWalletVersionSettings } = useWalletVersionSettingsNotification(); + const network = useActiveTonNetwork(); + const { mutateAsync: setActiveWallet } = useMutateActiveTonWallet(); const navigate = useNavigate(); const location = useLocation(); - const wallets = useAccountsState(); - const shouldShowIcon = wallets.length > 1; + const accounts = useAccountsState(); + const shouldShowIcon = accounts.length > 1; const handleNavigateHome = useCallback(() => { const navigateHomeFromRoutes = [AppProRoute.dashboard, AppRoute.settings, AppRoute.browser]; @@ -113,32 +161,144 @@ export const AsideMenuAccount: FC<{ wallet: TonWalletStandard; isSelected: boole } }, [location.pathname]); - const onClick = useCallback(() => { - mutateAsync(wallet.id).then(handleNavigateHome); - }, [wallet.id, mutateAsync, handleNavigateHome]); + const onClickWallet = (walletId: WalletId) => + setActiveWallet(walletId).then(handleNavigateHome); - if (!wallet) { + if (!account) { return null; } - const name = wallet.name ? wallet.name : t('wallet_title'); + if (account.allTonWallets.length === 1) { + return ( + onClickWallet(account.activeTonWallet.id)} + > + {shouldShowIcon && ( + + )} + {account.name} + + + ); + } - return ( - - {shouldShowIcon && ( - - )} - {name} - - ); + if (account.type === 'mnemonic') { + return ( + <> + onClickWallet(account.activeTonWallet.id)} + > + {shouldShowIcon && ( + + )} + {account.name} + + + + + {account.tonWallets.map(wallet => ( + onClickWallet(wallet.id)} + > + {toShortValue(formatAddress(wallet.rawAddress, network))} + {walletVersionText(wallet.version)} + + ))} + + ); + } + + if (account.type === 'ledger') { + return ( + <> + onClickWallet(account.activeTonWallet.id)} + > + {shouldShowIcon && ( + + )} + {account.name} + + + {account.derivations.map(derivation => { + const wallet = derivation.tonWallets.find( + w => w.id === derivation.activeTonWalletId + )!; + + return ( + onClickWallet(derivation.activeTonWalletId)} + > + + {toShortValue(formatAddress(wallet.rawAddress, network))} + + {'#' + derivation.index} + + ); + })} + + ); + } + + if (account.type === 'ton-only') { + return ( + <> + onClickWallet(account.activeTonWallet.id)} + > + {shouldShowIcon && ( + + )} + {account.name} + + + {account.tonWallets.map(wallet => ( + onClickWallet(wallet.id)} + > + {toShortValue(formatAddress(wallet.rawAddress, network))} + + ))} + + ); + } + + if (account.type === 'keystone') { + return ( + onClickWallet(account.activeTonWallet.id)} + > + {shouldShowIcon && ( + + )} + {account.name} + + + ); + } + + assertUnreachable(account); }; const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation(); const [isOpenImport, setIsOpenImport] = useState(false); const { proFeatures } = useAppContext(); - const wallets = useAccountsState().flatMap(a => a.allTonWallets); - const activeWallet = useActiveWallet(); + const accounts = useAccountsState(); + const activeAccount = useActiveAccount(); const navigate = useNavigate(); const location = useLocation(); const { ref, closeBottom } = useIsScrolled(); @@ -210,11 +370,11 @@ const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => { {t('aside_dashboard')} )} - {wallets.map(wallet => ( + {accounts.map(account => ( ))} diff --git a/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx new file mode 100644 index 000000000..a8a8ab941 --- /dev/null +++ b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx @@ -0,0 +1,28 @@ +import { Notification } from '../Notification'; +import { useCallback } from 'react'; +import { WalletVersionPageContent } from '../../pages/settings/Version'; +import { atom, useAtom } from '../../libs/atom'; +import { useTranslation } from '../../hooks/translation'; + +const walletVersionSettingsNotificationIsOpen = atom(false); + +export const useWalletVersionSettingsNotification = () => { + const [isOpen, setIsOpen] = useAtom(walletVersionSettingsNotificationIsOpen); + + return { + isOpen, + onOpen: useCallback(() => setIsOpen(true), []), + onClose: useCallback(() => setIsOpen(false), []) + }; +}; + +export const WalletVersionSettingsNotification = () => { + const { isOpen, onClose } = useWalletVersionSettingsNotification(); + const { t } = useTranslation(); + + return ( + onClose()}> + {() => } + + ); +}; diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx index 693ba6672..1ba6de7f1 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx @@ -1,7 +1,7 @@ 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'; @@ -29,7 +29,7 @@ export const DesktopWalletSettingsRouting = () => { } /> } /> - } /> + } /> } /> props.theme.textSecondary}; `; -export const WalletVersion = () => { +const ButtonsContainer = styled.div` + display: flex; + gap: 8px; +`; + +export const WalletVersionPage = () => { + const { t } = useTranslation(); + return ( + <> + + + + + + ); +}; + +export const WalletVersionPageContent: FC<{ afterWalletOpened?: () => void }> = ({ + afterWalletOpened +}) => { const { t } = useTranslation(); const isLedger = useIsActiveWalletLedger(); const isKeystone = useIsActiveWalletKeystone(); @@ -60,123 +76,91 @@ export const WalletVersion = () => { const { data: wallets } = useStandardTonWalletVersions(currentWallet.publicKey); - const { mutateAsync: createWalletAsync, isLoading: isCreateWalletLoading } = + const { mutate: createWallet, isLoading: isCreateWalletLoading } = useAddTonWalletVersionToActiveAccount(); - const { mutateAsync: renameWallet, isLoading: isRenameWalletLoading } = useRenameTonWallet(); + + const { mutate: hideWallet, isLoading: isHideWalletLoading } = + useRemoveTonWalletVersionFromActiveAccount(); const onOpenWallet = async (address: Address) => { if (address.toRawString() !== currentWallet.rawAddress) { await selectWallet(address.toRawString()); } navigate(AppRoute.home); + afterWalletOpened?.(); }; - const [editWalletNameNotificationPayload, setEditWalletNameNotificationPayload] = useState< - TonWalletStandard | undefined - >(); - const onAddWallet = async (w: { version: WalletVersionType; address: Address }) => { - const newWallet = await createWalletAsync({ + createWallet({ version: w.version }); - setEditWalletNameNotificationPayload(newWallet); }; - const onChangeName = async (args: { name: string; emoji: string; id: string }) => { - await renameWallet(args); - setEditWalletNameNotificationPayload(undefined); + const onHideWallet = async (w: { address: Address }) => { + hideWallet({ + walletId: w.address.toRawString() + }); }; if (!wallets) { - return ( - <> - - - - - - ); + return ; } - const isLoading = isSelectWalletLoading || isCreateWalletLoading || isRenameWalletLoading; + const isLoading = isSelectWalletLoading || isCreateWalletLoading || isHideWalletLoading; return ( <> - - - {!isLedger && !isKeystone && ( - - {wallets.map(wallet => { - const isWalletAdded = currentAccountWalletsVersions.some( - w => w.rawAddress === wallet.address.toRawString() - ); - - return ( - - - - {walletVersionText(wallet.version)} - - {toShortValue(formatAddress(wallet.address))} -  ·  - {toFormattedTonBalance(wallet.tonBalance)} TON - {wallet.hasJettons && - t('wallet_version_and_tokens')} - - - {isWalletAdded ? ( + {!isLedger && !isKeystone && ( + + {wallets.map(wallet => { + const isWalletAdded = currentAccountWalletsVersions.some( + w => w.rawAddress === wallet.address.toRawString() + ); + + return ( + + + + {walletVersionText(wallet.version)} + + {toShortValue(formatAddress(wallet.address))} +  ·  + {toFormattedTonBalance(wallet.tonBalance)} TON + {wallet.hasJettons && t('wallet_version_and_tokens')} + + + {isWalletAdded ? ( + - ) : ( - )} - - - ); - })} - - )} - {isLedger && {t('ledger_operation_not_supported')}} - {isKeystone && {t('operation_not_supported')}} - - - - ); -}; - -const UpdateWalletNameNotification: FC<{ - isOpen: boolean; - onClose: (isAdded: { name: string; emoji: string; id: string }) => void; - wallet: TonWalletStandard | undefined; -}> = ({ isOpen, onClose, wallet }) => { - return ( - - onClose({ name: wallet!.name, emoji: wallet!.emoji, id: wallet!.id }) - } - > - {() => ( - onClose({ ...val, id: wallet!.id })} - walletEmoji={wallet?.emoji || ''} - /> + + ) : ( + + )} + + + ); + })} +
)} - + {isLedger && {t('ledger_operation_not_supported')}} + {isKeystone && {t('operation_not_supported')}} + ); }; diff --git a/packages/uikit/src/pages/settings/index.tsx b/packages/uikit/src/pages/settings/index.tsx index 39de815e5..aadb2ad55 100644 --- a/packages/uikit/src/pages/settings/index.tsx +++ b/packages/uikit/src/pages/settings/index.tsx @@ -14,7 +14,7 @@ import { Notifications } from './Notification'; import { ActiveRecovery, Recovery } from './Recovery'; import { SecuritySettings } from './Security'; import { Settings } from './Settings'; -import { WalletVersion } from './Version'; +import { WalletVersionPage } from './Version'; import { ConnectedAppsSettings } from './ConnectedAppsSettings'; import { NFTSettings } from './Nft'; @@ -31,7 +31,7 @@ const SettingsRouter = () => { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 779bf1a7d..c89df1b6d 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -195,6 +195,7 @@ export const useCreateAccountMnemonic = () => { export const useAddTonWalletVersionToActiveAccount = () => { const accountsStore = useAccountsStorage(); const account = useActiveAccount(); + const client = useQueryClient(); return useMutation< TonWalletStandard, @@ -216,10 +217,29 @@ export const useAddTonWalletVersionToActiveAccount = () => { account.addTonWalletToActiveDerivation(wallet); await accountsStore.updateAccountInState(account); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, account.id, wallet.id)); return wallet; }); }; +export const useRemoveTonWalletVersionFromActiveAccount = () => { + const accountsStore = useAccountsStorage(); + const account = useActiveAccount(); + const client = useQueryClient(); + + return useMutation< + void, + Error, + { + walletId: WalletId; + } + >(async ({ walletId }) => { + account.removeTonWalletFromActiveDerivation(walletId); + await accountsStore.updateAccountInState(account); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, account.id, walletId)); + }); +}; + export const useRenameTonWallet = () => { const accountsStore = useAccountsStorage(); const account = useActiveAccount(); From 90af3080b5a5326239c8ebb3c15f7935ab4d3f61 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 26 Jul 2024 13:52:14 +0200 Subject: [PATCH 12/53] fix: aside & aside header improvements --- packages/core/src/entries/wallet.ts | 25 ++++ .../components/desktop/aside/AsideHeader.tsx | 13 +- .../components/desktop/aside/AsideMenu.tsx | 136 ++++++++++-------- .../WalletVersionSettingsNotification.tsx | 24 ++-- .../components/modals/createModalControl.ts | 20 +++ .../uikit/src/components/shared/AsideItem.tsx | 3 +- packages/uikit/src/hooks/useIsHovered.ts | 15 ++ packages/uikit/src/pages/settings/Version.tsx | 47 +++--- 8 files changed, 187 insertions(+), 96 deletions(-) create mode 100644 packages/uikit/src/components/modals/createModalControl.ts create mode 100644 packages/uikit/src/hooks/useIsHovered.ts diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index d0afeb8a4..38f1d9baf 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -12,6 +12,29 @@ export enum WalletVersion { 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; }; @@ -24,6 +47,8 @@ export const WalletVersions = [ WalletVersion.V5R1 ]; +export const backwardCompatibilityOnlyWalletVersions = [WalletVersion.V5_BETA]; + export const walletVersionText = (version: WalletVersion) => { switch (version) { case WalletVersion.V3R1: diff --git a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx index 365ef786d..edc3b8c82 100644 --- a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import { FC, useRef, useState } from 'react'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; import { Body3, Label2 } from '../../Text'; -import { useActiveWallet } 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'; @@ -62,13 +62,14 @@ const DoneIconStyled = styled(DoneIcon)` export const AsideHeader: FC<{ width: number }> = ({ width }) => { const { t } = useTranslation(); - const wallet = useActiveWallet(); + 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.rawAddress) : ''; + const address = formatAddress(activeWallet.rawAddress); const timeoutRef = useRef | undefined>(undefined); @@ -96,10 +97,10 @@ 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 6b4e49019..fb2419af6 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -25,10 +25,16 @@ 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 { WalletId, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { + sortDerivationsByIndex, + sortWalletsByVersion, + WalletId, + walletVersionText +} 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'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -118,9 +124,14 @@ const Badge = styled.div` line-height: 12px; `; -const GearIconButtonStyled = styled(IconButtonTransparentBackground)` +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; `; const AccountBadge: FC<{ account: Account }> = ({ account }) => { @@ -161,6 +172,8 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( } }, [location.pathname]); + const { isHovered, ref } = useIsHovered(); + const onClickWallet = (walletId: WalletId) => setActiveWallet(walletId).then(handleNavigateHome); @@ -168,56 +181,55 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( return null; } - if (account.allTonWallets.length === 1) { - return ( - onClickWallet(account.activeTonWallet.id)} - > - {shouldShowIcon && ( - - )} - {account.name} - - - ); - } - if (account.type === 'mnemonic') { + const sortedWallets = account.tonWallets.slice().sort(sortWalletsByVersion); return ( <> onClickWallet(account.activeTonWallet.id)} + onClick={() => onClickWallet(sortedWallets[0].id)} + ref={ref} > {shouldShowIcon && ( )} {account.name} - + { + e.preventDefault(); + e.stopPropagation(); + openWalletVersionSettings({ account }); + }} + isShown={isHovered} + > - {account.tonWallets.map(wallet => ( - onClickWallet(wallet.id)} - > - {toShortValue(formatAddress(wallet.rawAddress, network))} - {walletVersionText(wallet.version)} - - ))} + {sortedWallets.length > 1 && + sortedWallets.map(wallet => ( + onClickWallet(wallet.id)} + > + + {toShortValue(formatAddress(wallet.rawAddress, network))} + + {walletVersionText(wallet.version)} + + ))} ); } if (account.type === 'ledger') { + const sortedDerivations = account.derivations.slice().sort(sortDerivationsByIndex); return ( <> onClickWallet(account.activeTonWallet.id)} + onClick={() => onClickWallet(sortedDerivations[0].activeTonWalletId)} + ref={ref} > {shouldShowIcon && ( @@ -225,26 +237,27 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {account.name} - {account.derivations.map(derivation => { - const wallet = derivation.tonWallets.find( - w => w.id === derivation.activeTonWalletId - )!; - - return ( - onClickWallet(derivation.activeTonWalletId)} - > - - {toShortValue(formatAddress(wallet.rawAddress, network))} - - {'#' + derivation.index} - - ); - })} + {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} + + ); + })} ); } @@ -255,6 +268,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( onClickWallet(account.activeTonWallet.id)} + ref={ref} > {shouldShowIcon && ( @@ -262,15 +276,18 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {account.name} - {account.tonWallets.map(wallet => ( - onClickWallet(wallet.id)} - > - {toShortValue(formatAddress(wallet.rawAddress, network))} - - ))} + {account.tonWallets.length > 1 && + account.tonWallets.map(wallet => ( + onClickWallet(wallet.id)} + > + + {toShortValue(formatAddress(wallet.rawAddress, network))} + + + ))} ); } @@ -280,6 +297,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( onClickWallet(account.activeTonWallet.id)} + ref={ref} > {shouldShowIcon && ( diff --git a/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx index a8a8ab941..a552b63cb 100644 --- a/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx +++ b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx @@ -1,28 +1,24 @@ import { Notification } from '../Notification'; -import { useCallback } from 'react'; import { WalletVersionPageContent } from '../../pages/settings/Version'; -import { atom, useAtom } from '../../libs/atom'; +import { useAtom } from '../../libs/atom'; import { useTranslation } from '../../hooks/translation'; +import { Account } from '@tonkeeper/core/dist/entries/account'; +import { createModalControl } from './createModalControl'; -const walletVersionSettingsNotificationIsOpen = atom(false); +const { hook, control } = createModalControl<{ account?: Account }>(); -export const useWalletVersionSettingsNotification = () => { - const [isOpen, setIsOpen] = useAtom(walletVersionSettingsNotificationIsOpen); - - return { - isOpen, - onOpen: useCallback(() => setIsOpen(true), []), - onClose: useCallback(() => setIsOpen(false), []) - }; -}; +export const useWalletVersionSettingsNotification = hook; export const WalletVersionSettingsNotification = () => { const { isOpen, onClose } = useWalletVersionSettingsNotification(); const { t } = useTranslation(); + const [params] = useAtom(control); return ( - onClose()}> - {() => } + onClose()}> + {() => ( + + )} ); }; diff --git a/packages/uikit/src/components/modals/createModalControl.ts b/packages/uikit/src/components/modals/createModalControl.ts new file mode 100644 index 000000000..d9732e6fa --- /dev/null +++ b/packages/uikit/src/components/modals/createModalControl.ts @@ -0,0 +1,20 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { atom, useAtom } from '../../libs/atom'; +import { useCallback } from 'react'; + +export const createModalControl = () => { + const control = atom(undefined); + + return { + hook: () => { + const [isOpen, setOpenParams] = useAtom(control); + + return { + isOpen, + onOpen: useCallback((params: T = {} as T) => setOpenParams(params), []), + onClose: useCallback(() => setOpenParams(undefined), []) + }; + }, + control + }; +}; 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/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/pages/settings/Version.tsx b/packages/uikit/src/pages/settings/Version.tsx index c195b416f..ba60cfe19 100644 --- a/packages/uikit/src/pages/settings/Version.tsx +++ b/packages/uikit/src/pages/settings/Version.tsx @@ -1,9 +1,11 @@ import { + backwardCompatibilityOnlyWalletVersions, WalletVersion as WalletVersionType, WalletVersions, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import { Account } from '@tonkeeper/core/dist/entries/account'; import React, { FC } from 'react'; import styled from 'styled-components'; import { InnerBody } from '../../components/Body'; @@ -13,7 +15,6 @@ import { useTranslation } from '../../hooks/translation'; import { useIsActiveWalletKeystone } from '../../state/keystone'; import { useIsActiveWalletLedger } from '../../state/ledger'; import { - useActiveStandardTonWallet, useStandardTonWalletVersions, useActiveAccount, useAddTonWalletVersionToActiveAccount, @@ -60,21 +61,24 @@ export const WalletVersionPage = () => { ); }; -export const WalletVersionPageContent: FC<{ afterWalletOpened?: () => void }> = ({ - afterWalletOpened -}) => { +export const WalletVersionPageContent: FC<{ + afterWalletOpened?: () => void; + account?: Account; +}> = ({ afterWalletOpened, account }) => { const { t } = useTranslation(); const isLedger = useIsActiveWalletLedger(); const isKeystone = useIsActiveWalletKeystone(); - const currentWallet = useActiveStandardTonWallet(); - const currentAccount = useActiveAccount(); - const currentAccountWalletsVersions = currentAccount.activeDerivationTonWallets; + const activeAccount = useActiveAccount(); + const selectedAccount = account || activeAccount; + const selectedWallet = selectedAccount.activeTonWallet; + const appActiveWallet = activeAccount.activeTonWallet; + const currentAccountWalletsVersions = selectedAccount.activeDerivationTonWallets; const { mutateAsync: selectWallet, isLoading: isSelectWalletLoading } = useMutateActiveTonWallet(); const navigate = useNavigate(); - const { data: wallets } = useStandardTonWalletVersions(currentWallet.publicKey); + const { data: wallets } = useStandardTonWalletVersions(selectedWallet.publicKey); const { mutate: createWallet, isLoading: isCreateWalletLoading } = useAddTonWalletVersionToActiveAccount(); @@ -83,7 +87,7 @@ export const WalletVersionPageContent: FC<{ afterWalletOpened?: () => void }> = useRemoveTonWalletVersionFromActiveAccount(); const onOpenWallet = async (address: Address) => { - if (address.toRawString() !== currentWallet.rawAddress) { + if (address.toRawString() !== appActiveWallet.rawAddress) { await selectWallet(address.toRawString()); } navigate(AppRoute.home); @@ -107,12 +111,21 @@ export const WalletVersionPageContent: FC<{ afterWalletOpened?: () => void }> = } const isLoading = isSelectWalletLoading || isCreateWalletLoading || isHideWalletLoading; + const canHide = currentAccountWalletsVersions.length > 1; + + const walletsToShow = wallets.filter( + w => + !backwardCompatibilityOnlyWalletVersions.includes(w.version) || + currentAccountWalletsVersions.some(item => item.version === w.version) || + w.tonBalance || + w.hasJettons + ); return ( <> {!isLedger && !isKeystone && ( - {wallets.map(wallet => { + {walletsToShow.map(wallet => { const isWalletAdded = currentAccountWalletsVersions.some( w => w.rawAddress === wallet.address.toRawString() ); @@ -137,12 +150,14 @@ export const WalletVersionPageContent: FC<{ afterWalletOpened?: () => void }> = > {t('open')} - + {canHide && ( + + )} ) : ( } @@ -98,82 +78,56 @@ const Create = () => { ); } - if (!check) { + if (!wordsPagePassed) { return ( setOpen(false)} - onCheck={() => setCheck(true)} + onBack={() => setInfoPagePassed(false)} + onCheck={() => setWordsPagePassed(true)} /> ); } - if (!checked) { + if (!createdAccount) { return ( setCheck(false)} - onConfirm={() => setChecked(true)} + onBack={() => setWordsPagePassed(false)} + onConfirm={() => { + createWalletsAsync({ + mnemonic, + versions: [defaultWalletVersion], + selectAccount: true + }).then(setCreatedAccount); + }} isLoading={isCreateWalletLoading} /> ); } - if (authExists) { - if (!account) { - return ( - setCheck(false)} - onConfirm={() => setChecked(true)} - isLoading={isCreateWalletLoading} - /> - ); - } - } else { - if (!checked) { - return ( - setCheck(false)} - onConfirm={() => setChecked(true)} - /> - ); - } - - if (!account) { - return ( - - ); - } - } - - if (existingWallets.length > 1 && !passName) { + if (!editNamePagePassed) { return ( { renameWallet({ - id: account.id, + id: createdAccount.id, ...val }).then(newAcc => { - setPassName(true); - setAccount(newAcc); + setEditNamePagePassed(true); + setCreatedAccount(newAcc); }); }} - walletEmoji={account.emoji} + walletEmoji={createdAccount.emoji} isLoading={renameLoading} /> ); } - 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 7d809dc59..75af66e71 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -1,16 +1,10 @@ -import React, { useEffect, 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 { FinalView } from './Password'; import { Subscribe } from './Subscribe'; -import { - useCreateAccountMnemonic, - useMutateRenameAccount, - useAccountsState -} from '../../state/wallet'; -import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { useCreateAccountMnemonic, useMutateRenameAccount } from '../../state/wallet'; import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions'; import { AccountTonMnemonic } from '@tonkeeper/core/dist/entries/account'; @@ -18,103 +12,65 @@ const Import = () => { const sdk = useAppSdk(); const [mnemonic, setMnemonic] = useState(); - const [account, setAccount] = useState(undefined); - const [selectedVersions, setSelectedVersions] = useState( - undefined - ); + const [createdAccount, setCreatedAccount] = useState(undefined); - const [createdPassword, setCreatedPassword] = useState(undefined); - const [passName, setPassName] = useState(false); - const [passNotifications, setPassNotification] = useState(false); - const existingWallets = useAccountsState(); + const [editNamePagePassed, setEditNamePagePassed] = useState(false); + const [notificationsSubscribePagePassed, setNotificationsSubscribePagePassed] = useState(false); const { mutateAsync: renameAccount, isLoading: renameLoading } = useMutateRenameAccount(); - const { - mutateAsync: createWalletsAsync, - isLoading: isCreatingWallets, - reset: resetCreateWallets - } = useCreateAccountMnemonic(); - - const authExists = createdPassword || existingWallets.length >= 1; - - useEffect(() => { - if (authExists && selectedVersions && mnemonic) { - createWalletsAsync({ - mnemonic, - password: createdPassword, - versions: selectedVersions, - selectAccount: true - }).then(setAccount); - } - - return resetCreateWallets; - }, [authExists, createdPassword, selectedVersions, mnemonic, createWalletsAsync]); + const { mutateAsync: createWalletsAsync, isLoading: isCreatingWallets } = + useCreateAccountMnemonic(); if (!mnemonic) { return ; } - if (authExists) { - if (!account) { - return ( - { - setAccount(undefined); - setMnemonic(undefined); - }} - isLoading={isCreatingWallets} - /> - ); - } - } else { - if (!selectedVersions) { - return ( - { - setAccount(undefined); - setMnemonic(undefined); - }} - /> - ); - } - - if (!account) { - return ( - - ); - } + if (!createdAccount) { + return ( + { + createWalletsAsync({ + mnemonic, + versions, + selectAccount: true + }).then(setCreatedAccount); + }} + onBack={() => { + setCreatedAccount(undefined); + setMnemonic(undefined); + }} + isLoading={isCreatingWallets} + /> + ); } - if (existingWallets.length > 1 && !passName) { + if (!editNamePagePassed) { return ( { renameAccount({ - id: account.id, + id: createdAccount.id, ...val }).then(newAcc => { - setPassName(true); - setAccount(newAcc); + setEditNamePagePassed(true); + setCreatedAccount(newAcc); }); }} - walletEmoji={account.emoji} + walletEmoji={createdAccount.emoji} isLoading={renameLoading} /> ); } - if (sdk.notifications && !passNotifications) { + if (sdk.notifications && !notificationsSubscribePagePassed) { return ( setPassNotification(true)} + onDone={() => setNotificationsSubscribePagePassed(true)} /> ); } diff --git a/packages/uikit/src/state/keystone.ts b/packages/uikit/src/state/keystone.ts index ab16d1c55..b70cd2677 100644 --- a/packages/uikit/src/state/keystone.ts +++ b/packages/uikit/src/state/keystone.ts @@ -16,13 +16,14 @@ export const usePairKeystoneMutation = () => { return useMutation(async ur => { try { - const state = await accountByKeystone(ur, sdk.storage); - const duplicatedWallet = await accountsStorage.getAccount(state.id); + const newAccount = await accountByKeystone(ur, sdk.storage); + const duplicatedWallet = await accountsStorage.getAccount(newAccount.id); if (duplicatedWallet) { throw new Error('Wallet already exist'); } - await accountsStorage.addAccountToState(state); + await accountsStorage.addAccountToState(newAccount); + await accountsStorage.setActiveAccountId(newAccount.id); await client.invalidateQueries([QueryKey.account]); diff --git a/packages/uikit/src/state/ledger.ts b/packages/uikit/src/state/ledger.ts index 5167b700f..81347a717 100644 --- a/packages/uikit/src/state/ledger.ts +++ b/packages/uikit/src/state/ledger.ts @@ -157,9 +157,10 @@ export const useAddLedgerAccountMutation = () => { { accountId: string; wallets: LedgerAccount[]; name: string; emoji: string } >(async form => { try { - const state = accountByLedger(form.accountId, form.wallets, form.name, form.emoji); + const newAccount = accountByLedger(form.accountId, form.wallets, form.name, form.emoji); - await accStorage.addAccountToState(state); + await accStorage.addAccountToState(newAccount); + await accStorage.setActiveAccountId(newAccount.id); await client.invalidateQueries([QueryKey.account]); diff --git a/packages/uikit/src/state/signer.ts b/packages/uikit/src/state/signer.ts index 149ce72de..10229bfba 100644 --- a/packages/uikit/src/state/signer.ts +++ b/packages/uikit/src/state/signer.ts @@ -15,9 +15,10 @@ export const usePairSignerMutation = () => { const navigate = useNavigate(); return useMutation(async qrCode => { try { - const state = await accountBySignerQr(context, sdk.storage, qrCode); + const newAccount = await accountBySignerQr(context, sdk.storage, qrCode); - await accountsStorage.addAccountToState(state); + await accountsStorage.addAccountToState(newAccount); + await accountsStorage.setActiveAccountId(newAccount.id); await client.invalidateQueries([QueryKey.account]); From 0cb6da1006c6768238672326359fc6fad8414b6d Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 09:41:53 +0200 Subject: [PATCH 22/53] fix: added signer versions change setting for all platforms. Style scrollbar on windows and linux --- .../uikit/src/components/ScrollContainer.tsx | 30 +++++++++++++++++++ .../components/desktop/aside/AsideMenu.tsx | 15 +++++++--- .../components/settings/AccountSettings.tsx | 7 +++-- .../settings/DesktopWalletSettingsPage.tsx | 2 +- packages/uikit/src/libs/web.ts | 13 ++++++++ .../uikit/src/providers/UserThemeProvider.tsx | 3 ++ packages/uikit/src/styles/defaultTheme.ts | 3 +- packages/uikit/src/styles/lightTheme.ts | 3 +- packages/uikit/src/styles/proTheme.ts | 3 +- packages/uikit/src/types/theme.d.ts | 1 + 10 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 packages/uikit/src/components/ScrollContainer.tsx create mode 100644 packages/uikit/src/libs/web.ts diff --git a/packages/uikit/src/components/ScrollContainer.tsx b/packages/uikit/src/components/ScrollContainer.tsx new file mode 100644 index 000000000..f240d3641 --- /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.desktopOs === 'windows' || p.theme.desktopOs === '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/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 71343a1d2..4fb301148 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -35,6 +35,7 @@ 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'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -70,10 +71,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; @@ -275,6 +272,16 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} + { + e.preventDefault(); + e.stopPropagation(); + openWalletVersionSettings({ accountId: account.id }); + }} + isShown={isHovered} + > + + {account.tonWallets.length > 1 && account.tonWallets.map(wallet => ( diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index ef7c3fa07..557f0377a 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -1,4 +1,4 @@ -import { isStandardTonWallet, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAppContext } from '../../hooks/appContext'; @@ -45,7 +45,7 @@ const SingleAccountSettings = () => { }); }*/ - if (account.type === 'mnemonic') { + if (account.type === 'mnemonic' || account.type === 'ton-only') { items.push({ name: t('settings_wallet_version'), icon: walletVersionText(wallet.version), @@ -115,6 +115,7 @@ const MultipleAccountSettings = () => { const { data: jettons } = useJettonList(); const { data: nft } = useWalletNftList(); const { proFeatures } = useAppContext(); + const account = useActiveAccount(); const accountItems = useMemo(() => { const items: SettingsItem[] = [ @@ -152,7 +153,7 @@ const MultipleAccountSettings = () => { }); }*/ - if (isStandardTonWallet(wallet)) { + if (account.type === 'mnemonic' || account.type === 'ton-only') { items.push({ name: t('settings_wallet_version'), icon: walletVersionText(wallet.version), diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index 56cb37f8f..9353a5610 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -59,7 +59,7 @@ export const DesktopWalletSettingsPage = () => { const { isOpen: isRenameOpen, onClose: onRenameClose, onOpen: onRenameOpen } = useDisclosure(); const { isOpen: isLogoutOpen, onClose: onLogoutClose, onOpen: onLogoutOpen } = useDisclosure(); - const canChangeVersion = account.type === 'mnemonic'; + const canChangeVersion = account.type === 'mnemonic' || account.type === 'ton-only'; const activeWallet = account.activeTonWallet; return ( diff --git a/packages/uikit/src/libs/web.ts b/packages/uikit/src/libs/web.ts new file mode 100644 index 000000000..d8e02c17c --- /dev/null +++ b/packages/uikit/src/libs/web.ts @@ -0,0 +1,13 @@ +export function getUserDesktopOS() { + if (navigator.userAgent.includes('Win')) { + return 'windows'; + } + if (navigator.userAgent.includes('Mac')) { + return 'mac'; + } + if (navigator.userAgent.includes('Linux')) { + return 'linux'; + } + + return undefined; +} diff --git a/packages/uikit/src/providers/UserThemeProvider.tsx b/packages/uikit/src/providers/UserThemeProvider.tsx index 8b37dc869..dc47a76c6 100644 --- a/packages/uikit/src/providers/UserThemeProvider.tsx +++ b/packages/uikit/src/providers/UserThemeProvider.tsx @@ -2,6 +2,7 @@ import { FC, PropsWithChildren, useEffect, useMemo } from 'react'; import { DefaultTheme, ThemeProvider } from 'styled-components'; import { availableThemes, useMutateUserUIPreferences, useUserUIPreferences } from '../state/theme'; import { usePrevious } from '../hooks/usePrevious'; +import { getUserDesktopOS } from '../libs/web'; export const UserThemeProvider: FC< PropsWithChildren<{ displayType?: 'compact' | 'full-width'; @@ -36,6 +37,8 @@ export const UserThemeProvider: FC< theme.displayType = displayType; } + theme.desktopOs = getUserDesktopOS(); + window.document.body.style.background = theme.backgroundPage; return [theme, themeName]; diff --git a/packages/uikit/src/styles/defaultTheme.ts b/packages/uikit/src/styles/defaultTheme.ts index e47ccf07c..e822c75c3 100644 --- a/packages/uikit/src/styles/defaultTheme.ts +++ b/packages/uikit/src/styles/defaultTheme.ts @@ -88,5 +88,6 @@ export const defaultTheme: DefaultTheme = { cornerLarge: '24px', cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', - displayType: 'compact' + displayType: 'compact', + desktopOs: undefined }; diff --git a/packages/uikit/src/styles/lightTheme.ts b/packages/uikit/src/styles/lightTheme.ts index 0e0291855..fa09c7b75 100644 --- a/packages/uikit/src/styles/lightTheme.ts +++ b/packages/uikit/src/styles/lightTheme.ts @@ -86,5 +86,6 @@ export const lightTheme: DefaultTheme = { cornerLarge: '24px', cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', - displayType: 'compact' + displayType: 'compact', + desktopOs: undefined }; diff --git a/packages/uikit/src/styles/proTheme.ts b/packages/uikit/src/styles/proTheme.ts index 54b6e5c73..4649cdaf1 100644 --- a/packages/uikit/src/styles/proTheme.ts +++ b/packages/uikit/src/styles/proTheme.ts @@ -88,5 +88,6 @@ export const proTheme: DefaultTheme = { cornerLarge: '24px', cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', - displayType: 'compact' + displayType: 'compact', + desktopOs: undefined }; diff --git a/packages/uikit/src/types/theme.d.ts b/packages/uikit/src/types/theme.d.ts index eb4a30b71..e54ed9109 100644 --- a/packages/uikit/src/types/theme.d.ts +++ b/packages/uikit/src/types/theme.d.ts @@ -88,5 +88,6 @@ declare module 'styled-components' { * 'compact' is default mode and 'full-width' is for desktop 'pro' mode */ displayType: 'compact' | 'full-width'; + desktopOs: 'mac' | 'windows' | 'linux' | undefined; } } From 3fd5383ad644fbaefc59e7c317be458081e95130 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 11:58:35 +0200 Subject: [PATCH 23/53] fix: update badges --- .../uikit/src/components/AccountBadge.tsx | 73 ++++++++++++++++++ packages/uikit/src/components/Header.tsx | 37 +-------- .../components/desktop/aside/AsideMenu.tsx | 51 +++++------- .../uikit/src/components/home/Balance.tsx | 77 +++++++++++-------- .../uikit/src/components/shared/Badge.tsx | 54 ++++++++++--- 5 files changed, 185 insertions(+), 107 deletions(-) create mode 100644 packages/uikit/src/components/AccountBadge.tsx diff --git a/packages/uikit/src/components/AccountBadge.tsx b/packages/uikit/src/components/AccountBadge.tsx new file mode 100644 index 000000000..29856b997 --- /dev/null +++ b/packages/uikit/src/components/AccountBadge.tsx @@ -0,0 +1,73 @@ +import { FC, PropsWithChildren } from 'react'; +import { Account } from '@tonkeeper/core/dist/entries/account'; +import { Badge } from './shared'; +import { WalletVersion, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; + +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} + + ); +}; diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index d4080ab99..541586b33 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -23,6 +23,7 @@ import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; import { TonWalletStandard, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { Account } from '@tonkeeper/core/dist/entries/account'; +import { WalletIndexBadge } from './AccountBadge'; const Block = styled.div<{ center?: boolean; @@ -121,19 +122,6 @@ const Row = styled.div` } `; -const Badge = styled.div` - padding: 2px 4px; - margin-left: -4px; - height: fit-content; - background: ${p => p.theme.backgroundContentAttention}; - border-radius: 3px; - color: ${p => p.theme.textSecondary}; - font-size: 9px; - font-style: normal; - font-weight: 510; - line-height: 12px; -`; - const ListItemPayloadStyled = styled(ListItemPayload)` justify-content: flex-start; `; @@ -170,7 +158,7 @@ const WalletRow: FC<{ - {badge && {badge}} + {badge && {badge}} {activeWallet?.id === walletState.id ? ( @@ -195,7 +183,7 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ ({ wallet: d.tonWallets.find(w => w.id === d.activeTonWalletId)!, account: a, - badge: `LEDGER #${d.index}` + badge: a.derivations.length > 1 ? `Ledger #${d.index}` : undefined } as { wallet: TonWalletStandard; account: Account; badge?: string }) ); } @@ -205,14 +193,7 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ ({ wallet: w, account: a, - badge: - a.type === 'ton-only' - ? 'SIGNER' - : a.type === 'keystone' - ? 'KEYSTONE' - : a.allTonWallets.length > 1 - ? walletVersionText(w.version) - : undefined + badge: a.allTonWallets.length > 1 ? walletVersionText(w.version) : undefined } as { wallet: TonWalletStandard; account: Account; badge?: string }) ); }); @@ -275,15 +256,6 @@ export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => { const wallets = useAccountsState(); const shouldShowIcon = wallets.length > 1; - const accountBadge = - account.allTonWallets.length === 1 - ? undefined - : account.type === 'ledger' - ? `LEDGER #${account.activeDerivation.index}` - : account.type === 'mnemonic' - ? walletVersionText(account.activeTonWallet.version) - : undefined; - return ( @@ -297,7 +269,6 @@ export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => { {shouldShowIcon && } {account.name} - {accountBadge && {accountBadge}} diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 4fb301148..9d4ade1f0 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -28,14 +28,14 @@ import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { sortDerivationsByIndex, sortWalletsByVersion, - WalletId, - walletVersionText + 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 '../../AccountBadge'; const AsideContainer = styled.div<{ width: number }>` display: flex; @@ -109,16 +109,16 @@ const AsideMenuSubItem = styled(AsideMenuItem)` padding-left: 36px; `; -const Badge = styled.div` - padding: 2px 4px; +const AccountBadgeStyled = styled(AccountBadge)` + margin-left: -4px; +`; + +const WalletVersionBadgeStyled = styled(WalletVersionBadge)` + margin-left: -4px; +`; + +const WalletIndexBadgeStyled = styled(WalletIndexBadge)` margin-left: -4px; - background: ${p => p.theme.backgroundContentAttention}; - border-radius: 3px; - color: ${p => p.theme.textSecondary}; - font-size: 9px; - font-style: normal; - font-weight: 510; - line-height: 12px; `; const GearIconButtonStyled = styled(IconButtonTransparentBackground)<{ isShown: boolean }>` @@ -131,22 +131,6 @@ const GearIconButtonStyled = styled(IconButtonTransparentBackground)<{ isShown: transition: opacity 0.15s ease-in-out; `; -const AccountBadge: FC<{ account: Account }> = ({ account }) => { - if (account.type === 'ledger') { - return LEDGER; - } - - if (account.type === 'ton-only') { - return SIGNER; - } - - if (account.type === 'keystone') { - return KEYSTONE; - } - - return null; -}; - export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ({ account, isSelected @@ -212,7 +196,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {toShortValue(formatAddress(wallet.rawAddress, network))} - {walletVersionText(wallet.version)} + ))} @@ -232,7 +216,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} - + {sortedDerivations.length > 1 && sortedDerivations.map(derivation => { @@ -251,7 +235,9 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {toShortValue(formatAddress(wallet.rawAddress, network))} - {'#' + derivation.index} + + {'#' + derivation.index} + ); })} @@ -271,7 +257,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} - + { e.preventDefault(); @@ -293,6 +279,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {toShortValue(formatAddress(wallet.rawAddress, network))} + ))} @@ -310,7 +297,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} - + ); } diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index 5ea5c86b3..ea4473cf7 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -8,10 +8,11 @@ import { formatFiatCurrency } from '../../hooks/balance'; import { QueryKey } from '../../libs/queryKey'; import { useActiveAccount, useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; import { Body3, Label2, Num2 } from '../Text'; -import { Badge } from '../shared'; import { SkeletonText } from '../shared/Skeleton'; import { AssetData } from './Jettons'; import { useWalletTotalBalance } from '../../state/asset'; +import { AccountBadge, WalletVersionBadge } from '../AccountBadge'; +import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; const Block = styled.div` display: flex; @@ -24,6 +25,12 @@ const Body = styled(Label2)` color: ${props => props.theme.textSecondary}; user-select: none; display: flex; + cursor: pointer; + + transition: transform 0.2s ease; + &:active { + transform: scale(0.97); + } `; const Amount = styled(Num2)` @@ -67,40 +74,48 @@ export const BalanceSkeleton = () => { ); }; +const AccountBadgeStyled = styled(AccountBadge)` + display: inline-block; + margin-left: 3px; +`; + +const WalletVersionBadgeStyled = styled(WalletVersionBadge)` + display: inline-block; + margin-left: 3px; +`; + const Label = () => { const account = useActiveAccount(); - switch (account.type) { - case 'ton-only': - return ( - <> - {' '} - - Signer - - - ); - case 'ledger': - return ( - <> - {' '} - - Ledger - - - ); - case 'keystone': - return ( - <> - {' '} - - Keystone - - - ); - default: - return <>; + if (account.type === 'ledger') { + return ( + + {account.derivations.length > 1 + ? `Ledger #${account.activeDerivationIndex}` + : 'Ledger'} + + ); } + + if (account.type === 'ton-only') { + return ( + + {account.tonWallets.length > 1 + ? `Signer ${walletVersionText(account.activeTonWallet.version)}` + : 'Signer'} + + ); + } + + if (account.type === 'keystone') { + return ; + } + + if (account.type === 'mnemonic' && account.tonWallets.length > 1) { + return ; + } + + return null; }; export const Balance: FC<{ 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} ); From 1146004757c7bbc024d5eed05ae793fabce2caff Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 12:00:27 +0200 Subject: [PATCH 24/53] fix: add address copied toast when copy address on click on main screen --- packages/uikit/src/components/home/Balance.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index ea4473cf7..eefa095b8 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -13,6 +13,7 @@ import { AssetData } from './Jettons'; import { useWalletTotalBalance } from '../../state/asset'; import { AccountBadge, WalletVersionBadge } from '../AccountBadge'; import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; +import { useTranslation } from '../../hooks/translation'; const Block = styled.div` display: flex; @@ -123,6 +124,7 @@ export const Balance: FC<{ isFetching: boolean; assets: AssetData; }> = ({ error, isFetching }) => { + const { t } = useTranslation(); const sdk = useAppSdk(); const { fiat } = useAppContext(); const wallet = useActiveWallet(); @@ -149,7 +151,7 @@ export const Balance: FC<{ {formatFiatCurrency(fiat, total || 0)} - sdk.copyToClipboard(address)}> + sdk.copyToClipboard(address, t('address_copied'))}> {toShortValue(address)} ); diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 37946f12e..69355a30a 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -36,6 +36,7 @@ import { AccountId, AccountsState, AccountTonMnemonic, + getAccountByWalletById, getWalletById } from '@tonkeeper/core/dist/entries/account'; @@ -88,7 +89,7 @@ export const useMutateActiveTonWallet = () => { const client = useQueryClient(); return useMutation(async walletId => { const accounts = await storage.getAccounts(); - const account = accounts.find(a => !!a.getTonWallet(walletId)); + const account = getAccountByWalletById(accounts, walletId); if (!account) { throw new Error('Account not found'); From 34431d055a24a1018a39d36df7c109edf986e3c3 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 16:15:17 +0200 Subject: [PATCH 29/53] fix: update ton connect notification -- write full wallet and account info --- packages/core/src/utils/types.ts | 2 + packages/uikit/src/components/Header.tsx | 2 +- .../account/AccountAndWalletInfo.tsx | 63 +++++++++++++++++++ .../components/{ => account}/AccountBadge.tsx | 2 +- .../connect/TonConnectNotification.tsx | 21 ++----- .../connect/TonTransactionNotification.tsx | 51 +-------------- .../components/desktop/aside/AsideHeader.tsx | 2 +- .../components/desktop/aside/AsideMenu.tsx | 2 +- .../uikit/src/components/home/Balance.tsx | 2 +- 9 files changed, 79 insertions(+), 68 deletions(-) create mode 100644 packages/uikit/src/components/account/AccountAndWalletInfo.tsx rename packages/uikit/src/components/{ => account}/AccountBadge.tsx (99%) 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/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index fa8ce84a2..db50000f3 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -23,7 +23,7 @@ import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; import { Account } from '@tonkeeper/core/dist/entries/account'; -import { AccountAndWalletBadgesGroup } from './AccountBadge'; +import { AccountAndWalletBadgesGroup } from './account/AccountBadge'; const Block = styled.div<{ center?: boolean; diff --git a/packages/uikit/src/components/account/AccountAndWalletInfo.tsx b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx new file mode 100644 index 000000000..7446f6a52 --- /dev/null +++ b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx @@ -0,0 +1,63 @@ +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 { AllOrNone } from '@tonkeeper/core/dist/utils/types'; + +const WalletInfoStyled = styled.div` + display: flex; + align-items: center; + gap: 4px; + color: ${p => p.theme.textSecondary}; + + > ${Body2} { + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const Body2Tertiary = styled(Body2)` + 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/AccountBadge.tsx b/packages/uikit/src/components/account/AccountBadge.tsx similarity index 99% rename from packages/uikit/src/components/AccountBadge.tsx rename to packages/uikit/src/components/account/AccountBadge.tsx index 6f82032d3..e40196178 100644 --- a/packages/uikit/src/components/AccountBadge.tsx +++ b/packages/uikit/src/components/account/AccountBadge.tsx @@ -1,6 +1,6 @@ import { FC, PropsWithChildren } from 'react'; import { Account } from '@tonkeeper/core/dist/entries/account'; -import { Badge } from './shared'; +import { Badge } from '../shared'; import { WalletId, WalletVersion, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import styled from 'styled-components'; diff --git a/packages/uikit/src/components/connect/TonConnectNotification.tsx b/packages/uikit/src/components/connect/TonConnectNotification.tsx index 76194f397..7135ddcc6 100644 --- a/packages/uikit/src/components/connect/TonConnectNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx @@ -4,9 +4,7 @@ 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 { useAppSdk } from '../../hooks/appSdk'; @@ -20,15 +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 { useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; +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 = useActiveWallet(); - const { t } = useTranslation(); useEffect(() => { @@ -109,9 +103,6 @@ const ConnectContent: FC<{ } }; - const network = useActiveTonNetwork(); - const address = formatAddress(wallet.rawAddress, network); - let shortUrl = manifest.url; try { shortUrl = new URL(manifest.url).hostname; @@ -133,7 +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.version)} +
diff --git a/packages/uikit/src/components/connect/TonTransactionNotification.tsx b/packages/uikit/src/components/connect/TonTransactionNotification.tsx index 1444cf1eb..28cb51330 100644 --- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx +++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx @@ -26,16 +26,13 @@ import { NotificationTitleRow } from '../Notification'; import { SkeletonListWithImages } from '../Skeleton'; -import { Body2, H2, Label2, Label3 } from '../Text'; +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 { Dot } from '../Dot'; -import { AccountAndWalletBadgesGroup } from '../AccountBadge'; -import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import { AccountAndWalletInfo } from '../account/AccountAndWalletInfo'; const ButtonGap = styled.div` ${props => @@ -272,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 account = useActiveAccount(); const { t } = useTranslation(); return ( @@ -294,36 +278,7 @@ const NotificationTitleWithWalletName: FC<{ onClose: () => void }> = ({ onClose
{t('txActions_signRaw_title')} - - - {t('confirmSendModal_wallet')}  - {account.name} - - - {account.allTonWallets.length > 1 ? ( - <> - - - {toShortValue( - formatAddress(account.activeTonWallet.rawAddress) - )} - - - - ) : ( - - )} - +
diff --git a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx index b18ba0613..21a2e03af 100644 --- a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx @@ -9,7 +9,7 @@ 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 '../../AccountBadge'; +import { AccountAndWalletBadgesGroup } from '../../account/AccountBadge'; const HeaderContainer = styled.div<{ width: number }>` box-sizing: border-box; diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 9d4ade1f0..a644f91bc 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -35,7 +35,7 @@ import { IconButtonTransparentBackground } from '../../fields/IconButton'; import { useWalletVersionSettingsNotification } from '../../modals/WalletVersionSettingsNotification'; import { useIsHovered } from '../../../hooks/useIsHovered'; import { ScrollContainer } from '../../ScrollContainer'; -import { AccountBadge, WalletIndexBadge, WalletVersionBadge } from '../../AccountBadge'; +import { AccountBadge, WalletIndexBadge, WalletVersionBadge } from '../../account/AccountBadge'; const AsideContainer = styled.div<{ width: number }>` display: flex; diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index 3cda3f55f..ed4b192f2 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -12,7 +12,7 @@ import { SkeletonText } from '../shared/Skeleton'; import { AssetData } from './Jettons'; import { useWalletTotalBalance } from '../../state/asset'; import { useTranslation } from '../../hooks/translation'; -import { AccountAndWalletBadgesGroup } from '../AccountBadge'; +import { AccountAndWalletBadgesGroup } from '../account/AccountBadge'; const Block = styled.div` display: flex; From 150ac14b5eeefaca6c48108c557acada844f076a Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 16:22:30 +0200 Subject: [PATCH 30/53] fix: sort signer wallets in aside menu --- packages/uikit/src/components/desktop/aside/AsideMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index a644f91bc..c27adf527 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -246,6 +246,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( } if (account.type === 'ton-only') { + const sortedWallets = account.tonWallets.slice().sort(sortWalletsByVersion); return ( <> = (
- {account.tonWallets.length > 1 && - account.tonWallets.map(wallet => ( + {sortedWallets.length > 1 && + sortedWallets.map(wallet => ( Date: Wed, 31 Jul 2024 16:35:06 +0200 Subject: [PATCH 31/53] fix: fix recovery navigation --- packages/uikit/src/components/BackButton.tsx | 7 +++- .../account/AccountAndWalletInfo.tsx | 2 +- .../settings/DesktopWalletSettingsPage.tsx | 14 ++++--- .../settings/DesktopWalletSettingsRouting.tsx | 2 +- .../uikit/src/pages/settings/Recovery.tsx | 42 ++++++++++++------- .../uikit/src/pages/settings/Security.tsx | 4 +- packages/uikit/src/pages/settings/index.tsx | 2 +- 7 files changed, 46 insertions(+), 27 deletions(-) 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/account/AccountAndWalletInfo.tsx b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx index 7446f6a52..5ee584918 100644 --- a/packages/uikit/src/components/account/AccountAndWalletInfo.tsx +++ b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx @@ -9,7 +9,7 @@ import { TonWalletStandard, WalletId } from '@tonkeeper/core/dist/entries/wallet import { AccountAndWalletBadgesGroup } from './AccountBadge'; import { useTranslation } from '../../hooks/translation'; import styled from 'styled-components'; -import { AllOrNone } from '@tonkeeper/core/dist/utils/types'; +import type { AllOrNone } from '@tonkeeper/core/dist/utils/types'; const WalletInfoStyled = styled.div` display: flex; diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index 9353a5610..2aa43d0a3 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -78,12 +78,14 @@ export const DesktopWalletSettingsPage = () => { - - - - {t('settings_backup_seed')} - - + {account.type === 'mnemonic' && ( + + + + {t('settings_backup_seed')} + + + )} {canChangeVersion && ( diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx index 1ba6de7f1..b74b87436 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx @@ -26,7 +26,7 @@ export const DesktopWalletSettingsRouting = () => { }> - } /> + } /> } /> } /> diff --git a/packages/uikit/src/pages/settings/Recovery.tsx b/packages/uikit/src/pages/settings/Recovery.tsx index b52981697..bc9245774 100644 --- a/packages/uikit/src/pages/settings/Recovery.tsx +++ b/packages/uikit/src/pages/settings/Recovery.tsx @@ -1,6 +1,6 @@ import React, { FC, useEffect, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import styled from 'styled-components'; +import { Navigate, useNavigate, useParams } from 'react-router-dom'; +import styled, { css } from 'styled-components'; import { BackButtonBlock } from '../../components/BackButton'; import { Body1, Body2, H2 } from '../../components/Text'; import { WorldNumber, WorldsGrid } from '../../components/create/Words'; @@ -8,24 +8,28 @@ import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { getMnemonic } from '../../state/mnemonic'; import { useCheckTouchId } from '../../state/password'; -import { WalletId } from '@tonkeeper/core/dist/entries/wallet'; -import { useActiveWallet } from '../../state/wallet'; +import { useActiveAccount } from '../../state/wallet'; +import { AccountId } from '@tonkeeper/core/dist/entries/account'; export const ActiveRecovery = () => { - const wallet = useActiveWallet(); - return ; + const account = useActiveAccount(); + if (account.type === 'mnemonic') { + return ; + } else { + return ; + } }; export const Recovery = () => { - const { walletId } = useParams(); - if (walletId) { - return ; + const { accountId } = useParams(); + if (accountId) { + return ; } else { return ; } }; -const useMnemonic = (walletId: string) => { +const useMnemonic = (accountId: AccountId) => { const [mnemonic, setMnemonic] = useState(undefined); const sdk = useAppSdk(); const navigate = useNavigate(); @@ -34,12 +38,12 @@ const useMnemonic = (walletId: string) => { useEffect(() => { (async () => { try { - setMnemonic(await getMnemonic(sdk, walletId, checkTouchId)); + setMnemonic(await getMnemonic(sdk, accountId, checkTouchId)); } catch (e) { navigate(-1); } })(); - }, [walletId, checkTouchId]); + }, [accountId, checkTouchId]); return mnemonic; }; @@ -73,10 +77,18 @@ const Body = styled(Body2)` user-select: none; `; -const RecoveryContent: FC<{ walletId: WalletId }> = ({ walletId }) => { +const BackButtonBlockStyled = styled(BackButtonBlock)` + ${p => + p.theme.displayType === 'full-width' && + css` + margin-top: -64px; + `} +`; + +const RecoveryContent: FC<{ accountId: AccountId }> = ({ accountId }) => { const { t } = useTranslation(); const navigate = useNavigate(); - const mnemonic = useMnemonic(walletId); + const mnemonic = useMnemonic(accountId); const onBack = () => { navigate(-1); @@ -88,7 +100,7 @@ const RecoveryContent: FC<{ walletId: WalletId }> = ({ walletId }) => { return ( - + {t('secret_words_title')} {t('secret_words_caption')} diff --git a/packages/uikit/src/pages/settings/Security.tsx b/packages/uikit/src/pages/settings/Security.tsx index 5790d8e3e..157e534e4 100644 --- a/packages/uikit/src/pages/settings/Security.tsx +++ b/packages/uikit/src/pages/settings/Security.tsx @@ -20,6 +20,7 @@ import { useTouchIdEnabled } from '../../state/password'; import { useIsPasswordSet } from '../../state/wallet'; +import { useIsFullWidthMode } from '../../hooks/useIsFullWidthMode'; const LockSwitch = () => { const { t } = useTranslation(); @@ -104,6 +105,7 @@ const ShowPhrases = () => { const isLedger = useIsActiveWalletLedger(); const isKeystone = useIsActiveWalletKeystone(); + const isFullWidthMode = useIsFullWidthMode(); const items = useMemo(() => { const i: SettingsItem[] = [ @@ -116,7 +118,7 @@ const ShowPhrases = () => { return i; }, []); - if (isLedger || isKeystone) { + if (isLedger || isKeystone || isFullWidthMode) { return <>; } diff --git a/packages/uikit/src/pages/settings/index.tsx b/packages/uikit/src/pages/settings/index.tsx index aadb2ad55..2ea611441 100644 --- a/packages/uikit/src/pages/settings/index.tsx +++ b/packages/uikit/src/pages/settings/index.tsx @@ -28,7 +28,7 @@ const SettingsRouter = () => { } /> } /> - } /> + } /> } /> } /> From ef962b490f2114938e84525c8fdbb5d1617bcd65 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 16:42:51 +0200 Subject: [PATCH 32/53] fix: fix keystone name and color --- packages/core/src/service/walletService.ts | 6 +++--- packages/uikit/src/components/account/AccountBadge.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 799f18038..973a01089 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -296,16 +296,16 @@ export const accountByKeystone = async (ur: UR, storage: IStorage): Promise + {children || 'Keystone'} ); From 693e9749a96961cb698286b50624c423b155842f Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 17:55:53 +0200 Subject: [PATCH 33/53] fix: add ios and android values to `theme.os` --- packages/uikit/src/components/ScrollContainer.tsx | 2 +- packages/uikit/src/libs/web.ts | 12 +++++++++++- packages/uikit/src/providers/UserThemeProvider.tsx | 4 ++-- packages/uikit/src/styles/defaultTheme.ts | 2 +- packages/uikit/src/styles/lightTheme.ts | 2 +- packages/uikit/src/styles/proTheme.ts | 2 +- packages/uikit/src/types/theme.d.ts | 2 +- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/uikit/src/components/ScrollContainer.tsx b/packages/uikit/src/components/ScrollContainer.tsx index f240d3641..0c9ea51b3 100644 --- a/packages/uikit/src/components/ScrollContainer.tsx +++ b/packages/uikit/src/components/ScrollContainer.tsx @@ -4,7 +4,7 @@ export const ScrollContainer = styled.div<{ thumbColor?: string }>` overflow: auto; ${p => - (p.theme.desktopOs === 'windows' || p.theme.desktopOs === 'linux') && + (p.theme.os === 'windows' || p.theme.os === 'linux') && css` &:hover { &::-webkit-scrollbar-thumb { diff --git a/packages/uikit/src/libs/web.ts b/packages/uikit/src/libs/web.ts index d8e02c17c..ba4472eb9 100644 --- a/packages/uikit/src/libs/web.ts +++ b/packages/uikit/src/libs/web.ts @@ -1,4 +1,4 @@ -export function getUserDesktopOS() { +export function getUserOS() { if (navigator.userAgent.includes('Win')) { return 'windows'; } @@ -8,6 +8,16 @@ export function getUserDesktopOS() { 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/providers/UserThemeProvider.tsx b/packages/uikit/src/providers/UserThemeProvider.tsx index dc47a76c6..e5be6c5f6 100644 --- a/packages/uikit/src/providers/UserThemeProvider.tsx +++ b/packages/uikit/src/providers/UserThemeProvider.tsx @@ -2,7 +2,7 @@ import { FC, PropsWithChildren, useEffect, useMemo } from 'react'; import { DefaultTheme, ThemeProvider } from 'styled-components'; import { availableThemes, useMutateUserUIPreferences, useUserUIPreferences } from '../state/theme'; import { usePrevious } from '../hooks/usePrevious'; -import { getUserDesktopOS } from '../libs/web'; +import { getUserOS } from '../libs/web'; export const UserThemeProvider: FC< PropsWithChildren<{ displayType?: 'compact' | 'full-width'; @@ -37,7 +37,7 @@ export const UserThemeProvider: FC< theme.displayType = displayType; } - theme.desktopOs = getUserDesktopOS(); + theme.os = getUserOS(); window.document.body.style.background = theme.backgroundPage; diff --git a/packages/uikit/src/styles/defaultTheme.ts b/packages/uikit/src/styles/defaultTheme.ts index e822c75c3..0e17f5d76 100644 --- a/packages/uikit/src/styles/defaultTheme.ts +++ b/packages/uikit/src/styles/defaultTheme.ts @@ -89,5 +89,5 @@ export const defaultTheme: DefaultTheme = { cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', displayType: 'compact', - desktopOs: undefined + os: undefined }; diff --git a/packages/uikit/src/styles/lightTheme.ts b/packages/uikit/src/styles/lightTheme.ts index fa09c7b75..ec3977ca1 100644 --- a/packages/uikit/src/styles/lightTheme.ts +++ b/packages/uikit/src/styles/lightTheme.ts @@ -87,5 +87,5 @@ export const lightTheme: DefaultTheme = { cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', displayType: 'compact', - desktopOs: undefined + os: undefined }; diff --git a/packages/uikit/src/styles/proTheme.ts b/packages/uikit/src/styles/proTheme.ts index 4649cdaf1..30da3fab3 100644 --- a/packages/uikit/src/styles/proTheme.ts +++ b/packages/uikit/src/styles/proTheme.ts @@ -89,5 +89,5 @@ export const proTheme: DefaultTheme = { cornerFull: '100%', fontMono: 'ui-monospace, SF Mono, monospace, Roboto Mono, Menlo, Consolas, Courier', displayType: 'compact', - desktopOs: undefined + os: undefined }; diff --git a/packages/uikit/src/types/theme.d.ts b/packages/uikit/src/types/theme.d.ts index e54ed9109..d12664961 100644 --- a/packages/uikit/src/types/theme.d.ts +++ b/packages/uikit/src/types/theme.d.ts @@ -88,6 +88,6 @@ declare module 'styled-components' { * 'compact' is default mode and 'full-width' is for desktop 'pro' mode */ displayType: 'compact' | 'full-width'; - desktopOs: 'mac' | 'windows' | 'linux' | undefined; + os: 'mac' | 'windows' | 'linux' | 'android' | 'ios' | undefined; } } From 6ae86c20c87da3a2b7d35757dc06c83edb1e4771 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 18:19:01 +0200 Subject: [PATCH 34/53] fix: pro subscription wallets select updated --- .../src/components/settings/ProSettings.tsx | 78 ++++++++++++++----- packages/uikit/src/state/wallet.ts | 11 ++- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 6061733a9..1d4500806 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -18,7 +18,11 @@ import { useSelectWalletForProMutation, useWaitInvoiceMutation } from '../../state/pro'; -import { useAccountsState, useActiveTonNetwork, 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'; @@ -31,7 +35,10 @@ import { Notification } from '../Notification'; import { SubHeader } from '../SubHeader'; import { Body1, Label1, Title } from '../Text'; import { ConfirmView } from '../transfer/ConfirmView'; -import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { 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; @@ -55,17 +62,35 @@ const Description = styled(Body1)` margin-bottom: 16px; `; -const WalletItem: FC<{ wallet: TonWalletStandard }> = ({ wallet }) => { - const { t } = useTranslation(); - const network = useActiveTonNetwork(); +const WalletEmojiStyled = styled(WalletEmoji)` + margin-left: 3px; + display: inline-flex; +`; - const address = wallet ? toShortValue(formatAddress(wallet.rawAddress, 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} + + + } /> ); }; @@ -79,22 +104,29 @@ const SelectWallet: FC<{ onClose: () => void }> = ({ onClose }) => { const { t } = useTranslation(); const { mutateAsync, error } = useSelectWalletForProMutation(); useNotifyError(error); - const wallets = useAccountsState().flatMap(a => a.allTonWallets); + const accounts = useAccountsState().filter( + acc => acc.type === 'mnemonic' + ) as AccountTonMnemonic[]; return ( <> {t('select_wallet_for_authorization')} - {wallets.map(wallet => ( - mutateAsync(wallet.id).then(() => onClose())} - > - - - - - ))} + {accounts.flatMap(account => + account.allTonWallets + .slice() + .sort(sortWalletsByVersion) + .map(wallet => ( + mutateAsync(wallet.id).then(() => onClose())} + > + + + + + )) + )} ); @@ -111,13 +143,17 @@ const ProWallet: FC<{ onClick: () => void; disabled?: boolean; }> = ({ data, onClick, disabled }) => { - const wallet = useWalletState(data.wallet.rawAddress)!; + const { account, wallet } = useAccountAndWalletByWalletId(data.wallet.rawAddress)!; + + if (!account || !wallet) { + return null; + } return ( !disabled && onClick()}> - + diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 69355a30a..a5f3e3360 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -109,9 +109,16 @@ export const useAccountState = (id: AccountId | undefined) => { ); }; -export const useWalletState = (id: WalletId): TonWalletStandard | undefined => { +export const useAccountAndWalletByWalletId = ( + id: WalletId +): { account: Account | undefined; wallet: TonWalletStandard | undefined } => { const accounts = useAccountsState(); - return useMemo(() => getWalletById(accounts, id), [accounts]); + return useMemo(() => { + return { + wallet: getWalletById(accounts, id), + account: getAccountByWalletById(accounts, id) + }; + }, [accounts, id]); }; export const useAccountsStateQuery = () => { From f5271e5d676e92b73351572119b895ba497ffd32 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 18:25:18 +0200 Subject: [PATCH 35/53] fix: dashboard wallet name --- .../uikit/src/state/dashboard/useDashboardData.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index 3f95d2885..d68d7390e 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -1,7 +1,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Address } from '@ton/core'; import { DashboardCell } from '@tonkeeper/core/dist/entries/dashboard'; -import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { TonWalletStandard, walletVersionText } from '@tonkeeper/core/dist/entries/wallet'; import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; @@ -22,7 +22,9 @@ export function useDashboardData() { const client = useQueryClient(); const accountsState = useAccountsState(); - const mainnetWallets = accountsState.flatMap(a => a.allTonWallets); + const mainnetWallets = accountsState.flatMap(a => + a.allTonWallets.map(item => ({ ...item, accountName: a.name, accountEmoji: a.emoji })) + ); const idsMainnet = mainnetWallets.map(w => w!.id); return useQuery( @@ -61,7 +63,13 @@ export function useDashboardData() { result[rowIndex][colIndex] = { columnId: ClientColumnName.id, type: 'string', - value: wallet?.name || defaultWalletName + value: wallet + ? wallet.accountName + + ' ' + + wallet.accountEmoji + + ' ' + + walletVersionText(wallet.version) + : defaultWalletName }; return; } From a6d4e303ed2d532f75842e53eef35f1344444be1 Mon Sep 17 00:00:00 2001 From: siandreev Date: Wed, 31 Jul 2024 18:27:36 +0200 Subject: [PATCH 36/53] fix: remove w5 beta from wallets selection in pro subscription --- packages/uikit/src/components/settings/ProSettings.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 1d4500806..cc3f6316b 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -35,7 +35,11 @@ import { Notification } from '../Notification'; import { SubHeader } from '../SubHeader'; import { Body1, Label1, Title } from '../Text'; import { ConfirmView } from '../transfer/ConfirmView'; -import { sortWalletsByVersion, TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +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'; @@ -114,7 +118,7 @@ const SelectWallet: FC<{ onClose: () => void }> = ({ onClose }) => { {accounts.flatMap(account => account.allTonWallets - .slice() + .filter(w => !backwardCompatibilityOnlyWalletVersions.includes(w.version)) .sort(sortWalletsByVersion) .map(wallet => ( Date: Wed, 31 Jul 2024 18:45:00 +0200 Subject: [PATCH 37/53] fix: multisend fixes --- .../account/AccountAndWalletInfo.tsx | 21 ++++++++++-------- .../MultiSendConfirmNotification.tsx | 22 ++----------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/packages/uikit/src/components/account/AccountAndWalletInfo.tsx b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx index 5ee584918..5873e3136 100644 --- a/packages/uikit/src/components/account/AccountAndWalletInfo.tsx +++ b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx @@ -12,18 +12,21 @@ 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}; +`; - > ${Body2} { - overflow: hidden; - text-overflow: ellipsis; - } +const NameText = styled(Body2)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; -const Body2Tertiary = styled(Body2)` +const AddressText = styled(Body2)` + flex-shrink: 0; color: ${p => p.theme.textTertiary}; `; @@ -42,17 +45,17 @@ export const AccountAndWalletInfo: FC< return ( - + {t('confirmSendModal_wallet')}  {account.name} - + {account.allTonWallets.length > 1 ? ( <> - + {toShortValue(formatAddress(wallet.rawAddress, network))} - + ) : ( diff --git a/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx index 1730fe3af..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 { useActiveWallet } 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,8 +158,6 @@ const MultiSendConfirmContent: FC<{ willBeSentBN?.multipliedBy(rate?.prices || 0) ); - const wallet = useActiveWallet(); - const { mutateAsync: estimate, isLoading: estimateLoading, @@ -200,16 +191,7 @@ const MultiSendConfirmContent: FC<{ {t('send_screen_steps_comfirm_wallet')} - {wallet && ( - - - {wallet.name || t('wallet_title')} - - )} + {t('recipients')} From 15f078c210858e18ff4a64d177c31e1da514c981 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 10:48:21 +0200 Subject: [PATCH 38/53] fix: sort wallet versions in header --- packages/uikit/src/components/Header.tsx | 38 +++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index db50000f3..b8d3fa714 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -21,7 +21,11 @@ import { ScanButton } from './connect/ScanButton'; import { ImportNotification } from './create/ImportNotification'; import { SkeletonText } from './shared/Skeleton'; import { WalletEmoji } from './shared/emoji/WalletEmoji'; -import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet'; +import { + sortDerivationsByIndex, + sortWalletsByVersion, + TonWalletStandard +} from '@tonkeeper/core/dist/entries/wallet'; import { Account } from '@tonkeeper/core/dist/entries/account'; import { AccountAndWalletBadgesGroup } from './account/AccountBadge'; @@ -177,19 +181,25 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({ const accountsWallets: { wallet: TonWalletStandard; account: Account }[] = useAccountsState().flatMap(a => { if (a.type === 'ledger') { - return a.derivations.map( - d => - ({ - wallet: d.tonWallets.find(w => w.id === d.activeTonWalletId)!, - account: a - } as { wallet: TonWalletStandard; account: Account }) - ); + 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.map(w => ({ - wallet: w, - account: a - })); + return a.allTonWallets + .slice() + .sort(sortWalletsByVersion) + .map(w => ({ + wallet: w, + account: a + })); }); if (!accountsWallets) { @@ -246,8 +256,8 @@ export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => { const account = useActiveAccount(); const [isOpen, setOpen] = useState(false); - const wallets = useAccountsState(); - const shouldShowIcon = wallets.length > 1; + const accounts = useAccountsState(); + const shouldShowIcon = accounts.length > 1; return ( From c16d3c117e737972f1b60f987a5ba3e843c4d5f8 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 11:06:07 +0200 Subject: [PATCH 39/53] fix: remove wallet and derivation name and emoji --- packages/core/src/entries/wallet.ts | 4 -- packages/core/src/service/accountsStorage.ts | 13 +---- packages/core/src/service/walletService.ts | 53 ++------------------ packages/uikit/src/state/wallet.ts | 38 +------------- 4 files changed, 8 insertions(+), 100 deletions(-) diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index 38f1d9baf..01a12d7b6 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -117,16 +117,12 @@ export type TonContract = { }; export type TonWalletStandard = TonContract & { - name: string; - emoji: string; publicKey: string; version: WalletVersion; }; export type DerivationItem = { index: number; - name: string; - emoji: string; activeTonWalletId: WalletId; tonWallets: TonWalletStandard[]; // tronWallets: never; diff --git a/packages/core/src/service/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts index a1e9dffff..58bcfbf31 100644 --- a/packages/core/src/service/accountsStorage.ts +++ b/packages/core/src/service/accountsStorage.ts @@ -17,12 +17,7 @@ import { import { DeprecatedAccountState } from '../entries/account'; import { AuthState, DeprecatedAuthState } from '../entries/password'; import { assertUnreachable, notNullish } from '../utils/types'; -import { - getFallbackAccountEmoji, - getFallbackDerivationItemEmoji, - getFallbackTonStandardWalletEmoji, - getWalletNameAddress -} from './walletService'; +import { getFallbackAccountEmoji } from './walletService'; export class AccountsStorage { constructor(private storage: IStorage) {} @@ -217,9 +212,7 @@ async function migrateToAccountsState(storage: IStorage): Promise ({ index: item.accountIndex, - name: getFallbackWalletName(item.address), - emoji: getFallbackDerivationItemEmoji( - item.publicKey.toString('hex'), - item.accountIndex - ), activeTonWalletId: item.address, tonWallets: [ { id: item.address, publicKey: item.publicKey.toString('hex'), version: item.version, - rawAddress: item.address, - name: getFallbackWalletName(item.address), - emoji: getFallbackTonStandardWalletEmoji( - item.publicKey.toString('hex'), - item.version - ) + rawAddress: item.address } ] })) @@ -304,9 +287,7 @@ export const accountByKeystone = async (ur: UR, storage: IStorage): Promise { id: w.address.toRawString(), rawAddress: w.address.toRawString(), version, - publicKey, - name: getFallbackWalletName(w.address), - emoji: getFallbackTonStandardWalletEmoji(publicKey, version) + publicKey }; account.addTonWalletToActiveDerivation(wallet); @@ -258,38 +254,6 @@ export const useRemoveTonWalletVersionFromAccount = () => { }); }; -export const useRenameTonWallet = () => { - const accountsStore = useAccountsStorage(); - const account = useActiveAccount(); - const client = useQueryClient(); - - return useMutation< - TonWalletStandard, - Error, - { - id: WalletId; - name?: string; - emoji?: string; - } - >(async ({ id, name, emoji }) => { - const wallet = account.getTonWallet(id); - if (!wallet) { - throw new Error('Wallet to rename not found'); - } - - const newWallet = { - ...wallet, - name: name || wallet.name, - emoji: emoji || wallet.emoji - }; - - account.updateTonWallet(newWallet); - await accountsStore.updateAccountInState(account); - await client.invalidateQueries([QueryKey.account]); - return wallet; - }); -}; - export const useAddAccountToStateMutation = () => { const storage = useAccountsStorage(); const client = useQueryClient(); From b3ff1e9f545eb5a74bb9bc2955fe6591bc52f3ef Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 11:07:10 +0200 Subject: [PATCH 40/53] fix: account badge group styles --- packages/uikit/src/components/account/AccountBadge.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/uikit/src/components/account/AccountBadge.tsx b/packages/uikit/src/components/account/AccountBadge.tsx index 4fa2fe4ef..25ab45dbc 100644 --- a/packages/uikit/src/components/account/AccountBadge.tsx +++ b/packages/uikit/src/components/account/AccountBadge.tsx @@ -117,13 +117,19 @@ export const AccountAndWalletBadgesGroup: FC<{ } if (account.type === 'keystone') { - return ; + return ; } if (account.type === 'mnemonic' && account.tonWallets.length > 1) { const wallet = account.tonWallets.find(w => w.id === walletId); if (wallet) { - return ; + return ( + + ); } } From 9b6d41dade74b56558a2b6840a50155a23d0a977 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 13:50:50 +0200 Subject: [PATCH 41/53] fix: ledger subwallets page --- apps/extension/src/App.tsx | 2 + apps/twa/src/App.tsx | 2 + apps/web/src/App.tsx | 2 + packages/core/src/entries/account.ts | 44 ++++- packages/core/src/service/accountsStorage.ts | 33 ++-- packages/core/src/service/walletService.ts | 2 + packages/locales/src/tonkeeper-web/bg.json | 7 +- packages/locales/src/tonkeeper-web/en.json | 1 + packages/locales/src/tonkeeper-web/ru-RU.json | 1 + packages/uikit/src/components/ModalsRoot.tsx | 2 + .../components/create/ChoseLedgerIndexes.tsx | 137 +++++++++++++++ .../components/desktop/aside/AsideMenu.tsx | 14 +- .../LedgerIndexesSettingsNotification.tsx | 31 ++++ .../components/settings/AccountSettings.tsx | 18 ++ .../settings/DesktopWalletSettingsPage.tsx | 15 ++ .../settings/DesktopWalletSettingsRouting.tsx | 2 + packages/uikit/src/libs/routes.ts | 2 + packages/uikit/src/pages/import/Ledger.tsx | 19 +-- .../src/pages/settings/LedgerIndexes.tsx | 160 ++++++++++++++++++ packages/uikit/src/pages/settings/index.tsx | 2 + packages/uikit/src/state/ledger.ts | 26 ++- packages/uikit/src/state/wallet.ts | 96 +++++++++++ 22 files changed, 588 insertions(+), 30 deletions(-) create mode 100644 packages/uikit/src/components/create/ChoseLedgerIndexes.tsx create mode 100644 packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx create mode 100644 packages/uikit/src/pages/settings/LedgerIndexes.tsx diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 216f17eec..07e1ff675 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -56,6 +56,7 @@ import { useMutateUserLanguage } 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')); @@ -261,6 +262,7 @@ export const Content: FC<{ useWindowsScroll(!pageView); useAppWidth(); useTrackLocation(); + useDebuggingTools(); if (lock) { return ( diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index ca544b9d8..6e737f691 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -60,6 +60,7 @@ 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')); @@ -322,6 +323,7 @@ const Content: FC<{ const location = useLocation(); useWindowsScroll(); useTrackLocation(); + useDebuggingTools(); if (lock) { return ( diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 7ef079974..a44bd5084 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -51,6 +51,7 @@ 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')); @@ -273,6 +274,7 @@ export const Content: FC<{ useAppWidth(standalone); useKeyboardHeight(); useTrackLocation(); + useDebuggingTools(); if (lock) { return ( diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index d245eae71..0b491ab8a 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -128,6 +128,12 @@ export class AccountLedger extends Clonable implements IAccount { )!; } + get derivations(): DerivationItem[] { + return this.addedDerivationsIndexes.map( + index => this.allAvailabelDerivations.find(d => d.index === index)! + ); + } + /** * @param id index 0 derivation ton public key hex string without 0x */ @@ -136,9 +142,20 @@ export class AccountLedger extends Clonable implements IAccount { public name: string, public emoji: string, public activeDerivationIndex: number, - public readonly derivations: DerivationItem[] + public addedDerivationsIndexes: number[], + public readonly allAvailabelDerivations: DerivationItem[] ) { super(); + + if ( + addedDerivationsIndexes.some(index => + allAvailabelDerivations.every(d => d.index !== index) + ) + ) { + throw new Error('Derivations not found'); + } + + this.addedDerivationsIndexes = [...new Set(addedDerivationsIndexes)]; } getTonWallet(id: WalletId) { @@ -191,6 +208,31 @@ export class AccountLedger extends Clonable implements IAccount { 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.allAvailabelDerivations.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 { diff --git a/packages/core/src/service/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts index 58bcfbf31..02f75de2f 100644 --- a/packages/core/src/service/accountsStorage.ts +++ b/packages/core/src/service/accountsStorage.ts @@ -114,17 +114,21 @@ export class AccountsStorage { return this.updateAccountsInState([account]); }; - removeAccountFromState = async (id: AccountId) => { + removeAccountsFromState = async (ids: AccountId[]) => { const state = await this.getAccounts(); const activeAccountId = await this.getActiveAccountId(); - const newState = state.filter(w => w.id !== id); + const newState = state.filter(w => !ids.includes(w.id)); - if (activeAccountId === id) { + if (activeAccountId !== null && ids.includes(activeAccountId)) { await this.setActiveAccountId(newState[0]?.id || null); } - await this.setAccounts(state.filter(w => w.id !== id)); + await this.setAccounts(newState); + }; + + removeAccountFromState = async (id: AccountId) => { + return this.removeAccountsFromState([id]); }; async getNewAccountNameAndEmoji(accountId: AccountId) { @@ -237,13 +241,20 @@ async function migrateToAccountsState(storage: IStorage): Promise ({ index: item.accountIndex, activeTonWalletId: item.address, 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/ModalsRoot.tsx b/packages/uikit/src/components/ModalsRoot.tsx index bdb5c279f..5688d2605 100644 --- a/packages/uikit/src/components/ModalsRoot.tsx +++ b/packages/uikit/src/components/ModalsRoot.tsx @@ -1,9 +1,11 @@ import { WalletVersionSettingsNotification } from './modals/WalletVersionSettingsNotification'; +import { LedgerIndexesSettingsNotification } from './modals/LedgerIndexesSettingsNotification'; export const ModalsRoot = () => { return ( <> + ); }; diff --git a/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx b/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx new file mode 100644 index 000000000..1346c9318 --- /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.allAvailabelDerivations.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.allAvailabelDerivations[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/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index c27adf527..7856f4612 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -36,6 +36,7 @@ import { useWalletVersionSettingsNotification } from '../../modals/WalletVersion 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; @@ -136,6 +137,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( isSelected }) => { const { onOpen: openWalletVersionSettings } = useWalletVersionSettingsNotification(); + const { onOpen: openLedgerIndexesSettings } = useLedgerIndexesSettingsNotification(); const network = useActiveTonNetwork(); const { mutateAsync: setActiveWallet } = useMutateActiveTonWallet(); const navigate = useNavigate(); @@ -217,6 +219,16 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} + { + e.preventDefault(); + e.stopPropagation(); + openLedgerIndexesSettings({ accountId: account.id }); + }} + isShown={isHovered} + > + + {sortedDerivations.length > 1 && sortedDerivations.map(derivation => { @@ -236,7 +248,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {toShortValue(formatAddress(wallet.rawAddress, network))} - {'#' + derivation.index} + {'#' + (derivation.index + 1)}
); diff --git a/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx b/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx new file mode 100644 index 000000000..54eef5477 --- /dev/null +++ b/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx @@ -0,0 +1,31 @@ +import { Notification } from '../Notification'; +import { useAtom } from '../../libs/atom'; +import { useTranslation } from '../../hooks/translation'; +import { AccountId } from '@tonkeeper/core/dist/entries/account'; +import { createModalControl } from './createModalControl'; +import { LedgerIndexesPageContent } from '../../pages/settings/LedgerIndexes'; +import { useAccountState } from '../../state/wallet'; + +const { hook, control } = createModalControl<{ accountId: AccountId }>(); + +export const useLedgerIndexesSettingsNotification = hook; + +export const LedgerIndexesSettingsNotification = () => { + const { isOpen, onClose } = useLedgerIndexesSettingsNotification(); + const { t } = useTranslation(); + const [params] = useAtom(control); + const account = useAccountState(params?.accountId); + if (!account || account.type !== 'ledger') { + return null; + } + + return ( + onClose()} + > + {() => } + + ); +}; diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index 557f0377a..da5816150 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -53,6 +53,15 @@ const SingleAccountSettings = () => { }); } + // check available derivations length to filter and keep only non-legacy added ledger accounts + if (account.type === 'ledger' && account.allAvailabelDerivations.length > 1) { + items.push({ + name: t('settings_ledger_indexes'), + icon: `# ${account.activeDerivationIndex + 1}`, + action: () => navigate(relative(SettingsRoute.ledgerIndexes)) + }); + } + if (proFeatures) { items.unshift({ name: t('tonkeeper_pro'), @@ -161,6 +170,15 @@ const MultipleAccountSettings = () => { }); } + // check available derivations length to filter and keep only non-legacy added ledger accounts + if (account.type === 'ledger' && account.allAvailabelDerivations.length > 1) { + items.push({ + name: t('settings_ledger_indexes'), + icon: `# ${account.activeDerivationIndex + 1}`, + action: () => navigate(relative(SettingsRoute.ledgerIndexes)) + }); + } + if (jettons?.balances.length) { items.push({ name: t('settings_jettons_list'), diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index 2aa43d0a3..2505e6b10 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -60,6 +60,10 @@ export const DesktopWalletSettingsPage = () => { const { isOpen: isLogoutOpen, onClose: onLogoutClose, onOpen: onLogoutOpen } = 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.allAvailabelDerivations.length > 1; const activeWallet = account.activeTonWallet; return ( @@ -97,6 +101,17 @@ export const DesktopWalletSettingsPage = () => { )} + {canChangeLedgerIndex && ( + + + + + {t('settings_ledger_indexes')} + # {account.activeDerivationIndex + 1} + + + + )} diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx index b74b87436..0f73498e2 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx @@ -7,6 +7,7 @@ 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; @@ -30,6 +31,7 @@ export const DesktopWalletSettingsRouting = () => { } /> } /> + } /> } /> (); + const [accountsSelected, setAccountsSelected] = useState(); useEffect(() => { getLedgerWallets(tonTransport).then(data => setSelectedIndexes(data.preselectedIndexes)); @@ -174,17 +173,10 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: () }; const onAdd = () => { - const chosenIndexes = Object.entries(selectedIndexes) - .filter(([, v]) => v) - .map(([k]) => Number(k)); - setAccountsToAdd( - ledgerAccountData!.wallets.filter(account => - chosenIndexes.includes(account.accountIndex) - ) - ); + setAccountsSelected(true); }; - if (accountsToAdd) { + if (accountsSelected) { return ( v) + .map(([k]) => Number(k)), accountId: ledgerAccountData!.accountId }) } diff --git a/packages/uikit/src/pages/settings/LedgerIndexes.tsx b/packages/uikit/src/pages/settings/LedgerIndexes.tsx new file mode 100644 index 000000000..125da30a4 --- /dev/null +++ b/packages/uikit/src/pages/settings/LedgerIndexes.tsx @@ -0,0 +1,160 @@ +import { AccountLedger } from '@tonkeeper/core/dist/entries/account'; +import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; +import React, { FC } from 'react'; +import styled from 'styled-components'; +import { InnerBody } from '../../components/Body'; +import { SubHeader } from '../../components/SubHeader'; +import { Body2, Label1 } from '../../components/Text'; +import { useTranslation } from '../../hooks/translation'; +import { + useTonWalletsBalances, + useMutateActiveLedgerAccountDerivation, + useAddLedgerAccountDerivation, + useRemoveLedgerAccountDerivation, + useActiveAccount +} from '../../state/wallet'; +import { ListBlock, ListItem, ListItemPayload } from '../../components/List'; +import { toFormattedTonBalance } from '../../hooks/balance'; +import { Button } from '../../components/fields/Button'; +import { useNavigate } from 'react-router-dom'; +import { AppRoute } from '../../libs/routes'; +import { SkeletonList } from '../../components/Skeleton'; + +const TextContainer = styled.span` + flex-direction: column; + display: flex; + align-items: flex-start; +`; + +const Body2Secondary = styled(Body2)` + color: ${props => props.theme.textSecondary}; +`; + +const ButtonsContainer = styled.div` + display: flex; + gap: 8px; +`; + +export const LedgerIndexesPage = () => { + const { t } = useTranslation(); + const account = useActiveAccount(); + if (account.type !== 'ledger') { + return null; + } + + return ( + <> + + + + + + ); +}; + +export const LedgerIndexesPageContent: FC<{ + afterWalletOpened?: () => void; + account: AccountLedger; +}> = ({ afterWalletOpened, account }) => { + const { t } = useTranslation(); + + const { mutateAsync: selectDerivation, isLoading: isSelectDerivationLoading } = + useMutateActiveLedgerAccountDerivation(); + const navigate = useNavigate(); + + const { data: balances } = useTonWalletsBalances( + account.allAvailabelDerivations.map( + d => d.tonWallets.find(w => w.id === d.activeTonWalletId)!.rawAddress + ) + ); + + const { mutate: addDerivation, isLoading: isAddingDerivationLoading } = + useAddLedgerAccountDerivation(); + + const { mutate: hideDerivation, isLoading: isHideDerivationLoading } = + useRemoveLedgerAccountDerivation(); + + const onOpenDerivation = async (index: number) => { + if (index !== account.activeDerivationIndex) { + await selectDerivation({ accountId: account.id, derivationIndex: index }); + } + navigate(AppRoute.home); + afterWalletOpened?.(); + }; + + const onAddDerivation = async (index: number) => { + addDerivation({ + accountId: account.id, + derivationIndex: index + }); + }; + + const onHideDerivation = async (index: number) => { + hideDerivation({ + accountId: account.id, + derivationIndex: index + }); + }; + + if (!balances) { + return ; + } + + const isLoading = + isSelectDerivationLoading || isAddingDerivationLoading || isHideDerivationLoading; + const canHide = account.derivations.length > 1; + + return ( + + {balances.map((balance, index) => { + const derivationIndex = account.allAvailabelDerivations[index].index; + + const isDerivationAdded = account.derivations.some( + d => d.index === derivationIndex + ); + + return ( + + + + # {derivationIndex + 1} + + {toShortValue(formatAddress(balance.address))} +  ·  + {toFormattedTonBalance(balance.tonBalance)} TON + {balance.hasJettons && t('wallet_version_and_tokens')} + + + {isDerivationAdded ? ( + + + {canHide && ( + + )} + + ) : ( + + )} + + + ); + })} + + ); +}; diff --git a/packages/uikit/src/pages/settings/index.tsx b/packages/uikit/src/pages/settings/index.tsx index 2ea611441..f7e7b58fd 100644 --- a/packages/uikit/src/pages/settings/index.tsx +++ b/packages/uikit/src/pages/settings/index.tsx @@ -17,6 +17,7 @@ import { Settings } from './Settings'; import { WalletVersionPage } from './Version'; import { ConnectedAppsSettings } from './ConnectedAppsSettings'; import { NFTSettings } from './Nft'; +import { LedgerIndexesPage } from "./LedgerIndexes"; const SettingsRouter = () => { return ( @@ -32,6 +33,7 @@ const SettingsRouter = () => { } /> } /> + } /> } /> } /> } /> diff --git a/packages/uikit/src/state/ledger.ts b/packages/uikit/src/state/ledger.ts index 81347a717..88a261ee6 100644 --- a/packages/uikit/src/state/ledger.ts +++ b/packages/uikit/src/state/ledger.ts @@ -107,8 +107,9 @@ export const useLedgerWallets = ( const accountId = walletsIds[0].publicKey.toString('hex'); const { name, emoji } = await accountsStorage.getNewAccountNameAndEmoji(accountId); - const existingAccountWallets = - (await accountsStorage.getAccount(accountId))?.allTonWallets || []; + const existingAccountWallets = (await accountsStorage.getAccounts()).flatMap( + a => a.allTonWallets + ); const wallets = walletsIds.map((acc, i) => ({ accountIndex: i, @@ -154,10 +155,27 @@ export const useAddLedgerAccountMutation = () => { return useMutation< void, Error, - { accountId: string; wallets: LedgerAccount[]; name: string; emoji: string } + { + accountId: string; + allWallets: LedgerAccount[]; + name: string; + emoji: string; + walletsIndexesToAdd: number[]; + } >(async form => { try { - const newAccount = accountByLedger(form.accountId, form.wallets, form.name, form.emoji); + const newAccount = accountByLedger( + form.accountId, + form.walletsIndexesToAdd, + form.allWallets, + form.name, + form.emoji + ); + + // remove separately-added in legacy app version ledger wallets that should be replaced with a single account with subwallets + await accStorage.removeAccountsFromState( + form.allWallets.map(w => w.publicKey.toString('hex')) + ); await accStorage.addAccountToState(newAccount); await accStorage.setActiveAccountId(newAccount.id); diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index f316e6407..6f4ed3261 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -99,6 +99,68 @@ export const useMutateActiveTonWallet = () => { }); }; +export const useMutateActiveLedgerAccountDerivation = () => { + const storage = useAccountsStorage(); + const client = useQueryClient(); + return useMutation( + async ({ accountId, derivationIndex }) => { + const account = await storage.getAccount(accountId); + + if (!account || account.type !== 'ledger') { + throw new Error('Account not found'); + } + + account.setActiveDerivationIndex(derivationIndex); + const walletId = account.activeTonWallet.id; + await storage.updateAccountInState(account); + await storage.setActiveAccountId(account.id); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, walletId)); + } + ); +}; + +export const useAddLedgerAccountDerivation = () => { + const storage = useAccountsStorage(); + const client = useQueryClient(); + return useMutation( + async ({ accountId, derivationIndex }) => { + const account = await storage.getAccount(accountId); + + if (!account || account.type !== 'ledger') { + throw new Error('Account not found'); + } + + account.setAddedDerivationsIndexes( + account.addedDerivationsIndexes + .filter(i => i !== derivationIndex) + .concat(derivationIndex) + ); + await storage.updateAccountInState(account); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account)); + } + ); +}; + +export const useRemoveLedgerAccountDerivation = () => { + const storage = useAccountsStorage(); + const client = useQueryClient(); + return useMutation( + async ({ accountId, derivationIndex }) => { + const account = await storage.getAccount(accountId); + + if (!account || account.type !== 'ledger') { + throw new Error('Account not found'); + } + + account.setAddedDerivationsIndexes( + account.addedDerivationsIndexes.filter(i => i !== derivationIndex) + ); + await storage.updateAccountInState(account); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account)); + } + ); +}; + export const useAccountState = (id: AccountId | undefined) => { const accounts = useAccountsState(); return useMemo( @@ -408,6 +470,40 @@ export const useStandardTonWalletVersions = (publicKey?: string) => { ); }; +export const useTonWalletsBalances = (addresses: string[]) => { + const { api, fiat } = useAppContext(); + const network = useActiveTonNetwork(); + + return useQuery( + [QueryKey.walletVersions, addresses, network, fiat], + async () => { + const response = await new AccountsApi(api.tonApiV2).getAccounts({ + getAccountsRequest: { accountIds: addresses } + }); + + const walletsJettonsBalances = await Promise.all( + addresses.map(address => + new AccountsApi(api.tonApiV2).getAccountJettonsBalances({ + accountId: address, + currencies: [fiat] + }) + ) + ); + + return addresses.map((address, index) => ({ + address, + tonBalance: response.accounts[index].balance, + hasJettons: walletsJettonsBalances[index].balances.some( + b => b.price?.prices && Number(b.balance) > 0 + ) + })); + }, + { + keepPreviousData: true + } + ); +}; + export function useInvalidateActiveWalletQueries() { const account = useActiveAccount(); const client = useQueryClient(); From f6fa9e7b7ecd6adf86f05755a303e43bf24796c7 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 13:57:19 +0200 Subject: [PATCH 42/53] fix: hide ledger settings in aside menu for legacy ledger accounts --- .../components/desktop/aside/AsideMenu.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index 7856f4612..e33c4772b 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -219,16 +219,20 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( )} {account.name} - { - e.preventDefault(); - e.stopPropagation(); - openLedgerIndexesSettings({ accountId: account.id }); - }} - isShown={isHovered} - > - - + + {/*show settings only for non-legacy added ledger accounts*/} + {account.allAvailabelDerivations.length > 1 && ( + { + e.preventDefault(); + e.stopPropagation(); + openLedgerIndexesSettings({ accountId: account.id }); + }} + isShown={isHovered} + > + + + )} {sortedDerivations.length > 1 && sortedDerivations.map(derivation => { From 5c1629c336befe0eaad3bba1166b478566d8cc22 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 13:58:49 +0200 Subject: [PATCH 43/53] fix: remove address update info link from the settings --- .../uikit/src/components/settings/ThemeSettings.tsx | 12 ------------ 1 file changed, 12 deletions(-) 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]); From 31af02b9e98635c01db41a0255d98850c45c8089 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 14:00:44 +0200 Subject: [PATCH 44/53] fix: remove noticoin page --- apps/desktop/src/app/App.tsx | 2 - .../desktop/aside/WalletAsideMenu.tsx | 8 - .../src/desktop-pages/notcoin/NotcoinPage.tsx | 417 ------------------ .../src/desktop-pages/notcoin/address.ts | 70 --- packages/uikit/src/libs/routes.ts | 1 - 5 files changed, 498 deletions(-) delete mode 100644 packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx delete mode 100644 packages/uikit/src/desktop-pages/notcoin/address.ts diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index e655b4bb8..600f654f4 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -32,7 +32,6 @@ import { DesktopCoinPage } from '@tonkeeper/uikit/dist/desktop-pages/coin/Deskto import DashboardPage from '@tonkeeper/uikit/dist/desktop-pages/dashboard'; import { DesktopHistoryPage } from '@tonkeeper/uikit/dist/desktop-pages/history/DesktopHistoryPage'; import { DesktopMultiSendPage } from '@tonkeeper/uikit/dist/desktop-pages/multi-send'; -import { NotcoinPage } from '@tonkeeper/uikit/dist/desktop-pages/notcoin/NotcoinPage'; import { DesktopPreferencesRouting } from '@tonkeeper/uikit/dist/desktop-pages/preferences/DesktopPreferencesRouting'; import { DesktopWalletSettingsRouting } from '@tonkeeper/uikit/dist/desktop-pages/settings/DesktopWalletSettingsRouting'; import { DesktopSwapPage } from '@tonkeeper/uikit/dist/desktop-pages/swap'; @@ -425,7 +424,6 @@ const WalletContent = () => { element={} /> } /> - } /> } /> 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/desktop-pages/notcoin/NotcoinPage.tsx b/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx deleted file mode 100644 index 5e4d71be6..000000000 --- a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx +++ /dev/null @@ -1,417 +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 { TonWalletStandard } 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, formatAddress } 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 { SkeletonListWithImages } 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 } 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'; -import { useActiveAccount, useActiveStandardTonWallet, useActiveWallet } from '../../state/wallet'; - -const useVouchers = () => { - const wallet = useActiveWallet(); - const { api } = useAppContext(); - - const limit = 1000; - - const getItems = async (offset: number) => { - const items = await new AccountsApi(api.tonApiV2).getAccountNftItems({ - accountId: wallet.rawAddress, - collection: 'EQDmkj65Ab_m0aZaW8IpKw4kYqIgITw_HRstYEkVQ6NIYCyW', - limit: limit, - offset: offset - }); - - return items.nftItems; - }; - - return useQuery(['notcoin', 'length', wallet.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; - } - - const 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: TonWalletStandard, - 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.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: TonWalletStandard, - 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 = useActiveStandardTonWallet(); - 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.rawAddress); - - console.log('send', chunk); - - await sendNftMultiTransfer(api, wallet, chunk, config, signer as CellSigner); - - await confirmWalletSeqNo(wallet.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 - - - - - ); -}; - -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 account = useActiveAccount(); - const wallet = account.activeTonWallet; - - 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, account.id, 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/${formatAddress(wallet.rawAddress)}` - ); - }} - > - 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 - - . - - - - ); -}; - -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/libs/routes.ts b/packages/uikit/src/libs/routes.ts index f69444d81..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 = '/' } From 18d8e33dc5d153b1c6163562fe74e14269aa3978 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 14:05:56 +0200 Subject: [PATCH 45/53] fix: remove `disable_v5r1` tonendpoint flag dependencies --- apps/desktop/src/app/App.tsx | 6 +----- apps/twa/src/App.tsx | 2 +- packages/core/src/tonkeeperApi/tonendpoint.ts | 6 +++--- packages/uikit/src/state/tonendpoint.ts | 4 ---- packages/uikit/src/state/wallet.ts | 12 ++++-------- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 600f654f4..dcb4b4cea 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -56,11 +56,7 @@ import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProv import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat'; import { useCanPromptTouchId } from '@tonkeeper/uikit/dist/state/password'; import { useProBackupState } from '@tonkeeper/uikit/dist/state/pro'; -import { - isV5R1Enabled, - useTonendpoint, - useTonenpointConfig -} from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; import { useActiveAccountQuery, useAccountsStateQuery, diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index 6e737f691..7dd68790c 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -37,7 +37,7 @@ 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 { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint"; +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'; 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 function isV5R1Enabled(config: TonendpointConfig) { - return config.flags?.disable_v5r1 === false; -} - export const DefaultRefetchInterval = 60000; // 60 sec export const useTonendpointBuyMethods = () => { diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 6f4ed3261..65c92ff5c 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -1,7 +1,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { isStandardTonWallet, - isW5Version, WalletVersion, WalletVersions, WalletId, @@ -16,7 +15,7 @@ import { Account as TonapiAccount, AccountsApi } from '@tonkeeper/core/dist/tonA import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { anyOfKeysParts, QueryKey } from '../libs/queryKey'; -import { DefaultRefetchInterval, isV5R1Enabled } from './tonendpoint'; +import { DefaultRefetchInterval } from './tonendpoint'; import { useMemo } from 'react'; import { useAccountsStorage } from '../hooks/useStorage'; import { mnemonicValidate } from '@ton/crypto'; @@ -429,19 +428,16 @@ export const useMutateActiveTonWalletConfig = () => { }; export const useStandardTonWalletVersions = (publicKey?: string) => { - const { api, fiat, config } = useAppContext(); - const isV5Enabled = isV5R1Enabled(config); + const { api, fiat } = useAppContext(); const network = useActiveTonNetwork(); return useQuery( - [QueryKey.walletVersions, publicKey, network, isV5Enabled], + [QueryKey.walletVersions, publicKey, network], async () => { if (!publicKey) { return undefined; } - const versions = WalletVersions.filter(v => isV5Enabled || !isW5Version(v)).map(v => - getWalletAddress(publicKey, v, network) - ); + const versions = WalletVersions.map(v => getWalletAddress(publicKey, v, network)); const response = await new AccountsApi(api.tonApiV2).getAccounts({ getAccountsRequest: { accountIds: versions.map(v => v.address.toRawString()) } From 7e65c373ee050de4e14b1317fe20fa6a84f0e212 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 15:21:18 +0200 Subject: [PATCH 46/53] fix: account settings modals animation --- .../LedgerIndexesSettingsNotification.tsx | 6 +++--- .../WalletVersionSettingsNotification.tsx | 6 +++--- .../components/modals/createModalControl.ts | 18 +++++++++++++----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx b/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx index 54eef5477..64a27c64e 100644 --- a/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx +++ b/packages/uikit/src/components/modals/LedgerIndexesSettingsNotification.tsx @@ -6,14 +6,14 @@ import { createModalControl } from './createModalControl'; import { LedgerIndexesPageContent } from '../../pages/settings/LedgerIndexes'; import { useAccountState } from '../../state/wallet'; -const { hook, control } = createModalControl<{ accountId: AccountId }>(); +const { hook, paramsControl } = createModalControl<{ accountId: AccountId }>(); export const useLedgerIndexesSettingsNotification = hook; export const LedgerIndexesSettingsNotification = () => { const { isOpen, onClose } = useLedgerIndexesSettingsNotification(); const { t } = useTranslation(); - const [params] = useAtom(control); + const [params] = useAtom(paramsControl); const account = useAccountState(params?.accountId); if (!account || account.type !== 'ledger') { return null; @@ -22,7 +22,7 @@ export const LedgerIndexesSettingsNotification = () => { return ( onClose()} > {() => } diff --git a/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx index b8edbdc1f..436f5eab2 100644 --- a/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx +++ b/packages/uikit/src/components/modals/WalletVersionSettingsNotification.tsx @@ -5,17 +5,17 @@ import { useTranslation } from '../../hooks/translation'; import { AccountId } from '@tonkeeper/core/dist/entries/account'; import { createModalControl } from './createModalControl'; -const { hook, control } = createModalControl<{ accountId?: AccountId }>(); +const { hook, paramsControl } = createModalControl<{ accountId?: AccountId }>(); export const useWalletVersionSettingsNotification = hook; export const WalletVersionSettingsNotification = () => { const { isOpen, onClose } = useWalletVersionSettingsNotification(); const { t } = useTranslation(); - const [params] = useAtom(control); + const [params] = useAtom(paramsControl); return ( - onClose()}> + onClose()}> {() => ( () => { - const control = atom(undefined); + const paramsControl = atom(undefined); + const isOpenControl = atom(false); return { hook: () => { - const [isOpen, setOpenParams] = useAtom(control); + const [_, setParams] = useAtom(paramsControl); + const [isOpen, setIsOpen] = useAtom(isOpenControl); return { isOpen, - onOpen: useCallback((params: T = {} as T) => setOpenParams(params), []), - onClose: useCallback(() => setOpenParams(undefined), []) + onOpen: useCallback( + (p: T = {} as T) => { + setParams(p); + setIsOpen(true); + }, + [setParams, setParams] + ), + onClose: useCallback(() => setIsOpen(false), [setIsOpen]) }; }, - control + paramsControl }; }; From 7a9c0340c536cc793a3f4fe0b1fd9f51c0a019be Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 1 Aug 2024 15:29:05 +0200 Subject: [PATCH 47/53] fix: remove duplicated "sign out" option --- .../desktop/aside/PreferencesAsideMenu.tsx | 2 +- .../components/settings/AccountSettings.tsx | 14 +-- .../src/components/settings/ClearSettings.tsx | 2 +- ...tion.tsx => DeleteAccountNotification.tsx} | 85 ------------------- .../settings/DesktopWalletSettingsPage.tsx | 14 +-- packages/uikit/src/pages/settings/Account.tsx | 21 +---- 6 files changed, 17 insertions(+), 121 deletions(-) rename packages/uikit/src/components/settings/{LogOutNotification.tsx => DeleteAccountNotification.tsx} (68%) diff --git a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx index bc5b73704..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'; diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index da5816150..476557806 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -5,7 +5,7 @@ import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; import { SettingsRoute, relative, WalletSettingsRoute } from '../../libs/routes'; import { useJettonList } from '../../state/jetton'; -import { LogOutAccountNotification } from './LogOutNotification'; +import { DeleteAccountNotification } from './DeleteAccountNotification'; import { AppsIcon, ListOfTokensIcon, @@ -20,7 +20,7 @@ import { useActiveWallet, useAccountsState, useActiveAccount } from '../../state import { useWalletNftList } from '../../state/nft'; const SingleAccountSettings = () => { - const [logout, setLogout] = useState(false); + const [deleteAccount, setDeleteAccount] = useState(false); const { t } = useTranslation(); const navigate = useNavigate(); const account = useActiveAccount(); @@ -97,9 +97,9 @@ const SingleAccountSettings = () => { action: () => navigate(relative(WalletSettingsRoute.connectedApps)) }); items.push({ - name: t('settings_reset'), + name: t('Delete_wallet_data'), icon: , - action: () => setLogout(true) + action: () => setDeleteAccount(true) }); return items; @@ -108,9 +108,9 @@ const SingleAccountSettings = () => { return ( <> - setLogout(false)} + setDeleteAccount(false)} /> ); diff --git a/packages/uikit/src/components/settings/ClearSettings.tsx b/packages/uikit/src/components/settings/ClearSettings.tsx index 4d9a4d51d..9429c3e2d 100644 --- a/packages/uikit/src/components/settings/ClearSettings.tsx +++ b/packages/uikit/src/components/settings/ClearSettings.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; 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'; diff --git a/packages/uikit/src/components/settings/LogOutNotification.tsx b/packages/uikit/src/components/settings/DeleteAccountNotification.tsx similarity index 68% rename from packages/uikit/src/components/settings/LogOutNotification.tsx rename to packages/uikit/src/components/settings/DeleteAccountNotification.tsx index eb70aa0d5..25874e156 100644 --- a/packages/uikit/src/components/settings/LogOutNotification.tsx +++ b/packages/uikit/src/components/settings/DeleteAccountNotification.tsx @@ -36,91 +36,6 @@ const DisclaimerLink = styled(Label1)` margin-left: 2.35rem; `; -const LotOutContent: FC<{ - onClose: (action: () => void) => void; - accountId: AccountId; - isKeystone: boolean; -}> = ({ onClose, accountId, isKeystone }) => { - const navigate = useNavigate(); - const { t } = useTranslation(); - const [checked, setChecked] = useState(false); - const { mutateAsync, isLoading } = useMutateLogOut(); - - 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 + '/' + accountId - ) - ) - } - > - {t('Back_up_now')} - - - )} - {isKeystone &&
} - - - ); -}; - -export const LogOutAccountNotification: FC<{ - account?: Account; - handleClose: () => void; -}> = ({ account, handleClose }) => { - const Content = useCallback( - (afterClose: (action: () => void) => void) => { - if (!account) return undefined; - return ( - - ); - }, - [account] - ); - - return ( - - {Content} - - ); -}; - const DeleteContent: FC<{ onClose: (action: () => void) => void; accountId: AccountId; diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index 2505e6b10..bf442188a 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -15,7 +15,7 @@ import { DesktopViewHeader, DesktopViewPageLayout } from '../../components/desktop/DesktopViewLayout'; -import { LogOutAccountNotification } 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 { useTranslation } from '../../hooks/translation'; @@ -57,7 +57,7 @@ export const DesktopWalletSettingsPage = () => { const { t } = useTranslation(); 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'; @@ -133,9 +133,9 @@ export const DesktopWalletSettingsPage = () => { - + - {t('preferences_aside_sign_out')} + {t('Delete_wallet_data')} @@ -144,9 +144,9 @@ export const DesktopWalletSettingsPage = () => { account={isRenameOpen ? account : undefined} handleClose={onRenameClose} /> - ); diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index 0b1c48acb..60a643a4e 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -17,10 +17,7 @@ import { SkeletonListPayloadWithImage } from '../../components/Skeleton'; import { SubHeader } from '../../components/SubHeader'; import { Label1 } from '../../components/Text'; import { ImportNotification } from '../../components/create/ImportNotification'; -import { - DeleteAccountNotification, - LogOutAccountNotification -} from '../../components/settings/LogOutNotification'; +import { DeleteAccountNotification } from '../../components/settings/DeleteAccountNotification'; import { SetUpWalletIcon } from '../../components/settings/SettingsIcons'; import { SettingsList } from '../../components/settings/SettingsList'; import { RenameWalletNotification } from '../../components/settings/wallet-name/WalletNameNotification'; @@ -52,7 +49,6 @@ const WalletRow: FC<{ const { t } = useTranslation(); const [rename, setRename] = useState(false); - const [logout, setLogout] = useState(false); const [remove, setRemove] = useState(false); const secondary = useAccountLabel(account); @@ -101,17 +97,6 @@ const WalletRow: FC<{ )} - { - setLogout(true); - onClose(); - }} - > - - {t('settings_reset')} - - { @@ -136,10 +121,6 @@ const WalletRow: FC<{ account={rename ? account : undefined} handleClose={() => setRename(false)} /> - setLogout(false)} - /> setRemove(false)} From 15f9d58a45a0964e5c62b4d82cae1afa00ac2550 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 12:38:36 +0200 Subject: [PATCH 48/53] fix: extension render props bug in history --- .../src/components/activity/ActivityGroup.tsx | 63 ++++++++++--------- .../components/activity/ActivityLayout.tsx | 33 ---------- 2 files changed, 35 insertions(+), 61 deletions(-) diff --git a/packages/uikit/src/components/activity/ActivityGroup.tsx b/packages/uikit/src/components/activity/ActivityGroup.tsx index 59e450d19..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, SkeletonListWithImages } from '../Skeleton'; -import { ActivityBlock } from './ActivityLayout'; +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; @@ -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 ( - - - - ); - })} - - ); - })} - - ); -} From d2aa785a481ac23a2ec6e8d42496a2440434f9c2 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 12:53:55 +0200 Subject: [PATCH 49/53] fix: broken wallet version layout settings in the extension on a small screen --- packages/uikit/src/pages/settings/LedgerIndexes.tsx | 5 ++--- packages/uikit/src/pages/settings/Version.tsx | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/uikit/src/pages/settings/LedgerIndexes.tsx b/packages/uikit/src/pages/settings/LedgerIndexes.tsx index 125da30a4..24babf8fe 100644 --- a/packages/uikit/src/pages/settings/LedgerIndexes.tsx +++ b/packages/uikit/src/pages/settings/LedgerIndexes.tsx @@ -119,9 +119,8 @@ export const LedgerIndexesPageContent: FC<{ # {derivationIndex + 1} - {toShortValue(formatAddress(balance.address))} -  ·  - {toFormattedTonBalance(balance.tonBalance)} TON + {toShortValue(formatAddress(balance.address)) + ' '}· + {' ' + toFormattedTonBalance(balance.tonBalance)} TON {balance.hasJettons && t('wallet_version_and_tokens')} diff --git a/packages/uikit/src/pages/settings/Version.tsx b/packages/uikit/src/pages/settings/Version.tsx index 96dbd4610..9b130741f 100644 --- a/packages/uikit/src/pages/settings/Version.tsx +++ b/packages/uikit/src/pages/settings/Version.tsx @@ -139,9 +139,9 @@ export const WalletVersionPageContent: FC<{ {walletVersionText(wallet.version)} - {toShortValue(formatAddress(wallet.address))} -  ·  - {toFormattedTonBalance(wallet.tonBalance)} TON + {toShortValue(formatAddress(wallet.address)) + ' '}· + {' ' + toFormattedTonBalance(wallet.tonBalance)} +  TON {wallet.hasJettons && t('wallet_version_and_tokens')} From e44e5ada37423d0d82e185519505e0675e652c30 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 13:20:19 +0200 Subject: [PATCH 50/53] fix: ledger storage naming --- packages/core/src/entries/account.ts | 8 ++++---- packages/core/src/service/walletService.ts | 1 - .../uikit/src/components/create/ChoseLedgerIndexes.tsx | 6 +++--- packages/uikit/src/components/desktop/aside/AsideMenu.tsx | 2 +- packages/uikit/src/pages/settings/LedgerIndexes.tsx | 6 +++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index 0b491ab8a..bbb8f5d82 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -130,7 +130,7 @@ export class AccountLedger extends Clonable implements IAccount { get derivations(): DerivationItem[] { return this.addedDerivationsIndexes.map( - index => this.allAvailabelDerivations.find(d => d.index === index)! + index => this.allAvailableDerivations.find(d => d.index === index)! ); } @@ -143,13 +143,13 @@ export class AccountLedger extends Clonable implements IAccount { public emoji: string, public activeDerivationIndex: number, public addedDerivationsIndexes: number[], - public readonly allAvailabelDerivations: DerivationItem[] + public readonly allAvailableDerivations: DerivationItem[] ) { super(); if ( addedDerivationsIndexes.some(index => - allAvailabelDerivations.every(d => d.index !== index) + allAvailableDerivations.every(d => d.index !== index) ) ) { throw new Error('Derivations not found'); @@ -223,7 +223,7 @@ export class AccountLedger extends Clonable implements IAccount { } if ( addedDerivationsIndexes.some(index => - this.allAvailabelDerivations.every(d => d.index !== index) + this.allAvailableDerivations.every(d => d.index !== index) ) ) { throw new Error('Derivations not found'); diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index d3eb84990..27fc6bfa0 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -249,7 +249,6 @@ export const accountByLedger = ( name: string, emoji: string ): AccountLedger => { - // const zeroAccPublicKey = walletsInfo[0].publicKey.toString('hex'); return new AccountLedger( accountId, name, diff --git a/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx b/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx index 1346c9318..8ba9a3f59 100644 --- a/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx +++ b/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx @@ -67,7 +67,7 @@ export const ChoseLedgerIndexes: FC<{ const { t } = useTranslation(); const { data: balances } = useTonWalletsBalances( - account.allAvailabelDerivations.map( + account.allAvailableDerivations.map( d => d.tonWallets.find(w => w.id === d.activeTonWalletId)!.rawAddress ) ); @@ -89,12 +89,12 @@ export const ChoseLedgerIndexes: FC<{

{t('choose_wallets_title')}

{t('choose_wallets_subtitle')} {!balances ? ( - + ) : ( <> {balances.map((balance, index) => { - const derivationIndex = account.allAvailabelDerivations[index].index; + const derivationIndex = account.allAvailableDerivations[index].index; return ( diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx index e33c4772b..104358a3a 100644 --- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx @@ -221,7 +221,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ( {/*show settings only for non-legacy added ledger accounts*/} - {account.allAvailabelDerivations.length > 1 && ( + {account.allAvailableDerivations.length > 1 && ( { e.preventDefault(); diff --git a/packages/uikit/src/pages/settings/LedgerIndexes.tsx b/packages/uikit/src/pages/settings/LedgerIndexes.tsx index 24babf8fe..7f56be888 100644 --- a/packages/uikit/src/pages/settings/LedgerIndexes.tsx +++ b/packages/uikit/src/pages/settings/LedgerIndexes.tsx @@ -63,7 +63,7 @@ export const LedgerIndexesPageContent: FC<{ const navigate = useNavigate(); const { data: balances } = useTonWalletsBalances( - account.allAvailabelDerivations.map( + account.allAvailableDerivations.map( d => d.tonWallets.find(w => w.id === d.activeTonWalletId)!.rawAddress ) ); @@ -97,7 +97,7 @@ export const LedgerIndexesPageContent: FC<{ }; if (!balances) { - return ; + return ; } const isLoading = @@ -107,7 +107,7 @@ export const LedgerIndexesPageContent: FC<{ return ( {balances.map((balance, index) => { - const derivationIndex = account.allAvailabelDerivations[index].index; + const derivationIndex = account.allAvailableDerivations[index].index; const isDerivationAdded = account.derivations.some( d => d.index === derivationIndex From a33c7e43b214c88ad68bf4adc7aaa2bd49020abe Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 13:27:40 +0200 Subject: [PATCH 51/53] fix: ledger storage naming 2 --- packages/uikit/src/components/settings/AccountSettings.tsx | 4 ++-- .../src/desktop-pages/settings/DesktopWalletSettingsPage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index 476557806..209334cef 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -54,7 +54,7 @@ const SingleAccountSettings = () => { } // check available derivations length to filter and keep only non-legacy added ledger accounts - if (account.type === 'ledger' && account.allAvailabelDerivations.length > 1) { + if (account.type === 'ledger' && account.allAvailableDerivations.length > 1) { items.push({ name: t('settings_ledger_indexes'), icon: `# ${account.activeDerivationIndex + 1}`, @@ -171,7 +171,7 @@ const MultipleAccountSettings = () => { } // check available derivations length to filter and keep only non-legacy added ledger accounts - if (account.type === 'ledger' && account.allAvailabelDerivations.length > 1) { + if (account.type === 'ledger' && account.allAvailableDerivations.length > 1) { items.push({ name: t('settings_ledger_indexes'), icon: `# ${account.activeDerivationIndex + 1}`, diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx index bf442188a..a19f05f31 100644 --- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx +++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx @@ -63,7 +63,7 @@ export const DesktopWalletSettingsPage = () => { // check available derivations length to filter and keep only non-legacy added ledger accounts const canChangeLedgerIndex = - account.type === 'ledger' && account.allAvailabelDerivations.length > 1; + account.type === 'ledger' && account.allAvailableDerivations.length > 1; const activeWallet = account.activeTonWallet; return ( From a1758aa3b3333c8d8ff3ff0eb73a0e1368a21c08 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 13:45:30 +0200 Subject: [PATCH 52/53] fix: ledger account breaks when first deriviation is not selected --- packages/core/src/entries/account.ts | 4 ++++ packages/core/src/service/walletService.ts | 2 +- packages/uikit/src/state/ledger.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index bbb8f5d82..efa235aec 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -155,6 +155,10 @@ export class AccountLedger extends Clonable implements IAccount { throw new Error('Derivations not found'); } + if (!addedDerivationsIndexes.includes(activeDerivationIndex)) { + throw new Error('Active derivation not found'); + } + this.addedDerivationsIndexes = [...new Set(addedDerivationsIndexes)]; } diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 27fc6bfa0..701f758c4 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -253,7 +253,7 @@ export const accountByLedger = ( accountId, name, emoji, - walletsInfo[0].accountIndex, + walletsIndexesToAdd[0], walletsIndexesToAdd, walletsInfo.map(item => ({ index: item.accountIndex, diff --git a/packages/uikit/src/state/ledger.ts b/packages/uikit/src/state/ledger.ts index 88a261ee6..52f9ddaa1 100644 --- a/packages/uikit/src/state/ledger.ts +++ b/packages/uikit/src/state/ledger.ts @@ -172,14 +172,16 @@ export const useAddLedgerAccountMutation = () => { form.emoji ); + await accStorage.addAccountToState(newAccount); + await accStorage.setActiveAccountId(newAccount.id); + // remove separately-added in legacy app version ledger wallets that should be replaced with a single account with subwallets await accStorage.removeAccountsFromState( - form.allWallets.map(w => w.publicKey.toString('hex')) + form.allWallets + .filter(w => w.publicKey.toString('hex') !== newAccount.id) + .map(w => w.publicKey.toString('hex')) ); - await accStorage.addAccountToState(newAccount); - await accStorage.setActiveAccountId(newAccount.id); - await client.invalidateQueries([QueryKey.account]); navigate(AppRoute.home); From 0d6ea564033bf1b8d19a88c1d3c929055918e75a Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 2 Aug 2024 14:09:09 +0200 Subject: [PATCH 53/53] fix: web settings items --- .../components/settings/AccountSettings.tsx | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx index 209334cef..dc63a8e4c 100644 --- a/packages/uikit/src/components/settings/AccountSettings.tsx +++ b/packages/uikit/src/components/settings/AccountSettings.tsx @@ -10,6 +10,7 @@ import { AppsIcon, ListOfTokensIcon, LogOutIcon, + RecoveryPhraseIcon, SaleBadgeIcon, SecurityIcon, SettingsProIcon, @@ -20,7 +21,6 @@ import { useActiveWallet, useAccountsState, useActiveAccount } from '../../state import { useWalletNftList } from '../../state/nft'; const SingleAccountSettings = () => { - const [deleteAccount, setDeleteAccount] = useState(false); const { t } = useTranslation(); const navigate = useNavigate(); const account = useActiveAccount(); @@ -29,21 +29,15 @@ const SingleAccountSettings = () => { 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: , action: () => navigate(relative(SettingsRoute.recovery)) }); - }*/ + } if (account.type === 'mnemonic' || account.type === 'ton-only') { items.push({ @@ -96,11 +90,6 @@ const SingleAccountSettings = () => { icon: , action: () => navigate(relative(WalletSettingsRoute.connectedApps)) }); - items.push({ - name: t('Delete_wallet_data'), - icon: , - action: () => setDeleteAccount(true) - }); return items; }, [t, navigate, account, jettons, nft]); @@ -108,10 +97,6 @@ const SingleAccountSettings = () => { return ( <> - setDeleteAccount(false)} - /> ); }; @@ -126,6 +111,8 @@ const MultipleAccountSettings = () => { const { proFeatures } = useAppContext(); const account = useActiveAccount(); + const [deleteAccount, setDeleteAccount] = useState(false); + const accountItems = useMemo(() => { const items: SettingsItem[] = [ { @@ -154,13 +141,13 @@ const MultipleAccountSettings = () => { const mainItems = useMemo(() => { const items: SettingsItem[] = []; - /*if (wallet.auth == null) { + if (account.type === 'mnemonic') { items.push({ name: t('settings_recovery_phrase'), icon: , action: () => navigate(relative(SettingsRoute.recovery)) }); - }*/ + } if (account.type === 'mnemonic' || account.type === 'ton-only') { items.push({ @@ -205,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]); @@ -212,14 +204,18 @@ const MultipleAccountSettings = () => { <> + setDeleteAccount(false)} + /> ); }; export const AccountSettings = () => { - const wallets = useAccountsState(); + const accounts = useAccountsState(); - if (wallets.length > 1) { + if (accounts.length > 1) { return ; } else { return ;