diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx
index 6499b5728..e655b4bb8 100644
--- a/apps/desktop/src/app/App.tsx
+++ b/apps/desktop/src/app/App.tsx
@@ -326,8 +326,7 @@ export const Loader: FC = () => {
tgAuthBotId: REACT_APP_TG_BOT_ID,
stonfiReferralAddress: REACT_APP_STONFI_REFERRAL_ADDRESS
},
- defaultWalletVersion:
- isV5R1Enabled(config) || devSettings.enableV5 ? WalletVersion.V5R1 : WalletVersion.V4R2
+ defaultWalletVersion: WalletVersion.V5R1
};
return (
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 483a47a52..7ef079974 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, WalletVersion } from "@tonkeeper/core/dist/entries/wallet";
+import { getApiConfig } from '@tonkeeper/core/dist/entries/network';
+import { WalletVersion } from "@tonkeeper/core/dist/entries/wallet";
import { InnerBody, useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body';
import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification';
import { Footer, FooterGlobalStyle } from '@tonkeeper/uikit/dist/components/Footer';
@@ -38,8 +38,8 @@ 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 { isV5R1Enabled, useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint";
-import { useActiveAccountQuery, useAccountsStateQuery } from "@tonkeeper/uikit/dist/state/wallet";
+import { useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint";
+import { useActiveAccountQuery, useAccountsStateQuery, useActiveTonNetwork } from "@tonkeeper/uikit/dist/state/wallet";
import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle';
import React, { FC, PropsWithChildren, Suspense, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -49,6 +49,8 @@ import { BrowserAppSdk } from './libs/appSdk';
import { useAnalytics, useAppHeight, useAppWidth } from './libs/hooks';
import { useUserLanguage } from "@tonkeeper/uikit/dist/state/language";
import { useDevSettings } from "@tonkeeper/uikit/dist/state/dev";
+import { ModalsRoot } from "@tonkeeper/uikit/dist/components/ModalsRoot";
+import { Account } from "@tonkeeper/core/dist/entries/account";
const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import'));
const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings'));
@@ -178,8 +180,9 @@ const Wrapper = styled(FullSizeWrapper)<{ standalone: boolean }>`
`;
export const Loader: FC = () => {
- const { data: activeWallet, isLoading: activeWalletLoading } = useActiveAccountQuery();
- const { data: wallets, isLoading: isWalletsLoading } = useAccountsStateQuery();
+ const network = useActiveTonNetwork();
+ const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery();
+ const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery();
const { data: lang, isLoading: isLangLoading } = useUserLanguage();
const { data: fiat } = useUserFiat();
const { data: devSettings } = useDevSettings();
@@ -194,7 +197,7 @@ export const Loader: FC = () => {
const tonendpoint = useTonendpoint({
targetEnv: TARGET_ENV,
build: sdk.version,
- network: activeWallet?.network,
+ network,
lang
});
const { data: config } = useTonenpointConfig(tonendpoint);
@@ -202,11 +205,11 @@ export const Loader: FC = () => {
const navigate = useNavigate();
useAppHeight();
- const { data: tracker } = useAnalytics(activeWallet || undefined, wallets, sdk.version);
+ const { data: tracker } = useAnalytics(activeAccount || undefined, accounts, sdk.version);
useEffect(() => {
if (
- activeWallet &&
+ activeAccount &&
lang &&
i18n.language !== localizationText(lang)
) {
@@ -214,7 +217,7 @@ export const Loader: FC = () => {
i18n.changeLanguage(localizationText(lang))
);
}
- }, [activeWallet, i18n]);
+ }, [activeAccount, i18n]);
if (
isWalletsLoading ||
@@ -228,7 +231,6 @@ export const Loader: FC = () => {
return ;
}
- const network = activeWallet?.network ?? Network.MAINNET;
const context: IAppContext = {
api: getApiConfig(config, network),
fiat,
@@ -238,7 +240,7 @@ export const Loader: FC = () => {
extension: false,
proFeatures: false,
ios,
- defaultWalletVersion: (isV5R1Enabled(config) || devSettings.enableV5) ? WalletVersion.V5R1 : WalletVersion.V4R2
+ defaultWalletVersion: WalletVersion.V5R1
};
return (
@@ -248,11 +250,12 @@ export const Loader: FC = () => {
value={() => navigate(AppRoute.home, { replace: true })}
>
-
+
>}>
+
@@ -261,10 +264,10 @@ export const Loader: FC = () => {
};
export const Content: FC<{
- activeWallet?: WalletState | null;
+ activeAccount?: Account | null;
lock: boolean;
standalone: boolean;
-}> = ({ activeWallet, lock, standalone }) => {
+}> = ({ activeAccount, lock, standalone }) => {
const location = useLocation();
useWindowsScroll();
useAppWidth(standalone);
@@ -298,7 +301,7 @@ export const Content: FC<{
);
}
- if (!activeWallet || location.pathname.startsWith(AppRoute.import)) {
+ if (!activeAccount || location.pathname.startsWith(AppRoute.import)) {
return (
}>
diff --git a/apps/web/src/libs/hooks.ts b/apps/web/src/libs/hooks.ts
index 1b3702f1b..247f8a6d6 100644
--- a/apps/web/src/libs/hooks.ts
+++ b/apps/web/src/libs/hooks.ts
@@ -1,11 +1,12 @@
import { useQuery } from '@tanstack/react-query';
-import { WalletsState, WalletState } from "@tonkeeper/core/dist/entries/wallet";
+import { Account } from "@tonkeeper/core/dist/entries/account";
import { throttle } from '@tonkeeper/core/dist/utils/common';
import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics';
import { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web';
import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag';
import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey';
import { useEffect } from 'react';
+import { useActiveTonNetwork } from "@tonkeeper/uikit/dist/state/wallet";
export const useAppHeight = () => {
useEffect(() => {
@@ -48,12 +49,13 @@ export const useAppWidth = (standalone: boolean) => {
};
export const useAnalytics = (
- activeWallet?: WalletState,
- wallets?: WalletsState,
+ activeAccount?: Account,
+ accounts?: Account[],
version?: string
) => {
+ const network = useActiveTonNetwork();
return useQuery(
- [QueryKey.analytics],
+ [QueryKey.analytics, network],
async () => {
const tracker = new AnalyticsGroup(
new AptabaseWeb(
@@ -64,12 +66,17 @@ export const useAnalytics = (
new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID)
);
- tracker.init('Web', toWalletType(activeWallet),
- activeWallet,
- wallets,);
+ tracker.init({
+ application: 'Web',
+ walletType: toWalletType(activeAccount?.activeTonWallet),
+ activeAccount: activeAccount!,
+ accounts: accounts!,
+ network
+ }
+ );
return tracker;
},
- { enabled: wallets != null && activeWallet !== undefined }
+ { enabled: accounts != null && activeAccount !== undefined }
);
};
diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts
index d0d1adeab..216c69f41 100644
--- a/packages/core/src/entries/account.ts
+++ b/packages/core/src/entries/account.ts
@@ -14,6 +14,7 @@ export interface DeprecatedAccountState {
export type AccountId = string;
export interface IAccount {
+ id: AccountId;
name: string;
emoji: string;
@@ -51,6 +52,9 @@ export class AccountTonMnemonic extends Clonable implements IAccount {
return this.tonWallets.find(w => w.id === this.activeTonWalletId)!;
}
+ /**
+ * @param id ton public key hex string without 0x corresponding to the mnemonic
+ */
constructor(
public readonly id: AccountId,
public name: string,
@@ -75,7 +79,12 @@ export class AccountTonMnemonic extends Clonable implements IAccount {
}
addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
- this.tonWallets.push(wallet);
+ const walletExists = this.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.tonWallets = this.tonWallets.concat(wallet);
+ } else {
+ this.tonWallets[walletExists] = wallet;
+ }
}
removeTonWalletFromActiveDerivation(walletId: WalletId) {
@@ -90,6 +99,9 @@ export class AccountTonMnemonic extends Clonable implements IAccount {
}
setActiveTonWallet(walletId: WalletId) {
+ if (this.tonWallets.every(w => w.id !== walletId)) {
+ throw new Error('Wallet not found');
+ }
this.activeTonWalletId = walletId;
}
}
@@ -116,6 +128,9 @@ export class AccountLedger extends Clonable implements IAccount {
)!;
}
+ /**
+ * @param id index 0 derivation ton public key hex string without 0x
+ */
constructor(
public readonly id: AccountId,
public name: string,
@@ -143,7 +158,12 @@ export class AccountLedger extends Clonable implements IAccount {
}
addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
- this.activeDerivation.tonWallets.push(wallet);
+ const walletExists = this.activeDerivation.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.activeDerivation.tonWallets = this.activeDerivation.tonWallets.concat(wallet);
+ } else {
+ this.activeDerivation.tonWallets[walletExists] = wallet;
+ }
}
removeTonWalletFromActiveDerivation(walletId: WalletId) {
@@ -161,8 +181,8 @@ export class AccountLedger extends Clonable implements IAccount {
setActiveTonWallet(walletId: WalletId) {
for (const derivation of this.derivations) {
- const index = derivation.tonWallets.findIndex(w => w.id === walletId);
- if (index !== -1) {
+ const walletInDerivation = derivation.tonWallets.some(w => w.id === walletId);
+ if (walletInDerivation) {
derivation.activeTonWalletId = walletId;
this.activeDerivationIndex = derivation.index;
return;
@@ -188,6 +208,9 @@ export class AccountKeystone extends Clonable implements IAccount {
return this.tonWallet;
}
+ /**
+ * @param id ton public key hex string without 0x
+ */
constructor(
public readonly id: AccountId,
public name: string,
@@ -236,6 +259,9 @@ export class AccountTonOnly extends Clonable implements IAccount {
return this.tonWallets.find(w => w.id === this.activeTonWalletId)!;
}
+ /**
+ * @param id ton public key hex string without 0x
+ */
constructor(
public readonly id: AccountId,
public name: string,
@@ -260,7 +286,12 @@ export class AccountTonOnly extends Clonable implements IAccount {
}
addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
- this.tonWallets.push(wallet);
+ const walletExists = this.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.tonWallets = this.tonWallets.concat(wallet);
+ } else {
+ this.tonWallets[walletExists] = wallet;
+ }
}
removeTonWalletFromActiveDerivation(walletId: WalletId) {
@@ -275,6 +306,10 @@ export class AccountTonOnly extends Clonable implements IAccount {
}
setActiveTonWallet(walletId: WalletId) {
+ if (this.tonWallets.every(w => w.id !== walletId)) {
+ throw new Error('Wallet not found');
+ }
+
this.activeTonWalletId = walletId;
}
}
diff --git a/packages/core/src/service/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts
index 54c6e7f1b..fbf9a2b0f 100644
--- a/packages/core/src/service/accountsStorage.ts
+++ b/packages/core/src/service/accountsStorage.ts
@@ -18,6 +18,7 @@ import { DeprecatedAccountState } from '../entries/account';
import { AuthState, DeprecatedAuthState } from '../entries/password';
import { assertUnreachable, notNullish } from '../utils/types';
import {
+ getFallbackAccountEmoji,
getFallbackDerivationItemEmoji,
getFallbackTonStandardWalletEmoji,
getWalletNameAddress
@@ -69,6 +70,10 @@ export class AccountsStorage {
};
setActiveAccountId = async (activeAccountId: AccountId | null) => {
+ const accounts = await this.getAccounts();
+ if (accounts.every(a => a.id !== activeAccountId)) {
+ throw new Error('Account not found');
+ }
await this.storage.set(AppKey.ACTIVE_ACCOUNT_ID, activeAccountId);
};
@@ -78,9 +83,15 @@ export class AccountsStorage {
addAccountsToState = async (accounts: Account[]) => {
const state = await this.getAccounts();
- await this.setAccounts(
- state.concat(accounts.filter(acc => state.every(a => a.id !== acc.id)))
- );
+ accounts.forEach(account => {
+ const existingAccIndex = state.findIndex(a => a.id === account.id);
+ if (existingAccIndex !== -1) {
+ state[existingAccIndex] = account;
+ return;
+ }
+ state.push(account);
+ });
+ await this.setAccounts(state);
};
/**
@@ -121,6 +132,14 @@ export class AccountsStorage {
await this.setAccounts(state.filter(w => w.id !== id));
};
+ async getNewAccountNameAndEmoji(accountId: AccountId) {
+ const existingAccounts = await this.getAccounts();
+ const existingAccount = existingAccounts.find(a => a.id === accountId);
+ const name = existingAccount?.name || 'Account ' + (existingAccounts.length + 1);
+ const emoji = existingAccount?.emoji || getFallbackAccountEmoji(accountId);
+ return { name, emoji };
+ }
+
private migrateToActiveAccountIdState = async (): Promise => {
const state = await this.storage.get(AppKey.DEPRECATED_ACCOUNT);
if (!state || !state.activePublicKey) {
diff --git a/packages/core/src/service/devStorage.ts b/packages/core/src/service/devStorage.ts
index 5a7eff0e0..fbcd909e2 100644
--- a/packages/core/src/service/devStorage.ts
+++ b/packages/core/src/service/devStorage.ts
@@ -3,12 +3,10 @@ import { Network } from '../entries/network';
import { AppKey } from '../Keys';
export interface DevSettings {
- enableV5: boolean;
tonNetwork: Network;
}
const defaultDevSettings: DevSettings = {
- enableV5: false,
tonNetwork: Network.MAINNET
};
diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts
index 710e76e89..80877bb7f 100644
--- a/packages/core/src/service/walletService.ts
+++ b/packages/core/src/service/walletService.ts
@@ -18,9 +18,12 @@ import {
AccountTonMnemonic,
AccountTonOnly
} from '../entries/account';
+import { IStorage } from '../Storage';
+import { accountsStorage } from './accountsStorage';
export const createStandardTonAccountByMnemonic = async (
appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ storage: IStorage,
mnemonic: string[],
options: {
versions?: WalletVersion[];
@@ -54,10 +57,12 @@ export const createStandardTonAccountByMnemonic = async (
walletAuth = options.auth;
}
+ const { name, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(publicKey);
+
return new AccountTonMnemonic(
publicKey,
- getFallbackAccountName(publicKey),
- getFallbackAccountEmoji(publicKey),
+ name,
+ emoji,
walletAuth,
tonWallets[0].rawAddress,
tonWallets.map(w => ({
@@ -160,6 +165,7 @@ export const getWalletsAddresses = (
export const accountBySignerQr = async (
appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ storage: IStorage,
qrCode: string
): Promise => {
if (!qrCode.startsWith('tonkeeper://signer')) {
@@ -182,10 +188,14 @@ export const accountBySignerQr = async (
// TODO support multiple wallets versions configuration
const active = await findWalletAddress(appContext, publicKey);
+ const { name: fallbackName, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ publicKey
+ );
+
return new AccountTonOnly(
publicKey,
- name || getFallbackAccountName(publicKey),
- getFallbackAccountEmoji(publicKey),
+ name || fallbackName,
+ emoji,
{ kind: 'signer' },
active.rawAddress,
[
@@ -203,15 +213,20 @@ export const accountBySignerQr = async (
export const accountBySignerDeepLink = async (
appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ storage: IStorage,
publicKey: string,
name: string | null
): Promise => {
const active = await findWalletAddress(appContext, publicKey);
+ const { name: fallbackName, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ publicKey
+ );
+
return new AccountTonOnly(
publicKey,
- name || getFallbackAccountName(publicKey),
- getFallbackAccountEmoji(publicKey),
+ name || fallbackName,
+ emoji,
{ kind: 'signer-deeplink' },
active.rawAddress,
[
@@ -228,6 +243,7 @@ export const accountBySignerDeepLink = async (
};
export const accountByLedger = (
+ accountId: string,
walletsInfo: {
address: string;
publicKey: Buffer;
@@ -236,9 +252,9 @@ export const accountByLedger = (
name: string,
emoji: string
): AccountLedger => {
- const zeroAccPublicKey = walletsInfo[0].publicKey.toString('hex');
+ // const zeroAccPublicKey = walletsInfo[0].publicKey.toString('hex');
return new AccountLedger(
- zeroAccPublicKey,
+ accountId,
name,
emoji,
walletsInfo[0].accountIndex,
@@ -267,7 +283,7 @@ export const accountByLedger = (
);
};
-export const accountByKeystone = (ur: UR): AccountKeystone => {
+export const accountByKeystone = async (ur: UR, storage: IStorage): Promise => {
const account = parseTonAccount(ur);
const contact = WalletContractV4.create({
workchain: 0,
@@ -277,20 +293,18 @@ export const accountByKeystone = (ur: UR): AccountKeystone => {
const pathInfo =
account.path && account.xfp ? { path: account.path, mfp: account.xfp } : undefined;
- return new AccountKeystone(
- account.publicKey,
- getFallbackAccountName(account.publicKey),
- getFallbackAccountEmoji(account.publicKey),
- pathInfo,
- {
- 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)
- }
+ const { name, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ account.publicKey
);
+
+ return new AccountKeystone(account.publicKey, name, emoji, pathInfo, {
+ 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)
+ });
};
export function getFallbackAccountEmoji(publicKey: string) {
diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx
index 8ddba6556..d4080ab99 100644
--- a/packages/uikit/src/components/Header.tsx
+++ b/packages/uikit/src/components/Header.tsx
@@ -9,7 +9,8 @@ import {
useActiveWallet,
useAccountsState,
useMutateActiveTonWallet,
- useActiveTonNetwork
+ useActiveTonNetwork,
+ useActiveAccount
} from '../state/wallet';
import { DropDown } from './DropDown';
import { DoneIcon, DownIcon, PlusIcon, SettingsIcon } from './Icon';
@@ -20,7 +21,8 @@ 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 { TonWalletStandard, walletVersionText } from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
const Block = styled.div<{
center?: boolean;
@@ -101,6 +103,7 @@ const Icon = styled.span`
padding-left: 0.5rem;
color: ${props => props.theme.accentBlue};
display: flex;
+ margin-left: auto;
`;
const Row = styled.div`
@@ -118,10 +121,40 @@ 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;
+`;
+
+const ColumnTextStyled = styled(ColumnText)`
+ flex-grow: 0;
+`;
+
+const DropDownContainerStyle = createGlobalStyle`
+ .header-dd-container {
+ margin-left: -135px;
+ width: 270px;
+ }
+`;
+
const WalletRow: FC<{
+ account: Account;
walletState: TonWalletStandard;
onClose: () => void;
-}> = ({ walletState, onClose }) => {
+ badge?: string;
+}> = ({ account, walletState, onClose, badge }) => {
const network = useActiveTonNetwork();
const { mutate } = useMutateActiveTonWallet();
const address = toShortValue(formatAddress(walletState.rawAddress, network));
@@ -134,15 +167,16 @@ const WalletRow: FC<{
onClose();
}}
>
-
-
-
+
+
+
+ {badge && {badge}}
{activeWallet?.id === walletState.id ? (
) : undefined}
-
+
);
};
@@ -153,13 +187,41 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({
}) => {
const navigate = useNavigate();
const { t } = useTranslation();
- const wallets = useAccountsState().flatMap(a => a.allTonWallets);
-
- if (!wallets) {
+ const accountsWallets: { wallet: TonWalletStandard; account: Account; badge?: string }[] =
+ useAccountsState().flatMap(a => {
+ if (a.type === 'ledger') {
+ return a.derivations.map(
+ d =>
+ ({
+ wallet: d.tonWallets.find(w => w.id === d.activeTonWalletId)!,
+ account: a,
+ badge: `LEDGER #${d.index}`
+ } as { wallet: TonWalletStandard; account: Account; badge?: string })
+ );
+ }
+
+ return a.allTonWallets.map(
+ w =>
+ ({
+ wallet: w,
+ account: a,
+ badge:
+ a.type === 'ton-only'
+ ? 'SIGNER'
+ : a.type === 'keystone'
+ ? 'KEYSTONE'
+ : a.allTonWallets.length > 1
+ ? walletVersionText(w.version)
+ : undefined
+ } as { wallet: TonWalletStandard; account: Account; badge?: string })
+ );
+ });
+
+ if (!accountsWallets) {
return null;
}
- if (wallets.length === 1) {
+ if (accountsWallets.length === 1) {
return (
{
@@ -176,8 +238,14 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({
} else {
return (
<>
- {wallets.map(wallet => (
-
+ {accountsWallets.map(({ wallet, account, badge }) => (
+
))}
void; onCreate: () => void }> = ({
}
};
+const TitleStyled = styled(Title)`
+ align-items: center;
+`;
+
export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => {
- const { t } = useTranslation();
- const wallet = useActiveWallet();
+ const account = useActiveAccount();
const [isOpen, setOpen] = useState(false);
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 (
+
(
setOpen(true)} />
)}
+ containerClassName="header-dd-container"
>
-
- {shouldShowIcon && }
- {wallet.name ? wallet.name : t('wallet_title')}
+
+ {shouldShowIcon && }
+ {account.name}
+ {accountBadge && {accountBadge}}
-
+
{showQrScan && }
diff --git a/packages/uikit/src/components/create/ChoseWalletVersions.tsx b/packages/uikit/src/components/create/ChoseWalletVersions.tsx
index 5e9a9e395..2c024ac97 100644
--- a/packages/uikit/src/components/create/ChoseWalletVersions.tsx
+++ b/packages/uikit/src/components/create/ChoseWalletVersions.tsx
@@ -9,7 +9,7 @@ import {
} from '@tonkeeper/core/dist/entries/wallet';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC, useEffect, useLayoutEffect, useState } from 'react';
-import { useStandardTonWalletVersions } from '../../state/wallet';
+import { useAccountState, useStandardTonWalletVersions } from '../../state/wallet';
import { SkeletonList } from '../Skeleton';
import { toFormattedTonBalance } from '../../hooks/balance';
import { Checkbox } from '../fields/Checkbox';
@@ -76,6 +76,7 @@ export const ChoseWalletVersions: FC<{
const [publicKey, setPublicKey] = useState(undefined);
const { data: wallets } = useStandardTonWalletVersions(publicKey);
const [checkedVersions, setCheckedVersions] = useState([]);
+ const accountState = useAccountState(publicKey);
useEffect(() => {
mnemonicToWalletKey(mnemonic).then(keypair =>
@@ -85,6 +86,10 @@ export const ChoseWalletVersions: FC<{
useLayoutEffect(() => {
if (wallets) {
+ if (accountState) {
+ return setCheckedVersions(accountState.allTonWallets.map(w => w.version));
+ }
+
const versionsToCheck = wallets
.filter(w => w.tonBalance || w.hasJettons)
.map(w => w.version);
@@ -93,7 +98,7 @@ export const ChoseWalletVersions: FC<{
}
setCheckedVersions(versionsToCheck);
}
- }, [wallets]);
+ }, [wallets, accountState]);
const toggleVersion = (version: WalletVersion, isChecked: boolean) => {
setCheckedVersions(state =>
diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
index fb2419af6..71343a1d2 100644
--- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
+++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
@@ -198,7 +198,7 @@ export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = (
onClick={e => {
e.preventDefault();
e.stopPropagation();
- openWalletVersionSettings({ account });
+ openWalletVersionSettings({ accountId: account.id });
}}
isShown={isHovered}
>
diff --git a/packages/uikit/src/components/desktop/header/DesktopHeader.tsx b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
index f6d43e326..19718b2dc 100644
--- a/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
+++ b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
@@ -9,15 +9,18 @@ import { useDisclosure } from '../../../hooks/useDisclosure';
import { usePreFetchRates } from '../../../state/rates';
import { useTonendpointBuyMethods } from '../../../state/tonendpoint';
import { fallbackRenderOver } from '../../Error';
-import { ArrowDownIcon, ArrowUpIcon, PlusIcon, PlusIconSmall } from "../../Icon";
-import { Num2 } from '../../Text';
+import { ArrowDownIcon, ArrowUpIcon, PlusIconSmall } from '../../Icon';
+import { Body2Class, Num2 } from '../../Text';
import { Button } from '../../fields/Button';
import { IconButton } from '../../fields/IconButton';
import { Link } from 'react-router-dom';
-import { AppProRoute } from '../../../libs/routes';
+import { AppProRoute, AppRoute, SettingsRoute } from '../../../libs/routes';
import { BuyNotification } from '../../home/BuyAction';
import { Skeleton } from '../../shared/Skeleton';
-import { useWalletTotalBalance } from "../../../state/asset";
+import { useWalletTotalBalance } from '../../../state/asset';
+import { hexToRGBA } from '../../../libs/css';
+import { useActiveTonNetwork } from '../../../state/wallet';
+import { Network } from '@tonkeeper/core/dist/entries/network';
const DesktopHeaderStyled = styled.div`
padding-left: 1rem;
@@ -96,6 +99,26 @@ const LinkStyled = styled(Link)`
text-decoration: unset;
`;
+const TestnetBadge = styled(Link)`
+ background: ${p => hexToRGBA(p.theme.accentRed, 0.16)};
+ color: ${p => p.theme.accentRed};
+ padding: 4px 8px;
+ border-radius: ${p => p.theme.corner2xSmall};
+ border: none;
+ text-transform: uppercase;
+ margin-left: 10px;
+ margin-right: auto;
+ text-decoration: none;
+
+ transition: background 0.15s ease-in-out;
+
+ &:hover {
+ background: ${p => hexToRGBA(p.theme.accentRed, 0.36)};
+ }
+
+ ${Body2Class};
+`;
+
const DesktopHeaderPayload = () => {
usePreFetchRates();
const { fiat } = useAppContext();
@@ -104,6 +127,7 @@ const DesktopHeaderPayload = () => {
const { isOpen, onClose, onOpen } = useDisclosure();
const { data: buy } = useTonendpointBuyMethods();
const { t } = useTranslation();
+ const network = useActiveTonNetwork();
return (
@@ -114,7 +138,9 @@ const DesktopHeaderPayload = () => {
{formatFiatCurrency(fiat, balance || 0)}
)}
-
+ {network === Network.TESTNET && (
+ Testnet
+ )}
();
+const { hook, control } = createModalControl<{ accountId?: AccountId }>();
export const useWalletVersionSettingsNotification = hook;
@@ -17,7 +17,10 @@ export const WalletVersionSettingsNotification = () => {
return (
onClose()}>
{() => (
-
+
)}
);
diff --git a/packages/uikit/src/pages/import/Ledger.tsx b/packages/uikit/src/pages/import/Ledger.tsx
index 35b7347b4..fcceee506 100644
--- a/packages/uikit/src/pages/import/Ledger.tsx
+++ b/packages/uikit/src/pages/import/Ledger.tsx
@@ -3,9 +3,9 @@ import { Button } from '../../components/fields/Button';
import { useTranslation } from '../../hooks/translation';
import {
LedgerAccount,
- useAddLedgerAccountsMutation,
+ useAddLedgerAccountMutation,
useConnectLedgerMutation,
- useLedgerAccounts
+ useLedgerWallets
} from '../../state/ledger';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { LedgerTonTransport } from '@tonkeeper/core/dist/service/ledger/connector';
@@ -20,8 +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 { getFallbackAccountEmoji } from '@tonkeeper/core/dist/service/walletService';
-import { toFormattedTonBalance } from "../../hooks/balance";
+import { toFormattedTonBalance } from '../../hooks/balance';
const ConnectLedgerWrapper = styled.div`
display: flex;
@@ -155,15 +154,16 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: ()
}) => {
const { t } = useTranslation();
const totalAccounts = 10;
- const { mutate: getLedgerAccounts, data: ledgerAccounts } = useLedgerAccounts(totalAccounts);
+ const { mutateAsync: getLedgerWallets, data: ledgerAccountData } =
+ useLedgerWallets(totalAccounts);
const [selectedIndexes, setSelectedIndexes] = useState>({});
- const { mutate: addAccountsMutation, isLoading: isAdding } = useAddLedgerAccountsMutation();
+ const { mutate: addAccountsMutation, isLoading: isAdding } = useAddLedgerAccountMutation();
const [accountsToAdd, setAccountsToAdd] = useState();
useEffect(() => {
- getLedgerAccounts(tonTransport);
+ getLedgerWallets(tonTransport).then(data => setSelectedIndexes(data.preselectedIndexes));
}, [tonTransport]);
const chosenSomeAccounts = !!Object.values(selectedIndexes).filter(Boolean).length;
@@ -178,17 +178,24 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: ()
.filter(([, v]) => v)
.map(([k]) => Number(k));
setAccountsToAdd(
- ledgerAccounts!.filter(account => chosenIndexes.includes(account.accountIndex))
+ ledgerAccountData!.wallets.filter(account =>
+ chosenIndexes.includes(account.accountIndex)
+ )
);
};
if (accountsToAdd) {
- const fallbackEmoji = getFallbackAccountEmoji(accountsToAdd[0].publicKey.toString('hex'));
return (
- addAccountsMutation({ name, emoji, accounts: accountsToAdd })
+ addAccountsMutation({
+ name,
+ emoji,
+ wallets: accountsToAdd,
+ accountId: ledgerAccountData!.accountId
+ })
}
/>
);
@@ -198,13 +205,13 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: ()
{t('ledger_choose_wallets')}
- {!ledgerAccounts ? (
+ {!ledgerAccountData ? (
) : (
- {ledgerAccounts.map(account => (
+ {ledgerAccountData.wallets.map(account => (
{toFormattedAddress(account.address)}
@@ -233,7 +240,7 @@ const ChooseLedgerAccounts: FC<{ tonTransport: LedgerTonTransport; onCancel: ()