From b0fcb12b99dd82316c7d26d073efa653bf4b0f80 Mon Sep 17 00:00:00 2001 From: Bhavya gor <56181880+bhavyagor12@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:50:02 +0530 Subject: [PATCH 001/102] Update BannerAlert icon to match Figma Issue#20355 (#20407) * changed warning --> danger * snapshot updates * Adding comment * Updating snapshot --------- Co-authored-by: georgewrmarshall --- .../__snapshots__/blockaid-banner-alert.test.js.snap | 6 +++--- .../component-library/banner-alert/banner-alert.js | 2 +- .../__snapshots__/confirm-send-ether.test.js.snap | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap index 14e94f41f..fb17c6870 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap @@ -52,7 +52,7 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp >

Date: Tue, 15 Aug 2023 09:38:36 -0700 Subject: [PATCH 002/102] Updating props to optional and providing defaults (#20448) --- ui/components/component-library/button-link/README.mdx | 4 ---- .../component-library/button-link/button-link.tsx | 6 +++--- .../component-library/button-link/button-link.types.ts | 8 ++++---- .../component-library/button-primary/button-primary.tsx | 4 ++-- .../button-primary/button-primary.types.ts | 8 ++++---- .../button-secondary/button-secondary.tsx | 8 ++++---- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/ui/components/component-library/button-link/README.mdx b/ui/components/component-library/button-link/README.mdx index 413564658..cd37a4c7d 100644 --- a/ui/components/component-library/button-link/README.mdx +++ b/ui/components/component-library/button-link/README.mdx @@ -15,10 +15,6 @@ The `ButtonLink` is an extension of `ButtonBase` to support link styles -The `ButtonLink` accepts all [ButtonBase](/docs/components-componentlibrary-buttonbase--default-story#props) component props - - - ### Size Use the `size` prop and the `ButtonLinkSize` enum from `./ui/components/component-library` to change the size of `ButtonLink`. Defaults to `ButtonLinkSize.Auto` diff --git a/ui/components/component-library/button-link/button-link.tsx b/ui/components/component-library/button-link/button-link.tsx index 3462dde8e..221866183 100644 --- a/ui/components/component-library/button-link/button-link.tsx +++ b/ui/components/component-library/button-link/button-link.tsx @@ -15,9 +15,9 @@ export const ButtonLink: ButtonLinkComponent = React.forwardRef( { className, color, - danger, - disabled, - loading, + danger = false, + disabled = false, + loading = false, size = ButtonLinkSize.Auto, ...props }: ButtonLinkProps, diff --git a/ui/components/component-library/button-link/button-link.types.ts b/ui/components/component-library/button-link/button-link.types.ts index 5d4552c47..5ffe76076 100644 --- a/ui/components/component-library/button-link/button-link.types.ts +++ b/ui/components/component-library/button-link/button-link.types.ts @@ -16,20 +16,20 @@ export interface ButtonLinkStyleUtilityProps /** * Boolean to change button type to Danger when true */ - danger: boolean; + danger?: boolean; /** * Boolean to disable button */ - disabled: boolean; + disabled?: boolean; /** * Boolean to show loading spinner in button */ - loading: boolean; + loading?: boolean; /** * Possible size values: 'ButtonLinkSize.Auto'(auto), 'ButtonLinkSize.Sm'(32px), 'ButtonLinkSize.Md'(40px), 'ButtonLinkSize.Lg'(48px), 'ButtonLinkSize.Inherit'(inherits parents font-size) * Default value is 'ButtonLinkSize.Auto'. */ - size: ButtonLinkSize; + size?: ButtonLinkSize; } export type ButtonLinkProps = diff --git a/ui/components/component-library/button-primary/button-primary.tsx b/ui/components/component-library/button-primary/button-primary.tsx index 1bb863b15..c439364bf 100644 --- a/ui/components/component-library/button-primary/button-primary.tsx +++ b/ui/components/component-library/button-primary/button-primary.tsx @@ -18,8 +18,8 @@ export const ButtonPrimary: ButtonPrimaryComponent = React.forwardRef( ( { className, - danger, - disabled, + danger = false, + disabled = false, size = ButtonPrimarySize.Md, ...props }: ButtonPrimaryProps, diff --git a/ui/components/component-library/button-primary/button-primary.types.ts b/ui/components/component-library/button-primary/button-primary.types.ts index 87d6868a5..070c4b838 100644 --- a/ui/components/component-library/button-primary/button-primary.types.ts +++ b/ui/components/component-library/button-primary/button-primary.types.ts @@ -13,21 +13,21 @@ export interface ButtonPrimaryStyleUtilityProps /** * Boolean to change button type to Danger when true */ - danger: boolean; + danger?: boolean; /** * Boolean to disable button */ - disabled: boolean; + disabled?: boolean; /** * Boolean to show loading spinner in button */ - loading: boolean; + loading?: boolean; /** * Possible size values: 'ButtonPrimarySize.Sm'(32px), * 'ButtonPrimarySize.Md'(40px), 'ButtonPrimarySize.Lg'(48px) * Default value is 'ButtonPrimarySize.Auto'. */ - size: ButtonPrimarySize; + size?: ButtonPrimarySize; } export type ButtonPrimaryProps = diff --git a/ui/components/component-library/button-secondary/button-secondary.tsx b/ui/components/component-library/button-secondary/button-secondary.tsx index 404b97301..4daa72903 100644 --- a/ui/components/component-library/button-secondary/button-secondary.tsx +++ b/ui/components/component-library/button-secondary/button-secondary.tsx @@ -14,8 +14,8 @@ export const ButtonSecondary: ButtonSecondaryComponent = React.forwardRef( ( { className = '', - danger, - disabled, + danger = false, + disabled = false, size = ButtonSecondarySize.Md, ...props }: ButtonSecondaryProps, @@ -28,8 +28,8 @@ export const ButtonSecondary: ButtonSecondaryComponent = React.forwardRef( borderColor={buttonColor} color={buttonColor} className={classnames(className, 'mm-button-secondary', { - 'mm-button-secondary--type-danger': Boolean(danger), - 'mm-button-secondary--disabled': Boolean(disabled), + 'mm-button-secondary--type-danger': danger, + 'mm-button-secondary--disabled': disabled, })} size={size} ref={ref} From b8475f85d46fa3a6407718589685d635f8b38167 Mon Sep 17 00:00:00 2001 From: Harsh Shukla <125105825+PrgrmrHarshShukla@users.noreply.github.com> Date: Wed, 16 Aug 2023 02:35:04 +0530 Subject: [PATCH 003/102] Part of #17670 for: privacy-settings.js (#20288) * Update privacy-settings.js * Update ui/pages/onboarding-flow/privacy-settings/privacy-settings.js Co-authored-by: George Marshall * Update ui/pages/onboarding-flow/privacy-settings/privacy-settings.js Co-authored-by: George Marshall * Update ui/pages/onboarding-flow/privacy-settings/privacy-settings.js Co-authored-by: George Marshall * Update ui/pages/onboarding-flow/privacy-settings/privacy-settings.js Co-authored-by: George Marshall * Update ui/pages/onboarding-flow/privacy-settings/privacy-settings.js Co-authored-by: George Marshall * Update privacy-settings.js * Update index.scss * Update to html nesting and semantic fix * lint fix --------- Co-authored-by: George Marshall Co-authored-by: georgewrmarshall --- .../privacy-settings/index.scss | 5 --- .../privacy-settings/privacy-settings.js | 43 ++++++++++--------- .../privacy-settings/setting.js | 4 +- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/ui/pages/onboarding-flow/privacy-settings/index.scss b/ui/pages/onboarding-flow/privacy-settings/index.scss index 6eae5901f..feaa8b537 100644 --- a/ui/pages/onboarding-flow/privacy-settings/index.scss +++ b/ui/pages/onboarding-flow/privacy-settings/index.scss @@ -64,9 +64,4 @@ padding: 8px !important; } } - - & button { - max-width: 50%; - padding: 15px; - } } diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 4fa1d3538..76c164739 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -12,19 +12,18 @@ import { PRIVACY_POLICY_LINK, } from '../../../../shared/lib/ui-utils'; import { + BUTTON_SIZES, + BUTTON_VARIANT, + Box, + Button, PickerNetwork, Text, TextField, } from '../../../components/component-library'; -import Box from '../../../components/ui/box/box'; -import Button from '../../../components/ui/button'; -import Typography from '../../../components/ui/typography'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { - FONT_WEIGHT, TextColor, TextVariant, - TypographyVariant, } from '../../../helpers/constants/design-system'; import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes'; import { useI18nContext } from '../../../hooks/useI18nContext'; @@ -114,15 +113,12 @@ export default function PrivacySettings() { <>

- + {t('advancedConfiguration')} - - + + {t('setAdvancedPrivacySettingsDetails')} - +
) : (
-
); diff --git a/ui/pages/onboarding-flow/privacy-settings/setting.js b/ui/pages/onboarding-flow/privacy-settings/setting.js index 7c77cd93f..b4b99fb7a 100644 --- a/ui/pages/onboarding-flow/privacy-settings/setting.js +++ b/ui/pages/onboarding-flow/privacy-settings/setting.js @@ -33,7 +33,9 @@ export const Setting = ({ {title} - {description} + + {description} +
{showToggle ? (
From 73add90685d1596a34bc991101d2ad83037a9bee Mon Sep 17 00:00:00 2001 From: Dhruv <79097544+dhruvv173@users.noreply.github.com> Date: Wed, 16 Aug 2023 03:05:18 +0530 Subject: [PATCH 004/102] fix/AvatarFavicon to TS (#20430) * AvatarFavicon to TS * documentation updates * fix types import * Some doc updates --------- Co-authored-by: Garrett Bear Co-authored-by: georgewrmarshall --- .../avatar-account/README.mdx | 6 +- .../avatar-favicon/README.mdx | 40 +++----- ...t.js.snap => avatar-favicon.test.tsx.snap} | 0 .../avatar-favicon.constants.js | 9 -- .../avatar-favicon/avatar-favicon.js | 96 ------------------- ....stories.js => avatar-favicon.stories.tsx} | 29 +++--- ...avicon.test.js => avatar-favicon.test.tsx} | 43 +++++---- .../avatar-favicon/avatar-favicon.tsx | 61 ++++++++++++ .../avatar-favicon/avatar-favicon.types.ts | 50 ++++++++++ .../component-library/avatar-favicon/index.js | 2 - .../component-library/avatar-favicon/index.ts | 3 + .../component-library/avatar-icon/README.mdx | 6 +- .../component-library/button/README.mdx | 18 ++-- ui/components/component-library/index.js | 2 +- 14 files changed, 181 insertions(+), 184 deletions(-) rename ui/components/component-library/avatar-favicon/__snapshots__/{avatar-favicon.test.js.snap => avatar-favicon.test.tsx.snap} (100%) delete mode 100644 ui/components/component-library/avatar-favicon/avatar-favicon.constants.js delete mode 100644 ui/components/component-library/avatar-favicon/avatar-favicon.js rename ui/components/component-library/avatar-favicon/{avatar-favicon.stories.js => avatar-favicon.stories.tsx} (60%) rename ui/components/component-library/avatar-favicon/{avatar-favicon.test.js => avatar-favicon.test.tsx} (69%) create mode 100644 ui/components/component-library/avatar-favicon/avatar-favicon.tsx create mode 100644 ui/components/component-library/avatar-favicon/avatar-favicon.types.ts delete mode 100644 ui/components/component-library/avatar-favicon/index.js create mode 100644 ui/components/component-library/avatar-favicon/index.ts diff --git a/ui/components/component-library/avatar-account/README.mdx b/ui/components/component-library/avatar-account/README.mdx index 9150c7d54..5b64bded3 100644 --- a/ui/components/component-library/avatar-account/README.mdx +++ b/ui/components/component-library/avatar-account/README.mdx @@ -36,7 +36,7 @@ Defaults to `AvatarAccountSize.MD` ```jsx -import { AvatarAccount, AvatarAccountSize } from '../ui/component-library'; +import { AvatarAccount, AvatarAccountSize } from '../../component-library'; @@ -54,7 +54,7 @@ Use the `variant` prop and the `AvatarAccountVariant` enum from `../../component ```jsx -import { AvatarAccount, AvatarAccountVariant } from '../ui/component-library'; +import { AvatarAccount, AvatarAccountVariant } from '../../component-library'; @@ -69,7 +69,7 @@ Use the required `address` for generating images ```jsx -import { AvatarAccount, AvatarAccountVariant } from '../ui/component-library'; +import { AvatarAccount, AvatarAccountVariant } from '../../component-library'; diff --git a/ui/components/component-library/avatar-favicon/README.mdx b/ui/components/component-library/avatar-favicon/README.mdx index 55e74a823..5d548c452 100644 --- a/ui/components/component-library/avatar-favicon/README.mdx +++ b/ui/components/component-library/avatar-favicon/README.mdx @@ -6,7 +6,7 @@ import { AvatarFavicon } from './avatar-favicon'; # AvatarFavicon -The `AvatarFavicon` is an image component that renders an icon that is provided in the form of a URL. +The `AvatarFavicon` is an image component that represents a dapp or third party service @@ -14,44 +14,34 @@ The `AvatarFavicon` is an image component that renders an icon that is provided ## Props -The `AvatarFavicon` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - -`AvatarFavicon` accepts all [AvatarBase](components-componentlibrary-avatarbase--default-story)) -component props - - - ### Size -Use the `size` prop to set the size of the `AvatarFavicon`. - -Optional: `AVATAR_FAVICON_SIZES` from `./ui/components/component-library` object can be used instead of `Size` +Use the `size` prop and the `AvatarFaviconSize` enum from `../../component-library` to change the size of `AvatarFavicon` Possible sizes include: -- `Size.XS` 16px -- `Size.SM` 24px -- `Size.MD` 32px -- `Size.LG` 40px -- `Size.XL` 48px +- `AvatarFaviconSize.Xs` 16px +- `AvatarFaviconSize.Sm` 24px +- `AvatarFaviconSize.Md` 32px +- `AvatarFaviconSize.Lg` 40px +- `AvatarFaviconSize.Xl` 48px -Defaults to `Size.MD` +Defaults to `AvatarFaviconSize.MD` ```jsx -import { Size } from '../../../helpers/constants/design-system'; -import { AvatarFavicon } from '../ui/component-library'; +import { AvatarFavicon, AvatarFaviconSize } from '../../component-library'; - - - - - + + + + + ``` ### Src @@ -63,7 +53,7 @@ Use the `src` prop to set the image to be rendered of the `AvatarFavicon`. ```jsx -import { AvatarFavicon } from '../ui/component-library'; +import { AvatarFavicon } from '../../component-library'; diff --git a/ui/components/component-library/avatar-favicon/__snapshots__/avatar-favicon.test.js.snap b/ui/components/component-library/avatar-favicon/__snapshots__/avatar-favicon.test.tsx.snap similarity index 100% rename from ui/components/component-library/avatar-favicon/__snapshots__/avatar-favicon.test.js.snap rename to ui/components/component-library/avatar-favicon/__snapshots__/avatar-favicon.test.tsx.snap diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.constants.js b/ui/components/component-library/avatar-favicon/avatar-favicon.constants.js deleted file mode 100644 index c1e860e18..000000000 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.constants.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Size } from '../../../helpers/constants/design-system'; - -export const AVATAR_FAVICON_SIZES = { - XS: Size.XS, - SM: Size.SM, - MD: Size.MD, - LG: Size.LG, - XL: Size.XL, -}; diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.js b/ui/components/component-library/avatar-favicon/avatar-favicon.js deleted file mode 100644 index 835d7033c..000000000 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.js +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { AvatarBase } from '../avatar-base'; -import Box from '../../ui/box/box'; -import { IconName, Icon } from '../icon'; -import { - BorderColor, - Size, - Display, - AlignItems, - JustifyContent, - IconColor, -} from '../../../helpers/constants/design-system'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { AVATAR_FAVICON_SIZES } from './avatar-favicon.constants'; - -export const AvatarFavicon = React.forwardRef( - ( - { - size = Size.MD, - src, - name = 'avatar-favicon', - className, - fallbackIconProps, - borderColor = BorderColor.transparent, - ...props - }, - ref, - ) => { - const t = useI18nContext(); - return ( - - {src ? ( - {t('logo', - ) : ( - - )} - - ); - }, -); - -AvatarFavicon.propTypes = { - /** - * The src accepts the string of the image to be rendered - */ - src: PropTypes.string, - /** - * The alt text for the favicon avatar to be rendered - */ - name: PropTypes.string.isRequired, - /** - * Props for the fallback icon. All Icon props can be used - */ - fallbackIconProps: PropTypes.object, - /** - * The size of the AvatarFavicon - * Possible values could be 'Size.XS' 16px, 'Size.SM' 24px, 'Size.MD' 32px, 'Size.LG' 40px, 'Size.XL' 48px - * Defaults to Size.MD - */ - size: PropTypes.oneOf(Object.values(AVATAR_FAVICON_SIZES)), - /** - * The border color of the AvatarFavicon - * Defaults to Color.transparent - */ - borderColor: PropTypes.oneOf(Object.values(BorderColor)), - /** - * Additional classNames to be added to the AvatarFavicon - */ - className: PropTypes.string, - /** - * AvatarFavicon also accepts all Box props including but not limited to - * className, as(change root element of HTML element) and margin props - */ - ...Box.propTypes, -}; - -AvatarFavicon.displayName = 'AvatarFavicon'; diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.stories.js b/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx similarity index 60% rename from ui/components/component-library/avatar-favicon/avatar-favicon.stories.js rename to ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx index f429a821e..18b0248e3 100644 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.stories.js +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx @@ -1,19 +1,18 @@ import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; import { Display, AlignItems, BorderColor, - Size, } from '../../../helpers/constants/design-system'; -import Box from '../../ui/box/box'; +import { Box } from '../box'; import README from './README.mdx'; -import { AvatarFavicon, AVATAR_FAVICON_SIZES } from '.'; +import { AvatarFavicon, AvatarFaviconSize } from '.'; export default { title: 'Components/ComponentLibrary/AvatarFavicon', - component: AvatarFavicon, parameters: { docs: { @@ -23,7 +22,7 @@ export default { argTypes: { size: { control: 'select', - options: Object.values(AVATAR_FAVICON_SIZES), + options: Object.values(AvatarFaviconSize), }, src: { control: 'text', @@ -38,30 +37,30 @@ export default { }, args: { src: 'https://uniswap.org/favicon.ico', - size: Size.MD, + size: AvatarFaviconSize.Md, name: 'Uniswap', }, -}; +} as Meta; -const Template = (args) => { +const Template: StoryFn = (args) => { return ; }; export const DefaultStory = Template.bind({}); DefaultStory.storyName = 'Default'; -export const SizeStory = (args) => ( +export const SizeStory: StoryFn = (args) => ( - - - - - + + + + + ); SizeStory.storyName = 'Size'; -export const Src = (args) => ( +export const Src: StoryFn = (args) => ( { const args = { @@ -51,46 +52,46 @@ describe('AvatarFavicon', () => { const { getByTestId } = render( <> , ); - expect(getByTestId(AVATAR_FAVICON_SIZES.XS)).toHaveClass( - `mm-avatar-base--size-${AVATAR_FAVICON_SIZES.XS}`, + expect(getByTestId(AvatarFaviconSize.Xs)).toHaveClass( + `mm-avatar-base--size-${AvatarFaviconSize.Xs}`, ); - expect(getByTestId(AVATAR_FAVICON_SIZES.SM)).toHaveClass( - `mm-avatar-base--size-${AVATAR_FAVICON_SIZES.SM}`, + expect(getByTestId(AvatarFaviconSize.Sm)).toHaveClass( + `mm-avatar-base--size-${AvatarFaviconSize.Sm}`, ); - expect(getByTestId(AVATAR_FAVICON_SIZES.MD)).toHaveClass( - `mm-avatar-base--size-${AVATAR_FAVICON_SIZES.MD}`, + expect(getByTestId(AvatarFaviconSize.Md)).toHaveClass( + `mm-avatar-base--size-${AvatarFaviconSize.Md}`, ); - expect(getByTestId(AVATAR_FAVICON_SIZES.LG)).toHaveClass( - `mm-avatar-base--size-${AVATAR_FAVICON_SIZES.LG}`, + expect(getByTestId(AvatarFaviconSize.Lg)).toHaveClass( + `mm-avatar-base--size-${AvatarFaviconSize.Lg}`, ); - expect(getByTestId(AVATAR_FAVICON_SIZES.XL)).toHaveClass( - `mm-avatar-base--size-${AVATAR_FAVICON_SIZES.XL}`, + expect(getByTestId(AvatarFaviconSize.Xl)).toHaveClass( + `mm-avatar-base--size-${AvatarFaviconSize.Xl}`, ); }); diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.tsx b/ui/components/component-library/avatar-favicon/avatar-favicon.tsx new file mode 100644 index 000000000..4041a555c --- /dev/null +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import classnames from 'classnames'; +import { AvatarBase, AvatarBaseProps } from '../avatar-base'; +import { IconName, Icon, IconSize } from '../icon'; +import { + BorderColor, + Display, + AlignItems, + JustifyContent, + IconColor, +} from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { PolymorphicRef } from '../box'; +import { + AvatarFaviconComponent, + AvatarFaviconProps, + AvatarFaviconSize, +} from './avatar-favicon.types'; + +export const AvatarFavicon: AvatarFaviconComponent = React.forwardRef( + ( + { + size = AvatarFaviconSize.Md, + src, + name = 'avatar-favicon', + className = '', + fallbackIconProps, + borderColor = BorderColor.transparent, + ...props + }: AvatarFaviconProps, + ref?: PolymorphicRef, + ) => { + const t = useI18nContext(); + return ( + ) }} + > + {src ? ( + {t('logo', + ) : ( + + )} + + ); + }, +); diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts b/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts new file mode 100644 index 000000000..72316b5c2 --- /dev/null +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts @@ -0,0 +1,50 @@ +import { BorderColor } from '../../../helpers/constants/design-system'; +import type { AvatarBaseStyleUtilityProps } from '../avatar-base/avatar-base.types'; +import { PolymorphicComponentPropWithRef } from '../box'; +import { IconProps } from '../icon'; + +export enum AvatarFaviconSize { + Xs = 'xs', + Sm = 'sm', + Md = 'md', + Lg = 'lg', + Xl = 'xl', +} + +export interface AvatarFaviconStyleUtilityProps + extends Omit { + /** + * The src accepts the string of the image to be rendered + */ + src?: string; + /** + * The alt text for the favicon avatar to be rendered + */ + name: string; + /** + * Props for the fallback icon. All Icon props can be used + */ + fallbackIconProps?: IconProps<'span'>; + /** + * The size of the AvatarFavicon + * Possible values could be 'AvatarFaviconSize.Xs' 16px, 'AvatarFaviconSize.Sm' 24px, 'AvatarFaviconSize.Md' 32px, 'AvatarFaviconSize.Lg' 40px, 'AvatarFaviconSize.Xs' 48px + * Defaults to AvatarFaviconSize.Md + */ + size?: AvatarFaviconSize; + /** + * The border color of the AvatarFavicon + * Defaults to Color.transparent + */ + borderColor?: BorderColor; + /** + * Additional classNames to be added to the AvatarFavicon + */ + className?: string; +} + +export type AvatarFaviconProps = + PolymorphicComponentPropWithRef; + +export type AvatarFaviconComponent = ( + props: AvatarFaviconProps, +) => React.ReactElement | null; diff --git a/ui/components/component-library/avatar-favicon/index.js b/ui/components/component-library/avatar-favicon/index.js deleted file mode 100644 index d746275bf..000000000 --- a/ui/components/component-library/avatar-favicon/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { AvatarFavicon } from './avatar-favicon'; -export { AVATAR_FAVICON_SIZES } from './avatar-favicon.constants'; diff --git a/ui/components/component-library/avatar-favicon/index.ts b/ui/components/component-library/avatar-favicon/index.ts new file mode 100644 index 000000000..32dcebbb2 --- /dev/null +++ b/ui/components/component-library/avatar-favicon/index.ts @@ -0,0 +1,3 @@ +export { AvatarFavicon } from './avatar-favicon'; +export { AvatarFaviconSize } from './avatar-favicon.types'; +export type { AvatarFaviconProps } from './avatar-favicon.types'; diff --git a/ui/components/component-library/avatar-icon/README.mdx b/ui/components/component-library/avatar-icon/README.mdx index bd2e6fdb3..f5ca3e3e6 100644 --- a/ui/components/component-library/avatar-icon/README.mdx +++ b/ui/components/component-library/avatar-icon/README.mdx @@ -41,7 +41,7 @@ Defaults to `AvatarIconSize.Md` ```jsx -import { AvatarIcon, AvatarIconSize } from '../ui/component-library'; +import { AvatarIcon, AvatarIconSize } from '../../component-library'; @@ -61,7 +61,7 @@ Use the [IconSearch](/story/components-componentlibrary-icon--default-story) sto ```jsx -import { AvatarIcon, IconName } from '../ui/component-library'; +import { AvatarIcon, IconName } from '../../component-library'; @@ -83,7 +83,7 @@ Use the `color` and `backgroundColor` props with the `IconColor` and `Background ```jsx import { BackgroundColor, IconColor } from '../../../helpers/constants/design-system'; -import { AvatarIcon } from '../ui/component-library'; +import { AvatarIcon } from '../../component-library'; diff --git a/ui/components/component-library/button/README.mdx b/ui/components/component-library/button/README.mdx index 6b171cbdc..79de5ac6a 100644 --- a/ui/components/component-library/button/README.mdx +++ b/ui/components/component-library/button/README.mdx @@ -67,7 +67,7 @@ Possible sizes include: ```jsx import { Size } from '../../../helpers/constants/design-system'; -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; @@ -99,7 +99,7 @@ When an `href` prop is passed it will change the element to an anchor(`a`) tag. ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; ; ``` @@ -114,7 +114,7 @@ Use boolean `block` prop to quickly enable a full width block button ```jsx import { DISPLAY } from '../../../helpers/constants/design-system'; -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; @@ -136,7 +136,7 @@ Button `as` options: ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; @@ -154,7 +154,7 @@ Use the boolean `disabled` prop to disable button ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; ; ``` @@ -168,7 +168,7 @@ Use the boolean `loading` prop to set loading spinner ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; ; ``` @@ -184,7 +184,7 @@ Use the [IconSearch](/story/components-componentlibrary-icon--default-story) sto ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; import { IconName } from '../icon'; ; @@ -195,7 +195,7 @@ import { IconName } from '../icon'; ```jsx -import { Button } from '../ui/component-library'; +import { Button } from '../../component-library'; import { IconName } from '../icon'; ; diff --git a/ui/components/component-library/index.js b/ui/components/component-library/index.js index f7ee14fb1..e2308d721 100644 --- a/ui/components/component-library/index.js +++ b/ui/components/component-library/index.js @@ -4,7 +4,7 @@ export { AvatarAccountVariant, AvatarAccountDiameter, } from './avatar-account'; -export { AvatarFavicon, AVATAR_FAVICON_SIZES } from './avatar-favicon'; +export { AvatarFavicon, AvatarFaviconSize } from './avatar-favicon'; export { AvatarIcon, AvatarIconSize } from './avatar-icon'; export { AvatarNetwork, AvatarNetworkSize } from './avatar-network'; export { AvatarToken, AvatarTokenSize } from './avatar-token'; From d368f517e22b73839c93080503fef18a3fcb23a7 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 15 Aug 2023 19:19:48 -0230 Subject: [PATCH 005/102] Log before and after each migration run (#20424) * Log before and after each migration run * Use loglevel --- app/scripts/lib/migrator/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index ca2abb8ea..5925430f0 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,4 +1,5 @@ import EventEmitter from 'events'; +import log from 'loglevel'; /** * @typedef {object} Migration @@ -36,6 +37,8 @@ export default class Migrator extends EventEmitter { // perform each migration for (const migration of pendingMigrations) { try { + log.info(`Running migration ${migration.version}...`); + // attempt migration and validate const migratedData = await migration.migrate(versionedData); if (!migratedData.data) { @@ -52,6 +55,8 @@ export default class Migrator extends EventEmitter { // accept the migration as good // eslint-disable-next-line no-param-reassign versionedData = migratedData; + + log.info(`Migration ${migration.version} complete`); } catch (err) { // rewrite error message to add context without clobbering stack const originalErrorMessage = err.message; From cde910faecbd53226c9b0c956b87b330c197d42d Mon Sep 17 00:00:00 2001 From: jainex Date: Wed, 16 Aug 2023 03:27:34 +0530 Subject: [PATCH 006/102] Replacing deprecated components in ConfirmationWarningModal (#20416) * Replacing deprecated components in ConfirmationWarningModal * Layout and semantic html updates to align with design system conventions --------- Co-authored-by: georgewrmarshall --- .../confirmation-warning-modal.js | 155 +++++++++--------- .../app/confirmation-warning-modal/index.scss | 14 -- 2 files changed, 80 insertions(+), 89 deletions(-) diff --git a/ui/components/app/confirmation-warning-modal/confirmation-warning-modal.js b/ui/components/app/confirmation-warning-modal/confirmation-warning-modal.js index 79b53a435..e0e66bb41 100644 --- a/ui/components/app/confirmation-warning-modal/confirmation-warning-modal.js +++ b/ui/components/app/confirmation-warning-modal/confirmation-warning-modal.js @@ -2,102 +2,107 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import Popover from '../../ui/popover'; -import Box from '../../ui/box'; -import Button from '../../ui/button'; import { Display, FlexDirection, FontWeight, - JustifyContent, TextVariant, AlignItems, IconColor, + TextAlign, } from '../../../helpers/constants/design-system'; -import { Icon, IconName, IconSize, Text } from '../../component-library'; + +import { + Box, + Button, + BUTTON_SIZES, + BUTTON_VARIANT, + Icon, + IconName, + IconSize, + Modal, + ModalContent, + ModalHeader, + ModalOverlay, + Text, +} from '../../component-library'; const ConfirmationWarningModal = ({ onSubmit, onCancel }) => { const t = useI18nContext(); - return ( - + + + - + {t('addEthereumChainWarningModalTitle')} + + + + + {t('addEthereumChainWarningModalHeader', [ + + {t('addEthereumChainWarningModalHeaderPartTwo')} + , + ])} + + + {t('addEthereumChainWarningModalListHeader')} + +
    + + {t('addEthereumChainWarningModalListPointOne')} + + + {t('addEthereumChainWarningModalListPointTwo')} + + + {t('addEthereumChainWarningModalListPointThree')} + +
+
+ + - } - > - - - - {t('addEthereumChainWarningModalTitle')} - - - - - {t('addEthereumChainWarningModalHeader', [ - - {t('addEthereumChainWarningModalHeaderPartTwo')} - , - ])} - - - {t('addEthereumChainWarningModalListHeader')} - -
    -
  • - - {t('addEthereumChainWarningModalListPointOne')} - -
  • -
  • - - {t('addEthereumChainWarningModalListPointTwo')} - -
  • -
  • - - {t('addEthereumChainWarningModalListPointThree')} - -
  • -
-
-
+ + ); }; diff --git a/ui/components/app/confirmation-warning-modal/index.scss b/ui/components/app/confirmation-warning-modal/index.scss index fcd19395a..89c3f9948 100644 --- a/ui/components/app/confirmation-warning-modal/index.scss +++ b/ui/components/app/confirmation-warning-modal/index.scss @@ -9,19 +9,5 @@ li { display: list-item; } - - &__header { - border-bottom: 1px solid var(--color-border-muted); - - &__warning-icon { - padding-top: 7px; - margin-right: 10px; - } - } - } - - &__footer { - width: 100%; - height: 100px; } } From 6512cacec837b79703e7a76c949d05f1b0a4f722 Mon Sep 17 00:00:00 2001 From: Pritam Dhara <46365255+pritam1813@users.noreply.github.com> Date: Wed, 16 Aug 2023 03:29:29 +0530 Subject: [PATCH 007/102] Replaced ActionableMessage in confirm-page-container-content (#20417) * Replaced ActionableMessage in confirm-page-container-content * Updated Snapshot of confirm-transaction-base * Updated BannerAlert className * Using component API and replacing ErrorMessage * Update snapshot --------- Co-authored-by: George Marshall --- ...onfirm-page-container-content.component.js | 88 +++++++++---------- .../confirm-page-container-content/index.scss | 4 - .../confirm-transaction-base.test.js.snap | 35 +++----- 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index a1656c5c1..4630cc5f9 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -2,15 +2,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Tabs, Tab } from '../../../ui/tabs'; -///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) -import Button from '../../../ui/button'; -///: END:ONLY_INCLUDE_IN -import ActionableMessage from '../../../ui/actionable-message/actionable-message'; +import { + ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) + Button, + BUTTON_SIZES, + BUTTON_VARIANT, + ///: END:ONLY_INCLUDE_IN + BannerAlert, +} from '../../../component-library'; import { PageContainerFooter } from '../../../ui/page-container'; -import ErrorMessage from '../../../ui/error-message'; import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../../helpers/constants/error-keys'; -import Typography from '../../../ui/typography'; -import { TypographyVariant } from '../../../../helpers/constants/design-system'; +import { Severity } from '../../../../helpers/constants/design-system'; import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; ///: BEGIN:ONLY_INCLUDE_IN(blockaid) @@ -256,46 +258,42 @@ export default class ConfirmPageContainerContent extends Component { {!supportsEIP1559 && !showInsuffienctFundsError && (errorKey || errorMessage) && ( -
- -
+ )} {showInsuffienctFundsError && ( -
- - {t('insufficientCurrencyBuyOrDeposit', [ - nativeCurrency, - networkName, - ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) - , - ///: END:ONLY_INCLUDE_IN - ])} - - ) : ( - - {t('insufficientCurrencyDeposit', [ - nativeCurrency, - networkName, - ])} - - ) - } - useIcon - iconFillColor="var(--color-error-default)" - type="danger" - /> -
+ + {t('buyAsset', [nativeCurrency])} + , + ///: END:ONLY_INCLUDE_IN + ]) + : t('insufficientCurrencyDeposit', [ + nativeCurrency, + networkName, + ]) + } + /> )}
-
- +
+

- - - -

-
- You do not have enough in your account to pay for transaction fees on Goerli network. Deposit from another account. -
-
+ You do not have enough in your account to pay for transaction fees on Goerli network. Deposit from another account. +

+

Date: Wed, 16 Aug 2023 02:18:41 -0700 Subject: [PATCH 008/102] [FLASK] Create new E2E test for snaps lifecycle-hooks (#20352) --- test/e2e/snaps/enums.js | 2 +- test/e2e/snaps/test-snap-lifecycle.spec.js | 97 ++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/e2e/snaps/test-snap-lifecycle.spec.js diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index acf8f3298..4e5a781b5 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,6 +1,6 @@ module.exports = { TEST_SNAPS_WEBSITE_URL: - 'https://metamask.github.io/snaps/test-snaps/0.37.3-flask.1/', + 'https://metamask.github.io/snaps/test-snaps/0.38.0-flask.1/', TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL: 'https://metamask.github.io/snap-simple-keyring/latest/', }; diff --git a/test/e2e/snaps/test-snap-lifecycle.spec.js b/test/e2e/snaps/test-snap-lifecycle.spec.js new file mode 100644 index 000000000..4da6c1e94 --- /dev/null +++ b/test/e2e/snaps/test-snap-lifecycle.spec.js @@ -0,0 +1,97 @@ +const { strict: assert } = require('assert'); +const { withFixtures } = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); + +describe('Test Snap Lifecycle Hooks', function () { + it('can run lifecycle hook on connect', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 25000000000000000000, + }, + ], + }; + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + failOnConsoleError: false, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + + // enter pw into extension + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // navigate to test snaps page and connect + await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + await driver.delay(1000); + const snapButton = await driver.findElement('#connectlifecycle-hooks'); + await driver.scrollToElement(snapButton); + await driver.delay(1000); + await driver.clickElement('#connectlifecycle-hooks'); + await driver.delay(1000); + + // switch to metamask extension and click connect + let windowHandles = await driver.waitUntilXWindowHandles( + 3, + 1000, + 10000, + ); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement({ + text: 'Install', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'OK' }); + + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + // click send inputs on test snap page + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // wait for npm installation success + await driver.waitForSelector({ + css: '#connectlifecycle-hooks', + text: 'Reconnect to Lifecycle Hooks Snap', + }); + + // switch to dialog popup + windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + await driver.delay(500); + + // check dialog contents + const result = await driver.findElement('.snap-ui-renderer__panel'); + await driver.scrollToElement(result); + await driver.delay(500); + assert.equal( + await result.getText(), + 'Installation successful\nThe snap was installed successfully, and the "onInstall" handler was called.', + ); + }, + ); + }); +}); From 42d05ef9cd1e971027563244dc3b148e5785b2e3 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Wed, 16 Aug 2023 11:19:41 +0200 Subject: [PATCH 009/102] Use `addNewAccount` from core `KeyringController` (#19814) * refactor: use addNewAccount from core KeyringController * refactor: replace missed interaction * refactor: select account only when is new * refactor: use getAccounts to check if account is new --- .../metamask-controller.actions.test.js | 12 ++--- app/scripts/metamask-controller.js | 44 +++++-------------- app/scripts/metamask-controller.test.js | 4 +- ui/store/actions.test.js | 2 +- ui/store/actions.ts | 10 ++--- 5 files changed, 19 insertions(+), 53 deletions(-) diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 5e7c771a1..397648c37 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -145,27 +145,21 @@ describe('MetaMaskController', function () { metamaskController.addNewAccount(1), metamaskController.addNewAccount(1), ]); - assert.deepEqual( - Object.keys(addNewAccountResult1.identities), - Object.keys(addNewAccountResult2.identities), - ); + assert.equal(addNewAccountResult1, addNewAccountResult2); }); it('two successive calls with same accountCount give same result', async function () { await metamaskController.createNewVaultAndKeychain('test@123'); const addNewAccountResult1 = await metamaskController.addNewAccount(1); const addNewAccountResult2 = await metamaskController.addNewAccount(1); - assert.deepEqual( - Object.keys(addNewAccountResult1.identities), - Object.keys(addNewAccountResult2.identities), - ); + assert.equal(addNewAccountResult1, addNewAccountResult2); }); it('two successive calls with different accountCount give different results', async function () { await metamaskController.createNewVaultAndKeychain('test@123'); const addNewAccountResult1 = await metamaskController.addNewAccount(1); const addNewAccountResult2 = await metamaskController.addNewAccount(2); - assert.notDeepEqual(addNewAccountResult1, addNewAccountResult2); + assert.notEqual(addNewAccountResult1, addNewAccountResult2); }); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fa120200f..366556d35 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2943,12 +2943,10 @@ export default class MetamaskController extends EventEmitter { // seek out the first zero balance while (lastBalance !== '0x0') { - await this.coreKeyringController.addNewAccount(accounts.length); + const { addedAccountAddress } = + await this.coreKeyringController.addNewAccount(accounts.length); accounts = await this.coreKeyringController.getAccounts(); - lastBalance = await this.getBalance( - accounts[accounts.length - 1], - ethQuery, - ); + lastBalance = await this.getBalance(addedAccountAddress, ethQuery); } // remove extra zero balance account potentially created from seeking ahead @@ -3394,7 +3392,7 @@ export default class MetamaskController extends EventEmitter { * Adds a new account to the default (first) HD seed phrase Keyring. * * @param accountCount - * @returns {} keyState + * @returns {Promise} The address of the newly-created account. */ async addNewAccount(accountCount) { const isActionMetricsQueueE2ETest = @@ -3404,38 +3402,16 @@ export default class MetamaskController extends EventEmitter { await new Promise((resolve) => setTimeout(resolve, 5_000)); } - const [primaryKeyring] = this.coreKeyringController.getKeyringsByType( - KeyringType.hdKeyTree, - ); - if (!primaryKeyring) { - throw new Error('MetamaskController - No HD Key Tree found'); - } - const { keyringController } = this; - const { identities: oldIdentities } = - this.preferencesController.store.getState(); + const oldAccounts = await this.coreKeyringController.getAccounts(); - if (Object.keys(oldIdentities).length === accountCount) { - const oldAccounts = await keyringController.getAccounts(); - const keyState = await keyringController.addNewAccount(primaryKeyring); - const newAccounts = await keyringController.getAccounts(); + const { addedAccountAddress } = + await this.coreKeyringController.addNewAccount(accountCount); - await this.verifySeedPhrase(); - - this.preferencesController.setAddresses(newAccounts); - newAccounts.forEach((address) => { - if (!oldAccounts.includes(address)) { - this.preferencesController.setSelectedAddress(address); - } - }); - - const { identities } = this.preferencesController.store.getState(); - return { ...keyState, identities }; + if (!oldAccounts.includes(addedAccountAddress)) { + this.preferencesController.setSelectedAddress(addedAccountAddress); } - return { - ...keyringController.memStore.getState(), - identities: oldIdentities, - }; + return addedAccountAddress; } /** diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index b11e34372..f2c3fa6ad 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -737,7 +737,7 @@ describe('MetaMaskController', function () { metamaskController.keyringController, 'addNewAccount', ); - addNewAccountStub.returns({}); + addNewAccountStub.returns('0x123'); getAccountsStub = sinon.stub( metamaskController.keyringController, @@ -818,7 +818,7 @@ describe('MetaMaskController', function () { await addNewAccount; assert.fail('should throw'); } catch (e) { - assert.equal(e.message, 'MetamaskController - No HD Key Tree found'); + assert.equal(e.message, 'No HD keyring found'); } }); }); diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index 933ec6ce2..359e2a349 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -398,7 +398,7 @@ describe('Actions', () => { const addNewAccount = background.addNewAccount.callsFake((_, cb) => cb(null, { - identities: {}, + addedAccountAddress: '0x123', }), ); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 6d92e5b1b..00e94aa71 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -432,12 +432,11 @@ export function addNewAccount(): ThunkAction< const oldIdentities = getState().metamask.identities; dispatch(showLoadingIndication()); - let newIdentities; + let addedAccountAddress; try { - const { identities } = await submitRequestToBackground('addNewAccount', [ + addedAccountAddress = await submitRequestToBackground('addNewAccount', [ Object.keys(oldIdentities).length, ]); - newIdentities = identities; } catch (error) { dispatch(displayWarning(error)); throw error; @@ -445,11 +444,8 @@ export function addNewAccount(): ThunkAction< dispatch(hideLoadingIndication()); } - const newAccountAddress = Object.keys(newIdentities).find( - (address) => !oldIdentities[address], - ); await forceUpdateMetamaskState(dispatch); - return newAccountAddress; + return addedAccountAddress; }; } From 9e302ea84e3e09273867335ef62bf12b858b51fd Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 08:33:18 -0230 Subject: [PATCH 010/102] Update `protobufjs` (#20469) Update `protobufjs` to the latest version. This resolves a security advisory for this package. The advisory is concerning prototype pollution, so it likely never affected us due to LavaMoat protections. --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f9a0a9995..8b6b0fd81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28280,8 +28280,8 @@ __metadata: linkType: hard "protobufjs@npm:^6.11.3": - version: 6.11.3 - resolution: "protobufjs@npm:6.11.3" + version: 6.11.4 + resolution: "protobufjs@npm:6.11.4" dependencies: "@protobufjs/aspromise": "npm:^1.1.2" "@protobufjs/base64": "npm:^1.1.2" @@ -28299,7 +28299,7 @@ __metadata: bin: pbjs: bin/pbjs pbts: bin/pbts - checksum: ab7efcdc4d2e43ffad92272cf8c7bed7b8abfa75b00d059024abe7af446e7151bf71c265347b06dc21136187682c86cd1214e1fcf057ed3fc8142c8a6c47b613 + checksum: 6b7fd7540d74350d65c38f69f398c9995ae019da070e79d9cd464a458c6d19b40b07c9a026be4e10704c824a344b603307745863310c50026ebd661ce4da0663 languageName: node linkType: hard From a6ef7bb244da8592be4d3272c4394d8d741c81dc Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 11:52:25 -0230 Subject: [PATCH 011/102] Add additional Sentry E2E tests (#20425) * Reorganize Sentry error e2e tests The tests have been reorganized into different describe blocks. Each describe block is for either before or after initialization, and either with or without opting into metrics. This was done to simplify later test additions. The conditions for each test are now in the describe block, letting us test additional things in each of these conditions. The conditions were flattened to a single level to avoid excessive indentation. * Add error e2e test for background and UI errors The Sentry e2e tests before initialization only tested background errors, and the after initialization tests only tested UI errors. Now both types of errors are tested in both scenarios. * Add error e2e tests for Sentry error state E2E tests have been added to test the state object sent along with each Sentry error. At the moment this object is empty in some circumstances, but this will change in later PRs. * Rename throw test error function * Only setup debug/test state hooks in dev/test builds The state hooks used for debugging and testing are now only included in dev or test builds. The function name was updated and given a JSDoc description to explain this more clearly as well. * Add state snapshot assertions State snapshot assertions have been added to the e2e error tests. These snapshots will be very useful in reviewing a few PRs that will follow this one. We might decide to remove these snapshots after this set of Sentry refactors, as they might be more work to maintain than they're worth. But they will be useful at least in the short-term. The login step has been removed from a few tests because it introduced indeterminacy (the login process continued asynchronously after the login, and sometimes was not finished when the error was triggered). * Ensure login page has rendered during setup This fixes an intermittent failure on Firefox * Format snapshots with prettier before writing them * Use defined set of date fields rather than infering from name * Remove waits for error screen The error screen only appears after a long timeout, and it doesn't affect the next test steps at all. --- app/scripts/metamask-controller.js | 18 + test/e2e/tests/errors.spec.js | 466 ++++++++++++++++-- ...rs-after-init-opt-in-background-state.json | 50 ++ .../errors-after-init-opt-in-ui-state.json | 57 +++ ...s-before-init-opt-in-background-state.json | 1 + .../errors-before-init-opt-in-ui-state.json | 1 + ui/index.js | 47 +- ui/store/actions.ts | 11 + 8 files changed, 610 insertions(+), 41 deletions(-) create mode 100644 test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 366556d35..e0f7f476c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2771,6 +2771,9 @@ export default class MetamaskController extends EventEmitter { assetsContractController.getBalancesInSingleCall.bind( assetsContractController, ), + + // E2E testing + throwTestError: this.throwTestError.bind(this), }; } @@ -4515,6 +4518,21 @@ export default class MetamaskController extends EventEmitter { return nonceLock.nextNonce; } + /** + * Throw an artificial error in a timeout handler for testing purposes. + * + * @param message - The error message. + * @deprecated This is only mean to facilitiate E2E testing. We should not + * use this for handling errors. + */ + throwTestError(message) { + setTimeout(() => { + const error = new Error(message); + error.name = 'TestError'; + throw error; + }); + } + //============================================================================= // CONFIG //============================================================================= diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 960135215..4ab80a7de 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -1,8 +1,70 @@ +const { resolve } = require('path'); +const { promises: fs } = require('fs'); const { strict: assert } = require('assert'); +const { get, has, set } = require('lodash'); const { Browser } = require('selenium-webdriver'); +const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const dateFields = ['metamask.conversionDate']; + +/** + * Transform date properties to value types, to ensure that state is + * consistent between test runs. + * + * @param {unknown} data - The data to transform + */ +function transformDates(data) { + for (const field of dateFields) { + if (has(data, field)) { + set(data, field, typeof get(data, field)); + } + } + return data; +} + +/** + * Check that the data provided matches the snapshot. + * + * @param {object }args - Function arguments. + * @param {any} args.data - The data to compare with the snapshot. + * @param {string} args.snapshot - The name of the snapshot. + * @param {boolean} [args.update] - Whether to update the snapshot if it doesn't match. + */ +async function matchesSnapshot({ + data: unprocessedData, + snapshot, + update = process.env.UPDATE_SNAPSHOTS === 'true', +}) { + const data = transformDates(unprocessedData); + + const snapshotPath = resolve(__dirname, `./state-snapshots/${snapshot}.json`); + const rawSnapshotData = await fs.readFile(snapshotPath, { + encoding: 'utf-8', + }); + const snapshotData = JSON.parse(rawSnapshotData); + + try { + assert.deepStrictEqual(data, snapshotData); + } catch (error) { + if (update && error instanceof assert.AssertionError) { + const stringifiedData = JSON.stringify(data); + // filepath specified so that Prettier can infer which parser to use + // from the file extension + const formattedData = format(stringifiedData, { + filepath: 'something.json', + }); + await fs.writeFile(snapshotPath, formattedData, { + encoding: 'utf-8', + }); + console.log(`Snapshot '${snapshot}' updated`); + return; + } + throw error; + } +} + describe('Sentry errors', function () { const migrationError = process.env.SELENIUM_BROWSER === Browser.CHROME @@ -41,8 +103,8 @@ describe('Sentry errors', function () { ], }; - describe('before initialization', function () { - it('should NOT send error events when participateInMetaMetrics is false', async function () { + describe('before initialization, after opting out of metrics', function () { + it('should NOT send error events in the background', async function () { await withFixtures( { fixtures: { @@ -73,7 +135,43 @@ describe('Sentry errors', function () { }, ); }); - it('should send error events', async function () { + + it('should NOT send error events in the UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + + // Wait for Sentry request + await driver.delay(3000); + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + }); + + describe('before initialization, after opting into metrics', function () { + it('should send error events in background', async function () { await withFixtures( { fixtures: { @@ -112,40 +210,47 @@ describe('Sentry errors', function () { }, ); }); - }); - describe('after initialization', function () { - it('should NOT send error events when participateInMetaMetrics is false', async function () { + it('should capture background application state', async function () { await withFixtures( { - fixtures: new FixtureBuilder() - .withMetaMetricsController({ - metaMetricsId: null, - participateInMetaMetrics: false, - }) - .build(), + fixtures: { + ...new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + // Intentionally corrupt state to trigger migration error during initialization + meta: undefined, + }, ganacheOptions, title: this.test.title, failOnConsoleError: false, - testSpecificMock: mockSentryTestError, + testSpecificMock: mockSentryMigratorError, }, async ({ driver, mockedEndpoint }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - // Trigger error - driver.executeScript('window.stateHooks.throwTestError()'); - driver.delay(3000); + // Wait for Sentry request - const isPending = await mockedEndpoint.isPending(); - assert.ok( - isPending, - 'A request to sentry was sent when it should not have been', - ); + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + await matchesSnapshot({ + data: appState, + snapshot: 'errors-before-init-opt-in-background-state', + }); }, ); }); - it('should send error events', async function () { + + it('should send error events in UI', async function () { await withFixtures( { fixtures: new FixtureBuilder() @@ -161,15 +266,171 @@ describe('Sentry errors', function () { }, async ({ driver, mockedEndpoint }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + // Trigger error - driver.executeScript('window.stateHooks.throwTestError()'); + await driver.executeScript('window.stateHooks.throwTestError()'); + // Wait for Sentry request await driver.wait(async () => { const isPending = await mockedEndpoint.isPending(); return isPending === false; - }, 10000); + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const { level } = mockJsonBody; + const [{ type, value }] = mockJsonBody.exception.values; + // Verify request + assert.equal(type, 'TestError'); + assert.equal(value, 'Test Error'); + assert.equal(level, 'error'); + }, + ); + }); + + it('should capture UI application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + await matchesSnapshot({ + data: appState, + snapshot: 'errors-before-init-opt-in-ui-state', + }); + }, + ); + }); + }); + + describe('after initialization, after opting out of metrics', function () { + it('should NOT send error events in the background', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + + it('should NOT send error events in the UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + }); + + describe('after initialization, after opting into metrics', function () { + it('should send error events in background', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); const [mockedRequest] = await mockedEndpoint.getSeenRequests(); const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); @@ -184,5 +445,154 @@ describe('Sentry errors', function () { }, ); }); + + it('should capture background application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'store', + 'version', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); + await matchesSnapshot({ + data: appState.store, + snapshot: 'errors-after-init-opt-in-background-state', + }); + }, + ); + }); + + it('should send error events in UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const { level, extra } = mockJsonBody; + const [{ type, value }] = mockJsonBody.exception.values; + const { participateInMetaMetrics } = extra.appState.store.metamask; + // Verify request + assert.equal(type, 'TestError'); + assert.equal(value, 'Test Error'); + assert.equal(level, 'error'); + assert.equal(participateInMetaMetrics, true); + }, + ); + }); + + it('should capture UI application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'store', + 'version', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); + await matchesSnapshot({ + data: appState.store, + snapshot: 'errors-after-init-opt-in-ui-state', + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json new file mode 100644 index 000000000..efdf28622 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -0,0 +1,50 @@ +{ + "metamask": { + "isInitialized": true, + "connectedStatusPopoverHasBeenShown": true, + "defaultHomeActiveTabName": null, + "networkId": "1337", + "providerConfig": { + "nickname": "Localhost 8545", + "ticker": "ETH", + "type": "rpc" + }, + "isUnlocked": false, + "useBlockie": false, + "useNonceField": false, + "usePhishDetect": true, + "featureFlags": { "showIncomingTransactions": true }, + "currentLocale": "en", + "forgottenPassword": false, + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "ipfsGateway": "dweb.link", + "participateInMetaMetrics": true, + "metaMetricsId": "fake-metrics-id", + "conversionDate": "number", + "conversionRate": 1700, + "nativeCurrency": "ETH", + "currentCurrency": "usd", + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "seedPhraseBackedUp": true, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + }, + "currentBlockGasLimit": "0x1c9c380", + "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessagesCount": 0 + } +} diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json new file mode 100644 index 000000000..7300c3a05 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -0,0 +1,57 @@ +{ + "gas": { "customData": { "price": null, "limit": null } }, + "history": { "mostRecentOverviewPage": "/" }, + "metamask": { + "isInitialized": true, + "isUnlocked": false, + "isAccountMenuOpen": false, + "customNonceValue": "", + "useBlockie": false, + "featureFlags": { "showIncomingTransactions": true }, + "welcomeScreenSeen": false, + "currentLocale": "en", + "currentBlockGasLimit": "", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "participateInMetaMetrics": true, + "nextNonce": null, + "conversionRate": 1300, + "nativeCurrency": "ETH", + "connectedStatusPopoverHasBeenShown": true, + "defaultHomeActiveTabName": null, + "networkId": "1337", + "providerConfig": { + "nickname": "Localhost 8545", + "ticker": "ETH", + "type": "rpc" + }, + "useNonceField": false, + "usePhishDetect": true, + "forgottenPassword": false, + "ipfsGateway": "dweb.link", + "metaMetricsId": "fake-metrics-id", + "conversionDate": "number", + "currentCurrency": "usd", + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "seedPhraseBackedUp": true, + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + }, + "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessagesCount": 0 + }, + "unconnectedAccount": { "state": "CLOSED" } +} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -0,0 +1 @@ +{} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -0,0 +1 @@ +{} diff --git a/ui/index.js b/ui/index.js index 21b883115..e9ac462bd 100644 --- a/ui/index.js +++ b/ui/index.js @@ -81,7 +81,7 @@ export default function launchMetamaskUi(opts, cb) { return; } startApp(metamaskState, backgroundConnection, opts).then((store) => { - setupDebuggingHelpers(store); + setupStateHooks(store); cb( null, store, @@ -191,18 +191,39 @@ async function startApp(metamaskState, backgroundConnection, opts) { return store; } -function setupDebuggingHelpers(store) { - /** - * The following stateHook is a method intended to throw an error, used in - * our E2E test to ensure that errors are attempted to be sent to sentry. - * - * @param {string} [msg] - The error message to throw, defaults to 'Test Error' - */ - window.stateHooks.throwTestError = async function (msg = 'Test Error') { - const error = new Error(msg); - error.name = 'TestError'; - throw error; - }; +/** + * Setup functions on `window.stateHooks`. Some of these support + * application features, and some are just for debugging or testing. + * + * @param {object} store - The Redux store. + */ +function setupStateHooks(store) { + if (process.env.METAMASK_DEBUG || process.env.IN_TEST) { + /** + * The following stateHook is a method intended to throw an error, used in + * our E2E test to ensure that errors are attempted to be sent to sentry. + * + * @param {string} [msg] - The error message to throw, defaults to 'Test Error' + */ + window.stateHooks.throwTestError = async function (msg = 'Test Error') { + const error = new Error(msg); + error.name = 'TestError'; + throw error; + }; + /** + * The following stateHook is a method intended to throw an error in the + * background, used in our E2E test to ensure that errors are attempted to be + * sent to sentry. + * + * @param {string} [msg] - The error message to throw, defaults to 'Test Error' + */ + window.stateHooks.throwTestBackgroundError = async function ( + msg = 'Test Error', + ) { + store.dispatch(actions.throwTestBackgroundError(msg)); + }; + } + window.stateHooks.getCleanAppState = async function () { const state = clone(store.getState()); state.version = global.platform.getVersion(); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 00e94aa71..fe2dc5b44 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4489,6 +4489,17 @@ export async function getCurrentNetworkEIP1559Compatibility(): Promise< return networkEIP1559Compatibility; } +/** + * Throw an error in the background for testing purposes. + * + * @param message - The error message. + * @deprecated This is only mean to facilitiate E2E testing. We should not use + * this for handling errors. + */ +export async function throwTestBackgroundError(message: string): Promise { + await submitRequestToBackground('throwTestError', [message]); +} + ///: BEGIN:ONLY_INCLUDE_IN(snaps) /** * Set status of popover warning for the first snap installation. From 9bbabd4868fa7da70d8c2065e38ea8d702fcde14 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 16 Aug 2023 12:22:38 -0230 Subject: [PATCH 012/102] Capture app and migration version (#20458) * Add AppMetadataController so current and previous application and migration version can be captured in sentry * Add currentAppVersion, previousAppVersion, previousMigrationVersion, currentMigrationVersion to SENTRY_OBJECT * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Fix types * Add tests for app-metadata.test.ts * Lint fixes * Modify loadStateFromPersistence to return the whole versionData object, so that the migration version can be passed to the metamask-controller on instantiation * Remove reference to implementation details in test descriptions in app/scripts/controllers/app-metadata.test.ts * Reset all mocks afterEach in AppMetadataController * Refactor AppMetadataController to be passed version instead of calling platform.version directly (for ease of unit testing the MetaMask Controller) * Make maybeUpdateAppVersion and maybeUpdateMigrationVersion private, and remove unit tests of those specific functions --------- Co-authored-by: Mark Stacey --- app/scripts/background.js | 9 +- app/scripts/controllers/app-metadata.test.ts | 104 +++++++++++++++++++ app/scripts/controllers/app-metadata.ts | 99 ++++++++++++++++++ app/scripts/lib/setupSentry.js | 4 + app/scripts/metamask-controller.js | 11 ++ 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 app/scripts/controllers/app-metadata.test.ts create mode 100644 app/scripts/controllers/app-metadata.ts diff --git a/app/scripts/background.js b/app/scripts/background.js index 5eac5d1d6..264964cca 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -264,7 +264,8 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { */ async function initialize() { try { - const initState = await loadStateFromPersistence(); + const initData = await loadStateFromPersistence(); + const initState = initData.data; const initLangCode = await getFirstPreferredLangCode(); ///: BEGIN:ONLY_INCLUDE_IN(desktop) @@ -287,6 +288,7 @@ async function initialize() { initLangCode, {}, isFirstMetaMaskControllerSetup, + initData.meta, ); if (!isManifestV3) { await loadPhishingWarningPage(); @@ -417,7 +419,7 @@ export async function loadStateFromPersistence() { localStore.set(versionedData.data); // return just the data - return versionedData.data; + return versionedData; } /** @@ -430,12 +432,14 @@ export async function loadStateFromPersistence() { * @param {string} initLangCode - The region code for the language preferred by the current user. * @param {object} overrides - object with callbacks that are allowed to override the setup controller logic (usefull for desktop app) * @param isFirstMetaMaskControllerSetup + * @param {object} stateMetadata - Metadata about the initial state and migrations, including the most recent migration version */ export function setupController( initState, initLangCode, overrides, isFirstMetaMaskControllerSetup, + stateMetadata, ) { // // MetaMask Controller @@ -462,6 +466,7 @@ export function setupController( localStore, overrides, isFirstMetaMaskControllerSetup, + currentMigrationVersion: stateMetadata.version, }); setupEnsIpfsResolver({ diff --git a/app/scripts/controllers/app-metadata.test.ts b/app/scripts/controllers/app-metadata.test.ts new file mode 100644 index 000000000..890811ee2 --- /dev/null +++ b/app/scripts/controllers/app-metadata.test.ts @@ -0,0 +1,104 @@ +import assert from 'assert'; +import AppMetadataController from './app-metadata'; + +const EXPECTED_DEFAULT_STATE = { + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}; + +describe('AppMetadataController', () => { + describe('constructor', () => { + it('accepts initial state and does not modify it if currentMigrationVersion and platform.getVersion() match respective values in state', async () => { + const initState = { + currentAppVersion: '1', + previousAppVersion: '1', + previousMigrationVersion: 1, + currentMigrationVersion: 1, + }; + const appMetadataController = new AppMetadataController({ + state: initState, + currentMigrationVersion: 1, + currentAppVersion: '1', + }); + assert.deepStrictEqual(appMetadataController.store.getState(), initState); + }); + + it('sets default state and does not modify it', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + }); + assert.deepStrictEqual( + appMetadataController.store.getState(), + EXPECTED_DEFAULT_STATE, + ); + }); + + it('sets default state and does not modify it if options version parameters match respective default values', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '', + }); + assert.deepStrictEqual( + appMetadataController.store.getState(), + EXPECTED_DEFAULT_STATE, + ); + }); + + it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '1', + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentAppVersion: '1', + }); + }); + + it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', async () => { + const appMetadataController = new AppMetadataController({ + state: { + currentAppVersion: '2', + previousAppVersion: '1', + }, + currentAppVersion: '3', + currentMigrationVersion: 0, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentAppVersion: '3', + previousAppVersion: '2', + }); + }); + + it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 1, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentMigrationVersion: 1, + }); + }); + + it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', async () => { + const appMetadataController = new AppMetadataController({ + state: { + currentMigrationVersion: 2, + previousMigrationVersion: 1, + }, + currentMigrationVersion: 3, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentMigrationVersion: 3, + previousMigrationVersion: 2, + }); + }); + }); +}); diff --git a/app/scripts/controllers/app-metadata.ts b/app/scripts/controllers/app-metadata.ts new file mode 100644 index 000000000..0d745730d --- /dev/null +++ b/app/scripts/controllers/app-metadata.ts @@ -0,0 +1,99 @@ +import EventEmitter from 'events'; +import { ObservableStore } from '@metamask/obs-store'; + +/** + * The state of the AppMetadataController + */ +export type AppMetadataControllerState = { + currentAppVersion: string; + previousAppVersion: string; + previousMigrationVersion: number; + currentMigrationVersion: number; +}; + +/** + * The options that NetworkController takes. + */ +export type AppMetadataControllerOptions = { + currentMigrationVersion?: number; + currentAppVersion?: string; + state?: Partial; +}; + +const defaultState: AppMetadataControllerState = { + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}; + +/** + * The AppMetadata controller stores metadata about the current extension instance, + * including the currently and previously installed versions, and the most recently + * run migration. + * + */ +export default class AppMetadataController extends EventEmitter { + /** + * Observable store containing controller data. + */ + store: ObservableStore; + + /** + * Constructs a AppMetadata controller. + * + * @param options - the controller options + * @param options.state - Initial controller state. + * @param options.currentMigrationVersion + * @param options.currentAppVersion + */ + constructor({ + currentAppVersion = '', + currentMigrationVersion = 0, + state = {}, + }: AppMetadataControllerOptions) { + super(); + + this.store = new ObservableStore({ + ...defaultState, + ...state, + }); + + this.#maybeUpdateAppVersion(currentAppVersion); + + this.#maybeUpdateMigrationVersion(currentMigrationVersion); + } + + /** + * Updates the currentAppVersion in state, and sets the previousAppVersion to the old currentAppVersion. + * + * @param maybeNewAppVersion + */ + #maybeUpdateAppVersion(maybeNewAppVersion: string): void { + const oldCurrentAppVersion = this.store.getState().currentAppVersion; + + if (maybeNewAppVersion !== oldCurrentAppVersion) { + this.store.updateState({ + currentAppVersion: maybeNewAppVersion, + previousAppVersion: oldCurrentAppVersion, + }); + } + } + + /** + * Updates the migrationVersion in state. + * + * @param maybeNewMigrationVersion + */ + #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { + const oldCurrentMigrationVersion = + this.store.getState().currentMigrationVersion; + + if (maybeNewMigrationVersion !== oldCurrentMigrationVersion) { + this.store.updateState({ + previousMigrationVersion: oldCurrentMigrationVersion, + currentMigrationVersion: maybeNewMigrationVersion, + }); + } + } +} diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 8cdc65223..26fd5f01d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -35,9 +35,11 @@ export const SENTRY_STATE = { connectedStatusPopoverHasBeenShown: true, conversionDate: true, conversionRate: true, + currentAppVersion: true, currentBlockGasLimit: true, currentCurrency: true, currentLocale: true, + currentMigrationVersion: true, customNonceValue: true, defaultHomeActiveTabName: true, desktopEnabled: true, @@ -56,6 +58,8 @@ export const SENTRY_STATE = { nextNonce: true, participateInMetaMetrics: true, preferences: true, + previousAppVersion: true, + previousMigrationVersion: true, providerConfig: { nickname: true, ticker: true, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e0f7f476c..7c0afce37 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -198,6 +198,7 @@ import createMetaRPCHandler from './lib/createMetaRPCHandler'; import { previousValueComparator } from './lib/util'; import createMetamaskMiddleware from './lib/createMetamaskMiddleware'; import EncryptionPublicKeyController from './controllers/encryption-public-key'; +import AppMetadataController from './controllers/app-metadata'; import { CaveatMutatorFactories, @@ -267,6 +268,8 @@ export default class MetamaskController extends EventEmitter { // instance of a class that wraps the extension's storage local API. this.localStoreApiWrapper = opts.localStore; + this.currentMigrationVersion = opts.currentMigrationVersion; + // observable state store this.store = new ComposableObservableStore({ state: initState, @@ -287,6 +290,12 @@ export default class MetamaskController extends EventEmitter { } }); + this.appMetadataController = new AppMetadataController({ + state: initState.AppMetadataController, + currentMigrationVersion: this.currentMigrationVersion, + currentAppVersion: version, + }); + // next, we will initialize the controllers // controller initialization order matters @@ -1629,6 +1638,7 @@ export default class MetamaskController extends EventEmitter { this.store.updateStructure({ AppStateController: this.appStateController.store, + AppMetadataController: this.appMetadataController.store, TransactionController: this.txController.store, KeyringController: this.keyringController.store, PreferencesController: this.preferencesController.store, @@ -1676,6 +1686,7 @@ export default class MetamaskController extends EventEmitter { this.memStore = new ComposableObservableStore({ config: { AppStateController: this.appStateController.store, + AppMetadataController: this.appMetadataController.store, NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, KeyringController: this.keyringController.memStore, From c0fd7700c8c3fe6a0ba537657dabbd021f7346ce Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 13:34:12 -0230 Subject: [PATCH 013/102] Fix Sentry error e2e tests (#20479) The state fixtures in the Sentry e2e tests became invalid in #20458 due to a conflict with that change (the new state properties were missing). The state fixtures have been updated. --- .../errors-after-init-opt-in-background-state.json | 4 ++++ .../state-snapshots/errors-after-init-opt-in-ui-state.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index efdf28622..03fc85edf 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -3,6 +3,10 @@ "isInitialized": true, "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94, "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 7300c3a05..4ecaa0417 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -25,6 +25,10 @@ "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94, "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", From 72d20c92d69fc9ad13306d1cc232096dba0f8f5f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 16 Aug 2023 14:29:17 -0230 Subject: [PATCH 014/102] Version v10.34.5 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 583701a69..b7f1393ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [10.34.5] + ## [10.34.4] ### Changed - Updated snaps execution environment ([#20420](https://github.com/MetaMask/metamask-extension/pull/20420)) @@ -3885,7 +3887,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.34.4...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.34.5...HEAD +[10.34.5]: https://github.com/MetaMask/metamask-extension/compare/v10.34.4...v10.34.5 [10.34.4]: https://github.com/MetaMask/metamask-extension/compare/v10.34.3...v10.34.4 [10.34.3]: https://github.com/MetaMask/metamask-extension/compare/v10.34.2...v10.34.3 [10.34.2]: https://github.com/MetaMask/metamask-extension/compare/v10.34.1...v10.34.2 diff --git a/package.json b/package.json index 297c63612..b1790a1e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "10.34.4", + "version": "10.34.5", "private": true, "repository": { "type": "git", From 442181de949a474b20292a93b09a5ea56690438d Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 15 Aug 2023 19:19:48 -0230 Subject: [PATCH 015/102] Log before and after each migration run (#20424) * Log before and after each migration run * Use loglevel --- app/scripts/lib/migrator/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index ca2abb8ea..5925430f0 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,4 +1,5 @@ import EventEmitter from 'events'; +import log from 'loglevel'; /** * @typedef {object} Migration @@ -36,6 +37,8 @@ export default class Migrator extends EventEmitter { // perform each migration for (const migration of pendingMigrations) { try { + log.info(`Running migration ${migration.version}...`); + // attempt migration and validate const migratedData = await migration.migrate(versionedData); if (!migratedData.data) { @@ -52,6 +55,8 @@ export default class Migrator extends EventEmitter { // accept the migration as good // eslint-disable-next-line no-param-reassign versionedData = migratedData; + + log.info(`Migration ${migration.version} complete`); } catch (err) { // rewrite error message to add context without clobbering stack const originalErrorMessage = err.message; From 4e93b86116da0ee7bc14754771d85b54b369fe87 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 14:40:44 -0230 Subject: [PATCH 016/102] Split Sentry mask into UI and background masks (#20426) The state mask used to anonymize the Sentry state snapshots has been split into UI and background masks. This was done to simplify later refactors. There should be no functional changes. --- app/scripts/background.js | 11 ++-- app/scripts/lib/setupSentry.js | 101 +++++++++++++++++---------------- ui/index.js | 8 +-- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 264964cca..6b29e14d7 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -41,7 +41,7 @@ import Migrator from './lib/migrator'; import ExtensionPlatform from './platforms/extension'; import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; -import { SENTRY_STATE } from './lib/setupSentry'; +import { SENTRY_BACKGROUND_STATE } from './lib/setupSentry'; import createStreamSink from './lib/createStreamSink'; import NotificationManager, { @@ -886,11 +886,14 @@ browser.runtime.onInstalled.addListener(({ reason }) => { function setupSentryGetStateGlobal(store) { global.stateHooks.getSentryState = function () { - const fullState = store.getState(); - const debugState = maskObject({ metamask: fullState }, SENTRY_STATE); + const backgroundState = store.getState(); + const maskedBackgroundState = maskObject( + backgroundState, + SENTRY_BACKGROUND_STATE, + ); return { browser: window.navigator.userAgent, - store: debugState, + store: { metamask: maskedBackgroundState }, version: platform.getVersion(), }; }; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 26fd5f01d..2699d2e3d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -23,59 +23,64 @@ export const ERROR_URL_ALLOWLIST = { SEGMENT: 'segment.io', }; +// This describes the subset of background controller state attached to errors +// sent to Sentry These properties have some potential to be useful for +// debugging, and they do not contain any identifiable information. +export const SENTRY_BACKGROUND_STATE = { + alertEnabledness: true, + completedOnboarding: true, + connectedStatusPopoverHasBeenShown: true, + conversionDate: true, + conversionRate: true, + currentAppVersion: true, + currentBlockGasLimit: true, + currentCurrency: true, + currentLocale: true, + currentMigrationVersion: true, + customNonceValue: true, + defaultHomeActiveTabName: true, + desktopEnabled: true, + featureFlags: true, + firstTimeFlowType: true, + forgottenPassword: true, + incomingTxLastFetchedBlockByChainId: true, + ipfsGateway: true, + isAccountMenuOpen: true, + isInitialized: true, + isUnlocked: true, + metaMetricsId: true, + nativeCurrency: true, + networkId: true, + networkStatus: true, + nextNonce: true, + participateInMetaMetrics: true, + preferences: true, + previousAppVersion: true, + previousMigrationVersion: true, + providerConfig: { + nickname: true, + ticker: true, + type: true, + }, + seedPhraseBackedUp: true, + unapprovedDecryptMsgCount: true, + unapprovedEncryptionPublicKeyMsgCount: true, + unapprovedMsgCount: true, + unapprovedPersonalMsgCount: true, + unapprovedTypedMessagesCount: true, + useBlockie: true, + useNonceField: true, + usePhishDetect: true, + welcomeScreenSeen: true, +}; + // This describes the subset of Redux state attached to errors sent to Sentry // These properties have some potential to be useful for debugging, and they do // not contain any identifiable information. -export const SENTRY_STATE = { +export const SENTRY_UI_STATE = { gas: true, history: true, - metamask: { - alertEnabledness: true, - completedOnboarding: true, - connectedStatusPopoverHasBeenShown: true, - conversionDate: true, - conversionRate: true, - currentAppVersion: true, - currentBlockGasLimit: true, - currentCurrency: true, - currentLocale: true, - currentMigrationVersion: true, - customNonceValue: true, - defaultHomeActiveTabName: true, - desktopEnabled: true, - featureFlags: true, - firstTimeFlowType: true, - forgottenPassword: true, - incomingTxLastFetchedBlockByChainId: true, - ipfsGateway: true, - isAccountMenuOpen: true, - isInitialized: true, - isUnlocked: true, - metaMetricsId: true, - nativeCurrency: true, - networkId: true, - networkStatus: true, - nextNonce: true, - participateInMetaMetrics: true, - preferences: true, - previousAppVersion: true, - previousMigrationVersion: true, - providerConfig: { - nickname: true, - ticker: true, - type: true, - }, - seedPhraseBackedUp: true, - unapprovedDecryptMsgCount: true, - unapprovedEncryptionPublicKeyMsgCount: true, - unapprovedMsgCount: true, - unapprovedPersonalMsgCount: true, - unapprovedTypedMessagesCount: true, - useBlockie: true, - useNonceField: true, - usePhishDetect: true, - welcomeScreenSeen: true, - }, + metamask: SENTRY_BACKGROUND_STATE, unconnectedAccount: true, }; diff --git a/ui/index.js b/ui/index.js index e9ac462bd..6f9a148ed 100644 --- a/ui/index.js +++ b/ui/index.js @@ -8,7 +8,7 @@ import browser from 'webextension-polyfill'; import { getEnvironmentType } from '../app/scripts/lib/util'; import { AlertTypes } from '../shared/constants/alerts'; import { maskObject } from '../shared/modules/object.utils'; -import { SENTRY_STATE } from '../app/scripts/lib/setupSentry'; +import { SENTRY_UI_STATE } from '../app/scripts/lib/setupSentry'; import { ENVIRONMENT_TYPE_POPUP } from '../shared/constants/app'; import switchDirection from '../shared/lib/switch-direction'; import { setupLocale } from '../shared/lib/error-utils'; @@ -234,11 +234,11 @@ function setupStateHooks(store) { return state; }; window.stateHooks.getSentryState = function () { - const fullState = store.getState(); - const debugState = maskObject(fullState, SENTRY_STATE); + const reduxState = store.getState(); + const maskedReduxState = maskObject(reduxState, SENTRY_UI_STATE); return { browser: window.navigator.userAgent, - store: debugState, + store: maskedReduxState, version: global.platform.getVersion(), }; }; From 594dde58b15315c146f53f1a90b3c3b616868d1e Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 11:52:25 -0230 Subject: [PATCH 017/102] Add additional Sentry E2E tests (#20425) * Reorganize Sentry error e2e tests The tests have been reorganized into different describe blocks. Each describe block is for either before or after initialization, and either with or without opting into metrics. This was done to simplify later test additions. The conditions for each test are now in the describe block, letting us test additional things in each of these conditions. The conditions were flattened to a single level to avoid excessive indentation. * Add error e2e test for background and UI errors The Sentry e2e tests before initialization only tested background errors, and the after initialization tests only tested UI errors. Now both types of errors are tested in both scenarios. * Add error e2e tests for Sentry error state E2E tests have been added to test the state object sent along with each Sentry error. At the moment this object is empty in some circumstances, but this will change in later PRs. * Rename throw test error function * Only setup debug/test state hooks in dev/test builds The state hooks used for debugging and testing are now only included in dev or test builds. The function name was updated and given a JSDoc description to explain this more clearly as well. * Add state snapshot assertions State snapshot assertions have been added to the e2e error tests. These snapshots will be very useful in reviewing a few PRs that will follow this one. We might decide to remove these snapshots after this set of Sentry refactors, as they might be more work to maintain than they're worth. But they will be useful at least in the short-term. The login step has been removed from a few tests because it introduced indeterminacy (the login process continued asynchronously after the login, and sometimes was not finished when the error was triggered). * Ensure login page has rendered during setup This fixes an intermittent failure on Firefox * Format snapshots with prettier before writing them * Use defined set of date fields rather than infering from name * Remove waits for error screen The error screen only appears after a long timeout, and it doesn't affect the next test steps at all. --- app/scripts/metamask-controller.js | 18 + test/e2e/tests/errors.spec.js | 466 ++++++++++++++++-- ...rs-after-init-opt-in-background-state.json | 50 ++ .../errors-after-init-opt-in-ui-state.json | 57 +++ ...s-before-init-opt-in-background-state.json | 1 + .../errors-before-init-opt-in-ui-state.json | 1 + ui/index.js | 45 +- ui/store/actions.ts | 11 + 8 files changed, 610 insertions(+), 39 deletions(-) create mode 100644 test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json create mode 100644 test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7c40d960a..364f8ee8b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2633,6 +2633,9 @@ export default class MetamaskController extends EventEmitter { assetsContractController.getBalancesInSingleCall.bind( assetsContractController, ), + + // E2E testing + throwTestError: this.throwTestError.bind(this), }; } @@ -4340,6 +4343,21 @@ export default class MetamaskController extends EventEmitter { return nonceLock.nextNonce; } + /** + * Throw an artificial error in a timeout handler for testing purposes. + * + * @param message - The error message. + * @deprecated This is only mean to facilitiate E2E testing. We should not + * use this for handling errors. + */ + throwTestError(message) { + setTimeout(() => { + const error = new Error(message); + error.name = 'TestError'; + throw error; + }); + } + //============================================================================= // CONFIG //============================================================================= diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 960135215..4ab80a7de 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -1,8 +1,70 @@ +const { resolve } = require('path'); +const { promises: fs } = require('fs'); const { strict: assert } = require('assert'); +const { get, has, set } = require('lodash'); const { Browser } = require('selenium-webdriver'); +const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const dateFields = ['metamask.conversionDate']; + +/** + * Transform date properties to value types, to ensure that state is + * consistent between test runs. + * + * @param {unknown} data - The data to transform + */ +function transformDates(data) { + for (const field of dateFields) { + if (has(data, field)) { + set(data, field, typeof get(data, field)); + } + } + return data; +} + +/** + * Check that the data provided matches the snapshot. + * + * @param {object }args - Function arguments. + * @param {any} args.data - The data to compare with the snapshot. + * @param {string} args.snapshot - The name of the snapshot. + * @param {boolean} [args.update] - Whether to update the snapshot if it doesn't match. + */ +async function matchesSnapshot({ + data: unprocessedData, + snapshot, + update = process.env.UPDATE_SNAPSHOTS === 'true', +}) { + const data = transformDates(unprocessedData); + + const snapshotPath = resolve(__dirname, `./state-snapshots/${snapshot}.json`); + const rawSnapshotData = await fs.readFile(snapshotPath, { + encoding: 'utf-8', + }); + const snapshotData = JSON.parse(rawSnapshotData); + + try { + assert.deepStrictEqual(data, snapshotData); + } catch (error) { + if (update && error instanceof assert.AssertionError) { + const stringifiedData = JSON.stringify(data); + // filepath specified so that Prettier can infer which parser to use + // from the file extension + const formattedData = format(stringifiedData, { + filepath: 'something.json', + }); + await fs.writeFile(snapshotPath, formattedData, { + encoding: 'utf-8', + }); + console.log(`Snapshot '${snapshot}' updated`); + return; + } + throw error; + } +} + describe('Sentry errors', function () { const migrationError = process.env.SELENIUM_BROWSER === Browser.CHROME @@ -41,8 +103,8 @@ describe('Sentry errors', function () { ], }; - describe('before initialization', function () { - it('should NOT send error events when participateInMetaMetrics is false', async function () { + describe('before initialization, after opting out of metrics', function () { + it('should NOT send error events in the background', async function () { await withFixtures( { fixtures: { @@ -73,7 +135,43 @@ describe('Sentry errors', function () { }, ); }); - it('should send error events', async function () { + + it('should NOT send error events in the UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + + // Wait for Sentry request + await driver.delay(3000); + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + }); + + describe('before initialization, after opting into metrics', function () { + it('should send error events in background', async function () { await withFixtures( { fixtures: { @@ -112,40 +210,47 @@ describe('Sentry errors', function () { }, ); }); - }); - describe('after initialization', function () { - it('should NOT send error events when participateInMetaMetrics is false', async function () { + it('should capture background application state', async function () { await withFixtures( { - fixtures: new FixtureBuilder() - .withMetaMetricsController({ - metaMetricsId: null, - participateInMetaMetrics: false, - }) - .build(), + fixtures: { + ...new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + // Intentionally corrupt state to trigger migration error during initialization + meta: undefined, + }, ganacheOptions, title: this.test.title, failOnConsoleError: false, - testSpecificMock: mockSentryTestError, + testSpecificMock: mockSentryMigratorError, }, async ({ driver, mockedEndpoint }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - // Trigger error - driver.executeScript('window.stateHooks.throwTestError()'); - driver.delay(3000); + // Wait for Sentry request - const isPending = await mockedEndpoint.isPending(); - assert.ok( - isPending, - 'A request to sentry was sent when it should not have been', - ); + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + await matchesSnapshot({ + data: appState, + snapshot: 'errors-before-init-opt-in-background-state', + }); }, ); }); - it('should send error events', async function () { + + it('should send error events in UI', async function () { await withFixtures( { fixtures: new FixtureBuilder() @@ -161,15 +266,171 @@ describe('Sentry errors', function () { }, async ({ driver, mockedEndpoint }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + // Trigger error - driver.executeScript('window.stateHooks.throwTestError()'); + await driver.executeScript('window.stateHooks.throwTestError()'); + // Wait for Sentry request await driver.wait(async () => { const isPending = await mockedEndpoint.isPending(); return isPending === false; - }, 10000); + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const { level } = mockJsonBody; + const [{ type, value }] = mockJsonBody.exception.values; + // Verify request + assert.equal(type, 'TestError'); + assert.equal(value, 'Test Error'); + assert.equal(level, 'error'); + }, + ); + }); + + it('should capture UI application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + // Erase `getSentryState` hook, simulating a "before initialization" state + await driver.executeScript( + 'window.stateHooks.getSentryState = undefined', + ); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + await matchesSnapshot({ + data: appState, + snapshot: 'errors-before-init-opt-in-ui-state', + }); + }, + ); + }); + }); + + describe('after initialization, after opting out of metrics', function () { + it('should NOT send error events in the background', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + + it('should NOT send error events in the UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: null, + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + const isPending = await mockedEndpoint.isPending(); + assert.ok( + isPending, + 'A request to sentry was sent when it should not have been', + ); + }, + ); + }); + }); + + describe('after initialization, after opting into metrics', function () { + it('should send error events in background', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); const [mockedRequest] = await mockedEndpoint.getSeenRequests(); const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); @@ -184,5 +445,154 @@ describe('Sentry errors', function () { }, ); }); + + it('should capture background application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript( + 'window.stateHooks.throwTestBackgroundError()', + ); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'store', + 'version', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); + await matchesSnapshot({ + data: appState.store, + snapshot: 'errors-after-init-opt-in-background-state', + }); + }, + ); + }); + + it('should send error events in UI', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const { level, extra } = mockJsonBody; + const [{ type, value }] = mockJsonBody.exception.values; + const { participateInMetaMetrics } = extra.appState.store.metamask; + // Verify request + assert.equal(type, 'TestError'); + assert.equal(value, 'Test Error'); + assert.equal(level, 'error'); + assert.equal(participateInMetaMetrics, true); + }, + ); + }); + + it('should capture UI application state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryTestError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + await driver.findElement('#password'); + + // Trigger error + await driver.executeScript('window.stateHooks.throwTestError()'); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'store', + 'version', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); + await matchesSnapshot({ + data: appState.store, + snapshot: 'errors-after-init-opt-in-ui-state', + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json new file mode 100644 index 000000000..efdf28622 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -0,0 +1,50 @@ +{ + "metamask": { + "isInitialized": true, + "connectedStatusPopoverHasBeenShown": true, + "defaultHomeActiveTabName": null, + "networkId": "1337", + "providerConfig": { + "nickname": "Localhost 8545", + "ticker": "ETH", + "type": "rpc" + }, + "isUnlocked": false, + "useBlockie": false, + "useNonceField": false, + "usePhishDetect": true, + "featureFlags": { "showIncomingTransactions": true }, + "currentLocale": "en", + "forgottenPassword": false, + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "ipfsGateway": "dweb.link", + "participateInMetaMetrics": true, + "metaMetricsId": "fake-metrics-id", + "conversionDate": "number", + "conversionRate": 1700, + "nativeCurrency": "ETH", + "currentCurrency": "usd", + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "seedPhraseBackedUp": true, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + }, + "currentBlockGasLimit": "0x1c9c380", + "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessagesCount": 0 + } +} diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json new file mode 100644 index 000000000..7300c3a05 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -0,0 +1,57 @@ +{ + "gas": { "customData": { "price": null, "limit": null } }, + "history": { "mostRecentOverviewPage": "/" }, + "metamask": { + "isInitialized": true, + "isUnlocked": false, + "isAccountMenuOpen": false, + "customNonceValue": "", + "useBlockie": false, + "featureFlags": { "showIncomingTransactions": true }, + "welcomeScreenSeen": false, + "currentLocale": "en", + "currentBlockGasLimit": "", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "participateInMetaMetrics": true, + "nextNonce": null, + "conversionRate": 1300, + "nativeCurrency": "ETH", + "connectedStatusPopoverHasBeenShown": true, + "defaultHomeActiveTabName": null, + "networkId": "1337", + "providerConfig": { + "nickname": "Localhost 8545", + "ticker": "ETH", + "type": "rpc" + }, + "useNonceField": false, + "usePhishDetect": true, + "forgottenPassword": false, + "ipfsGateway": "dweb.link", + "metaMetricsId": "fake-metrics-id", + "conversionDate": "number", + "currentCurrency": "usd", + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "seedPhraseBackedUp": true, + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + }, + "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessagesCount": 0 + }, + "unconnectedAccount": { "state": "CLOSED" } +} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -0,0 +1 @@ +{} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -0,0 +1 @@ +{} diff --git a/ui/index.js b/ui/index.js index 1f1aeb281..e9ac462bd 100644 --- a/ui/index.js +++ b/ui/index.js @@ -81,7 +81,7 @@ export default function launchMetamaskUi(opts, cb) { return; } startApp(metamaskState, backgroundConnection, opts).then((store) => { - setupDebuggingHelpers(store); + setupStateHooks(store); cb( null, store, @@ -191,16 +191,39 @@ async function startApp(metamaskState, backgroundConnection, opts) { return store; } -function setupDebuggingHelpers(store) { - /** - * The following stateHook is a method intended to throw an error, used in - * our E2E test to ensure that errors are attempted to be sent to sentry. - */ - window.stateHooks.throwTestError = async function () { - const error = new Error('Test Error'); - error.name = 'TestError'; - throw error; - }; +/** + * Setup functions on `window.stateHooks`. Some of these support + * application features, and some are just for debugging or testing. + * + * @param {object} store - The Redux store. + */ +function setupStateHooks(store) { + if (process.env.METAMASK_DEBUG || process.env.IN_TEST) { + /** + * The following stateHook is a method intended to throw an error, used in + * our E2E test to ensure that errors are attempted to be sent to sentry. + * + * @param {string} [msg] - The error message to throw, defaults to 'Test Error' + */ + window.stateHooks.throwTestError = async function (msg = 'Test Error') { + const error = new Error(msg); + error.name = 'TestError'; + throw error; + }; + /** + * The following stateHook is a method intended to throw an error in the + * background, used in our E2E test to ensure that errors are attempted to be + * sent to sentry. + * + * @param {string} [msg] - The error message to throw, defaults to 'Test Error' + */ + window.stateHooks.throwTestBackgroundError = async function ( + msg = 'Test Error', + ) { + store.dispatch(actions.throwTestBackgroundError(msg)); + }; + } + window.stateHooks.getCleanAppState = async function () { const state = clone(store.getState()); state.version = global.platform.getVersion(); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 509127bf6..16cb68ef0 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4378,6 +4378,17 @@ export async function getCurrentNetworkEIP1559Compatibility(): Promise< return networkEIP1559Compatibility; } +/** + * Throw an error in the background for testing purposes. + * + * @param message - The error message. + * @deprecated This is only mean to facilitiate E2E testing. We should not use + * this for handling errors. + */ +export async function throwTestBackgroundError(message: string): Promise { + await submitRequestToBackground('throwTestError', [message]); +} + ///: BEGIN:ONLY_INCLUDE_IN(snaps) /** * Set status of popover warning for the first snap installation. From 789122d5878bec8b6db7abf2e5a7b87057950efc Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 16 Aug 2023 12:22:38 -0230 Subject: [PATCH 018/102] Capture app and migration version (#20458) * Add AppMetadataController so current and previous application and migration version can be captured in sentry * Add currentAppVersion, previousAppVersion, previousMigrationVersion, currentMigrationVersion to SENTRY_OBJECT * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Update app/scripts/controllers/app-metadata.ts Co-authored-by: Mark Stacey * Fix types * Add tests for app-metadata.test.ts * Lint fixes * Modify loadStateFromPersistence to return the whole versionData object, so that the migration version can be passed to the metamask-controller on instantiation * Remove reference to implementation details in test descriptions in app/scripts/controllers/app-metadata.test.ts * Reset all mocks afterEach in AppMetadataController * Refactor AppMetadataController to be passed version instead of calling platform.version directly (for ease of unit testing the MetaMask Controller) * Make maybeUpdateAppVersion and maybeUpdateMigrationVersion private, and remove unit tests of those specific functions --------- Co-authored-by: Mark Stacey --- app/scripts/background.js | 9 +- app/scripts/controllers/app-metadata.test.ts | 104 +++++++++++++++++++ app/scripts/controllers/app-metadata.ts | 99 ++++++++++++++++++ app/scripts/lib/setupSentry.js | 4 + app/scripts/metamask-controller.js | 11 ++ 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 app/scripts/controllers/app-metadata.test.ts create mode 100644 app/scripts/controllers/app-metadata.ts diff --git a/app/scripts/background.js b/app/scripts/background.js index 44d3870ad..4ce5419c5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -264,7 +264,8 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { */ async function initialize() { try { - const initState = await loadStateFromPersistence(); + const initData = await loadStateFromPersistence(); + const initState = initData.data; const initLangCode = await getFirstPreferredLangCode(); ///: BEGIN:ONLY_INCLUDE_IN(desktop) @@ -287,6 +288,7 @@ async function initialize() { initLangCode, {}, isFirstMetaMaskControllerSetup, + initData.meta, ); if (!isManifestV3) { await loadPhishingWarningPage(); @@ -417,7 +419,7 @@ export async function loadStateFromPersistence() { localStore.set(versionedData.data); // return just the data - return versionedData.data; + return versionedData; } /** @@ -430,12 +432,14 @@ export async function loadStateFromPersistence() { * @param {string} initLangCode - The region code for the language preferred by the current user. * @param {object} overrides - object with callbacks that are allowed to override the setup controller logic (usefull for desktop app) * @param isFirstMetaMaskControllerSetup + * @param {object} stateMetadata - Metadata about the initial state and migrations, including the most recent migration version */ export function setupController( initState, initLangCode, overrides, isFirstMetaMaskControllerSetup, + stateMetadata, ) { // // MetaMask Controller @@ -462,6 +466,7 @@ export function setupController( localStore, overrides, isFirstMetaMaskControllerSetup, + currentMigrationVersion: stateMetadata.version, }); setupEnsIpfsResolver({ diff --git a/app/scripts/controllers/app-metadata.test.ts b/app/scripts/controllers/app-metadata.test.ts new file mode 100644 index 000000000..890811ee2 --- /dev/null +++ b/app/scripts/controllers/app-metadata.test.ts @@ -0,0 +1,104 @@ +import assert from 'assert'; +import AppMetadataController from './app-metadata'; + +const EXPECTED_DEFAULT_STATE = { + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}; + +describe('AppMetadataController', () => { + describe('constructor', () => { + it('accepts initial state and does not modify it if currentMigrationVersion and platform.getVersion() match respective values in state', async () => { + const initState = { + currentAppVersion: '1', + previousAppVersion: '1', + previousMigrationVersion: 1, + currentMigrationVersion: 1, + }; + const appMetadataController = new AppMetadataController({ + state: initState, + currentMigrationVersion: 1, + currentAppVersion: '1', + }); + assert.deepStrictEqual(appMetadataController.store.getState(), initState); + }); + + it('sets default state and does not modify it', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + }); + assert.deepStrictEqual( + appMetadataController.store.getState(), + EXPECTED_DEFAULT_STATE, + ); + }); + + it('sets default state and does not modify it if options version parameters match respective default values', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '', + }); + assert.deepStrictEqual( + appMetadataController.store.getState(), + EXPECTED_DEFAULT_STATE, + ); + }); + + it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '1', + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentAppVersion: '1', + }); + }); + + it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', async () => { + const appMetadataController = new AppMetadataController({ + state: { + currentAppVersion: '2', + previousAppVersion: '1', + }, + currentAppVersion: '3', + currentMigrationVersion: 0, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentAppVersion: '3', + previousAppVersion: '2', + }); + }); + + it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', async () => { + const appMetadataController = new AppMetadataController({ + state: {}, + currentMigrationVersion: 1, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentMigrationVersion: 1, + }); + }); + + it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', async () => { + const appMetadataController = new AppMetadataController({ + state: { + currentMigrationVersion: 2, + previousMigrationVersion: 1, + }, + currentMigrationVersion: 3, + }); + assert.deepStrictEqual(appMetadataController.store.getState(), { + ...EXPECTED_DEFAULT_STATE, + currentMigrationVersion: 3, + previousMigrationVersion: 2, + }); + }); + }); +}); diff --git a/app/scripts/controllers/app-metadata.ts b/app/scripts/controllers/app-metadata.ts new file mode 100644 index 000000000..0d745730d --- /dev/null +++ b/app/scripts/controllers/app-metadata.ts @@ -0,0 +1,99 @@ +import EventEmitter from 'events'; +import { ObservableStore } from '@metamask/obs-store'; + +/** + * The state of the AppMetadataController + */ +export type AppMetadataControllerState = { + currentAppVersion: string; + previousAppVersion: string; + previousMigrationVersion: number; + currentMigrationVersion: number; +}; + +/** + * The options that NetworkController takes. + */ +export type AppMetadataControllerOptions = { + currentMigrationVersion?: number; + currentAppVersion?: string; + state?: Partial; +}; + +const defaultState: AppMetadataControllerState = { + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}; + +/** + * The AppMetadata controller stores metadata about the current extension instance, + * including the currently and previously installed versions, and the most recently + * run migration. + * + */ +export default class AppMetadataController extends EventEmitter { + /** + * Observable store containing controller data. + */ + store: ObservableStore; + + /** + * Constructs a AppMetadata controller. + * + * @param options - the controller options + * @param options.state - Initial controller state. + * @param options.currentMigrationVersion + * @param options.currentAppVersion + */ + constructor({ + currentAppVersion = '', + currentMigrationVersion = 0, + state = {}, + }: AppMetadataControllerOptions) { + super(); + + this.store = new ObservableStore({ + ...defaultState, + ...state, + }); + + this.#maybeUpdateAppVersion(currentAppVersion); + + this.#maybeUpdateMigrationVersion(currentMigrationVersion); + } + + /** + * Updates the currentAppVersion in state, and sets the previousAppVersion to the old currentAppVersion. + * + * @param maybeNewAppVersion + */ + #maybeUpdateAppVersion(maybeNewAppVersion: string): void { + const oldCurrentAppVersion = this.store.getState().currentAppVersion; + + if (maybeNewAppVersion !== oldCurrentAppVersion) { + this.store.updateState({ + currentAppVersion: maybeNewAppVersion, + previousAppVersion: oldCurrentAppVersion, + }); + } + } + + /** + * Updates the migrationVersion in state. + * + * @param maybeNewMigrationVersion + */ + #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { + const oldCurrentMigrationVersion = + this.store.getState().currentMigrationVersion; + + if (maybeNewMigrationVersion !== oldCurrentMigrationVersion) { + this.store.updateState({ + previousMigrationVersion: oldCurrentMigrationVersion, + currentMigrationVersion: maybeNewMigrationVersion, + }); + } + } +} diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index dd0eb7bd6..967361315 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -35,9 +35,11 @@ export const SENTRY_STATE = { connectedStatusPopoverHasBeenShown: true, conversionDate: true, conversionRate: true, + currentAppVersion: true, currentBlockGasLimit: true, currentCurrency: true, currentLocale: true, + currentMigrationVersion: true, customNonceValue: true, defaultHomeActiveTabName: true, desktopEnabled: true, @@ -56,6 +58,8 @@ export const SENTRY_STATE = { nextNonce: true, participateInMetaMetrics: true, preferences: true, + previousAppVersion: true, + previousMigrationVersion: true, providerConfig: { nickname: true, ticker: true, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 364f8ee8b..64bc9a26f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -192,6 +192,7 @@ import createMetaRPCHandler from './lib/createMetaRPCHandler'; import { previousValueComparator } from './lib/util'; import createMetamaskMiddleware from './lib/createMetamaskMiddleware'; import EncryptionPublicKeyController from './controllers/encryption-public-key'; +import AppMetadataController from './controllers/app-metadata'; import { CaveatMutatorFactories, @@ -258,6 +259,8 @@ export default class MetamaskController extends EventEmitter { // instance of a class that wraps the extension's storage local API. this.localStoreApiWrapper = opts.localStore; + this.currentMigrationVersion = opts.currentMigrationVersion; + // observable state store this.store = new ComposableObservableStore({ state: initState, @@ -278,6 +281,12 @@ export default class MetamaskController extends EventEmitter { } }); + this.appMetadataController = new AppMetadataController({ + state: initState.AppMetadataController, + currentMigrationVersion: this.currentMigrationVersion, + currentAppVersion: version, + }); + // next, we will initialize the controllers // controller initialization order matters @@ -1506,6 +1515,7 @@ export default class MetamaskController extends EventEmitter { this.store.updateStructure({ AppStateController: this.appStateController.store, + AppMetadataController: this.appMetadataController.store, TransactionController: this.txController.store, KeyringController: this.keyringController.store, PreferencesController: this.preferencesController.store, @@ -1550,6 +1560,7 @@ export default class MetamaskController extends EventEmitter { this.memStore = new ComposableObservableStore({ config: { AppStateController: this.appStateController.store, + AppMetadataController: this.appMetadataController.store, NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, KeyringController: this.keyringController.memStore, From 419bf92282a4dd469e2bba2fe0ebb7dd107a2c24 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Wed, 16 Aug 2023 10:34:08 -0700 Subject: [PATCH 019/102] Removing Box props description from TS component docs (#20451) * Removing Box props description from TS component docs * Making style utility prop comments more generic --- .../avatar-account/README.mdx | 2 - .../component-library/avatar-base/README.mdx | 2 - .../component-library/avatar-icon/README.mdx | 2 - .../avatar-network/README.mdx | 2 - .../component-library/avatar-token/README.mdx | 2 - .../badge-wrapper/README.mdx | 2 - .../component-library/box/box.types.ts | 62 +++++++++---------- .../component-library/button-base/README.mdx | 2 - .../component-library/button-icon/README.mdx | 2 - .../component-library/checkbox/README.mdx | 2 - .../component-library/header-base/README.mdx | 2 - .../component-library/help-text/README.mdx | 2 - .../component-library/icon/README.mdx | 2 - .../component-library/input/README.mdx | 2 - .../component-library/label/README.mdx | 2 - .../modal-content/README.mdx | 2 - .../modal-overlay/README.mdx | 2 - .../component-library/modal/README.mdx | 2 - .../component-library/tag/README.mdx | 4 +- .../component-library/text/README.mdx | 2 - 20 files changed, 32 insertions(+), 70 deletions(-) diff --git a/ui/components/component-library/avatar-account/README.mdx b/ui/components/component-library/avatar-account/README.mdx index 5b64bded3..50f1287d2 100644 --- a/ui/components/component-library/avatar-account/README.mdx +++ b/ui/components/component-library/avatar-account/README.mdx @@ -13,8 +13,6 @@ The `AvatarAccount` is a type of avatar reserved for representing accounts. ## Props -The `AvatarAccount` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Size diff --git a/ui/components/component-library/avatar-base/README.mdx b/ui/components/component-library/avatar-base/README.mdx index c02f1fb34..b4c743e03 100644 --- a/ui/components/component-library/avatar-base/README.mdx +++ b/ui/components/component-library/avatar-base/README.mdx @@ -14,8 +14,6 @@ The `AvatarBase` is a wrapper component responsible for enforcing dimensions and ## Props -The `AvatarBase` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props. - ### Size diff --git a/ui/components/component-library/avatar-icon/README.mdx b/ui/components/component-library/avatar-icon/README.mdx index f5ca3e3e6..6e5a689ad 100644 --- a/ui/components/component-library/avatar-icon/README.mdx +++ b/ui/components/component-library/avatar-icon/README.mdx @@ -13,8 +13,6 @@ The `AvatarIcon` is an avatar component that renders only an icon inside and is ## Props -The `AvatarIcon` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - `AvatarIcon` accepts all [AvatarBase](/docs/components-componentlibrary-avatarbase--default-story)) diff --git a/ui/components/component-library/avatar-network/README.mdx b/ui/components/component-library/avatar-network/README.mdx index beddfd9c9..23d23f7b8 100644 --- a/ui/components/component-library/avatar-network/README.mdx +++ b/ui/components/component-library/avatar-network/README.mdx @@ -12,8 +12,6 @@ The `AvatarNetwork` is a component responsible for display of the image of a giv ## Props -The `AvatarNetwork` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Size diff --git a/ui/components/component-library/avatar-token/README.mdx b/ui/components/component-library/avatar-token/README.mdx index 435a2fbf1..0c4b85f9b 100644 --- a/ui/components/component-library/avatar-token/README.mdx +++ b/ui/components/component-library/avatar-token/README.mdx @@ -12,8 +12,6 @@ The `AvatarToken` is a component responsible for display of the image of a given ## Props -The `AvatarToken` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Size diff --git a/ui/components/component-library/badge-wrapper/README.mdx b/ui/components/component-library/badge-wrapper/README.mdx index 5ca0851ce..2037b074e 100644 --- a/ui/components/component-library/badge-wrapper/README.mdx +++ b/ui/components/component-library/badge-wrapper/README.mdx @@ -12,8 +12,6 @@ The `BadgeWrapper` positions a badge on top of another component ## Props -The `BadgeWrapper` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props. - ### Children diff --git a/ui/components/component-library/box/box.types.ts b/ui/components/component-library/box/box.types.ts index 8344b72de..badb3f5ce 100644 --- a/ui/components/component-library/box/box.types.ts +++ b/ui/components/component-library/box/box.types.ts @@ -225,187 +225,187 @@ export type PolymorphicComponentPropWithRef< */ export interface StyleUtilityProps { /** - * The flex direction of the Box component. + * The flex direction of the component. * Use the FlexDirection enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ flexDirection?: FlexDirection | FlexDirectionArray; /** - * The flex wrap of the Box component. + * The flex wrap of the component. * Use the FlexWrap enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ flexWrap?: FlexWrap | FlexWrapArray; /** - * The gap between the Box component's children. + * The gap between the component's children. * Use 1-12 for a gap of 4px-48px. * Accepts responsive props in the form of an array. */ gap?: SizeNumber | SizeNumberArray | undefined; /** - * The margin of the Box component. + * The margin of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ margin?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-top of the Box component. + * The margin-top of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginTop?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-bottom of the Box component. + * The margin-bottom of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginBottom?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-right of the Box component. + * The margin-right of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginRight?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-left of the Box component. + * The margin-left of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginLeft?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-inline of the Box component. + * The margin-inline of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginInline?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-inline-start of the Box component. + * The margin-inline-start of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginInlineStart?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The margin-inline-end of the Box component. + * The margin-inline-end of the component. * Use 1-12 for 4px-48px or 'auto'. * Accepts responsive props in the form of an array. */ marginInlineEnd?: SizeNumberAndAuto | SizeNumberAndAutoArray; /** - * The padding of the Box component. + * The padding of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ padding?: SizeNumber | SizeNumberArray; /** - * The padding-top of the Box component. + * The padding-top of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingTop?: SizeNumber | SizeNumberArray; /** - * The padding-bottom of the Box component. + * The padding-bottom of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingBottom?: SizeNumber | SizeNumberArray; /** - * The padding-right of the Box component. + * The padding-right of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingRight?: SizeNumber | SizeNumberArray; /** - * The padding-left of the Box component. + * The padding-left of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingLeft?: SizeNumber | SizeNumberArray; /** - * The padding-inline of the Box component. + * The padding-inline of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingInline?: SizeNumber | SizeNumberArray; /** - * The padding-inline-start of the Box component. + * The padding-inline-start of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingInlineStart?: SizeNumber | SizeNumberArray; /** - * The padding-inline-end of the Box component. + * The padding-inline-end of the component. * Use 1-12 for 4px-48px. * Accepts responsive props in the form of an array. */ paddingInlineEnd?: SizeNumber | SizeNumberArray; /** - * The border-color of the Box component. + * The border-color of the component. * Use BorderColor enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ borderColor?: BorderColor | BorderColorArray; /** - * The border-width of the Box component. + * The border-width of the component. * Use 1-12 for 1px-12px. * Accepts responsive props in the form of an array. */ borderWidth?: SizeNumber | SizeNumberArray; /** - * The border-radius of the Box component. + * The border-radius of the component. * Use BorderRadius enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ borderRadius?: BorderRadius | BorderRadiusArray; /** - * The border-style of the Box component. + * The border-style of the component. * Use BorderStyle enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ borderStyle?: BorderStyle | BorderStyleArray; /** - * The align-items of the Box component. + * The align-items of the component. * Use AlignItems enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ alignItems?: AlignItems | AlignItemsArray; /** - * The justify-content of the Box component. + * The justify-content of the component. * Use JustifyContent enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ justifyContent?: JustifyContent | JustifyContentArray; /** - * The text-align of the Box component. + * The text-align of the component. * Use TextAlign enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ textAlign?: TextAlign | TextAlignArray; /** - * The display of the Box component. + * The display of the component. * Use Display enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ display?: Display | DisplayArray; /** - * The width of the Box component. + * The width of the component. * Use BlockSize enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ width?: BlockSize | BlockSizeArray; /** - * The height of the Box component. + * The height of the component. * Use BlockSize enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ height?: BlockSize | BlockSizeArray; /** - * The background-color of the Box component. + * The background-color of the component. * Use BackgroundColor enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ backgroundColor?: BackgroundColor | BackgroundColorArray; /** - * The text-color of the Box component. + * The text-color of the component. * Use TextColor enum from '../../../helpers/constants/design-system'; * Accepts responsive props in the form of an array. */ diff --git a/ui/components/component-library/button-base/README.mdx b/ui/components/component-library/button-base/README.mdx index 2aeca22fc..6267882ac 100644 --- a/ui/components/component-library/button-base/README.mdx +++ b/ui/components/component-library/button-base/README.mdx @@ -13,8 +13,6 @@ The `ButtonBase` is the base component for buttons. ## Props -The `ButtonBase` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Size diff --git a/ui/components/component-library/button-icon/README.mdx b/ui/components/component-library/button-icon/README.mdx index 4a732dc63..01128d859 100644 --- a/ui/components/component-library/button-icon/README.mdx +++ b/ui/components/component-library/button-icon/README.mdx @@ -11,8 +11,6 @@ The `ButtonIcon` is used for icons associated with a user action. ## Props -The `ButtonIcon` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Icon Name\* diff --git a/ui/components/component-library/checkbox/README.mdx b/ui/components/component-library/checkbox/README.mdx index dc2f04e8a..507a5e2d0 100644 --- a/ui/components/component-library/checkbox/README.mdx +++ b/ui/components/component-library/checkbox/README.mdx @@ -12,8 +12,6 @@ Checkbox is a graphical element that allows users to select one or more options ## Props -The `Checkbox` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Label diff --git a/ui/components/component-library/header-base/README.mdx b/ui/components/component-library/header-base/README.mdx index 78ccaf20b..285eb8202 100644 --- a/ui/components/component-library/header-base/README.mdx +++ b/ui/components/component-library/header-base/README.mdx @@ -13,8 +13,6 @@ The `HeaderBase` component is a reusable UI component for displaying a header wi ## Props -The `HeaderBase` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Children diff --git a/ui/components/component-library/help-text/README.mdx b/ui/components/component-library/help-text/README.mdx index fec76e3ea..82409bf53 100644 --- a/ui/components/component-library/help-text/README.mdx +++ b/ui/components/component-library/help-text/README.mdx @@ -14,8 +14,6 @@ The `HelpText` is used as feedback text under a form field including error, succ ## Props -The `HelpText` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - `HelpText` accepts all [Text](/docs/components-componentlibrary-text--default-story#props) component props diff --git a/ui/components/component-library/icon/README.mdx b/ui/components/component-library/icon/README.mdx index cb82b7995..7108165a3 100644 --- a/ui/components/component-library/icon/README.mdx +++ b/ui/components/component-library/icon/README.mdx @@ -12,8 +12,6 @@ The `Icon` component in conjunction with `IconName` can be used for all icons in ## Props -The `Icon` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Name diff --git a/ui/components/component-library/input/README.mdx b/ui/components/component-library/input/README.mdx index a45c1244b..927f65fbe 100644 --- a/ui/components/component-library/input/README.mdx +++ b/ui/components/component-library/input/README.mdx @@ -12,8 +12,6 @@ import { Input } from './input'; ## Props -The `Input` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Type diff --git a/ui/components/component-library/label/README.mdx b/ui/components/component-library/label/README.mdx index d5036cb20..f99cedea6 100644 --- a/ui/components/component-library/label/README.mdx +++ b/ui/components/component-library/label/README.mdx @@ -14,8 +14,6 @@ import { Label } from './label'; ## Props -The `Label` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - `Label` accepts all [Text](/docs/components-componentlibrary-text--default-story#props) component props diff --git a/ui/components/component-library/modal-content/README.mdx b/ui/components/component-library/modal-content/README.mdx index cd33f72dd..aef60ac89 100644 --- a/ui/components/component-library/modal-content/README.mdx +++ b/ui/components/component-library/modal-content/README.mdx @@ -12,8 +12,6 @@ import { ModalContent } from './modal-content'; ## Props -The `ModalContent` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Children diff --git a/ui/components/component-library/modal-overlay/README.mdx b/ui/components/component-library/modal-overlay/README.mdx index 18eacaf67..b55ecd740 100644 --- a/ui/components/component-library/modal-overlay/README.mdx +++ b/ui/components/component-library/modal-overlay/README.mdx @@ -12,8 +12,6 @@ import { ModalOverlay } from './modal-overlay'; ## Props -The `ModalOverlay` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### On Click diff --git a/ui/components/component-library/modal/README.mdx b/ui/components/component-library/modal/README.mdx index d106a78f0..b3d9ae2b2 100644 --- a/ui/components/component-library/modal/README.mdx +++ b/ui/components/component-library/modal/README.mdx @@ -12,8 +12,6 @@ The `Modal` focuses the user's attention exclusively on information via a window ## Props -The `Modal` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Usage diff --git a/ui/components/component-library/tag/README.mdx b/ui/components/component-library/tag/README.mdx index 23cfdbe60..53af8bc0c 100644 --- a/ui/components/component-library/tag/README.mdx +++ b/ui/components/component-library/tag/README.mdx @@ -12,8 +12,6 @@ The `Tag` is a component used to display text within a container. ## Props -The `Tag` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props. - ### Label @@ -22,4 +20,4 @@ The text content of the `Tag` component - \ No newline at end of file + diff --git a/ui/components/component-library/text/README.mdx b/ui/components/component-library/text/README.mdx index 8e8ebee3a..38d5415f7 100644 --- a/ui/components/component-library/text/README.mdx +++ b/ui/components/component-library/text/README.mdx @@ -14,8 +14,6 @@ Good typography improves readability, legibility and hierarchy of information. ## Props -The `Text` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props - ### Variant From 63a0ae765f8a7589f91d9c56c0a2f43aa2f685cb Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 15:21:18 -0230 Subject: [PATCH 020/102] Use unflattened state for Sentry (#20428) The unflattened background state is now attached to any Sentry errors from the background process. This provides a clearer picture of the state of the wallet, and unblocks further improvements to Sentry state which will come in later PRs. --- app/scripts/background.js | 4 +- app/scripts/lib/setupSentry.js | 133 ++++++++++++------ test/e2e/tests/errors.spec.js | 37 +++-- ...rs-after-init-opt-in-background-state.json | 64 +++------ 4 files changed, 138 insertions(+), 100 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 6b29e14d7..5b97bb34d 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -886,14 +886,14 @@ browser.runtime.onInstalled.addListener(({ reason }) => { function setupSentryGetStateGlobal(store) { global.stateHooks.getSentryState = function () { - const backgroundState = store.getState(); + const backgroundState = store.memStore.getState(); const maskedBackgroundState = maskObject( backgroundState, SENTRY_BACKGROUND_STATE, ); return { browser: window.navigator.userAgent, - store: { metamask: maskedBackgroundState }, + store: maskedBackgroundState, version: platform.getVersion(), }; }; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 2699d2e3d..384f246db 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -27,60 +27,103 @@ export const ERROR_URL_ALLOWLIST = { // sent to Sentry These properties have some potential to be useful for // debugging, and they do not contain any identifiable information. export const SENTRY_BACKGROUND_STATE = { - alertEnabledness: true, - completedOnboarding: true, - connectedStatusPopoverHasBeenShown: true, - conversionDate: true, - conversionRate: true, - currentAppVersion: true, - currentBlockGasLimit: true, - currentCurrency: true, - currentLocale: true, - currentMigrationVersion: true, - customNonceValue: true, - defaultHomeActiveTabName: true, - desktopEnabled: true, - featureFlags: true, - firstTimeFlowType: true, - forgottenPassword: true, - incomingTxLastFetchedBlockByChainId: true, - ipfsGateway: true, - isAccountMenuOpen: true, - isInitialized: true, - isUnlocked: true, - metaMetricsId: true, - nativeCurrency: true, - networkId: true, - networkStatus: true, - nextNonce: true, - participateInMetaMetrics: true, - preferences: true, - previousAppVersion: true, - previousMigrationVersion: true, - providerConfig: { - nickname: true, - ticker: true, - type: true, + AccountTracker: { + currentBlockGasLimit: true, + }, + AlertController: { + alertEnabledness: true, + }, + AppMetadataController: { + currentAppVersion: true, + previousAppVersion: true, + previousMigrationVersion: true, + currentMigrationVersion: true, + }, + AppStateController: { + connectedStatusPopoverHasBeenShown: true, + defaultHomeActiveTabName: true, + }, + CurrencyController: { + conversionDate: true, + conversionRate: true, + currentCurrency: true, + nativeCurrency: true, + }, + DecryptMessageController: { + unapprovedDecryptMsgCount: true, + }, + DesktopController: { + desktopEnabled: true, + }, + EncryptionPublicKeyController: { + unapprovedEncryptionPublicKeyMsgCount: true, + }, + IncomingTransactionsController: { + incomingTxLastFetchedBlockByChainId: true, + }, + KeyringController: { + isUnlocked: true, + }, + MetaMetricsController: { + metaMetricsId: true, + participateInMetaMetrics: true, + }, + NetworkController: { + networkId: true, + networkStatus: true, + providerConfig: { + nickname: true, + ticker: true, + type: true, + }, + }, + OnboardingController: { + completedOnboarding: true, + firstTimeFlowType: true, + seedPhraseBackedUp: true, + }, + PreferencesController: { + currentLocale: true, + featureFlags: true, + forgottenPassword: true, + ipfsGateway: true, + preferences: true, + useBlockie: true, + useNonceField: true, + usePhishDetect: true, + }, + SignatureController: { + unapprovedMsgCount: true, + unapprovedPersonalMsgCount: true, + unapprovedTypedMessagesCount: true, }, - seedPhraseBackedUp: true, - unapprovedDecryptMsgCount: true, - unapprovedEncryptionPublicKeyMsgCount: true, - unapprovedMsgCount: true, - unapprovedPersonalMsgCount: true, - unapprovedTypedMessagesCount: true, - useBlockie: true, - useNonceField: true, - usePhishDetect: true, - welcomeScreenSeen: true, }; +const flattenedBackgroundStateMask = Object.values( + SENTRY_BACKGROUND_STATE, +).reduce((partialBackgroundState, controllerState) => { + return { + ...partialBackgroundState, + ...controllerState, + }; +}, {}); + // This describes the subset of Redux state attached to errors sent to Sentry // These properties have some potential to be useful for debugging, and they do // not contain any identifiable information. export const SENTRY_UI_STATE = { gas: true, history: true, - metamask: SENTRY_BACKGROUND_STATE, + metamask: { + ...flattenedBackgroundStateMask, + // This property comes from the background but isn't in controller state + isInitialized: true, + // These properties are in the `metamask` slice but not in the background state + customNonceValue: true, + isAccountMenuOpen: true, + nextNonce: true, + welcomeScreenSeen: true, + }, unconnectedAccount: true, }; diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 4ab80a7de..8784ef168 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -7,7 +7,8 @@ const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const dateFields = ['metamask.conversionDate']; +const backgroundDateFields = ['CurrencyController.conversionDate']; +const uiDateFields = ['metamask.conversionDate']; /** * Transform date properties to value types, to ensure that state is @@ -15,8 +16,23 @@ const dateFields = ['metamask.conversionDate']; * * @param {unknown} data - The data to transform */ -function transformDates(data) { - for (const field of dateFields) { +function transformBackgroundDates(data) { + for (const field of backgroundDateFields) { + if (has(data, field)) { + set(data, field, typeof get(data, field)); + } + } + return data; +} + +/** + * Transform date properties to value types, to ensure that state is + * consistent between test runs. + * + * @param {unknown} data - The data to transform + */ +function transformUiDates(data) { + for (const field of uiDateFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } @@ -33,12 +49,10 @@ function transformDates(data) { * @param {boolean} [args.update] - Whether to update the snapshot if it doesn't match. */ async function matchesSnapshot({ - data: unprocessedData, + data, snapshot, update = process.env.UPDATE_SNAPSHOTS === 'true', }) { - const data = transformDates(unprocessedData); - const snapshotPath = resolve(__dirname, `./state-snapshots/${snapshot}.json`); const rawSnapshotData = await fs.readFile(snapshotPath, { encoding: 'utf-8', @@ -243,7 +257,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: appState, + data: transformBackgroundDates(appState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -328,7 +342,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: appState, + data: transformUiDates(appState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -436,7 +450,8 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; - const { participateInMetaMetrics } = extra.appState.store.metamask; + const { participateInMetaMetrics } = + extra.appState.store.MetaMetricsController; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -494,7 +509,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: appState.store, + data: transformBackgroundDates(appState.store), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -588,7 +603,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: appState.store, + data: transformUiDates(appState.store), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 03fc85edf..f41fee885 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,52 +1,32 @@ { - "metamask": { - "isInitialized": true, + "AccountTracker": { "currentBlockGasLimit": "0x1c9c380" }, + "AppStateController": { "connectedStatusPopoverHasBeenShown": true, - "defaultHomeActiveTabName": null, - "currentAppVersion": "10.34.4", - "previousAppVersion": "", - "previousMigrationVersion": 0, - "currentMigrationVersion": 94, + "defaultHomeActiveTabName": null + }, + "CurrencyController": { + "conversionDate": "number", + "conversionRate": 1700, + "nativeCurrency": "ETH", + "currentCurrency": "usd" + }, + "DecryptMessageController": { "unapprovedDecryptMsgCount": 0 }, + "EncryptionPublicKeyController": { + "unapprovedEncryptionPublicKeyMsgCount": 0 + }, + "MetaMetricsController": { + "participateInMetaMetrics": true, + "metaMetricsId": "fake-metrics-id" + }, + "NetworkController": { "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", "ticker": "ETH", "type": "rpc" - }, - "isUnlocked": false, - "useBlockie": false, - "useNonceField": false, - "usePhishDetect": true, - "featureFlags": { "showIncomingTransactions": true }, - "currentLocale": "en", - "forgottenPassword": false, - "preferences": { - "hideZeroBalanceTokens": false, - "showFiatInTestnets": false, - "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true - }, - "ipfsGateway": "dweb.link", - "participateInMetaMetrics": true, - "metaMetricsId": "fake-metrics-id", - "conversionDate": "number", - "conversionRate": 1700, - "nativeCurrency": "ETH", - "currentCurrency": "usd", - "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, - "seedPhraseBackedUp": true, - "firstTimeFlowType": "import", - "completedOnboarding": true, - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - }, - "currentBlockGasLimit": "0x1c9c380", - "unapprovedDecryptMsgCount": 0, - "unapprovedEncryptionPublicKeyMsgCount": 0, + } + }, + "SignatureController": { "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 From 5cbfa82018ed643772cbb339c8c7910808cfe07d Mon Sep 17 00:00:00 2001 From: Unik0rnMaggie <128788650+Unik0rnMaggie@users.noreply.github.com> Date: Wed, 16 Aug 2023 21:06:33 +0300 Subject: [PATCH 021/102] Fix Typo in en.json (#20429) * Fix Typo in en.json There is a typo in "smart contact" * updated snapshot --------- Co-authored-by: Howard Braham --- app/_locales/en/messages.json | 2 +- .../security-tab/__snapshots__/security-tab.test.js.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 07d28c054..dbbb8911b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5211,7 +5211,7 @@ "message": "Decode smart contracts" }, "use4ByteResolutionDescription": { - "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contact that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." + "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." }, "useMultiAccountBalanceChecker": { "message": "Batch account balance requests" diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 1190abd2a..85b5b4e77 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -135,7 +135,7 @@ exports[`Security Tab should match snapshot 1`] = `
- To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contact that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared. + To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared.
Date: Wed, 16 Aug 2023 13:34:12 -0230 Subject: [PATCH 022/102] Fix Sentry error e2e tests (#20479) The state fixtures in the Sentry e2e tests became invalid in #20458 due to a conflict with that change (the new state properties were missing). The state fixtures have been updated. --- .../errors-after-init-opt-in-background-state.json | 4 ++++ .../state-snapshots/errors-after-init-opt-in-ui-state.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index efdf28622..03fc85edf 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -3,6 +3,10 @@ "isInitialized": true, "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94, "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 7300c3a05..4ecaa0417 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -25,6 +25,10 @@ "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94, "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", From b874a301f5ac855494d4fb5868d96994c41ce584 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 16 Aug 2023 16:56:20 -0230 Subject: [PATCH 023/102] Capture exception with sentry when invariant conditions are met in migrations (#20427) * capture exception for sentry when invariant conditions are met in migration 82 * Code cleanup * Capture exceptions in invariant conditions for migrations 83,84,85,86,89,91,93,94 * Update app/scripts/migrations/082.test.js Co-authored-by: Mark Stacey * Code cleanup * Fix SentryObject type declaration * Stop throwing error if preferences controller is undefined * Refactor 084 and 086 to remove double negative * Capture exceptions for invariant states in in migrations 87,88,90 and 92 * lint fix * log warning in migration 82 when preferences controller is undefined --------- Co-authored-by: Mark Stacey --- app/scripts/migrations/082.test.js | 178 +++++++++++++++++++- app/scripts/migrations/082.ts | 55 +++++- app/scripts/migrations/083.test.js | 65 +++++++ app/scripts/migrations/083.ts | 10 ++ app/scripts/migrations/084.test.js | 72 ++++++++ app/scripts/migrations/084.ts | 21 ++- app/scripts/migrations/085.test.js | 29 ++++ app/scripts/migrations/085.ts | 5 + app/scripts/migrations/086.test.js | 75 +++++++++ app/scripts/migrations/086.ts | 19 +++ app/scripts/migrations/087.test.js | 69 ++++++++ app/scripts/migrations/087.ts | 5 + app/scripts/migrations/088.test.ts | 262 +++++++++++++++++++++++++++++ app/scripts/migrations/088.ts | 80 +++++++++ app/scripts/migrations/089.test.ts | 58 +++++++ app/scripts/migrations/089.ts | 13 ++ app/scripts/migrations/090.test.js | 19 ++- app/scripts/migrations/090.ts | 22 ++- app/scripts/migrations/091.test.ts | 58 +++++++ app/scripts/migrations/091.ts | 13 ++ app/scripts/migrations/092.test.ts | 29 ++++ app/scripts/migrations/092.ts | 9 + app/scripts/migrations/093.test.ts | 58 +++++++ app/scripts/migrations/093.ts | 16 ++ app/scripts/migrations/094.test.ts | 163 ++++++++++++++++++ app/scripts/migrations/094.ts | 29 ++++ types/global.d.ts | 4 +- 27 files changed, 1417 insertions(+), 19 deletions(-) diff --git a/app/scripts/migrations/082.test.js b/app/scripts/migrations/082.test.js index 6f221c5c6..0060c853c 100644 --- a/app/scripts/migrations/082.test.js +++ b/app/scripts/migrations/082.test.js @@ -1,6 +1,12 @@ import { v4 } from 'uuid'; import { migrate, version } from './082'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -472,10 +478,72 @@ describe('migration #82', () => { }, }; const newStorage = await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalled(); expect(newStorage.data).toStrictEqual(oldStorage.data); }); - it('should not change anything if there is no frequentRpcListDetail property on PreferencesController', async () => { + it('should capture an exception if any PreferencesController.frequentRpcListDetail entries are not objects', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: { + transactionSecurityCheckEnabled: false, + useBlockie: false, + useCurrencyRateCheck: true, + useMultiAccountBalanceChecker: true, + useNftDetection: false, + useNonceField: false, + frequentRpcListDetail: [ + { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcPrefs: {}, + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + }, + 'invalid entry type', + 1, + ], + }, + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `state.PreferencesController.frequentRpcListDetail contains an element of type string`, + ), + ); + }); + + it('should not change anything, and not capture an exception, if there is no frequentRpcListDetail property on PreferencesController but there is a networkConfigurations object', async () => { const oldStorage = { meta: { version: 81, @@ -556,9 +624,60 @@ describe('migration #82', () => { }, }; const newStorage = await migrate(oldStorage); + expect(sentryCaptureExceptionMock).not.toHaveBeenCalled(); expect(newStorage.data).toStrictEqual(oldStorage.data); }); + it('should capture an exception if there is no frequentRpcListDetail property on PreferencesController and no networkConfiguration object', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: { + transactionSecurityCheckEnabled: false, + useBlockie: false, + useCurrencyRateCheck: true, + useMultiAccountBalanceChecker: true, + useNftDetection: false, + useNonceField: false, + }, + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.PreferencesController.frequentRpcListDetail is undefined`, + ), + ); + }); + it('should change nothing if PreferencesController is undefined', async () => { const oldStorage = { meta: { @@ -595,4 +714,61 @@ describe('migration #82', () => { const newStorage = await migrate(oldStorage); expect(newStorage.data).toStrictEqual(oldStorage.data); }); + + it('should capture an exception if PreferencesController is not an object', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + PreferencesController: false, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PreferencesController is boolean`), + ); + }); + + it('should capture an exception if NetworkController is undefined', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: {}, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); }); diff --git a/app/scripts/migrations/082.ts b/app/scripts/migrations/082.ts index fdda1fb8d..29ff59eea 100644 --- a/app/scripts/migrations/082.ts +++ b/app/scripts/migrations/082.ts @@ -1,6 +1,7 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; import { v4 } from 'uuid'; +import log from 'loglevel'; export const version = 82; @@ -25,14 +26,56 @@ export async function migrate(originalVersionedData: { } function transformState(state: Record) { + if (!hasProperty(state, 'PreferencesController')) { + log.warn(`state.PreferencesController is undefined`); + return state; + } + if (!isObject(state.PreferencesController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.PreferencesController is ${typeof state.PreferencesController}`, + ), + ); + return state; + } if ( - !hasProperty(state, 'PreferencesController') || - !isObject(state.PreferencesController) || - !isObject(state.NetworkController) || - !hasProperty(state.PreferencesController, 'frequentRpcListDetail') || - !Array.isArray(state.PreferencesController.frequentRpcListDetail) || - !state.PreferencesController.frequentRpcListDetail.every(isObject) + !hasProperty(state, 'NetworkController') || + !isObject(state.NetworkController) ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + return state; + } + if ( + !hasProperty(state.PreferencesController, 'frequentRpcListDetail') || + !Array.isArray(state.PreferencesController.frequentRpcListDetail) + ) { + const inPost077SupplementFor082State = + state.NetworkController.networkConfigurations && + state.PreferencesController.frequentRpcListDetail === undefined; + if (!inPost077SupplementFor082State) { + global.sentry?.captureException?.( + new Error( + `typeof state.PreferencesController.frequentRpcListDetail is ${typeof state + .PreferencesController.frequentRpcListDetail}`, + ), + ); + } + return state; + } + if (!state.PreferencesController.frequentRpcListDetail.every(isObject)) { + const erroneousElement = + state.PreferencesController.frequentRpcListDetail.find( + (element) => !isObject(element), + ); + global.sentry?.captureException?.( + new Error( + `state.PreferencesController.frequentRpcListDetail contains an element of type ${typeof erroneousElement}`, + ), + ); return state; } const { PreferencesController, NetworkController } = state; diff --git a/app/scripts/migrations/083.test.js b/app/scripts/migrations/083.test.js index c59951aef..b8f91dfcd 100644 --- a/app/scripts/migrations/083.test.js +++ b/app/scripts/migrations/083.test.js @@ -1,6 +1,12 @@ import { v4 } from 'uuid'; import { migrate, version } from './083'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -165,6 +171,24 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController is undefined', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should not modify state if state.NetworkController is not an object', async () => { const oldStorage = { meta: { @@ -190,6 +214,25 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController is not an object', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + NetworkController: false, + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is boolean`), + ); + }); + it('should not modify state if state.NetworkController.networkConfigurations is undefined', async () => { const oldStorage = { meta: { @@ -221,6 +264,28 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController.networkConfigurations is undefined', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + NetworkController: { + testNetworkControllerProperty: 'testNetworkControllerValue', + networkConfigurations: undefined, + }, + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof NetworkController.networkConfigurations is undefined`), + ); + }); + it('should not modify state if state.NetworkController.networkConfigurations is an empty object', async () => { const oldStorage = { meta: { diff --git a/app/scripts/migrations/083.ts b/app/scripts/migrations/083.ts index cc3e3b16b..bd7ba62e4 100644 --- a/app/scripts/migrations/083.ts +++ b/app/scripts/migrations/083.ts @@ -25,11 +25,21 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); return state; } const { NetworkController } = state; if (!isObject(NetworkController.networkConfigurations)) { + global.sentry?.captureException?.( + new Error( + `typeof NetworkController.networkConfigurations is ${typeof NetworkController.networkConfigurations}`, + ), + ); return state; } diff --git a/app/scripts/migrations/084.test.js b/app/scripts/migrations/084.test.js index 138bfacb6..bd0e1b35c 100644 --- a/app/scripts/migrations/084.test.js +++ b/app/scripts/migrations/084.test.js @@ -1,6 +1,16 @@ import { migrate } from './084'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #84', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const originalVersionedData = buildOriginalVersionedData({ meta: { @@ -27,6 +37,21 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + it('captures an exception if the network controller state does not exist', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + test: '123', + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + const nonObjects = [undefined, null, 'test', 1, ['test']]; for (const invalidState of nonObjects) { it(`does not change the state if the network controller state is ${invalidState}`, async () => { @@ -40,6 +65,21 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + + it(`captures an exception if the network controller state is ${invalidState}`, async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: invalidState, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is ${typeof invalidState}`), + ); + }); } it('does not change the state if the network controller state does not include "network"', async () => { @@ -56,6 +96,38 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + it('captures an exception if the network controller state does not include "network" and does not include "networkId"', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: { + test: '123', + }, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.network is undefined`), + ); + }); + + it('does not capture an exception if the network controller state does not include "network" but does include "networkId"', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: { + test: '123', + networkId: 'foobar', + }, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('replaces "network" in the network controller state with "networkId": null, "networkStatus": "unknown" if it is "loading"', async () => { const originalVersionedData = buildOriginalVersionedData({ data: { diff --git a/app/scripts/migrations/084.ts b/app/scripts/migrations/084.ts index 66a2f45ae..7551ed2c6 100644 --- a/app/scripts/migrations/084.ts +++ b/app/scripts/migrations/084.ts @@ -25,9 +25,26 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if ( !hasProperty(state, 'NetworkController') || - !isObject(state.NetworkController) || - !hasProperty(state.NetworkController, 'network') + !isObject(state.NetworkController) ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + return state; + } + if (!hasProperty(state.NetworkController, 'network')) { + const thePost077SupplementFor084HasNotModifiedState = + state.NetworkController.networkId === undefined; + if (thePost077SupplementFor084HasNotModifiedState) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.network is ${typeof state + .NetworkController.network}`, + ), + ); + } return state; } diff --git a/app/scripts/migrations/085.test.js b/app/scripts/migrations/085.test.js index 6b7b4967d..cfe7ff1b0 100644 --- a/app/scripts/migrations/085.test.js +++ b/app/scripts/migrations/085.test.js @@ -1,5 +1,11 @@ import { migrate, version } from './085'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +16,10 @@ jest.mock('uuid', () => { }); describe('migration #85', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +49,25 @@ describe('migration #85', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 84, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller previous provider state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/085.ts b/app/scripts/migrations/085.ts index 03499d2b2..2ba22a346 100644 --- a/app/scripts/migrations/085.ts +++ b/app/scripts/migrations/085.ts @@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); return state; } diff --git a/app/scripts/migrations/086.test.js b/app/scripts/migrations/086.test.js index f38f0444d..cd6a61377 100644 --- a/app/scripts/migrations/086.test.js +++ b/app/scripts/migrations/086.test.js @@ -1,5 +1,11 @@ import { migrate, version } from './086'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +16,10 @@ jest.mock('uuid', () => { }); describe('migration #86', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +49,25 @@ describe('migration #86', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller provider state', async () => { const oldData = { other: 'data', @@ -59,6 +88,52 @@ describe('migration #86', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller provider state and no providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + foo: 'bar', + }, + }, + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.provider is undefined`), + ); + }); + + it('should not capture an exception if there is no network controller provider state but there is a providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + foo: 'bar', + }, + providerConfig: {}, + }, + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('should rename the provider config state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/086.ts b/app/scripts/migrations/086.ts index c4b60e270..709934f50 100644 --- a/app/scripts/migrations/086.ts +++ b/app/scripts/migrations/086.ts @@ -37,5 +37,24 @@ function transformState(state: Record) { NetworkController: networkControllerState, }; } + if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!hasProperty(state.NetworkController, 'provider')) { + const thePost077SupplementFor086HasNotModifiedState = + state.NetworkController.providerConfig === undefined; + if (thePost077SupplementFor086HasNotModifiedState) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.provider is ${typeof state + .NetworkController.provider}`, + ), + ); + } + } + return state; } diff --git a/app/scripts/migrations/087.test.js b/app/scripts/migrations/087.test.js index 1d631e926..ebd9495ad 100644 --- a/app/scripts/migrations/087.test.js +++ b/app/scripts/migrations/087.test.js @@ -1,6 +1,16 @@ import { migrate, version } from './087'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #87', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -53,6 +63,65 @@ describe('migration #87', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should return state unaltered if TokensController state is not an object', async () => { + const oldData = { + other: 'data', + TokensController: false, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should capture an exception if TokensController state is not an object', async () => { + const oldData = { + other: 'data', + TokensController: false, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is boolean`), + ); + }); + + it('should not capture an exception if TokensController state is an object', async () => { + const oldData = { + other: 'data', + TokensController: { + allDetectedTokens: {}, + allIgnoredTokens: {}, + allTokens: {}, + detectedTokens: [], + ignoredTokens: [], + suggestedAssets: [], + tokens: [], + }, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('should remove the suggested assets state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/087.ts b/app/scripts/migrations/087.ts index 92093f967..f900faab6 100644 --- a/app/scripts/migrations/087.ts +++ b/app/scripts/migrations/087.ts @@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.TokensController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController is ${typeof state.TokensController}`, + ), + ); return state; } diff --git a/app/scripts/migrations/088.test.ts b/app/scripts/migrations/088.test.ts index a33c30eff..ca672f982 100644 --- a/app/scripts/migrations/088.test.ts +++ b/app/scripts/migrations/088.test.ts @@ -1,6 +1,16 @@ import { migrate } from './088'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #88', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const oldStorage = { meta: { version: 87 }, @@ -26,6 +36,24 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the NftController property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController is boolean`), + ); + }); + it('returns the state unaltered if the NftController object has no allNftContracts property', async () => { const oldData = { NftController: { @@ -58,6 +86,26 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it NftController.allNftContracts is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: { + allNftContracts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController.allNftContracts is string`), + ); + }); + it('returns the state unaltered if any value of the NftController.allNftContracts object is not an object itself', async () => { const oldData = { NftController: { @@ -324,6 +372,26 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it NftController.allNfts is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: { + allNfts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController.allNfts is string`), + ); + }); + it('returns the state unaltered if any value of the NftController.allNfts object is not an object itself', async () => { const oldData = { NftController: { @@ -656,6 +724,91 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it has no TokenListController property', async () => { + const oldData = { + TokensController: {}, + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + }, + }, + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + }, + }, + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is undefined`), + ); + }); + + it('captures an exception if the TokenListController property is not an object', async () => { + const oldData = { + TokensController: {}, + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + }, + }, + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + }, + }, + }, + TokenListController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is boolean`), + ); + }); + it('returns the state unaltered if the TokenListController object has no tokensChainsCache property', async () => { const oldData = { TokenListController: { @@ -688,6 +841,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokenListController.tokensChainsCache property is not an object', async () => { + const oldData = { + TokenListController: { + tokensChainsCache: 'foo', + }, + TokensController: {}, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController.tokensChainsCache is string`), + ); + }); + it('rewrites TokenListController.tokensChainsCache so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -919,6 +1091,39 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it has no TokensController property', async () => { + const oldData = { + TokenListController: {}, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is undefined`), + ); + }); + + it('captures an exception if the TokensController property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is boolean`), + ); + }); + it('returns the state unaltered if the TokensController object has no allTokens property', async () => { const oldData = { TokensController: { @@ -951,6 +1156,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allTokens is string`), + ); + }); + it('rewrites TokensController.allTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -1163,6 +1387,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allIgnoredTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allIgnoredTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allIgnoredTokens is string`), + ); + }); + it('rewrites TokensController.allIgnoredTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -1323,6 +1566,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allDetectedTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allDetectedTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allDetectedTokens is string`), + ); + }); + it('rewrites TokensController.allDetectedTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts index a4b874b2f..5ede1b0fa 100644 --- a/app/scripts/migrations/088.ts +++ b/app/scripts/migrations/088.ts @@ -1,6 +1,7 @@ import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils'; import { BN } from 'ethereumjs-util'; import { cloneDeep, mapKeys } from 'lodash'; +import log from 'loglevel'; type VersionedData = { meta: { version: number }; @@ -70,6 +71,16 @@ function migrateData(state: Record): void { } }); } + } else if (hasProperty(nftControllerState, 'allNftContracts')) { + global.sentry?.captureException?.( + new Error( + `typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`, + ), + ); + } else { + log.warn( + `typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`, + ); } // Migrate NftController.allNfts @@ -96,9 +107,25 @@ function migrateData(state: Record): void { } }); } + } else if (hasProperty(nftControllerState, 'allNfts')) { + global.sentry?.captureException?.( + new Error( + `typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`, + ), + ); + } else { + log.warn( + `typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`, + ); } state.NftController = nftControllerState; + } else if (hasProperty(state, 'NftController')) { + global.sentry?.captureException?.( + new Error(`typeof state.NftController is ${typeof state.NftController}`), + ); + } else { + log.warn(`typeof state.NftController is undefined`); } if ( @@ -124,7 +151,24 @@ function migrateData(state: Record): void { tokenListControllerState.tokensChainsCache, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokenListControllerState, 'tokensChainsCache')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController.tokensChainsCache is ${typeof state + .TokenListController.tokensChainsCache}`, + ), + ); + } else { + log.warn( + `typeof state.TokenListController.tokensChainsCache is undefined`, + ); } + } else { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); } if ( @@ -150,6 +194,16 @@ function migrateData(state: Record): void { allTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`, + ); } // Migrate TokensController.allIgnoredTokens @@ -169,6 +223,16 @@ function migrateData(state: Record): void { allIgnoredTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allIgnoredTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`, + ); } // Migrate TokensController.allDetectedTokens @@ -188,9 +252,25 @@ function migrateData(state: Record): void { allDetectedTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allDetectedTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`, + ); } state.TokensController = tokensControllerState; + } else { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController is ${typeof state.TokensController}`, + ), + ); } } diff --git a/app/scripts/migrations/089.test.ts b/app/scripts/migrations/089.test.ts index 00868ff74..91511d315 100644 --- a/app/scripts/migrations/089.test.ts +++ b/app/scripts/migrations/089.test.ts @@ -1,5 +1,14 @@ import { migrate, version } from './089'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +19,10 @@ jest.mock('uuid', () => { }); describe('migration #89', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +52,25 @@ describe('migration #89', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 88, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller providerConfig state', async () => { const oldData = { other: 'data', @@ -61,6 +93,32 @@ describe('migration #89', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + id1: { + foo: 'bar', + }, + }, + }, + }; + const oldStorage = { + meta: { + version: 88, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.providerConfig is undefined`), + ); + }); + it('should return state unaltered if the providerConfig already has an id', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/089.ts b/app/scripts/migrations/089.ts index cc1bfa4dc..d4faebc22 100644 --- a/app/scripts/migrations/089.ts +++ b/app/scripts/migrations/089.ts @@ -66,6 +66,19 @@ function transformState(state: Record) { ...state, NetworkController: state.NetworkController, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!isObject(state.NetworkController.providerConfig)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.providerConfig is ${typeof state + .NetworkController.providerConfig}`, + ), + ); } return state; } diff --git a/app/scripts/migrations/090.test.js b/app/scripts/migrations/090.test.js index 6a28c60f2..13e39e648 100644 --- a/app/scripts/migrations/090.test.js +++ b/app/scripts/migrations/090.test.js @@ -2,7 +2,17 @@ import { migrate, version } from './090'; const PREVIOUS_VERSION = version - 1; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #90', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const oldStorage = { meta: { @@ -31,7 +41,7 @@ describe('migration #90', () => { expect(newStorage.data).toStrictEqual(oldStorage.data); }); - it('does not change the state if the phishing controller state is invalid', async () => { + it('captures an exception if the phishing controller state is invalid', async () => { const oldStorage = { meta: { version: PREVIOUS_VERSION, @@ -39,9 +49,12 @@ describe('migration #90', () => { data: { PhishingController: 'this is not valid' }, }; - const newStorage = await migrate(oldStorage); + await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(oldStorage.data); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PhishingController is string`), + ); }); it('does not change the state if the listState property does not exist', async () => { diff --git a/app/scripts/migrations/090.ts b/app/scripts/migrations/090.ts index e45ec05e4..a2d50cab6 100644 --- a/app/scripts/migrations/090.ts +++ b/app/scripts/migrations/090.ts @@ -1,5 +1,6 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; export const version = 90; @@ -23,11 +24,22 @@ export async function migrate(originalVersionedData: { } function transformState(state: Record) { - if ( - !hasProperty(state, 'PhishingController') || - !isObject(state.PhishingController) || - !hasProperty(state.PhishingController, 'listState') - ) { + if (!hasProperty(state, 'PhishingController')) { + log.warn(`typeof state.PhishingController is undefined`); + return state; + } + if (!isObject(state.PhishingController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.PhishingController is ${typeof state.PhishingController}`, + ), + ); + return state; + } + if (!hasProperty(state.PhishingController, 'listState')) { + log.warn( + `typeof state.PhishingController.listState is ${typeof state.PhishingController}`, + ); return state; } diff --git a/app/scripts/migrations/091.test.ts b/app/scripts/migrations/091.test.ts index d4836f003..6a1f14ed7 100644 --- a/app/scripts/migrations/091.test.ts +++ b/app/scripts/migrations/091.test.ts @@ -1,6 +1,15 @@ import { cloneDeep } from 'lodash'; import { migrate, version } from './091'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -11,6 +20,10 @@ jest.mock('uuid', () => { }); describe('migration #91', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -40,6 +53,25 @@ describe('migration #91', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 90, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller networkConfigurations state', async () => { const oldData = { other: 'data', @@ -60,6 +92,32 @@ describe('migration #91', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller networkConfigurations state', async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + foo: 'bar', + }, + }, + }; + const oldStorage = { + meta: { + version: 90, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.NetworkController.networkConfigurations is undefined`, + ), + ); + }); + it('should return state unaltered if the networkConfigurations all have a chainId', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/091.ts b/app/scripts/migrations/091.ts index c0661746a..874f6ed9f 100644 --- a/app/scripts/migrations/091.ts +++ b/app/scripts/migrations/091.ts @@ -50,6 +50,19 @@ function transformState(state: Record) { ...state, NetworkController: state.NetworkController, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!isObject(state.NetworkController.networkConfigurations)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.networkConfigurations is ${typeof state + .NetworkController.networkConfigurations}`, + ), + ); } return state; } diff --git a/app/scripts/migrations/092.test.ts b/app/scripts/migrations/092.test.ts index b44c04602..b7337c871 100644 --- a/app/scripts/migrations/092.test.ts +++ b/app/scripts/migrations/092.test.ts @@ -3,7 +3,20 @@ import { migrate, version } from './092'; const PREVIOUS_VERSION = version - 1; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #92', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -33,6 +46,22 @@ describe('migration #92', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the phishing controller state is invalid', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: { PhishingController: 'this is not valid' }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PhishingController is string`), + ); + }); + it('should return state unaltered if there is no phishing controller last fetched state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/092.ts b/app/scripts/migrations/092.ts index bf5469614..44ef2a6c7 100644 --- a/app/scripts/migrations/092.ts +++ b/app/scripts/migrations/092.ts @@ -1,5 +1,6 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; export const version = 92; @@ -30,6 +31,14 @@ function transformState(state: Record) { ) { delete state.PhishingController.stalelistLastFetched; delete state.PhishingController.hotlistLastFetched; + } else if (hasProperty(state, 'PhishingController')) { + global.sentry?.captureException?.( + new Error( + `typeof state.PhishingController is ${typeof state.PhishingController}`, + ), + ); + } else { + log.warn(`typeof state.PhishingController is undefined`); } return state; } diff --git a/app/scripts/migrations/093.test.ts b/app/scripts/migrations/093.test.ts index 0d19ddae6..eff417f01 100644 --- a/app/scripts/migrations/093.test.ts +++ b/app/scripts/migrations/093.test.ts @@ -3,7 +3,20 @@ import { migrate, version } from './093'; const PREVIOUS_VERSION = version - 1; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #93', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -33,6 +46,25 @@ describe('migration #93', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller providerConfig state', async () => { const oldData = { other: 'data', @@ -55,6 +87,32 @@ describe('migration #93', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + id1: { + foo: 'bar', + }, + }, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.providerConfig is undefined`), + ); + }); + it('should return state unaltered if there is already a ticker in the providerConfig state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/093.ts b/app/scripts/migrations/093.ts index 8aee8f9e8..665c2f50d 100644 --- a/app/scripts/migrations/093.ts +++ b/app/scripts/migrations/093.ts @@ -44,6 +44,22 @@ function transformState(state: Record) { ...state, NetworkController: state.NetworkController, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if ( + isObject(state.NetworkController) && + !isObject(state.NetworkController.providerConfig) + ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.providerConfig is ${typeof state + .NetworkController.providerConfig}`, + ), + ); } return state; } diff --git a/app/scripts/migrations/094.test.ts b/app/scripts/migrations/094.test.ts index f37d46c81..955c206d2 100644 --- a/app/scripts/migrations/094.test.ts +++ b/app/scripts/migrations/094.test.ts @@ -2,7 +2,20 @@ import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; import { migrate, version } from './094'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #94', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -32,6 +45,25 @@ describe('migration #94', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller providerConfig state', async () => { const oldData = { other: 'data', @@ -54,6 +86,137 @@ describe('migration #94', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + id1: { + foo: 'bar', + }, + }, + }, + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.providerConfig is undefined`), + ); + }); + + it('should capture an exception if there is no providerConfig.id and no providerConfig.type value in state', async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + ticker: 'NET', + chainId: '0x189123', + nickname: 'A Network', + }, + }, + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.NetworkController.providerConfig.id is undefined and state.NetworkController.providerConfig.type is undefined`, + ), + ); + }); + + it('should not capture an exception if there is a providerConfig.id in state', async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + ticker: 'NET', + chainId: '0x189123', + nickname: 'A Network', + id: 'foobar', + }, + }, + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + + it(`should capture an exception if there is no providerConfig.id and the providerConfig.type value is ${NetworkType.rpc} in state`, async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + ticker: 'NET', + chainId: '0x189123', + nickname: 'A Network', + type: NetworkType.rpc, + }, + }, + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.NetworkController.providerConfig.id is undefined and state.NetworkController.providerConfig.type is ${NetworkType.rpc}`, + ), + ); + }); + + it(`should not capture an exception if there is no providerConfig.id and the providerConfig.type value is not ${NetworkType.rpc} in state`, async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + ticker: 'NET', + chainId: '0x189123', + nickname: 'A Network', + type: 'NOT_AN_RPC_TYPE', + }, + }, + }; + const oldStorage = { + meta: { + version: 93, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('should return state unaltered if there is a providerConfig.id value in state but it is not a string', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/094.ts b/app/scripts/migrations/094.ts index 78fbe9641..12734f7e6 100644 --- a/app/scripts/migrations/094.ts +++ b/app/scripts/migrations/094.ts @@ -84,6 +84,35 @@ function transformState(state: Record) { selectedNetworkClientId, }, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if ( + isObject(state.NetworkController) && + !isObject(state.NetworkController.providerConfig) + ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.providerConfig is ${typeof state + .NetworkController.providerConfig}`, + ), + ); + } else if ( + isObject(state.NetworkController) && + isObject(state.NetworkController.providerConfig) + ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.providerConfig.id is ${typeof state + .NetworkController.providerConfig + .id} and state.NetworkController.providerConfig.type is ${ + state.NetworkController.providerConfig.type + }`, + ), + ); } return state; } diff --git a/types/global.d.ts b/types/global.d.ts index 2c9c1d126..4c6f9c226 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -9,7 +9,7 @@ declare class Platform { closeCurrentWindow: () => void; } -declare class SentryObject extends Sentry { +type SentryObject = Sentry & { // Verifies that the user has opted into metrics and then updates the sentry // instance to track sessions and begins the session. startSession: () => void; @@ -20,7 +20,7 @@ declare class SentryObject extends Sentry { // Calls either startSession or endSession based on optin status toggleSession: () => void; -} +}; export declare global { var platform: Platform; From 3ab5c1bf8810564e3c22bccc78853419f4b76780 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 16:59:17 -0230 Subject: [PATCH 024/102] Rename BackupController to Backup (#20465) The backup module has been renamed so that it isn't confused with a controller. The "backup controller" has never managed any state or extended the controller base class. It is a module for backing up data, but it's not a controller. The backup controller's current inclusion in the `store` and `memstore` `ComposableObservableStore`s was getting in the way of some other enhancements to that class (which will come in a later PR). --- app/scripts/{controllers => lib}/backup.js | 2 +- .../{controllers => lib}/backup.test.js | 44 +++++++++---------- app/scripts/metamask-controller.js | 14 +++--- 3 files changed, 27 insertions(+), 33 deletions(-) rename app/scripts/{controllers => lib}/backup.js (98%) rename app/scripts/{controllers => lib}/backup.test.js (81%) diff --git a/app/scripts/controllers/backup.js b/app/scripts/lib/backup.js similarity index 98% rename from app/scripts/controllers/backup.js rename to app/scripts/lib/backup.js index 159f02d2d..01c49e0b1 100644 --- a/app/scripts/controllers/backup.js +++ b/app/scripts/lib/backup.js @@ -1,6 +1,6 @@ import { prependZero } from '../../../shared/modules/string-utils'; -export default class BackupController { +export default class Backup { constructor(opts = {}) { const { preferencesController, diff --git a/app/scripts/controllers/backup.test.js b/app/scripts/lib/backup.test.js similarity index 81% rename from app/scripts/controllers/backup.test.js rename to app/scripts/lib/backup.test.js index 25f153a37..e1ab84c9e 100644 --- a/app/scripts/controllers/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -1,6 +1,6 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; -import BackupController from './backup'; +import Backup from './backup'; function getMockPreferencesController() { const mcState = { @@ -151,9 +151,9 @@ const jsonData = JSON.stringify({ }, }); -describe('BackupController', function () { - const getBackupController = () => { - return new BackupController({ +describe('Backup', function () { + const getBackup = () => { + return new Backup({ preferencesController: getMockPreferencesController(), addressBookController: getMockAddressBookController(), networkController: getMockNetworkController(), @@ -163,85 +163,81 @@ describe('BackupController', function () { describe('constructor', function () { it('should setup correctly', async function () { - const backupController = getBackupController(); - const selectedAddress = - backupController.preferencesController.getSelectedAddress(); + const backup = getBackup(); + const selectedAddress = backup.preferencesController.getSelectedAddress(); assert.equal(selectedAddress, '0x01'); }); it('should restore backup', async function () { - const backupController = getBackupController(); - await backupController.restoreUserData(jsonData); + const backup = getBackup(); + await backup.restoreUserData(jsonData); // check networks backup assert.equal( - backupController.networkController.state.networkConfigurations[ + backup.networkController.state.networkConfigurations[ 'network-configuration-id-1' ].chainId, '0x539', ); assert.equal( - backupController.networkController.state.networkConfigurations[ + backup.networkController.state.networkConfigurations[ 'network-configuration-id-2' ].chainId, '0x38', ); assert.equal( - backupController.networkController.state.networkConfigurations[ + backup.networkController.state.networkConfigurations[ 'network-configuration-id-3' ].chainId, '0x61', ); assert.equal( - backupController.networkController.state.networkConfigurations[ + backup.networkController.state.networkConfigurations[ 'network-configuration-id-4' ].chainId, '0x89', ); // make sure identities are not lost after restore assert.equal( - backupController.preferencesController.store.identities[ + backup.preferencesController.store.identities[ '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B' ].lastSelected, 1655380342907, ); assert.equal( - backupController.preferencesController.store.identities[ + backup.preferencesController.store.identities[ '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B' ].name, 'Account 3', ); assert.equal( - backupController.preferencesController.store.lostIdentities[ + backup.preferencesController.store.lostIdentities[ '0xfd59bbe569376e3d3e4430297c3c69ea93f77435' ].lastSelected, 1655379648197, ); assert.equal( - backupController.preferencesController.store.lostIdentities[ + backup.preferencesController.store.lostIdentities[ '0xfd59bbe569376e3d3e4430297c3c69ea93f77435' ].name, 'Ledger 1', ); // make sure selected address is not lost after restore - assert.equal( - backupController.preferencesController.store.selectedAddress, - '0x01', - ); + assert.equal(backup.preferencesController.store.selectedAddress, '0x01'); // check address book backup assert.equal( - backupController.addressBookController.store.addressBook['0x61'][ + backup.addressBookController.store.addressBook['0x61'][ '0x42EB768f2244C8811C63729A21A3569731535f06' ].chainId, '0x61', ); assert.equal( - backupController.addressBookController.store.addressBook['0x61'][ + backup.addressBookController.store.addressBook['0x61'][ '0x42EB768f2244C8811C63729A21A3569731535f06' ].address, '0x42EB768f2244C8811C63729A21A3569731535f06', ); assert.equal( - backupController.addressBookController.store.addressBook['0x61'][ + backup.addressBookController.store.addressBook['0x61'][ '0x42EB768f2244C8811C63729A21A3569731535f06' ].isEns, false, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7c0afce37..6658d7ad7 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -184,7 +184,7 @@ import AppStateController from './controllers/app-state'; import CachedBalancesController from './controllers/cached-balances'; import AlertController from './controllers/alert'; import OnboardingController from './controllers/onboarding'; -import BackupController from './controllers/backup'; +import Backup from './lib/backup'; import IncomingTransactionsController from './controllers/incoming-transactions'; import DecryptMessageController from './controllers/decrypt-message'; import TransactionController from './controllers/transactions'; @@ -1157,7 +1157,7 @@ export default class MetamaskController extends EventEmitter { }); ///: END:ONLY_INCLUDE_IN - this.backupController = new BackupController({ + this.backup = new Backup({ preferencesController: this.preferencesController, addressBookController: this.addressBookController, networkController: this.networkController, @@ -1653,7 +1653,6 @@ export default class MetamaskController extends EventEmitter { PermissionController: this.permissionController, PermissionLogController: this.permissionLogController.store, SubjectMetadataController: this.subjectMetadataController, - BackupController: this.backupController, AnnouncementController: this.announcementController, GasFeeController: this.gasFeeController, TokenListController: this.tokenListController, @@ -1701,7 +1700,6 @@ export default class MetamaskController extends EventEmitter { PermissionController: this.permissionController, PermissionLogController: this.permissionLogController.store, SubjectMetadataController: this.subjectMetadataController, - BackupController: this.backupController, AnnouncementController: this.announcementController, GasFeeController: this.gasFeeController, TokenListController: this.tokenListController, @@ -2213,7 +2211,7 @@ export default class MetamaskController extends EventEmitter { smartTransactionsController, txController, assetsContractController, - backupController, + backup, approvalController, } = this; @@ -2759,9 +2757,9 @@ export default class MetamaskController extends EventEmitter { removePollingTokenFromAppState: appStateController.removePollingToken.bind(appStateController), - // BackupController - backupUserData: backupController.backupUserData.bind(backupController), - restoreUserData: backupController.restoreUserData.bind(backupController), + // Backup + backupUserData: backup.backupUserData.bind(backup), + restoreUserData: backup.restoreUserData.bind(backup), // DetectTokenController detectNewTokens: detectTokensController.detectNewTokens.bind( From b2a56cadc43944042419763ed6b611c9e9ec0a81 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 19:45:31 -0230 Subject: [PATCH 025/102] Add additional validation for persisted state metadata (#20462) Additional validation has been added for persisted state metadata. Beforehand we just checked that the state itself wasn't falsy. Now we ensure that the metadata is an object with a valid version as well. --- app/scripts/background.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/scripts/background.js b/app/scripts/background.js index 5b97bb34d..86d4b5525 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -13,6 +13,7 @@ import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; +import { isObject } from '@metamask/utils'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { ApprovalType } from '@metamask/controller-utils'; ///: END:ONLY_INCLUDE_IN @@ -411,6 +412,19 @@ export async function loadStateFromPersistence() { versionedData = await migrator.migrateData(versionedData); if (!versionedData) { throw new Error('MetaMask - migrator returned undefined'); + } else if (!isObject(versionedData.meta)) { + throw new Error( + `MetaMask - migrator metadata has invalid type '${typeof versionedData.meta}'`, + ); + } else if (typeof versionedData.meta.version !== 'number') { + throw new Error( + `MetaMask - migrator metadata version has invalid type '${typeof versionedData + .meta.version}'`, + ); + } else if (!isObject(versionedData.data)) { + throw new Error( + `MetaMask - migrator data has invalid type '${typeof versionedData.data}'`, + ); } // this initializes the meta/version data as a class variable to be used for future writes localStore.setMetadata(versionedData.meta); From 1f508a30d9939175dd113cf274624e46595a9138 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 21:11:17 -0230 Subject: [PATCH 026/102] Add types of hidden properties to Sentry data (#20457) * Add types of hidden properties to Sentry data The masked wallet state object sent to Sentry has been updated to include the type of each property omitted from the mask. This lets us at least see the full state shape, making it easier to see when errors are caused by invalid state. Relates to #20449 * Remove inconsistent state snapshot properties The state snapshot tests have been updated to exclude properties that were shown to differ between runs. --- shared/modules/object.utils.js | 4 + test/e2e/tests/errors.spec.js | 60 +++++++--- ...rs-after-init-opt-in-background-state.json | 63 ++++++++-- .../errors-after-init-opt-in-ui-state.json | 110 +++++++++++++++++- 4 files changed, 211 insertions(+), 26 deletions(-) diff --git a/shared/modules/object.utils.js b/shared/modules/object.utils.js index 24119be56..ce7eb1da1 100644 --- a/shared/modules/object.utils.js +++ b/shared/modules/object.utils.js @@ -7,6 +7,8 @@ * should be included, and a sub-mask implies the property should be further * masked according to that sub-mask. * + * If a property is not found in the last, its type is included instead. + * * @param {object} object - The object to mask * @param {Object} mask - The mask to apply to the object */ @@ -16,6 +18,8 @@ export function maskObject(object, mask) { state[key] = object[key]; } else if (mask[key]) { state[key] = maskObject(object[key], mask[key]); + } else { + state[key] = typeof object[key]; } return state; }, {}); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 8784ef168..3098a5083 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -1,42 +1,72 @@ const { resolve } = require('path'); const { promises: fs } = require('fs'); const { strict: assert } = require('assert'); -const { get, has, set } = require('lodash'); +const { get, has, set, unset } = require('lodash'); const { Browser } = require('selenium-webdriver'); const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const backgroundDateFields = ['CurrencyController.conversionDate']; -const uiDateFields = ['metamask.conversionDate']; +const maskedBackgroundFields = [ + 'CurrencyController.conversionDate', // This is a timestamp that changes each run +]; +const maskedUiFields = [ + 'metamask.conversionDate', // This is a timestamp that changes each run +]; + +const removedBackgroundFields = [ + // This property is timing-dependent + 'AccountTracker.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'AppStateController.currentPopupId', + 'AppStateController.timeoutMinutes', + 'TokenListController.tokensChainsCache', +]; + +const removedUiFields = [ + // This property is timing-dependent + 'metamask.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'metamask.currentPopupId', + 'metamask.timeoutMinutes', + 'metamask.tokensChainsCache', +]; /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform background state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformBackgroundDates(data) { - for (const field of backgroundDateFields) { +function transformBackgroundState(data) { + for (const field of maskedBackgroundFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedBackgroundFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform UI state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformUiDates(data) { - for (const field of uiDateFields) { +function transformUiState(data) { + for (const field of maskedUiFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedUiFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } @@ -257,7 +287,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformBackgroundDates(appState), + data: transformBackgroundState(appState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -342,7 +372,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformUiDates(appState), + data: transformUiState(appState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -509,7 +539,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundDates(appState.store), + data: transformBackgroundState(appState.store), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -603,7 +633,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformUiDates(appState.store), + data: transformUiState(appState.store), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index f41fee885..2f29cc98c 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,34 +1,81 @@ { - "AccountTracker": { "currentBlockGasLimit": "0x1c9c380" }, + "AccountTracker": { "accounts": "object" }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, - "defaultHomeActiveTabName": null + "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number" }, + "ApprovalController": "object", + "CachedBalancesController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, "nativeCurrency": "ETH", - "currentCurrency": "usd" + "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number" + }, + "DecryptMessageController": { + "unapprovedDecryptMsgs": "object", + "unapprovedDecryptMsgCount": 0 }, - "DecryptMessageController": { "unapprovedDecryptMsgCount": 0 }, "EncryptionPublicKeyController": { + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0 }, + "EnsController": "object", "MetaMetricsController": { "participateInMetaMetrics": true, - "metaMetricsId": "fake-metrics-id" + "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object" }, "NetworkController": { + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" - } + "type": "rpc", + "id": "string" + }, + "networksMetadata": "object", + "networkConfigurations": "object" }, "SignatureController": { + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 - } + }, + "SwapsController": "object", + "TokenRatesController": "object", + "TokensController": "object", + "TxController": "object" } diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 4ecaa0417..129ae885a 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -1,16 +1,28 @@ { + "DNS": "object", + "activeTab": "object", + "appState": "object", + "confirmTransaction": "object", "gas": { "customData": { "price": null, "limit": null } }, "history": { "mostRecentOverviewPage": "/" }, + "invalidCustomNetwork": "object", + "localeMessages": "object", "metamask": { "isInitialized": true, "isUnlocked": false, "isAccountMenuOpen": false, + "isNetworkMenuOpen": "boolean", + "identities": "object", + "unapprovedTxs": "object", + "networkConfigurations": "object", + "addressBook": "object", + "contractExchangeRates": "object", + "pendingTokens": "object", "customNonceValue": "", "useBlockie": false, "featureFlags": { "showIncomingTransactions": true }, "welcomeScreenSeen": false, "currentLocale": "en", - "currentBlockGasLimit": "", "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -19,31 +31,89 @@ }, "firstTimeFlowType": "import", "completedOnboarding": true, + "knownMethodData": "object", + "use4ByteResolution": "boolean", "participateInMetaMetrics": true, "nextNonce": null, "conversionRate": 1300, "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number", "currentAppVersion": "10.34.4", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": 94, + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" + "type": "rpc", + "id": "string" }, + "networksMetadata": "object", + "cachedBalances": "object", + "keyringTypes": "object", + "keyrings": "object", "useNonceField": false, "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "lostIdentities": "object", "forgottenPassword": false, "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object", "conversionDate": "number", "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number", "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object", "seedPhraseBackedUp": true, + "onboardingTabs": "object", + "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { "0x1": null, "0xe708": null, @@ -51,11 +121,45 @@ "0xaa36a7": null, "0xe704": null }, + "subjects": "object", + "permissionHistory": "object", + "permissionActivityLog": "object", + "subjectMetadata": "object", + "announcements": "object", + "gasFeeEstimates": "object", + "estimatedGasFeeTimeBounds": "object", + "gasEstimateType": "string", + "tokenList": "object", + "preventPollingOnNetworkRestart": "boolean", + "tokens": "object", + "ignoredTokens": "object", + "detectedTokens": "object", + "allTokens": "object", + "allIgnoredTokens": "object", + "allDetectedTokens": "object", + "smartTransactionsState": "object", + "allNftContracts": "object", + "allNfts": "object", + "ignoredNfts": "object", + "accounts": "object", + "currentNetworkTxList": "object", + "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, - "unapprovedTypedMessagesCount": 0 + "unapprovedTypedMessagesCount": 0, + "swapsState": "object", + "ensResolutionsByAddress": "object", + "pendingApprovals": "object", + "pendingApprovalCount": "number", + "approvalFlows": "object" }, + "send": "object", + "swaps": "object", "unconnectedAccount": { "state": "CLOSED" } } From e7b113caa25d49ac6116065dc442f06807065700 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 14 Aug 2023 21:00:34 +0200 Subject: [PATCH 027/102] Bump SES to fix audit failure (#20434) * Bump SES to fix audit failure * Freeze Symbol --- app/scripts/lockdown-more.js | 2 +- package.json | 2 +- yarn.lock | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/scripts/lockdown-more.js b/app/scripts/lockdown-more.js index e6637602a..317052312 100644 --- a/app/scripts/lockdown-more.js +++ b/app/scripts/lockdown-more.js @@ -28,7 +28,7 @@ try { const namedIntrinsics = Reflect.ownKeys(new Compartment().globalThis); // These named intrinsics are not automatically hardened by `lockdown` - const shouldHardenManually = new Set(['eval', 'Function']); + const shouldHardenManually = new Set(['eval', 'Function', 'Symbol']); const globalProperties = new Set([ // universalPropertyNames is a constant added by lockdown to global scope diff --git a/package.json b/package.json index b1790a1e9..7e47069af 100644 --- a/package.json +++ b/package.json @@ -351,7 +351,7 @@ "redux-thunk": "^2.3.0", "remove-trailing-slash": "^0.1.1", "reselect": "^3.0.1", - "ses": "^0.18.4", + "ses": "^0.18.7", "single-call-balance-checker-abi": "^1.0.0", "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", diff --git a/yarn.lock b/yarn.lock index 476f1f709..3a3e78fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1884,6 +1884,13 @@ __metadata: languageName: node linkType: hard +"@endo/env-options@npm:^0.1.3": + version: 0.1.3 + resolution: "@endo/env-options@npm:0.1.3" + checksum: da8c66865d4d30b0053a00960657dc36f022975a888f0dd6a2f6bb37b9fe731f45a02a2cf263d93b1a40fcb37b25f8ba7076cb8af9e93fd95f496365d9382930 + languageName: node + linkType: hard + "@ensdomains/address-encoder@npm:^0.1.7": version: 0.1.9 resolution: "@ensdomains/address-encoder@npm:0.1.9" @@ -24758,7 +24765,7 @@ __metadata: selenium-webdriver: ^4.9.0 semver: ^7.3.5 serve-handler: ^6.1.2 - ses: ^0.18.4 + ses: ^0.18.7 single-call-balance-checker-abi: ^1.0.0 sinon: ^9.0.0 source-map: ^0.7.2 @@ -31342,13 +31349,22 @@ __metadata: languageName: node linkType: hard -"ses@npm:^0.18.1, ses@npm:^0.18.4": +"ses@npm:^0.18.1": version: 0.18.4 resolution: "ses@npm:0.18.4" checksum: 9afd6edcf390a693926ef728ebb5a435994bbb0f915009ad524c6588cf62e2f66f6d4b4b2694f093b2af2e92c003947ad55404750d756ba75ce70c8636a7ba02 languageName: node linkType: hard +"ses@npm:^0.18.7": + version: 0.18.7 + resolution: "ses@npm:0.18.7" + dependencies: + "@endo/env-options": ^0.1.3 + checksum: 75ac014771d9bc1f747193c6d0f9e7d2d7700a10311ba8d805d9bc78d4c20d4ef40537f0535b1ea6abf06babf67e70f8bd37b2ad68ad54992a0c5ce842181c87 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" From 83c8a6be994cf74d81c081648a9e0a4518818dbd Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 08:33:18 -0230 Subject: [PATCH 028/102] Update `protobufjs` (#20469) Update `protobufjs` to the latest version. This resolves a security advisory for this package. The advisory is concerning prototype pollution, so it likely never affected us due to LavaMoat protections. --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3a3e78fa4..944abfeba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28543,8 +28543,8 @@ __metadata: linkType: hard "protobufjs@npm:^6.11.3": - version: 6.11.3 - resolution: "protobufjs@npm:6.11.3" + version: 6.11.4 + resolution: "protobufjs@npm:6.11.4" dependencies: "@protobufjs/aspromise": ^1.1.2 "@protobufjs/base64": ^1.1.2 @@ -28562,7 +28562,7 @@ __metadata: bin: pbjs: bin/pbjs pbts: bin/pbts - checksum: 4a6ce1964167e4c45c53fd8a312d7646415c777dd31b4ba346719947b88e61654912326101f927da387d6b6473ab52a7ea4f54d6f15d63b31130ce28e2e15070 + checksum: 6b7fd7540d74350d65c38f69f398c9995ae019da070e79d9cd464a458c6d19b40b07c9a026be4e10704c824a344b603307745863310c50026ebd661ce4da0663 languageName: node linkType: hard From d610aad2fd3300dfb1bdb07c061cfef4cf444eb0 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 14:40:44 -0230 Subject: [PATCH 029/102] Split Sentry mask into UI and background masks (#20426) The state mask used to anonymize the Sentry state snapshots has been split into UI and background masks. This was done to simplify later refactors. There should be no functional changes. --- app/scripts/background.js | 11 ++-- app/scripts/lib/setupSentry.js | 101 +++++++++++++++++---------------- ui/index.js | 8 +-- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 4ce5419c5..0c07adf4a 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -41,7 +41,7 @@ import Migrator from './lib/migrator'; import ExtensionPlatform from './platforms/extension'; import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; -import { SENTRY_STATE } from './lib/setupSentry'; +import { SENTRY_BACKGROUND_STATE } from './lib/setupSentry'; import createStreamSink from './lib/createStreamSink'; import NotificationManager, { @@ -876,11 +876,14 @@ browser.runtime.onInstalled.addListener(({ reason }) => { function setupSentryGetStateGlobal(store) { global.stateHooks.getSentryState = function () { - const fullState = store.getState(); - const debugState = maskObject({ metamask: fullState }, SENTRY_STATE); + const backgroundState = store.getState(); + const maskedBackgroundState = maskObject( + backgroundState, + SENTRY_BACKGROUND_STATE, + ); return { browser: window.navigator.userAgent, - store: debugState, + store: { metamask: maskedBackgroundState }, version: platform.getVersion(), }; }; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 967361315..881f3c49e 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -23,59 +23,64 @@ export const ERROR_URL_ALLOWLIST = { SEGMENT: 'segment.io', }; +// This describes the subset of background controller state attached to errors +// sent to Sentry These properties have some potential to be useful for +// debugging, and they do not contain any identifiable information. +export const SENTRY_BACKGROUND_STATE = { + alertEnabledness: true, + completedOnboarding: true, + connectedStatusPopoverHasBeenShown: true, + conversionDate: true, + conversionRate: true, + currentAppVersion: true, + currentBlockGasLimit: true, + currentCurrency: true, + currentLocale: true, + currentMigrationVersion: true, + customNonceValue: true, + defaultHomeActiveTabName: true, + desktopEnabled: true, + featureFlags: true, + firstTimeFlowType: true, + forgottenPassword: true, + incomingTxLastFetchedBlockByChainId: true, + ipfsGateway: true, + isAccountMenuOpen: true, + isInitialized: true, + isUnlocked: true, + metaMetricsId: true, + nativeCurrency: true, + networkId: true, + networkStatus: true, + nextNonce: true, + participateInMetaMetrics: true, + preferences: true, + previousAppVersion: true, + previousMigrationVersion: true, + providerConfig: { + nickname: true, + ticker: true, + type: true, + }, + seedPhraseBackedUp: true, + unapprovedDecryptMsgCount: true, + unapprovedEncryptionPublicKeyMsgCount: true, + unapprovedMsgCount: true, + unapprovedPersonalMsgCount: true, + unapprovedTypedMessagesCount: true, + useBlockie: true, + useNonceField: true, + usePhishDetect: true, + welcomeScreenSeen: true, +}; + // This describes the subset of Redux state attached to errors sent to Sentry // These properties have some potential to be useful for debugging, and they do // not contain any identifiable information. -export const SENTRY_STATE = { +export const SENTRY_UI_STATE = { gas: true, history: true, - metamask: { - alertEnabledness: true, - completedOnboarding: true, - connectedStatusPopoverHasBeenShown: true, - conversionDate: true, - conversionRate: true, - currentAppVersion: true, - currentBlockGasLimit: true, - currentCurrency: true, - currentLocale: true, - currentMigrationVersion: true, - customNonceValue: true, - defaultHomeActiveTabName: true, - desktopEnabled: true, - featureFlags: true, - firstTimeFlowType: true, - forgottenPassword: true, - incomingTxLastFetchedBlockByChainId: true, - ipfsGateway: true, - isAccountMenuOpen: true, - isInitialized: true, - isUnlocked: true, - metaMetricsId: true, - nativeCurrency: true, - networkId: true, - networkStatus: true, - nextNonce: true, - participateInMetaMetrics: true, - preferences: true, - previousAppVersion: true, - previousMigrationVersion: true, - providerConfig: { - nickname: true, - ticker: true, - type: true, - }, - seedPhraseBackedUp: true, - unapprovedDecryptMsgCount: true, - unapprovedEncryptionPublicKeyMsgCount: true, - unapprovedMsgCount: true, - unapprovedPersonalMsgCount: true, - unapprovedTypedMessagesCount: true, - useBlockie: true, - useNonceField: true, - usePhishDetect: true, - welcomeScreenSeen: true, - }, + metamask: SENTRY_BACKGROUND_STATE, unconnectedAccount: true, }; diff --git a/ui/index.js b/ui/index.js index e9ac462bd..6f9a148ed 100644 --- a/ui/index.js +++ b/ui/index.js @@ -8,7 +8,7 @@ import browser from 'webextension-polyfill'; import { getEnvironmentType } from '../app/scripts/lib/util'; import { AlertTypes } from '../shared/constants/alerts'; import { maskObject } from '../shared/modules/object.utils'; -import { SENTRY_STATE } from '../app/scripts/lib/setupSentry'; +import { SENTRY_UI_STATE } from '../app/scripts/lib/setupSentry'; import { ENVIRONMENT_TYPE_POPUP } from '../shared/constants/app'; import switchDirection from '../shared/lib/switch-direction'; import { setupLocale } from '../shared/lib/error-utils'; @@ -234,11 +234,11 @@ function setupStateHooks(store) { return state; }; window.stateHooks.getSentryState = function () { - const fullState = store.getState(); - const debugState = maskObject(fullState, SENTRY_STATE); + const reduxState = store.getState(); + const maskedReduxState = maskObject(reduxState, SENTRY_UI_STATE); return { browser: window.navigator.userAgent, - store: debugState, + store: maskedReduxState, version: global.platform.getVersion(), }; }; From 1ad47c660bff8d856dc2806c5f01b04808afe47e Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 15:21:18 -0230 Subject: [PATCH 030/102] Use unflattened state for Sentry (#20428) The unflattened background state is now attached to any Sentry errors from the background process. This provides a clearer picture of the state of the wallet, and unblocks further improvements to Sentry state which will come in later PRs. --- app/scripts/background.js | 4 +- app/scripts/lib/setupSentry.js | 133 ++++++++++++------ test/e2e/tests/errors.spec.js | 37 +++-- ...rs-after-init-opt-in-background-state.json | 64 +++------ 4 files changed, 138 insertions(+), 100 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 0c07adf4a..c368728d6 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -876,14 +876,14 @@ browser.runtime.onInstalled.addListener(({ reason }) => { function setupSentryGetStateGlobal(store) { global.stateHooks.getSentryState = function () { - const backgroundState = store.getState(); + const backgroundState = store.memStore.getState(); const maskedBackgroundState = maskObject( backgroundState, SENTRY_BACKGROUND_STATE, ); return { browser: window.navigator.userAgent, - store: { metamask: maskedBackgroundState }, + store: maskedBackgroundState, version: platform.getVersion(), }; }; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 881f3c49e..1838598d0 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -27,60 +27,103 @@ export const ERROR_URL_ALLOWLIST = { // sent to Sentry These properties have some potential to be useful for // debugging, and they do not contain any identifiable information. export const SENTRY_BACKGROUND_STATE = { - alertEnabledness: true, - completedOnboarding: true, - connectedStatusPopoverHasBeenShown: true, - conversionDate: true, - conversionRate: true, - currentAppVersion: true, - currentBlockGasLimit: true, - currentCurrency: true, - currentLocale: true, - currentMigrationVersion: true, - customNonceValue: true, - defaultHomeActiveTabName: true, - desktopEnabled: true, - featureFlags: true, - firstTimeFlowType: true, - forgottenPassword: true, - incomingTxLastFetchedBlockByChainId: true, - ipfsGateway: true, - isAccountMenuOpen: true, - isInitialized: true, - isUnlocked: true, - metaMetricsId: true, - nativeCurrency: true, - networkId: true, - networkStatus: true, - nextNonce: true, - participateInMetaMetrics: true, - preferences: true, - previousAppVersion: true, - previousMigrationVersion: true, - providerConfig: { - nickname: true, - ticker: true, - type: true, + AccountTracker: { + currentBlockGasLimit: true, + }, + AlertController: { + alertEnabledness: true, + }, + AppMetadataController: { + currentAppVersion: true, + previousAppVersion: true, + previousMigrationVersion: true, + currentMigrationVersion: true, + }, + AppStateController: { + connectedStatusPopoverHasBeenShown: true, + defaultHomeActiveTabName: true, + }, + CurrencyController: { + conversionDate: true, + conversionRate: true, + currentCurrency: true, + nativeCurrency: true, + }, + DecryptMessageController: { + unapprovedDecryptMsgCount: true, + }, + DesktopController: { + desktopEnabled: true, + }, + EncryptionPublicKeyController: { + unapprovedEncryptionPublicKeyMsgCount: true, + }, + IncomingTransactionsController: { + incomingTxLastFetchedBlockByChainId: true, + }, + KeyringController: { + isUnlocked: true, + }, + MetaMetricsController: { + metaMetricsId: true, + participateInMetaMetrics: true, + }, + NetworkController: { + networkId: true, + networkStatus: true, + providerConfig: { + nickname: true, + ticker: true, + type: true, + }, + }, + OnboardingController: { + completedOnboarding: true, + firstTimeFlowType: true, + seedPhraseBackedUp: true, + }, + PreferencesController: { + currentLocale: true, + featureFlags: true, + forgottenPassword: true, + ipfsGateway: true, + preferences: true, + useBlockie: true, + useNonceField: true, + usePhishDetect: true, + }, + SignatureController: { + unapprovedMsgCount: true, + unapprovedPersonalMsgCount: true, + unapprovedTypedMessagesCount: true, }, - seedPhraseBackedUp: true, - unapprovedDecryptMsgCount: true, - unapprovedEncryptionPublicKeyMsgCount: true, - unapprovedMsgCount: true, - unapprovedPersonalMsgCount: true, - unapprovedTypedMessagesCount: true, - useBlockie: true, - useNonceField: true, - usePhishDetect: true, - welcomeScreenSeen: true, }; +const flattenedBackgroundStateMask = Object.values( + SENTRY_BACKGROUND_STATE, +).reduce((partialBackgroundState, controllerState) => { + return { + ...partialBackgroundState, + ...controllerState, + }; +}, {}); + // This describes the subset of Redux state attached to errors sent to Sentry // These properties have some potential to be useful for debugging, and they do // not contain any identifiable information. export const SENTRY_UI_STATE = { gas: true, history: true, - metamask: SENTRY_BACKGROUND_STATE, + metamask: { + ...flattenedBackgroundStateMask, + // This property comes from the background but isn't in controller state + isInitialized: true, + // These properties are in the `metamask` slice but not in the background state + customNonceValue: true, + isAccountMenuOpen: true, + nextNonce: true, + welcomeScreenSeen: true, + }, unconnectedAccount: true, }; diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 4ab80a7de..8784ef168 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -7,7 +7,8 @@ const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const dateFields = ['metamask.conversionDate']; +const backgroundDateFields = ['CurrencyController.conversionDate']; +const uiDateFields = ['metamask.conversionDate']; /** * Transform date properties to value types, to ensure that state is @@ -15,8 +16,23 @@ const dateFields = ['metamask.conversionDate']; * * @param {unknown} data - The data to transform */ -function transformDates(data) { - for (const field of dateFields) { +function transformBackgroundDates(data) { + for (const field of backgroundDateFields) { + if (has(data, field)) { + set(data, field, typeof get(data, field)); + } + } + return data; +} + +/** + * Transform date properties to value types, to ensure that state is + * consistent between test runs. + * + * @param {unknown} data - The data to transform + */ +function transformUiDates(data) { + for (const field of uiDateFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } @@ -33,12 +49,10 @@ function transformDates(data) { * @param {boolean} [args.update] - Whether to update the snapshot if it doesn't match. */ async function matchesSnapshot({ - data: unprocessedData, + data, snapshot, update = process.env.UPDATE_SNAPSHOTS === 'true', }) { - const data = transformDates(unprocessedData); - const snapshotPath = resolve(__dirname, `./state-snapshots/${snapshot}.json`); const rawSnapshotData = await fs.readFile(snapshotPath, { encoding: 'utf-8', @@ -243,7 +257,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: appState, + data: transformBackgroundDates(appState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -328,7 +342,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: appState, + data: transformUiDates(appState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -436,7 +450,8 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; - const { participateInMetaMetrics } = extra.appState.store.metamask; + const { participateInMetaMetrics } = + extra.appState.store.MetaMetricsController; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -494,7 +509,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: appState.store, + data: transformBackgroundDates(appState.store), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -588,7 +603,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: appState.store, + data: transformUiDates(appState.store), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 03fc85edf..f41fee885 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,52 +1,32 @@ { - "metamask": { - "isInitialized": true, + "AccountTracker": { "currentBlockGasLimit": "0x1c9c380" }, + "AppStateController": { "connectedStatusPopoverHasBeenShown": true, - "defaultHomeActiveTabName": null, - "currentAppVersion": "10.34.4", - "previousAppVersion": "", - "previousMigrationVersion": 0, - "currentMigrationVersion": 94, + "defaultHomeActiveTabName": null + }, + "CurrencyController": { + "conversionDate": "number", + "conversionRate": 1700, + "nativeCurrency": "ETH", + "currentCurrency": "usd" + }, + "DecryptMessageController": { "unapprovedDecryptMsgCount": 0 }, + "EncryptionPublicKeyController": { + "unapprovedEncryptionPublicKeyMsgCount": 0 + }, + "MetaMetricsController": { + "participateInMetaMetrics": true, + "metaMetricsId": "fake-metrics-id" + }, + "NetworkController": { "networkId": "1337", "providerConfig": { "nickname": "Localhost 8545", "ticker": "ETH", "type": "rpc" - }, - "isUnlocked": false, - "useBlockie": false, - "useNonceField": false, - "usePhishDetect": true, - "featureFlags": { "showIncomingTransactions": true }, - "currentLocale": "en", - "forgottenPassword": false, - "preferences": { - "hideZeroBalanceTokens": false, - "showFiatInTestnets": false, - "showTestNetworks": false, - "useNativeCurrencyAsPrimaryCurrency": true - }, - "ipfsGateway": "dweb.link", - "participateInMetaMetrics": true, - "metaMetricsId": "fake-metrics-id", - "conversionDate": "number", - "conversionRate": 1700, - "nativeCurrency": "ETH", - "currentCurrency": "usd", - "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, - "seedPhraseBackedUp": true, - "firstTimeFlowType": "import", - "completedOnboarding": true, - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - }, - "currentBlockGasLimit": "0x1c9c380", - "unapprovedDecryptMsgCount": 0, - "unapprovedEncryptionPublicKeyMsgCount": 0, + } + }, + "SignatureController": { "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 From 3aa5b7d362be3aec86e733f88f70a8e61ba97ba0 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Thu, 17 Aug 2023 09:11:51 +0200 Subject: [PATCH 031/102] Use `importAccountWithStrategy` from core `KeyringController` (#19815) * refactor: use new importaccount from keyring controller * Update LavaMoat policies * Update LavaMoat policies * Update LavaMoat policies --------- Co-authored-by: MetaMask Bot --- .../account-import-strategies.test.js | 72 ------- .../account-import-strategies/index.js | 75 ------- .../metamask-controller.actions.test.js | 4 +- app/scripts/metamask-controller.js | 19 +- app/scripts/metamask-controller.test.js | 2 +- lavamoat/browserify/beta/policy.json | 194 +++++++---------- lavamoat/browserify/desktop/policy.json | 194 +++++++---------- lavamoat/browserify/flask/policy.json | 194 +++++++---------- lavamoat/browserify/main/policy.json | 194 +++++++---------- lavamoat/browserify/mmi/policy.json | 200 ++++++++---------- lavamoat/build-system/policy.json | 20 +- package.json | 1 - .../import-account/import-account.js | 2 +- .../multichain/import-account/json.js | 2 +- .../multichain/import-account/json.test.tsx | 7 +- .../multichain/import-account/private-key.js | 2 +- yarn.lock | 31 +-- 17 files changed, 435 insertions(+), 778 deletions(-) delete mode 100644 app/scripts/account-import-strategies/account-import-strategies.test.js delete mode 100644 app/scripts/account-import-strategies/index.js diff --git a/app/scripts/account-import-strategies/account-import-strategies.test.js b/app/scripts/account-import-strategies/account-import-strategies.test.js deleted file mode 100644 index 0c2c6fc3a..000000000 --- a/app/scripts/account-import-strategies/account-import-strategies.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import { strict as assert } from 'assert'; -import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; -import accountImporter from '.'; - -describe('Account Import Strategies', function () { - const privkey = - '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; - const json = - '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'; - - describe('private key import', function () { - it('imports a private key and strips 0x prefix', async function () { - const importPrivKey = await accountImporter.importAccount('Private Key', [ - privkey, - ]); - assert.equal(importPrivKey, stripHexPrefix(privkey)); - }); - - it('throws an error for empty string private key', async function () { - await assert.rejects( - async () => { - await accountImporter.importAccount('Private Key', ['']); - }, - Error, - 'no empty strings', - ); - }); - - it('throws an error for undefined string private key', async function () { - await assert.rejects(async () => { - await accountImporter.importAccount('Private Key', [undefined]); - }); - - await assert.rejects(async () => { - await accountImporter.importAccount('Private Key', []); - }); - }); - - it('throws an error for invalid private key', async function () { - await assert.rejects(async () => { - await accountImporter.importAccount('Private Key', ['popcorn']); - }); - }); - }); - - describe('JSON keystore import', function () { - it('fails when password is incorrect for keystore', async function () { - const wrongPassword = 'password2'; - - try { - await accountImporter.importAccount('JSON File', [json, wrongPassword]); - } catch (error) { - assert.equal( - error.message, - 'Key derivation failed - possibly wrong passphrase', - ); - } - }); - - it('imports json string and password to return a private key', async function () { - const fileContentsPassword = 'password1'; - const importJson = await accountImporter.importAccount('JSON File', [ - json, - fileContentsPassword, - ]); - assert.equal( - importJson, - '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7', - ); - }); - }); -}); diff --git a/app/scripts/account-import-strategies/index.js b/app/scripts/account-import-strategies/index.js deleted file mode 100644 index 286452c5a..000000000 --- a/app/scripts/account-import-strategies/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import { isValidMnemonic } from '@ethersproject/hdnode'; -import { - bufferToHex, - getBinarySize, - isValidPrivate, - toBuffer, -} from 'ethereumjs-util'; -import Wallet from 'ethereumjs-wallet'; -import importers from 'ethereumjs-wallet/thirdparty'; -import log from 'loglevel'; -import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; -import { addHexPrefix } from '../lib/util'; - -const accountImporter = { - async importAccount(strategy, args) { - const importer = this.strategies[strategy]; - const privateKeyHex = importer(...args); - return privateKeyHex; - }, - - strategies: { - 'Private Key': (privateKey) => { - if (!privateKey) { - throw new Error('Cannot import an empty key.'); // It should never get here, because this should be stopped in the UI - } - - // Check if the user has entered an SRP by mistake instead of a private key - if (isValidMnemonic(privateKey.trim())) { - throw new Error(`t('importAccountErrorIsSRP')`); - } - - const trimmedPrivateKey = privateKey.replace(/\s+/gu, ''); // Remove all whitespace - - const prefixedPrivateKey = addHexPrefix(trimmedPrivateKey); - let buffer; - try { - buffer = toBuffer(prefixedPrivateKey); - } catch (e) { - throw new Error(`t('importAccountErrorNotHexadecimal')`); - } - - try { - if ( - !isValidPrivate(buffer) || - getBinarySize(prefixedPrivateKey) !== 64 + '0x'.length // Fixes issue #17719 -- isValidPrivate() will let a key of 63 hex digits through without complaining, this line ensures 64 hex digits + '0x' = 66 digits - ) { - throw new Error(`t('importAccountErrorNotAValidPrivateKey')`); - } - } catch (e) { - throw new Error(`t('importAccountErrorNotAValidPrivateKey')`); - } - - const strippedPrivateKey = stripHexPrefix(prefixedPrivateKey); - return strippedPrivateKey; - }, - 'JSON File': (input, password) => { - let wallet; - try { - wallet = importers.fromEtherWallet(input, password); - } catch (e) { - log.debug('Attempt to import as EtherWallet format failed, trying V3'); - wallet = Wallet.fromV3(input, password, true); - } - - return walletToPrivateKey(wallet); - }, - }, -}; - -function walletToPrivateKey(wallet) { - const privateKeyBuffer = wallet.getPrivateKey(); - return bufferToHex(privateKeyBuffer); -} - -export default accountImporter; diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 397648c37..8c015c9c5 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -172,14 +172,14 @@ describe('MetaMaskController', function () { await metamaskController.createNewVaultAndKeychain('test@123'); await Promise.all([ - metamaskController.importAccountWithStrategy('Private Key', [ + metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, ]), Promise.resolve(1).then(() => { keyringControllerState1 = JSON.stringify( metamaskController.keyringController.memStore.getState(), ); - metamaskController.importAccountWithStrategy('Private Key', [ + metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, ]); }), diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6658d7ad7..58a81e47f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -190,7 +190,6 @@ import DecryptMessageController from './controllers/decrypt-message'; import TransactionController from './controllers/transactions'; import DetectTokensController from './controllers/detect-tokens'; import SwapsController from './controllers/swaps'; -import accountImporter from './account-import-strategies'; import seedPhraseVerifier from './lib/seed-phrase-verifier'; import MetaMetricsController from './controllers/metametrics'; import { segment } from './lib/segment'; @@ -3560,21 +3559,17 @@ export default class MetamaskController extends EventEmitter { * These are defined in app/scripts/account-import-strategies * Each strategy represents a different way of serializing an Ethereum key pair. * - * @param {string} strategy - A unique identifier for an account import strategy. + * @param {'privateKey' | 'json'} strategy - A unique identifier for an account import strategy. * @param {any} args - The data required by that strategy to import an account. */ async importAccountWithStrategy(strategy, args) { - const privateKey = await accountImporter.importAccount(strategy, args); - const keyring = await this.keyringController.addNewKeyring( - KeyringType.imported, - [privateKey], - ); - const [firstAccount] = await keyring.getAccounts(); - // update accounts in preferences controller - const allAccounts = await this.keyringController.getAccounts(); - this.preferencesController.setAddresses(allAccounts); + const { importedAccountAddress } = + await this.coreKeyringController.importAccountWithStrategy( + strategy, + args, + ); // set new account as selected - this.preferencesController.setSelectedAddress(firstAccount); + this.preferencesController.setSelectedAddress(importedAccountAddress); } // --------------------------------------------------------------------------- diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index f2c3fa6ad..20f496aba 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -326,7 +326,7 @@ describe('MetaMaskController', function () { beforeEach(async function () { const password = 'a-fake-password'; await metamaskController.createNewVaultAndRestore(password, TEST_SEED); - await metamaskController.importAccountWithStrategy('Private Key', [ + await metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, ]); }); diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 3207a448f..12cd90e66 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -107,9 +107,14 @@ }, "@ensdomains/content-hash>multihashes>multibase": { "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true, "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true } }, "@ensdomains/content-hash>multihashes>web-encoding": { @@ -492,7 +497,7 @@ "packages": { "@ngraveio/bc-ur": true, "browserify>buffer": true, - "ethereumjs-wallet>bs58check": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "mockttp>graphql-tag>tslib": true } }, @@ -500,9 +505,9 @@ "packages": { "browserify>assert": true, "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@keystonehq/metamask-airgapped-keyring": { @@ -1035,7 +1040,7 @@ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { @@ -1096,7 +1101,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey": { @@ -1105,7 +1110,7 @@ "@metamask/eth-trezor-keyring>hdkey>coinstring": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { @@ -1115,7 +1120,7 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { @@ -1409,7 +1414,7 @@ "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>coinstring": { @@ -1426,12 +1431,12 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/etherscan-link": { @@ -1583,11 +1588,11 @@ "@truffle/codec>utf8": true, "browserify>buffer": true, "browserify>crypto-browserify": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>scrypt-js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, + "mocha>serialize-javascript>randombytes": true, "uuid": true } }, @@ -2486,9 +2491,9 @@ "bn.js": true, "browserify>buffer": true, "ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, "ethjs>ethjs-unit": true, - "ethjs>number-to-bn": true + "ethjs>number-to-bn": true, + "mocha>serialize-javascript>randombytes": true } }, "@truffle/codec>web3-utils>ethereum-bloom-filters": { @@ -2547,7 +2552,7 @@ "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, "browserify>buffer": true, "eth-ens-namehash": true, - "ethereumjs-wallet>bs58check>bs58": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { @@ -2580,8 +2585,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { @@ -2605,8 +2610,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { @@ -2770,7 +2775,7 @@ }, "addons-linter>sha.js": { "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -2860,7 +2865,7 @@ "browserify>crypto-browserify>public-encrypt": true, "browserify>crypto-browserify>randomfill": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>browserify-cipher": { @@ -2887,7 +2892,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>browserify-sign": { @@ -2916,7 +2921,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -2925,7 +2930,7 @@ "bn.js": true, "browserify>buffer": true, "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>diffie-hellman>miller-rabin": { @@ -2947,7 +2952,7 @@ "browserify>process": true, "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>public-encrypt": { @@ -2957,14 +2962,14 @@ "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, "browserify>crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>browserify-rsa": { "packages": { "bn.js": true, "browserify>buffer": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>parse-asn1": { @@ -2992,8 +2997,8 @@ }, "packages": { "browserify>process": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "browserify>events": { @@ -3078,7 +3083,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>timers-browserify": { @@ -3347,6 +3352,7 @@ "browserify>buffer": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3356,9 +3362,8 @@ "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, "lodash": true } }, @@ -3405,6 +3410,11 @@ "crypto": true } }, + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3531,7 +3541,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "eth-sig-util>ethereumjs-util>ethjs-util": { @@ -3603,21 +3613,21 @@ "packages": { "browserify>stream-browserify": true, "browserify>string_decoder": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3644,12 +3654,12 @@ "browserify>assert": true, "browserify>buffer": true, "browserify>crypto-browserify>create-hmac": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "ethereumjs-util>ethereum-cryptography>keccak": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "ethereumjs-util>ethereum-cryptography>browserify-aes": { @@ -3658,7 +3668,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3667,6 +3677,18 @@ "browserify>buffer": true } }, + "ethereumjs-util>ethereum-cryptography>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>hash.js": { "packages": { "@metamask/ppom-validator>elliptic>minimalistic-assert": true, @@ -3710,78 +3732,9 @@ "browserify>buffer": true } }, - "ethereumjs-wallet": { - "packages": { - "@truffle/codec>utf8": true, - "browserify>crypto-browserify": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true, - "ethereumjs-wallet>scryptsy": true, - "ethereumjs-wallet>uuid": true - } - }, - "ethereumjs-wallet>aes-js": { - "globals": { - "define": true - } - }, - "ethereumjs-wallet>bs58check": { - "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>bs58check>bs58": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>bs58check>bs58": { - "packages": { - "ethereumjs-wallet>bs58check>bs58>base-x": true - } - }, - "ethereumjs-wallet>bs58check>bs58>base-x": { - "packages": { - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true - } - }, "ethereumjs-wallet>randombytes": { "globals": { - "crypto": true, - "msCrypto": true - }, - "packages": { - "browserify>process": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "ethereumjs-wallet>scryptsy": { - "packages": { - "browserify>buffer": true, - "browserify>crypto-browserify>pbkdf2": true - } - }, - "ethereumjs-wallet>uuid": { - "globals": { - "crypto": true, - "msCrypto": true + "crypto.getRandomValues": true } }, "ethers>@ethersproject/random": { @@ -3980,6 +3933,11 @@ "readable-stream": true } }, + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -4040,6 +3998,16 @@ "Intl": true } }, + "mocha>serialize-javascript>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "koa>content-disposition>safe-buffer": true + } + }, "mockttp>graphql-tag>tslib": { "globals": { "define": true diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index cc3765332..56edb5e32 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -107,9 +107,14 @@ }, "@ensdomains/content-hash>multihashes>multibase": { "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true, "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true } }, "@ensdomains/content-hash>multihashes>web-encoding": { @@ -492,7 +497,7 @@ "packages": { "@ngraveio/bc-ur": true, "browserify>buffer": true, - "ethereumjs-wallet>bs58check": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "mockttp>graphql-tag>tslib": true } }, @@ -500,9 +505,9 @@ "packages": { "browserify>assert": true, "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@keystonehq/metamask-airgapped-keyring": { @@ -1106,7 +1111,7 @@ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { @@ -1167,7 +1172,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey": { @@ -1176,7 +1181,7 @@ "@metamask/eth-trezor-keyring>hdkey>coinstring": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { @@ -1186,7 +1191,7 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-snap-keyring": { @@ -1537,7 +1542,7 @@ "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>coinstring": { @@ -1554,12 +1559,12 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/etherscan-link": { @@ -1734,11 +1739,11 @@ "@truffle/codec>utf8": true, "browserify>buffer": true, "browserify>crypto-browserify": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>scrypt-js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, + "mocha>serialize-javascript>randombytes": true, "uuid": true } }, @@ -3021,9 +3026,9 @@ "bn.js": true, "browserify>buffer": true, "ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, "ethjs>ethjs-unit": true, - "ethjs>number-to-bn": true + "ethjs>number-to-bn": true, + "mocha>serialize-javascript>randombytes": true } }, "@truffle/codec>web3-utils>ethereum-bloom-filters": { @@ -3082,7 +3087,7 @@ "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, "browserify>buffer": true, "eth-ens-namehash": true, - "ethereumjs-wallet>bs58check>bs58": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { @@ -3115,8 +3120,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { @@ -3140,8 +3145,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { @@ -3305,7 +3310,7 @@ }, "addons-linter>sha.js": { "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3395,7 +3400,7 @@ "browserify>crypto-browserify>public-encrypt": true, "browserify>crypto-browserify>randomfill": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>browserify-cipher": { @@ -3422,7 +3427,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>browserify-sign": { @@ -3451,7 +3456,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3460,7 +3465,7 @@ "bn.js": true, "browserify>buffer": true, "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>diffie-hellman>miller-rabin": { @@ -3482,7 +3487,7 @@ "browserify>process": true, "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>public-encrypt": { @@ -3492,14 +3497,14 @@ "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, "browserify>crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>browserify-rsa": { "packages": { "bn.js": true, "browserify>buffer": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>parse-asn1": { @@ -3527,8 +3532,8 @@ }, "packages": { "browserify>process": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "browserify>events": { @@ -3613,7 +3618,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>timers-browserify": { @@ -3898,6 +3903,7 @@ "browserify>buffer": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3907,9 +3913,8 @@ "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, "lodash": true } }, @@ -3956,6 +3961,11 @@ "crypto": true } }, + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -4082,7 +4092,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "eth-sig-util>ethereumjs-util>ethjs-util": { @@ -4154,21 +4164,21 @@ "packages": { "browserify>stream-browserify": true, "browserify>string_decoder": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -4195,12 +4205,12 @@ "browserify>assert": true, "browserify>buffer": true, "browserify>crypto-browserify>create-hmac": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "ethereumjs-util>ethereum-cryptography>keccak": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "ethereumjs-util>ethereum-cryptography>browserify-aes": { @@ -4209,7 +4219,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -4218,6 +4228,18 @@ "browserify>buffer": true } }, + "ethereumjs-util>ethereum-cryptography>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>hash.js": { "packages": { "@metamask/ppom-validator>elliptic>minimalistic-assert": true, @@ -4261,78 +4283,9 @@ "browserify>buffer": true } }, - "ethereumjs-wallet": { - "packages": { - "@truffle/codec>utf8": true, - "browserify>crypto-browserify": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true, - "ethereumjs-wallet>scryptsy": true, - "ethereumjs-wallet>uuid": true - } - }, - "ethereumjs-wallet>aes-js": { - "globals": { - "define": true - } - }, - "ethereumjs-wallet>bs58check": { - "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>bs58check>bs58": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>bs58check>bs58": { - "packages": { - "ethereumjs-wallet>bs58check>bs58>base-x": true - } - }, - "ethereumjs-wallet>bs58check>bs58>base-x": { - "packages": { - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true - } - }, "ethereumjs-wallet>randombytes": { "globals": { - "crypto": true, - "msCrypto": true - }, - "packages": { - "browserify>process": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "ethereumjs-wallet>scryptsy": { - "packages": { - "browserify>buffer": true, - "browserify>crypto-browserify>pbkdf2": true - } - }, - "ethereumjs-wallet>uuid": { - "globals": { - "crypto": true, - "msCrypto": true + "crypto.getRandomValues": true } }, "ethers>@ethersproject/random": { @@ -4531,6 +4484,11 @@ "readable-stream": true } }, + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -4609,6 +4567,16 @@ "readable-stream>util-deprecate": true } }, + "mocha>serialize-javascript>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "koa>content-disposition>safe-buffer": true + } + }, "mockttp>graphql-tag>tslib": { "globals": { "define": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 08100deb2..e725d3af4 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -107,9 +107,14 @@ }, "@ensdomains/content-hash>multihashes>multibase": { "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true, "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true } }, "@ensdomains/content-hash>multihashes>web-encoding": { @@ -492,7 +497,7 @@ "packages": { "@ngraveio/bc-ur": true, "browserify>buffer": true, - "ethereumjs-wallet>bs58check": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "mockttp>graphql-tag>tslib": true } }, @@ -500,9 +505,9 @@ "packages": { "browserify>assert": true, "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@keystonehq/metamask-airgapped-keyring": { @@ -1106,7 +1111,7 @@ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { @@ -1167,7 +1172,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey": { @@ -1176,7 +1181,7 @@ "@metamask/eth-trezor-keyring>hdkey>coinstring": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { @@ -1186,7 +1191,7 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-snap-keyring": { @@ -1537,7 +1542,7 @@ "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>coinstring": { @@ -1554,12 +1559,12 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/etherscan-link": { @@ -1734,11 +1739,11 @@ "@truffle/codec>utf8": true, "browserify>buffer": true, "browserify>crypto-browserify": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>scrypt-js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, + "mocha>serialize-javascript>randombytes": true, "uuid": true } }, @@ -3036,9 +3041,9 @@ "bn.js": true, "browserify>buffer": true, "ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, "ethjs>ethjs-unit": true, - "ethjs>number-to-bn": true + "ethjs>number-to-bn": true, + "mocha>serialize-javascript>randombytes": true } }, "@truffle/codec>web3-utils>ethereum-bloom-filters": { @@ -3097,7 +3102,7 @@ "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, "browserify>buffer": true, "eth-ens-namehash": true, - "ethereumjs-wallet>bs58check>bs58": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { @@ -3130,8 +3135,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { @@ -3155,8 +3160,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { @@ -3320,7 +3325,7 @@ }, "addons-linter>sha.js": { "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3410,7 +3415,7 @@ "browserify>crypto-browserify>public-encrypt": true, "browserify>crypto-browserify>randomfill": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>browserify-cipher": { @@ -3437,7 +3442,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>browserify-sign": { @@ -3466,7 +3471,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3475,7 +3480,7 @@ "bn.js": true, "browserify>buffer": true, "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>diffie-hellman>miller-rabin": { @@ -3497,7 +3502,7 @@ "browserify>process": true, "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>public-encrypt": { @@ -3507,14 +3512,14 @@ "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, "browserify>crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>browserify-rsa": { "packages": { "bn.js": true, "browserify>buffer": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>parse-asn1": { @@ -3542,8 +3547,8 @@ }, "packages": { "browserify>process": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "browserify>events": { @@ -3628,7 +3633,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>timers-browserify": { @@ -3913,6 +3918,7 @@ "browserify>buffer": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3922,9 +3928,8 @@ "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, "lodash": true } }, @@ -3971,6 +3976,11 @@ "crypto": true } }, + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -4097,7 +4107,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "eth-sig-util>ethereumjs-util>ethjs-util": { @@ -4169,21 +4179,21 @@ "packages": { "browserify>stream-browserify": true, "browserify>string_decoder": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -4210,12 +4220,12 @@ "browserify>assert": true, "browserify>buffer": true, "browserify>crypto-browserify>create-hmac": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "ethereumjs-util>ethereum-cryptography>keccak": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "ethereumjs-util>ethereum-cryptography>browserify-aes": { @@ -4224,7 +4234,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -4233,6 +4243,18 @@ "browserify>buffer": true } }, + "ethereumjs-util>ethereum-cryptography>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>hash.js": { "packages": { "@metamask/ppom-validator>elliptic>minimalistic-assert": true, @@ -4276,78 +4298,9 @@ "browserify>buffer": true } }, - "ethereumjs-wallet": { - "packages": { - "@truffle/codec>utf8": true, - "browserify>crypto-browserify": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true, - "ethereumjs-wallet>scryptsy": true, - "ethereumjs-wallet>uuid": true - } - }, - "ethereumjs-wallet>aes-js": { - "globals": { - "define": true - } - }, - "ethereumjs-wallet>bs58check": { - "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>bs58check>bs58": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>bs58check>bs58": { - "packages": { - "ethereumjs-wallet>bs58check>bs58>base-x": true - } - }, - "ethereumjs-wallet>bs58check>bs58>base-x": { - "packages": { - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true - } - }, "ethereumjs-wallet>randombytes": { "globals": { - "crypto": true, - "msCrypto": true - }, - "packages": { - "browserify>process": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "ethereumjs-wallet>scryptsy": { - "packages": { - "browserify>buffer": true, - "browserify>crypto-browserify>pbkdf2": true - } - }, - "ethereumjs-wallet>uuid": { - "globals": { - "crypto": true, - "msCrypto": true + "crypto.getRandomValues": true } }, "ethers>@ethersproject/random": { @@ -4546,6 +4499,11 @@ "readable-stream": true } }, + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -4624,6 +4582,16 @@ "readable-stream>util-deprecate": true } }, + "mocha>serialize-javascript>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "koa>content-disposition>safe-buffer": true + } + }, "mockttp>graphql-tag>tslib": { "globals": { "define": true diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 3207a448f..12cd90e66 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -107,9 +107,14 @@ }, "@ensdomains/content-hash>multihashes>multibase": { "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true, "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true } }, "@ensdomains/content-hash>multihashes>web-encoding": { @@ -492,7 +497,7 @@ "packages": { "@ngraveio/bc-ur": true, "browserify>buffer": true, - "ethereumjs-wallet>bs58check": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "mockttp>graphql-tag>tslib": true } }, @@ -500,9 +505,9 @@ "packages": { "browserify>assert": true, "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@keystonehq/metamask-airgapped-keyring": { @@ -1035,7 +1040,7 @@ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { @@ -1096,7 +1101,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey": { @@ -1105,7 +1110,7 @@ "@metamask/eth-trezor-keyring>hdkey>coinstring": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { @@ -1115,7 +1120,7 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { @@ -1409,7 +1414,7 @@ "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>coinstring": { @@ -1426,12 +1431,12 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/etherscan-link": { @@ -1583,11 +1588,11 @@ "@truffle/codec>utf8": true, "browserify>buffer": true, "browserify>crypto-browserify": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>scrypt-js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, + "mocha>serialize-javascript>randombytes": true, "uuid": true } }, @@ -2486,9 +2491,9 @@ "bn.js": true, "browserify>buffer": true, "ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, "ethjs>ethjs-unit": true, - "ethjs>number-to-bn": true + "ethjs>number-to-bn": true, + "mocha>serialize-javascript>randombytes": true } }, "@truffle/codec>web3-utils>ethereum-bloom-filters": { @@ -2547,7 +2552,7 @@ "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, "browserify>buffer": true, "eth-ens-namehash": true, - "ethereumjs-wallet>bs58check>bs58": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { @@ -2580,8 +2585,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { @@ -2605,8 +2610,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { @@ -2770,7 +2775,7 @@ }, "addons-linter>sha.js": { "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -2860,7 +2865,7 @@ "browserify>crypto-browserify>public-encrypt": true, "browserify>crypto-browserify>randomfill": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>browserify-cipher": { @@ -2887,7 +2892,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>browserify-sign": { @@ -2916,7 +2921,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -2925,7 +2930,7 @@ "bn.js": true, "browserify>buffer": true, "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>diffie-hellman>miller-rabin": { @@ -2947,7 +2952,7 @@ "browserify>process": true, "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>public-encrypt": { @@ -2957,14 +2962,14 @@ "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, "browserify>crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>browserify-rsa": { "packages": { "bn.js": true, "browserify>buffer": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>parse-asn1": { @@ -2992,8 +2997,8 @@ }, "packages": { "browserify>process": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "browserify>events": { @@ -3078,7 +3083,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>timers-browserify": { @@ -3347,6 +3352,7 @@ "browserify>buffer": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3356,9 +3362,8 @@ "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, "lodash": true } }, @@ -3405,6 +3410,11 @@ "crypto": true } }, + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3531,7 +3541,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "eth-sig-util>ethereumjs-util>ethjs-util": { @@ -3603,21 +3613,21 @@ "packages": { "browserify>stream-browserify": true, "browserify>string_decoder": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3644,12 +3654,12 @@ "browserify>assert": true, "browserify>buffer": true, "browserify>crypto-browserify>create-hmac": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "ethereumjs-util>ethereum-cryptography>keccak": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "ethereumjs-util>ethereum-cryptography>browserify-aes": { @@ -3658,7 +3668,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3667,6 +3677,18 @@ "browserify>buffer": true } }, + "ethereumjs-util>ethereum-cryptography>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>hash.js": { "packages": { "@metamask/ppom-validator>elliptic>minimalistic-assert": true, @@ -3710,78 +3732,9 @@ "browserify>buffer": true } }, - "ethereumjs-wallet": { - "packages": { - "@truffle/codec>utf8": true, - "browserify>crypto-browserify": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true, - "ethereumjs-wallet>scryptsy": true, - "ethereumjs-wallet>uuid": true - } - }, - "ethereumjs-wallet>aes-js": { - "globals": { - "define": true - } - }, - "ethereumjs-wallet>bs58check": { - "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>bs58check>bs58": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>bs58check>bs58": { - "packages": { - "ethereumjs-wallet>bs58check>bs58>base-x": true - } - }, - "ethereumjs-wallet>bs58check>bs58>base-x": { - "packages": { - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true - } - }, "ethereumjs-wallet>randombytes": { "globals": { - "crypto": true, - "msCrypto": true - }, - "packages": { - "browserify>process": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "ethereumjs-wallet>scryptsy": { - "packages": { - "browserify>buffer": true, - "browserify>crypto-browserify>pbkdf2": true - } - }, - "ethereumjs-wallet>uuid": { - "globals": { - "crypto": true, - "msCrypto": true + "crypto.getRandomValues": true } }, "ethers>@ethersproject/random": { @@ -3980,6 +3933,11 @@ "readable-stream": true } }, + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -4040,6 +3998,16 @@ "Intl": true } }, + "mocha>serialize-javascript>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "koa>content-disposition>safe-buffer": true + } + }, "mockttp>graphql-tag>tslib": { "globals": { "define": true diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index d8c191d6d..db6f4bba9 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -107,9 +107,14 @@ }, "@ensdomains/content-hash>multihashes>multibase": { "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true, "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true } }, "@ensdomains/content-hash>multihashes>web-encoding": { @@ -492,7 +497,7 @@ "packages": { "@ngraveio/bc-ur": true, "browserify>buffer": true, - "ethereumjs-wallet>bs58check": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "mockttp>graphql-tag>tslib": true } }, @@ -500,9 +505,9 @@ "packages": { "browserify>assert": true, "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@keystonehq/metamask-airgapped-keyring": { @@ -905,7 +910,7 @@ "browserify>process": true, "browserify>stream-browserify": true, "browserify>util": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask-institutional/sdk>jsonwebtoken>jws>jwa": { @@ -914,7 +919,7 @@ "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>ecdsa-sig-formatter": true, "browserify>crypto-browserify": true, "browserify>util": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>buffer-equal-constant-time": { @@ -924,7 +929,7 @@ }, "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>ecdsa-sig-formatter": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask-institutional/transaction-update": { @@ -1263,7 +1268,7 @@ "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { @@ -1324,7 +1329,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey": { @@ -1333,7 +1338,7 @@ "@metamask/eth-trezor-keyring>hdkey>coinstring": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { @@ -1343,7 +1348,7 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { @@ -1637,7 +1642,7 @@ "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, "browserify>assert": true, "browserify>crypto-browserify": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>coinstring": { @@ -1654,12 +1659,12 @@ "bn.js": true, "browserify>insert-module-globals>is-buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "@metamask/etherscan-link": { @@ -1811,11 +1816,11 @@ "@truffle/codec>utf8": true, "browserify>buffer": true, "browserify>crypto-browserify": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>scrypt-js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, + "mocha>serialize-javascript>randombytes": true, "uuid": true } }, @@ -2714,9 +2719,9 @@ "bn.js": true, "browserify>buffer": true, "ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, "ethjs>ethjs-unit": true, - "ethjs>number-to-bn": true + "ethjs>number-to-bn": true, + "mocha>serialize-javascript>randombytes": true } }, "@truffle/codec>web3-utils>ethereum-bloom-filters": { @@ -2775,7 +2780,7 @@ "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, "browserify>buffer": true, "eth-ens-namehash": true, - "ethereumjs-wallet>bs58check>bs58": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { @@ -2808,8 +2813,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { @@ -2833,8 +2838,8 @@ }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { "packages": { - "browserify>buffer": true, - "ethereumjs-wallet>bs58check>bs58>base-x": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true } }, "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { @@ -2998,7 +3003,7 @@ }, "addons-linter>sha.js": { "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3088,7 +3093,7 @@ "browserify>crypto-browserify>public-encrypt": true, "browserify>crypto-browserify>randomfill": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>browserify-cipher": { @@ -3115,7 +3120,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>browserify-sign": { @@ -3144,7 +3149,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3153,7 +3158,7 @@ "bn.js": true, "browserify>buffer": true, "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>diffie-hellman>miller-rabin": { @@ -3175,7 +3180,7 @@ "browserify>process": true, "ethereumjs-util>create-hash": true, "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>crypto-browserify>public-encrypt": { @@ -3185,14 +3190,14 @@ "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, "browserify>crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>browserify-rsa": { "packages": { "bn.js": true, "browserify>buffer": true, - "ethereumjs-wallet>randombytes": true + "mocha>serialize-javascript>randombytes": true } }, "browserify>crypto-browserify>public-encrypt>parse-asn1": { @@ -3220,8 +3225,8 @@ }, "packages": { "browserify>process": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "browserify>events": { @@ -3306,7 +3311,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>timers-browserify": { @@ -3575,6 +3580,7 @@ "browserify>buffer": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3584,9 +3590,8 @@ "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, "lodash": true } }, @@ -3633,6 +3638,11 @@ "crypto": true } }, + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3759,7 +3769,7 @@ "ethereumjs-util>create-hash": true, "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "eth-sig-util>ethereumjs-util>ethjs-util": { @@ -3831,21 +3841,21 @@ "packages": { "browserify>stream-browserify": true, "browserify>string_decoder": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3872,12 +3882,12 @@ "browserify>assert": true, "browserify>buffer": true, "browserify>crypto-browserify>create-hmac": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "ethereumjs-util>ethereum-cryptography>keccak": true, "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true, + "mocha>serialize-javascript>randombytes": true } }, "ethereumjs-util>ethereum-cryptography>browserify-aes": { @@ -3886,7 +3896,7 @@ "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, "ethereumjs-util>create-hash>cipher-base": true, "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "pumpify>inherits": true } }, @@ -3895,6 +3905,18 @@ "browserify>buffer": true } }, + "ethereumjs-util>ethereum-cryptography>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true + } + }, "ethereumjs-util>ethereum-cryptography>hash.js": { "packages": { "@metamask/ppom-validator>elliptic>minimalistic-assert": true, @@ -3938,78 +3960,9 @@ "browserify>buffer": true } }, - "ethereumjs-wallet": { - "packages": { - "@truffle/codec>utf8": true, - "browserify>crypto-browserify": true, - "ethereumjs-wallet>aes-js": true, - "ethereumjs-wallet>bs58check": true, - "ethereumjs-wallet>ethereumjs-util": true, - "ethereumjs-wallet>randombytes": true, - "ethereumjs-wallet>safe-buffer": true, - "ethereumjs-wallet>scryptsy": true, - "ethereumjs-wallet>uuid": true - } - }, - "ethereumjs-wallet>aes-js": { - "globals": { - "define": true - } - }, - "ethereumjs-wallet>bs58check": { - "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-wallet>bs58check>bs58": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>bs58check>bs58": { - "packages": { - "ethereumjs-wallet>bs58check>bs58>base-x": true - } - }, - "ethereumjs-wallet>bs58check>bs58>base-x": { - "packages": { - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethjs-util": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true - } - }, "ethereumjs-wallet>randombytes": { "globals": { - "crypto": true, - "msCrypto": true - }, - "packages": { - "browserify>process": true, - "ethereumjs-wallet>safe-buffer": true - } - }, - "ethereumjs-wallet>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "ethereumjs-wallet>scryptsy": { - "packages": { - "browserify>buffer": true, - "browserify>crypto-browserify>pbkdf2": true - } - }, - "ethereumjs-wallet>uuid": { - "globals": { - "crypto": true, - "msCrypto": true + "crypto.getRandomValues": true } }, "ethers>@ethersproject/random": { @@ -4208,6 +4161,11 @@ "readable-stream": true } }, + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -4268,6 +4226,16 @@ "Intl": true } }, + "mocha>serialize-javascript>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "koa>content-disposition>safe-buffer": true + } + }, "mockttp>graphql-tag>tslib": { "globals": { "define": true diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 4db67787c..4ab9521cf 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1730,7 +1730,7 @@ "@lavamoat/lavapack>umd": true, "browserify>JSONStream": true, "browserify>browser-pack>through2": true, - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "watchify>defined": true } }, @@ -1947,7 +1947,7 @@ }, "browserify>string_decoder": { "packages": { - "ethereumjs-wallet>safe-buffer": true + "koa>content-disposition>safe-buffer": true } }, "browserify>syntax-error": { @@ -3273,11 +3273,6 @@ "eslint>strip-ansi>ansi-regex": true } }, - "ethereumjs-wallet>safe-buffer": { - "builtin": { - "buffer": true - } - }, "fancy-log": { "builtin": { "console.Console": true @@ -6064,9 +6059,9 @@ }, "gulp>vinyl-fs>remove-bom-stream": { "packages": { - "ethereumjs-wallet>safe-buffer": true, "gulp>vinyl-fs>remove-bom-buffer": true, - "gulp>vinyl-fs>remove-bom-stream>through2": true + "gulp>vinyl-fs>remove-bom-stream>through2": true, + "koa>content-disposition>safe-buffer": true } }, "gulp>vinyl-fs>remove-bom-stream>through2": { @@ -6172,6 +6167,11 @@ "define": true } }, + "koa>content-disposition>safe-buffer": { + "builtin": { + "buffer": true + } + }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -8751,7 +8751,7 @@ "util.inherits": true }, "packages": { - "ethereumjs-wallet>safe-buffer": true, + "koa>content-disposition>safe-buffer": true, "readable-stream": true } }, diff --git a/package.json b/package.json index 88a03d3bd..7ab36645e 100644 --- a/package.json +++ b/package.json @@ -310,7 +310,6 @@ "ethereum-ens-network-map": "^1.0.2", "ethereumjs-abi": "^0.6.4", "ethereumjs-util": "^7.0.10", - "ethereumjs-wallet": "^0.6.4", "ethjs": "^0.4.0", "ethjs-contract": "^0.2.3", "ethjs-query": "^0.3.4", diff --git a/ui/components/multichain/import-account/import-account.js b/ui/components/multichain/import-account/import-account.js index 478a2e3ac..6b266425b 100644 --- a/ui/components/multichain/import-account/import-account.js +++ b/ui/components/multichain/import-account/import-account.js @@ -79,7 +79,7 @@ export const ImportAccount = ({ onActionComplete }) => { } function getLoadingMessage(strategy) { - if (strategy === 'JSON File') { + if (strategy === 'json') { return ( <> diff --git a/ui/components/multichain/import-account/json.js b/ui/components/multichain/import-account/json.js index 7dd9e0235..8ba553832 100644 --- a/ui/components/multichain/import-account/json.js +++ b/ui/components/multichain/import-account/json.js @@ -46,7 +46,7 @@ export default function JsonImportSubview({ if (isPrimaryDisabled) { displayWarning(t('needImportFile')); } else { - importAccountFunc('JSON File', [fileContents, password]); + importAccountFunc('json', [fileContents, password]); } } diff --git a/ui/components/multichain/import-account/json.test.tsx b/ui/components/multichain/import-account/json.test.tsx index 26795e830..5798b68f3 100644 --- a/ui/components/multichain/import-account/json.test.tsx +++ b/ui/components/multichain/import-account/json.test.tsx @@ -63,7 +63,7 @@ describe('Json', () => { fireEvent.click(importButton); await waitFor(() => { - expect(mockImportFunc).toHaveBeenCalledWith('JSON File', ['0', '']); + expect(mockImportFunc).toHaveBeenCalledWith('json', ['0', '']); }); }); @@ -102,10 +102,7 @@ describe('Json', () => { fireEvent.click(importButton); await waitFor(() => { - expect(mockImportFunc).toHaveBeenCalledWith('JSON File', [ - '0', - 'password', - ]); + expect(mockImportFunc).toHaveBeenCalledWith('json', ['0', 'password']); }); }); }); diff --git a/ui/components/multichain/import-account/private-key.js b/ui/components/multichain/import-account/private-key.js index 958b4ca87..ba0640d2e 100644 --- a/ui/components/multichain/import-account/private-key.js +++ b/ui/components/multichain/import-account/private-key.js @@ -31,7 +31,7 @@ export default function PrivateKeyImportView({ } function _importAccountFunc() { - importAccountFunc('Private Key', [privateKey]); + importAccountFunc('privateKey', [privateKey]); } return ( diff --git a/yarn.lock b/yarn.lock index 8b6b0fd81..417ab089b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16278,23 +16278,6 @@ __metadata: languageName: node linkType: hard -"ethereumjs-wallet@npm:^0.6.4": - version: 0.6.5 - resolution: "ethereumjs-wallet@npm:0.6.5" - dependencies: - aes-js: "npm:^3.1.1" - bs58check: "npm:^2.1.2" - ethereum-cryptography: "npm:^0.1.3" - ethereumjs-util: "npm:^6.0.0" - randombytes: "npm:^2.0.6" - safe-buffer: "npm:^5.1.2" - scryptsy: "npm:^1.2.1" - utf8: "npm:^3.0.0" - uuid: "npm:^3.3.2" - checksum: 011d4205dfbf4c405b02fe77b7d4924ed39ab3124bc2d982cc436fd052ccf7392a1a408c704bf159d7fc5b000dfce254b313f7d5a224e6e950f2444de4904953 - languageName: node - linkType: hard - "ethereumjs-wallet@npm:^1.0.1": version: 1.0.2 resolution: "ethereumjs-wallet@npm:1.0.2" @@ -24419,7 +24402,6 @@ __metadata: ethereum-ens-network-map: "npm:^1.0.2" ethereumjs-abi: "npm:^0.6.4" ethereumjs-util: "npm:^7.0.10" - ethereumjs-wallet: "npm:^0.6.4" ethjs: "npm:^0.4.0" ethjs-contract: "npm:^0.2.3" ethjs-query: "npm:^0.3.4" @@ -28642,7 +28624,7 @@ __metadata: languageName: node linkType: hard -"randombytes@npm:2.1.0, randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.0.6, randombytes@npm:^2.1.0": +"randombytes@npm:2.1.0, randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" dependencies: @@ -30688,15 +30670,6 @@ __metadata: languageName: node linkType: hard -"scryptsy@npm:^1.2.1": - version: 1.2.1 - resolution: "scryptsy@npm:1.2.1" - dependencies: - pbkdf2: "npm:^3.0.3" - checksum: ec053305f1da3067269b36ac59f4a76aeb0b3dad1b9dd1b5438a60c9c93da8d09e3886a4dd2395450441df401774c41192c73700931ba38c0f77c000a384a434 - languageName: node - linkType: hard - "scss-parser@npm:^1.0.4": version: 1.0.5 resolution: "scss-parser@npm:1.0.5" @@ -34276,7 +34249,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^3.3.2, uuid@npm:^3.3.3": +"uuid@npm:^3.3.3": version: 3.4.0 resolution: "uuid@npm:3.4.0" bin: From 6b72316eb68485ff4548275668646ea667c586ac Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Thu, 17 Aug 2023 09:36:30 +0200 Subject: [PATCH 032/102] Fix infinite rerender on network change while active signature request (#20473) --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 58a81e47f..8daf37320 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1498,6 +1498,7 @@ export default class MetamaskController extends EventEmitter { this.encryptionPublicKeyController.clearUnapproved(); this.decryptMessageController.clearUnapproved(); this.signatureController.clearUnapproved(); + this.approvalController.clear(); }, ); From 915bf2ae88d8ee904a92669a1ceb162c07fc1cac Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 16 Aug 2023 16:56:20 -0230 Subject: [PATCH 033/102] Capture exception with sentry when invariant conditions are met in migrations (#20427) * capture exception for sentry when invariant conditions are met in migration 82 * Code cleanup * Capture exceptions in invariant conditions for migrations 83,84,85,86,89,91,93,94 * Update app/scripts/migrations/082.test.js Co-authored-by: Mark Stacey * Code cleanup * Fix SentryObject type declaration * Stop throwing error if preferences controller is undefined * Refactor 084 and 086 to remove double negative * Capture exceptions for invariant states in in migrations 87,88,90 and 92 * lint fix * log warning in migration 82 when preferences controller is undefined --------- Co-authored-by: Mark Stacey --- app/scripts/migrations/082.test.js | 178 +++++++++++++++++++- app/scripts/migrations/082.ts | 55 +++++- app/scripts/migrations/083.test.js | 65 +++++++ app/scripts/migrations/083.ts | 10 ++ app/scripts/migrations/084.test.js | 72 ++++++++ app/scripts/migrations/084.ts | 21 ++- app/scripts/migrations/085.test.js | 29 ++++ app/scripts/migrations/085.ts | 5 + app/scripts/migrations/086.test.js | 75 +++++++++ app/scripts/migrations/086.ts | 19 +++ app/scripts/migrations/087.test.js | 69 ++++++++ app/scripts/migrations/087.ts | 5 + app/scripts/migrations/088.test.ts | 262 +++++++++++++++++++++++++++++ app/scripts/migrations/088.ts | 80 +++++++++ app/scripts/migrations/089.test.ts | 58 +++++++ app/scripts/migrations/089.ts | 13 ++ app/scripts/migrations/090.test.js | 19 ++- app/scripts/migrations/090.ts | 22 ++- app/scripts/migrations/091.test.ts | 58 +++++++ app/scripts/migrations/091.ts | 13 ++ app/scripts/migrations/092.test.ts | 29 ++++ app/scripts/migrations/092.ts | 9 + types/global.d.ts | 4 + 23 files changed, 1153 insertions(+), 17 deletions(-) diff --git a/app/scripts/migrations/082.test.js b/app/scripts/migrations/082.test.js index 6f221c5c6..0060c853c 100644 --- a/app/scripts/migrations/082.test.js +++ b/app/scripts/migrations/082.test.js @@ -1,6 +1,12 @@ import { v4 } from 'uuid'; import { migrate, version } from './082'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -472,10 +478,72 @@ describe('migration #82', () => { }, }; const newStorage = await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalled(); expect(newStorage.data).toStrictEqual(oldStorage.data); }); - it('should not change anything if there is no frequentRpcListDetail property on PreferencesController', async () => { + it('should capture an exception if any PreferencesController.frequentRpcListDetail entries are not objects', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: { + transactionSecurityCheckEnabled: false, + useBlockie: false, + useCurrencyRateCheck: true, + useMultiAccountBalanceChecker: true, + useNftDetection: false, + useNonceField: false, + frequentRpcListDetail: [ + { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcPrefs: {}, + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + }, + 'invalid entry type', + 1, + ], + }, + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `state.PreferencesController.frequentRpcListDetail contains an element of type string`, + ), + ); + }); + + it('should not change anything, and not capture an exception, if there is no frequentRpcListDetail property on PreferencesController but there is a networkConfigurations object', async () => { const oldStorage = { meta: { version: 81, @@ -556,9 +624,60 @@ describe('migration #82', () => { }, }; const newStorage = await migrate(oldStorage); + expect(sentryCaptureExceptionMock).not.toHaveBeenCalled(); expect(newStorage.data).toStrictEqual(oldStorage.data); }); + it('should capture an exception if there is no frequentRpcListDetail property on PreferencesController and no networkConfiguration object', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: { + transactionSecurityCheckEnabled: false, + useBlockie: false, + useCurrencyRateCheck: true, + useMultiAccountBalanceChecker: true, + useNftDetection: false, + useNonceField: false, + }, + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.PreferencesController.frequentRpcListDetail is undefined`, + ), + ); + }); + it('should change nothing if PreferencesController is undefined', async () => { const oldStorage = { meta: { @@ -595,4 +714,61 @@ describe('migration #82', () => { const newStorage = await migrate(oldStorage); expect(newStorage.data).toStrictEqual(oldStorage.data); }); + + it('should capture an exception if PreferencesController is not an object', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + NetworkController: { + network: '1', + networkDetails: { + EIPS: { + 1559: true, + }, + }, + previousProviderStore: { + chainId: '0x89', + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748', + ticker: 'MATIC', + type: 'rpc', + }, + provider: { + chainId: '0x1', + nickname: '', + rpcPrefs: {}, + rpcUrl: '', + ticker: 'ETH', + type: 'mainnet', + }, + }, + PreferencesController: false, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PreferencesController is boolean`), + ); + }); + + it('should capture an exception if NetworkController is undefined', async () => { + const oldStorage = { + meta: { + version: 81, + }, + data: { + PreferencesController: {}, + }, + }; + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); }); diff --git a/app/scripts/migrations/082.ts b/app/scripts/migrations/082.ts index fdda1fb8d..29ff59eea 100644 --- a/app/scripts/migrations/082.ts +++ b/app/scripts/migrations/082.ts @@ -1,6 +1,7 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; import { v4 } from 'uuid'; +import log from 'loglevel'; export const version = 82; @@ -25,14 +26,56 @@ export async function migrate(originalVersionedData: { } function transformState(state: Record) { + if (!hasProperty(state, 'PreferencesController')) { + log.warn(`state.PreferencesController is undefined`); + return state; + } + if (!isObject(state.PreferencesController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.PreferencesController is ${typeof state.PreferencesController}`, + ), + ); + return state; + } if ( - !hasProperty(state, 'PreferencesController') || - !isObject(state.PreferencesController) || - !isObject(state.NetworkController) || - !hasProperty(state.PreferencesController, 'frequentRpcListDetail') || - !Array.isArray(state.PreferencesController.frequentRpcListDetail) || - !state.PreferencesController.frequentRpcListDetail.every(isObject) + !hasProperty(state, 'NetworkController') || + !isObject(state.NetworkController) ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + return state; + } + if ( + !hasProperty(state.PreferencesController, 'frequentRpcListDetail') || + !Array.isArray(state.PreferencesController.frequentRpcListDetail) + ) { + const inPost077SupplementFor082State = + state.NetworkController.networkConfigurations && + state.PreferencesController.frequentRpcListDetail === undefined; + if (!inPost077SupplementFor082State) { + global.sentry?.captureException?.( + new Error( + `typeof state.PreferencesController.frequentRpcListDetail is ${typeof state + .PreferencesController.frequentRpcListDetail}`, + ), + ); + } + return state; + } + if (!state.PreferencesController.frequentRpcListDetail.every(isObject)) { + const erroneousElement = + state.PreferencesController.frequentRpcListDetail.find( + (element) => !isObject(element), + ); + global.sentry?.captureException?.( + new Error( + `state.PreferencesController.frequentRpcListDetail contains an element of type ${typeof erroneousElement}`, + ), + ); return state; } const { PreferencesController, NetworkController } = state; diff --git a/app/scripts/migrations/083.test.js b/app/scripts/migrations/083.test.js index c59951aef..b8f91dfcd 100644 --- a/app/scripts/migrations/083.test.js +++ b/app/scripts/migrations/083.test.js @@ -1,6 +1,12 @@ import { v4 } from 'uuid'; import { migrate, version } from './083'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -165,6 +171,24 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController is undefined', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should not modify state if state.NetworkController is not an object', async () => { const oldStorage = { meta: { @@ -190,6 +214,25 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController is not an object', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + NetworkController: false, + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is boolean`), + ); + }); + it('should not modify state if state.NetworkController.networkConfigurations is undefined', async () => { const oldStorage = { meta: { @@ -221,6 +264,28 @@ describe('migration #83', () => { expect(newStorage).toStrictEqual(expectedNewStorage); }); + it('should capture an exception if state.NetworkController.networkConfigurations is undefined', async () => { + const oldStorage = { + meta: { + version, + }, + data: { + NetworkController: { + testNetworkControllerProperty: 'testNetworkControllerValue', + networkConfigurations: undefined, + }, + testProperty: 'testValue', + }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof NetworkController.networkConfigurations is undefined`), + ); + }); + it('should not modify state if state.NetworkController.networkConfigurations is an empty object', async () => { const oldStorage = { meta: { diff --git a/app/scripts/migrations/083.ts b/app/scripts/migrations/083.ts index cc3e3b16b..bd7ba62e4 100644 --- a/app/scripts/migrations/083.ts +++ b/app/scripts/migrations/083.ts @@ -25,11 +25,21 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); return state; } const { NetworkController } = state; if (!isObject(NetworkController.networkConfigurations)) { + global.sentry?.captureException?.( + new Error( + `typeof NetworkController.networkConfigurations is ${typeof NetworkController.networkConfigurations}`, + ), + ); return state; } diff --git a/app/scripts/migrations/084.test.js b/app/scripts/migrations/084.test.js index 138bfacb6..bd0e1b35c 100644 --- a/app/scripts/migrations/084.test.js +++ b/app/scripts/migrations/084.test.js @@ -1,6 +1,16 @@ import { migrate } from './084'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #84', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const originalVersionedData = buildOriginalVersionedData({ meta: { @@ -27,6 +37,21 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + it('captures an exception if the network controller state does not exist', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + test: '123', + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + const nonObjects = [undefined, null, 'test', 1, ['test']]; for (const invalidState of nonObjects) { it(`does not change the state if the network controller state is ${invalidState}`, async () => { @@ -40,6 +65,21 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + + it(`captures an exception if the network controller state is ${invalidState}`, async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: invalidState, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is ${typeof invalidState}`), + ); + }); } it('does not change the state if the network controller state does not include "network"', async () => { @@ -56,6 +96,38 @@ describe('migration #84', () => { expect(newVersionedData.data).toStrictEqual(originalVersionedData.data); }); + it('captures an exception if the network controller state does not include "network" and does not include "networkId"', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: { + test: '123', + }, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.network is undefined`), + ); + }); + + it('does not capture an exception if the network controller state does not include "network" but does include "networkId"', async () => { + const originalVersionedData = buildOriginalVersionedData({ + data: { + NetworkController: { + test: '123', + networkId: 'foobar', + }, + }, + }); + + await migrate(originalVersionedData); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('replaces "network" in the network controller state with "networkId": null, "networkStatus": "unknown" if it is "loading"', async () => { const originalVersionedData = buildOriginalVersionedData({ data: { diff --git a/app/scripts/migrations/084.ts b/app/scripts/migrations/084.ts index 66a2f45ae..7551ed2c6 100644 --- a/app/scripts/migrations/084.ts +++ b/app/scripts/migrations/084.ts @@ -25,9 +25,26 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if ( !hasProperty(state, 'NetworkController') || - !isObject(state.NetworkController) || - !hasProperty(state.NetworkController, 'network') + !isObject(state.NetworkController) ) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + return state; + } + if (!hasProperty(state.NetworkController, 'network')) { + const thePost077SupplementFor084HasNotModifiedState = + state.NetworkController.networkId === undefined; + if (thePost077SupplementFor084HasNotModifiedState) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.network is ${typeof state + .NetworkController.network}`, + ), + ); + } return state; } diff --git a/app/scripts/migrations/085.test.js b/app/scripts/migrations/085.test.js index 6b7b4967d..cfe7ff1b0 100644 --- a/app/scripts/migrations/085.test.js +++ b/app/scripts/migrations/085.test.js @@ -1,5 +1,11 @@ import { migrate, version } from './085'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +16,10 @@ jest.mock('uuid', () => { }); describe('migration #85', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +49,25 @@ describe('migration #85', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 84, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller previous provider state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/085.ts b/app/scripts/migrations/085.ts index 03499d2b2..2ba22a346 100644 --- a/app/scripts/migrations/085.ts +++ b/app/scripts/migrations/085.ts @@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); return state; } diff --git a/app/scripts/migrations/086.test.js b/app/scripts/migrations/086.test.js index f38f0444d..cd6a61377 100644 --- a/app/scripts/migrations/086.test.js +++ b/app/scripts/migrations/086.test.js @@ -1,5 +1,11 @@ import { migrate, version } from './086'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +16,10 @@ jest.mock('uuid', () => { }); describe('migration #86', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +49,25 @@ describe('migration #86', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller provider state', async () => { const oldData = { other: 'data', @@ -59,6 +88,52 @@ describe('migration #86', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller provider state and no providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + foo: 'bar', + }, + }, + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.provider is undefined`), + ); + }); + + it('should not capture an exception if there is no network controller provider state but there is a providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + foo: 'bar', + }, + providerConfig: {}, + }, + }; + const oldStorage = { + meta: { + version: 85, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('should rename the provider config state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/086.ts b/app/scripts/migrations/086.ts index c4b60e270..709934f50 100644 --- a/app/scripts/migrations/086.ts +++ b/app/scripts/migrations/086.ts @@ -37,5 +37,24 @@ function transformState(state: Record) { NetworkController: networkControllerState, }; } + if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!hasProperty(state.NetworkController, 'provider')) { + const thePost077SupplementFor086HasNotModifiedState = + state.NetworkController.providerConfig === undefined; + if (thePost077SupplementFor086HasNotModifiedState) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.provider is ${typeof state + .NetworkController.provider}`, + ), + ); + } + } + return state; } diff --git a/app/scripts/migrations/087.test.js b/app/scripts/migrations/087.test.js index 1d631e926..ebd9495ad 100644 --- a/app/scripts/migrations/087.test.js +++ b/app/scripts/migrations/087.test.js @@ -1,6 +1,16 @@ import { migrate, version } from './087'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #87', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -53,6 +63,65 @@ describe('migration #87', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should return state unaltered if TokensController state is not an object', async () => { + const oldData = { + other: 'data', + TokensController: false, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should capture an exception if TokensController state is not an object', async () => { + const oldData = { + other: 'data', + TokensController: false, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is boolean`), + ); + }); + + it('should not capture an exception if TokensController state is an object', async () => { + const oldData = { + other: 'data', + TokensController: { + allDetectedTokens: {}, + allIgnoredTokens: {}, + allTokens: {}, + detectedTokens: [], + ignoredTokens: [], + suggestedAssets: [], + tokens: [], + }, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0); + }); + it('should remove the suggested assets state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/087.ts b/app/scripts/migrations/087.ts index 92093f967..f900faab6 100644 --- a/app/scripts/migrations/087.ts +++ b/app/scripts/migrations/087.ts @@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: { function transformState(state: Record) { if (!isObject(state.TokensController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController is ${typeof state.TokensController}`, + ), + ); return state; } diff --git a/app/scripts/migrations/088.test.ts b/app/scripts/migrations/088.test.ts index a33c30eff..ca672f982 100644 --- a/app/scripts/migrations/088.test.ts +++ b/app/scripts/migrations/088.test.ts @@ -1,6 +1,16 @@ import { migrate } from './088'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #88', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const oldStorage = { meta: { version: 87 }, @@ -26,6 +36,24 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the NftController property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController is boolean`), + ); + }); + it('returns the state unaltered if the NftController object has no allNftContracts property', async () => { const oldData = { NftController: { @@ -58,6 +86,26 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it NftController.allNftContracts is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: { + allNftContracts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController.allNftContracts is string`), + ); + }); + it('returns the state unaltered if any value of the NftController.allNftContracts object is not an object itself', async () => { const oldData = { NftController: { @@ -324,6 +372,26 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it NftController.allNfts is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: {}, + NftController: { + allNfts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NftController.allNfts is string`), + ); + }); + it('returns the state unaltered if any value of the NftController.allNfts object is not an object itself', async () => { const oldData = { NftController: { @@ -656,6 +724,91 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it has no TokenListController property', async () => { + const oldData = { + TokensController: {}, + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + }, + }, + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + }, + }, + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is undefined`), + ); + }); + + it('captures an exception if the TokenListController property is not an object', async () => { + const oldData = { + TokensController: {}, + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + }, + }, + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + }, + }, + }, + TokenListController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is boolean`), + ); + }); + it('returns the state unaltered if the TokenListController object has no tokensChainsCache property', async () => { const oldData = { TokenListController: { @@ -688,6 +841,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokenListController.tokensChainsCache property is not an object', async () => { + const oldData = { + TokenListController: { + tokensChainsCache: 'foo', + }, + TokensController: {}, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController.tokensChainsCache is string`), + ); + }); + it('rewrites TokenListController.tokensChainsCache so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -919,6 +1091,39 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if it has no TokensController property', async () => { + const oldData = { + TokenListController: {}, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is undefined`), + ); + }); + + it('captures an exception if the TokensController property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: false, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController is boolean`), + ); + }); + it('returns the state unaltered if the TokensController object has no allTokens property', async () => { const oldData = { TokensController: { @@ -951,6 +1156,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allTokens is string`), + ); + }); + it('rewrites TokensController.allTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -1163,6 +1387,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allIgnoredTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allIgnoredTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allIgnoredTokens is string`), + ); + }); + it('rewrites TokensController.allIgnoredTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, @@ -1323,6 +1566,25 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the TokensController.allDetectedTokens property is not an object', async () => { + const oldData = { + TokenListController: {}, + TokensController: { + allDetectedTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + await migrate(oldStorage); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokensController.allDetectedTokens is string`), + ); + }); + it('rewrites TokensController.allDetectedTokens so that decimal chain IDs are converted to hex strings', async () => { const oldStorage = { meta: { version: 87 }, diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts index a4b874b2f..5ede1b0fa 100644 --- a/app/scripts/migrations/088.ts +++ b/app/scripts/migrations/088.ts @@ -1,6 +1,7 @@ import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils'; import { BN } from 'ethereumjs-util'; import { cloneDeep, mapKeys } from 'lodash'; +import log from 'loglevel'; type VersionedData = { meta: { version: number }; @@ -70,6 +71,16 @@ function migrateData(state: Record): void { } }); } + } else if (hasProperty(nftControllerState, 'allNftContracts')) { + global.sentry?.captureException?.( + new Error( + `typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`, + ), + ); + } else { + log.warn( + `typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`, + ); } // Migrate NftController.allNfts @@ -96,9 +107,25 @@ function migrateData(state: Record): void { } }); } + } else if (hasProperty(nftControllerState, 'allNfts')) { + global.sentry?.captureException?.( + new Error( + `typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`, + ), + ); + } else { + log.warn( + `typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`, + ); } state.NftController = nftControllerState; + } else if (hasProperty(state, 'NftController')) { + global.sentry?.captureException?.( + new Error(`typeof state.NftController is ${typeof state.NftController}`), + ); + } else { + log.warn(`typeof state.NftController is undefined`); } if ( @@ -124,7 +151,24 @@ function migrateData(state: Record): void { tokenListControllerState.tokensChainsCache, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokenListControllerState, 'tokensChainsCache')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController.tokensChainsCache is ${typeof state + .TokenListController.tokensChainsCache}`, + ), + ); + } else { + log.warn( + `typeof state.TokenListController.tokensChainsCache is undefined`, + ); } + } else { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); } if ( @@ -150,6 +194,16 @@ function migrateData(state: Record): void { allTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`, + ); } // Migrate TokensController.allIgnoredTokens @@ -169,6 +223,16 @@ function migrateData(state: Record): void { allIgnoredTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allIgnoredTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`, + ); } // Migrate TokensController.allDetectedTokens @@ -188,9 +252,25 @@ function migrateData(state: Record): void { allDetectedTokens, (_, chainId: string) => toHex(chainId), ); + } else if (hasProperty(tokensControllerState, 'allDetectedTokens')) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`, + ), + ); + } else { + log.warn( + `typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`, + ); } state.TokensController = tokensControllerState; + } else { + global.sentry?.captureException?.( + new Error( + `typeof state.TokensController is ${typeof state.TokensController}`, + ), + ); } } diff --git a/app/scripts/migrations/089.test.ts b/app/scripts/migrations/089.test.ts index 00868ff74..91511d315 100644 --- a/app/scripts/migrations/089.test.ts +++ b/app/scripts/migrations/089.test.ts @@ -1,5 +1,14 @@ import { migrate, version } from './089'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -10,6 +19,10 @@ jest.mock('uuid', () => { }); describe('migration #89', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -39,6 +52,25 @@ describe('migration #89', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 88, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller providerConfig state', async () => { const oldData = { other: 'data', @@ -61,6 +93,32 @@ describe('migration #89', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller providerConfig state', async () => { + const oldData = { + other: 'data', + NetworkController: { + networkConfigurations: { + id1: { + foo: 'bar', + }, + }, + }, + }; + const oldStorage = { + meta: { + version: 88, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController.providerConfig is undefined`), + ); + }); + it('should return state unaltered if the providerConfig already has an id', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/089.ts b/app/scripts/migrations/089.ts index cc1bfa4dc..d4faebc22 100644 --- a/app/scripts/migrations/089.ts +++ b/app/scripts/migrations/089.ts @@ -66,6 +66,19 @@ function transformState(state: Record) { ...state, NetworkController: state.NetworkController, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!isObject(state.NetworkController.providerConfig)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.providerConfig is ${typeof state + .NetworkController.providerConfig}`, + ), + ); } return state; } diff --git a/app/scripts/migrations/090.test.js b/app/scripts/migrations/090.test.js index 6a28c60f2..13e39e648 100644 --- a/app/scripts/migrations/090.test.js +++ b/app/scripts/migrations/090.test.js @@ -2,7 +2,17 @@ import { migrate, version } from './090'; const PREVIOUS_VERSION = version - 1; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + describe('migration #90', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('updates the version metadata', async () => { const oldStorage = { meta: { @@ -31,7 +41,7 @@ describe('migration #90', () => { expect(newStorage.data).toStrictEqual(oldStorage.data); }); - it('does not change the state if the phishing controller state is invalid', async () => { + it('captures an exception if the phishing controller state is invalid', async () => { const oldStorage = { meta: { version: PREVIOUS_VERSION, @@ -39,9 +49,12 @@ describe('migration #90', () => { data: { PhishingController: 'this is not valid' }, }; - const newStorage = await migrate(oldStorage); + await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(oldStorage.data); + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PhishingController is string`), + ); }); it('does not change the state if the listState property does not exist', async () => { diff --git a/app/scripts/migrations/090.ts b/app/scripts/migrations/090.ts index e45ec05e4..a2d50cab6 100644 --- a/app/scripts/migrations/090.ts +++ b/app/scripts/migrations/090.ts @@ -1,5 +1,6 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; export const version = 90; @@ -23,11 +24,22 @@ export async function migrate(originalVersionedData: { } function transformState(state: Record) { - if ( - !hasProperty(state, 'PhishingController') || - !isObject(state.PhishingController) || - !hasProperty(state.PhishingController, 'listState') - ) { + if (!hasProperty(state, 'PhishingController')) { + log.warn(`typeof state.PhishingController is undefined`); + return state; + } + if (!isObject(state.PhishingController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.PhishingController is ${typeof state.PhishingController}`, + ), + ); + return state; + } + if (!hasProperty(state.PhishingController, 'listState')) { + log.warn( + `typeof state.PhishingController.listState is ${typeof state.PhishingController}`, + ); return state; } diff --git a/app/scripts/migrations/091.test.ts b/app/scripts/migrations/091.test.ts index d4836f003..6a1f14ed7 100644 --- a/app/scripts/migrations/091.test.ts +++ b/app/scripts/migrations/091.test.ts @@ -1,6 +1,15 @@ import { cloneDeep } from 'lodash'; import { migrate, version } from './091'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + jest.mock('uuid', () => { const actual = jest.requireActual('uuid'); @@ -11,6 +20,10 @@ jest.mock('uuid', () => { }); describe('migration #91', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -40,6 +53,25 @@ describe('migration #91', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 90, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + it('should return state unaltered if there is no network controller networkConfigurations state', async () => { const oldData = { other: 'data', @@ -60,6 +92,32 @@ describe('migration #91', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('should capture an exception if there is no network controller networkConfigurations state', async () => { + const oldData = { + other: 'data', + NetworkController: { + providerConfig: { + foo: 'bar', + }, + }, + }; + const oldStorage = { + meta: { + version: 90, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `typeof state.NetworkController.networkConfigurations is undefined`, + ), + ); + }); + it('should return state unaltered if the networkConfigurations all have a chainId', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/091.ts b/app/scripts/migrations/091.ts index c0661746a..874f6ed9f 100644 --- a/app/scripts/migrations/091.ts +++ b/app/scripts/migrations/091.ts @@ -50,6 +50,19 @@ function transformState(state: Record) { ...state, NetworkController: state.NetworkController, }; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } else if (!isObject(state.NetworkController.networkConfigurations)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController.networkConfigurations is ${typeof state + .NetworkController.networkConfigurations}`, + ), + ); } return state; } diff --git a/app/scripts/migrations/092.test.ts b/app/scripts/migrations/092.test.ts index b44c04602..b7337c871 100644 --- a/app/scripts/migrations/092.test.ts +++ b/app/scripts/migrations/092.test.ts @@ -3,7 +3,20 @@ import { migrate, version } from './092'; const PREVIOUS_VERSION = version - 1; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #92', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('should update the version metadata', async () => { const oldStorage = { meta: { @@ -33,6 +46,22 @@ describe('migration #92', () => { expect(newStorage.data).toStrictEqual(oldData); }); + it('captures an exception if the phishing controller state is invalid', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: { PhishingController: 'this is not valid' }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.PhishingController is string`), + ); + }); + it('should return state unaltered if there is no phishing controller last fetched state', async () => { const oldData = { other: 'data', diff --git a/app/scripts/migrations/092.ts b/app/scripts/migrations/092.ts index bf5469614..44ef2a6c7 100644 --- a/app/scripts/migrations/092.ts +++ b/app/scripts/migrations/092.ts @@ -1,5 +1,6 @@ import { cloneDeep } from 'lodash'; import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; export const version = 92; @@ -30,6 +31,14 @@ function transformState(state: Record) { ) { delete state.PhishingController.stalelistLastFetched; delete state.PhishingController.hotlistLastFetched; + } else if (hasProperty(state, 'PhishingController')) { + global.sentry?.captureException?.( + new Error( + `typeof state.PhishingController is ${typeof state.PhishingController}`, + ), + ); + } else { + log.warn(`typeof state.PhishingController is undefined`); } return state; } diff --git a/types/global.d.ts b/types/global.d.ts index f1e8c1b18..a288e0f77 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,6 +1,7 @@ // In order for variables to be considered on the global scope they must be // declared using var and not const or let, which is why this rule is disabled /* eslint-disable no-var */ +import * as Sentry from '@sentry/browser'; declare class Platform { openTab: (opts: { url: string }) => void; @@ -11,6 +12,9 @@ declare class Platform { export declare global { var platform: Platform; + // Sentry is undefined in dev, so use optional chaining + var sentry: Sentry | undefined; + namespace jest { interface Matchers { toBeFulfilled(): Promise; From dc7ebe979eaea281c23f47f85b9b49e1edcaac61 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 19:45:31 -0230 Subject: [PATCH 034/102] Add additional validation for persisted state metadata (#20462) Additional validation has been added for persisted state metadata. Beforehand we just checked that the state itself wasn't falsy. Now we ensure that the metadata is an object with a valid version as well. --- app/scripts/background.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/scripts/background.js b/app/scripts/background.js index c368728d6..f4e46c9f0 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -13,6 +13,7 @@ import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; +import { isObject } from '@metamask/utils'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { ApprovalType } from '@metamask/controller-utils'; ///: END:ONLY_INCLUDE_IN @@ -411,6 +412,19 @@ export async function loadStateFromPersistence() { versionedData = await migrator.migrateData(versionedData); if (!versionedData) { throw new Error('MetaMask - migrator returned undefined'); + } else if (!isObject(versionedData.meta)) { + throw new Error( + `MetaMask - migrator metadata has invalid type '${typeof versionedData.meta}'`, + ); + } else if (typeof versionedData.meta.version !== 'number') { + throw new Error( + `MetaMask - migrator metadata version has invalid type '${typeof versionedData + .meta.version}'`, + ); + } else if (!isObject(versionedData.data)) { + throw new Error( + `MetaMask - migrator data has invalid type '${typeof versionedData.data}'`, + ); } // this initializes the meta/version data as a class variable to be used for future writes localStore.setMetadata(versionedData.meta); From 805ce29e11a096e7adb755b05498a221dc2a9f2e Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 21:11:17 -0230 Subject: [PATCH 035/102] Add types of hidden properties to Sentry data (#20457) * Add types of hidden properties to Sentry data The masked wallet state object sent to Sentry has been updated to include the type of each property omitted from the mask. This lets us at least see the full state shape, making it easier to see when errors are caused by invalid state. Relates to #20449 * Remove inconsistent state snapshot properties The state snapshot tests have been updated to exclude properties that were shown to differ between runs. --- shared/modules/object.utils.js | 4 + test/e2e/tests/errors.spec.js | 60 +++++++--- ...rs-after-init-opt-in-background-state.json | 63 ++++++++-- .../errors-after-init-opt-in-ui-state.json | 110 +++++++++++++++++- 4 files changed, 211 insertions(+), 26 deletions(-) diff --git a/shared/modules/object.utils.js b/shared/modules/object.utils.js index 24119be56..ce7eb1da1 100644 --- a/shared/modules/object.utils.js +++ b/shared/modules/object.utils.js @@ -7,6 +7,8 @@ * should be included, and a sub-mask implies the property should be further * masked according to that sub-mask. * + * If a property is not found in the last, its type is included instead. + * * @param {object} object - The object to mask * @param {Object} mask - The mask to apply to the object */ @@ -16,6 +18,8 @@ export function maskObject(object, mask) { state[key] = object[key]; } else if (mask[key]) { state[key] = maskObject(object[key], mask[key]); + } else { + state[key] = typeof object[key]; } return state; }, {}); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 8784ef168..3098a5083 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -1,42 +1,72 @@ const { resolve } = require('path'); const { promises: fs } = require('fs'); const { strict: assert } = require('assert'); -const { get, has, set } = require('lodash'); +const { get, has, set, unset } = require('lodash'); const { Browser } = require('selenium-webdriver'); const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const backgroundDateFields = ['CurrencyController.conversionDate']; -const uiDateFields = ['metamask.conversionDate']; +const maskedBackgroundFields = [ + 'CurrencyController.conversionDate', // This is a timestamp that changes each run +]; +const maskedUiFields = [ + 'metamask.conversionDate', // This is a timestamp that changes each run +]; + +const removedBackgroundFields = [ + // This property is timing-dependent + 'AccountTracker.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'AppStateController.currentPopupId', + 'AppStateController.timeoutMinutes', + 'TokenListController.tokensChainsCache', +]; + +const removedUiFields = [ + // This property is timing-dependent + 'metamask.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'metamask.currentPopupId', + 'metamask.timeoutMinutes', + 'metamask.tokensChainsCache', +]; /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform background state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformBackgroundDates(data) { - for (const field of backgroundDateFields) { +function transformBackgroundState(data) { + for (const field of maskedBackgroundFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedBackgroundFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform UI state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformUiDates(data) { - for (const field of uiDateFields) { +function transformUiState(data) { + for (const field of maskedUiFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedUiFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } @@ -257,7 +287,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformBackgroundDates(appState), + data: transformBackgroundState(appState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -342,7 +372,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformUiDates(appState), + data: transformUiState(appState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -509,7 +539,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundDates(appState.store), + data: transformBackgroundState(appState.store), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -603,7 +633,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformUiDates(appState.store), + data: transformUiState(appState.store), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index f41fee885..2f29cc98c 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,34 +1,81 @@ { - "AccountTracker": { "currentBlockGasLimit": "0x1c9c380" }, + "AccountTracker": { "accounts": "object" }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, - "defaultHomeActiveTabName": null + "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number" }, + "ApprovalController": "object", + "CachedBalancesController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, "nativeCurrency": "ETH", - "currentCurrency": "usd" + "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number" + }, + "DecryptMessageController": { + "unapprovedDecryptMsgs": "object", + "unapprovedDecryptMsgCount": 0 }, - "DecryptMessageController": { "unapprovedDecryptMsgCount": 0 }, "EncryptionPublicKeyController": { + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0 }, + "EnsController": "object", "MetaMetricsController": { "participateInMetaMetrics": true, - "metaMetricsId": "fake-metrics-id" + "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object" }, "NetworkController": { + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" - } + "type": "rpc", + "id": "string" + }, + "networksMetadata": "object", + "networkConfigurations": "object" }, "SignatureController": { + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 - } + }, + "SwapsController": "object", + "TokenRatesController": "object", + "TokensController": "object", + "TxController": "object" } diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 4ecaa0417..129ae885a 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -1,16 +1,28 @@ { + "DNS": "object", + "activeTab": "object", + "appState": "object", + "confirmTransaction": "object", "gas": { "customData": { "price": null, "limit": null } }, "history": { "mostRecentOverviewPage": "/" }, + "invalidCustomNetwork": "object", + "localeMessages": "object", "metamask": { "isInitialized": true, "isUnlocked": false, "isAccountMenuOpen": false, + "isNetworkMenuOpen": "boolean", + "identities": "object", + "unapprovedTxs": "object", + "networkConfigurations": "object", + "addressBook": "object", + "contractExchangeRates": "object", + "pendingTokens": "object", "customNonceValue": "", "useBlockie": false, "featureFlags": { "showIncomingTransactions": true }, "welcomeScreenSeen": false, "currentLocale": "en", - "currentBlockGasLimit": "", "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -19,31 +31,89 @@ }, "firstTimeFlowType": "import", "completedOnboarding": true, + "knownMethodData": "object", + "use4ByteResolution": "boolean", "participateInMetaMetrics": true, "nextNonce": null, "conversionRate": 1300, "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number", "currentAppVersion": "10.34.4", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": 94, + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" + "type": "rpc", + "id": "string" }, + "networksMetadata": "object", + "cachedBalances": "object", + "keyringTypes": "object", + "keyrings": "object", "useNonceField": false, "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "lostIdentities": "object", "forgottenPassword": false, "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object", "conversionDate": "number", "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number", "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object", "seedPhraseBackedUp": true, + "onboardingTabs": "object", + "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { "0x1": null, "0xe708": null, @@ -51,11 +121,45 @@ "0xaa36a7": null, "0xe704": null }, + "subjects": "object", + "permissionHistory": "object", + "permissionActivityLog": "object", + "subjectMetadata": "object", + "announcements": "object", + "gasFeeEstimates": "object", + "estimatedGasFeeTimeBounds": "object", + "gasEstimateType": "string", + "tokenList": "object", + "preventPollingOnNetworkRestart": "boolean", + "tokens": "object", + "ignoredTokens": "object", + "detectedTokens": "object", + "allTokens": "object", + "allIgnoredTokens": "object", + "allDetectedTokens": "object", + "smartTransactionsState": "object", + "allNftContracts": "object", + "allNfts": "object", + "ignoredNfts": "object", + "accounts": "object", + "currentNetworkTxList": "object", + "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, - "unapprovedTypedMessagesCount": 0 + "unapprovedTypedMessagesCount": 0, + "swapsState": "object", + "ensResolutionsByAddress": "object", + "pendingApprovals": "object", + "pendingApprovalCount": "number", + "approvalFlows": "object" }, + "send": "object", + "swaps": "object", "unconnectedAccount": { "state": "CLOSED" } } From f033a59b17992c067e1c901c2f50dcd554b4bd29 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 09:04:30 -0230 Subject: [PATCH 036/102] Remove invalid `tokensChainsCache` state (#20495) Migration #77 would set the `TokenListController.tokensChainsCache` state to `undefined` if it wasn't already set to anything when that migration was run. This is probably harmless except that it results in Sentry errors during migrations, and it results in that property having a value (at least temporarily) that doesn't match its type. Migration #77 has been updated to prevent this property from being set to `undefined` going forward. A new migration has been added to delete this value for any users already affected by this problem. The new migration was named "92.1" so that it could run after 92 but before 93, to make backporting this to v10.34.x easier (v10.34.x is currently on migration 92). "92.1" is still a valid number so this should work just as well as a whole number. --- app/scripts/migrations/077.js | 21 ++- app/scripts/migrations/077.test.js | 70 +++++++++ app/scripts/migrations/092.1.test.ts | 139 ++++++++++++++++++ app/scripts/migrations/092.1.ts | 53 +++++++ app/scripts/migrations/093.test.ts | 2 +- app/scripts/migrations/index.js | 2 + test/e2e/tests/errors.spec.js | 2 - .../errors-after-init-opt-in-ui-state.json | 1 + 8 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 app/scripts/migrations/092.1.test.ts create mode 100644 app/scripts/migrations/092.1.ts diff --git a/app/scripts/migrations/077.js b/app/scripts/migrations/077.js index 5a5d4f1ac..9adb293ba 100644 --- a/app/scripts/migrations/077.js +++ b/app/scripts/migrations/077.js @@ -1,4 +1,6 @@ import { cloneDeep } from 'lodash'; +import log from 'loglevel'; +import { hasProperty, isObject } from '@metamask/utils'; import transformState077For082 from './077-supplements/077-supplement-for-082'; import transformState077For084 from './077-supplements/077-supplement-for-084'; import transformState077For086 from './077-supplements/077-supplement-for-086'; @@ -29,8 +31,23 @@ export default { }; function transformState(state) { - const TokenListController = state?.TokenListController || {}; - + if (!hasProperty(state, 'TokenListController')) { + log.warn('Skipping migration, TokenListController state is missing'); + return state; + } else if (!isObject(state.TokenListController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); + return state; + } else if (!hasProperty(state.TokenListController, 'tokensChainsCache')) { + log.warn( + 'Skipping migration, TokenListController.tokensChainsCache state is missing', + ); + return state; + } + const { TokenListController } = state; const { tokensChainsCache } = TokenListController; let dataCache; diff --git a/app/scripts/migrations/077.test.js b/app/scripts/migrations/077.test.js index 53efb5cd5..67de1a8fa 100644 --- a/app/scripts/migrations/077.test.js +++ b/app/scripts/migrations/077.test.js @@ -1,11 +1,22 @@ +import { cloneDeep } from 'lodash'; import migration77 from './077'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #77', () => { it('should update the version metadata', async () => { const oldStorage = { meta: { version: 76, }, + data: {}, }; const newStorage = await migration77.migrate(oldStorage); @@ -13,6 +24,65 @@ describe('migration #77', () => { version: 77, }); }); + + it('should return state unchanged if token list controller is missing', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + Foo: { + bar: 'baz', + }, + }, + }; + + const newStorage = await migration77.migrate(cloneDeep(oldStorage)); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('should capture an exception if the TokenListController state is invalid', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + TokenListController: 'test', + }, + }; + + await migration77.migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is string`), + ); + }); + + it('should return state unchanged if tokenChainsCache is missing', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + TokenListController: { + tokenList: { + '0x514910771af9ca656af840dff83e8264ecf986ca': { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + symbol: 'LINK', + decimals: 18, + }, + }, + }, + }, + }; + + const newStorage = await migration77.migrate(cloneDeep(oldStorage)); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + it('should change the data from array to object for a single network', async () => { const oldStorage = { meta: { diff --git a/app/scripts/migrations/092.1.test.ts b/app/scripts/migrations/092.1.test.ts new file mode 100644 index 000000000..7ab1045ca --- /dev/null +++ b/app/scripts/migrations/092.1.test.ts @@ -0,0 +1,139 @@ +import { cloneDeep } from 'lodash'; +import { migrate, version } from './092.1'; + +const PREVIOUS_VERSION = 92; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + +describe('migration #92.1', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should update the version metadata', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ + version, + }); + }); + + it('should return state unaltered if there is no TokenListController state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('captures an exception if the TokenListController state is invalid', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: { TokenListController: 'this is not valid' }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is string`), + ); + }); + + it('should return state unaltered if there is no TokenListController tokensChainsCache state', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokenList: {}, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should return state unaltered if the tokensChainsCache state is an unexpected type', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: 'unexpected string', + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should return state unaltered if the tokensChainsCache state is valid', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: {}, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should remove undefined tokensChainsCache state', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: undefined, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual({ + other: 'data', + TokenListController: {}, + }); + }); +}); diff --git a/app/scripts/migrations/092.1.ts b/app/scripts/migrations/092.1.ts new file mode 100644 index 000000000..62b47fb04 --- /dev/null +++ b/app/scripts/migrations/092.1.ts @@ -0,0 +1,53 @@ +import { cloneDeep } from 'lodash'; +import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; + +export const version = 92.1; + +/** + * Check whether the `TokenListController.tokensChainsCache` state is + * `undefined`, and delete it if so. + * + * This property was accidentally set to `undefined` by an earlier revision of + * migration #77 in some cases. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate(originalVersionedData: { + meta: { version: number }; + data: Record; +}) { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + versionedData.data = transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if (!hasProperty(state, 'TokenListController')) { + log.warn('Skipping migration, TokenListController state is missing'); + return state; + } else if (!isObject(state.TokenListController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); + return state; + } else if (!hasProperty(state.TokenListController, 'tokensChainsCache')) { + log.warn( + 'Skipping migration, TokenListController.tokensChainsCache state is missing', + ); + return state; + } + + if (state.TokenListController.tokensChainsCache === undefined) { + delete state.TokenListController.tokensChainsCache; + } + + return state; +} diff --git a/app/scripts/migrations/093.test.ts b/app/scripts/migrations/093.test.ts index eff417f01..ed10bec5d 100644 --- a/app/scripts/migrations/093.test.ts +++ b/app/scripts/migrations/093.test.ts @@ -1,7 +1,7 @@ import { InfuraNetworkType, NetworkType } from '@metamask/controller-utils'; import { migrate, version } from './093'; -const PREVIOUS_VERSION = version - 1; +const PREVIOUS_VERSION = 92.1; const sentryCaptureExceptionMock = jest.fn(); diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index de5b11744..aa7e7ddbc 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -96,6 +96,7 @@ import * as m089 from './089'; import * as m090 from './090'; import * as m091 from './091'; import * as m092 from './092'; +import * as m092point1 from './092.1'; import * as m093 from './093'; import * as m094 from './094'; @@ -191,6 +192,7 @@ const migrations = [ m090, m091, m092, + m092point1, m093, m094, ]; diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 3098a5083..ff3b2e56f 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -20,7 +20,6 @@ const removedBackgroundFields = [ // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'AppStateController.currentPopupId', 'AppStateController.timeoutMinutes', - 'TokenListController.tokensChainsCache', ]; const removedUiFields = [ @@ -29,7 +28,6 @@ const removedUiFields = [ // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'metamask.currentPopupId', 'metamask.timeoutMinutes', - 'metamask.tokensChainsCache', ]; /** diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 129ae885a..89bb21722 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -130,6 +130,7 @@ "estimatedGasFeeTimeBounds": "object", "gasEstimateType": "string", "tokenList": "object", + "tokensChainsCache": "object", "preventPollingOnNetworkRestart": "boolean", "tokens": "object", "ignoredTokens": "object", From 20e16d41be8fef1fd4668b6134531189a773d7e8 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 09:29:05 -0230 Subject: [PATCH 037/102] Improve Sentry state pre-initialization (#20491) * Improve Sentry state pre-initialization Previously the masked state snapshot sent to Sentry would be blank for errors that occured during initialization. Instead we'll now include some basic information in all cases, and a masked copy of the persisted state if it happens after the first time the persisted state is read. * Add test * Fix crash when persisted state not yet fetched * Add descriptions for initial state hooks * Update comments to reflect recent changes * Re-order imports to follow conventions * Move initial state hooks back to module-level The initial state hooks are now setup at the top-level of their module. This ensures that they're setup prior to later imports. Calling a function to setup these hooks in the entrypoint module wouldn't accomplish this even if it was run "before" the imports because ES6 imports always get hoisted to the top of the file. The `localStore` instance wasn't available statically, so a new state hook was introduced for retrieving the most recent retrieved persisted state. * Fix error e2e tests --- app/scripts/background.js | 27 ++--- app/scripts/lib/local-store.js | 3 + app/scripts/lib/local-store.test.js | 57 +++++++-- app/scripts/lib/network-store.js | 2 + app/scripts/lib/setup-initial-state-hooks.js | 61 ++++++++++ app/scripts/lib/setup-persisted-state-hook.js | 10 -- app/scripts/ui.js | 18 ++- test/e2e/tests/errors.spec.js | 58 ++++++--- ...s-before-init-opt-in-background-state.json | 112 ++++++++++++++++- .../errors-before-init-opt-in-ui-state.json | 113 +++++++++++++++++- ui/index.js | 9 +- 11 files changed, 408 insertions(+), 62 deletions(-) create mode 100644 app/scripts/lib/setup-initial-state-hooks.js delete mode 100644 app/scripts/lib/setup-persisted-state-hook.js diff --git a/app/scripts/background.js b/app/scripts/background.js index 86d4b5525..7b45cdfd5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -2,9 +2,11 @@ * @file The entry point for the web extension singleton process. */ -// This import sets up a global function required for Sentry to function. +// Disabled to allow setting up initial state hooks first + +// This import sets up global functions required for Sentry to function. // It must be run first in case an error is thrown later during initialization. -import './lib/setup-persisted-state-hook'; +import './lib/setup-initial-state-hooks'; import EventEmitter from 'events'; import endOfStream from 'end-of-stream'; @@ -69,6 +71,12 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; + const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -92,9 +100,6 @@ const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; -// state persistence -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { @@ -899,17 +904,9 @@ browser.runtime.onInstalled.addListener(({ reason }) => { }); function setupSentryGetStateGlobal(store) { - global.stateHooks.getSentryState = function () { + global.stateHooks.getSentryAppState = function () { const backgroundState = store.memStore.getState(); - const maskedBackgroundState = maskObject( - backgroundState, - SENTRY_BACKGROUND_STATE, - ); - return { - browser: window.navigator.userAgent, - store: maskedBackgroundState, - version: platform.getVersion(), - }; + return maskObject(backgroundState, SENTRY_BACKGROUND_STATE); }; } diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index b0a223561..40908af5e 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -16,6 +16,7 @@ export default class ExtensionStore { // once data persistence fails once and it flips true we don't send further // data persistence errors to sentry this.dataPersistenceFailing = false; + this.mostRecentRetrievedState = null; } setMetadata(initMetaData) { @@ -66,8 +67,10 @@ export default class ExtensionStore { // extension.storage.local always returns an obj // if the object is empty, treat it as undefined if (isEmpty(result)) { + this.mostRecentRetrievedState = null; return undefined; } + this.mostRecentRetrievedState = result; return result; } diff --git a/app/scripts/lib/local-store.test.js b/app/scripts/lib/local-store.test.js index 2c3cea405..8b786ca81 100644 --- a/app/scripts/lib/local-store.test.js +++ b/app/scripts/lib/local-store.test.js @@ -2,11 +2,12 @@ import browser from 'webextension-polyfill'; import LocalStore from './local-store'; jest.mock('webextension-polyfill', () => ({ + runtime: { lastError: null }, storage: { local: true }, })); -const setup = ({ isSupported }) => { - browser.storage.local = isSupported; +const setup = ({ localMock = jest.fn() } = {}) => { + browser.storage.local = localMock; return new LocalStore(); }; describe('LocalStore', () => { @@ -15,21 +16,27 @@ describe('LocalStore', () => { }); describe('contructor', () => { it('should set isSupported property to false when browser does not support local storage', () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); expect(localStore.isSupported).toBe(false); }); it('should set isSupported property to true when browser supports local storage', () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); expect(localStore.isSupported).toBe(true); }); + + it('should initialize mostRecentRetrievedState to null', () => { + const localStore = setup({ localMock: false }); + + expect(localStore.mostRecentRetrievedState).toBeNull(); + }); }); describe('setMetadata', () => { it('should set the metadata property on LocalStore', () => { const metadata = { version: 74 }; - const localStore = setup({ isSupported: true }); + const localStore = setup(); localStore.setMetadata(metadata); expect(localStore.metadata).toStrictEqual(metadata); @@ -38,21 +45,21 @@ describe('LocalStore', () => { describe('set', () => { it('should throw an error if called in a browser that does not support local storage', async () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); await expect(() => localStore.set()).rejects.toThrow( 'Metamask- cannot persist state to local store as this browser does not support this action', ); }); it('should throw an error if not passed a truthy value as an argument', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); await expect(() => localStore.set()).rejects.toThrow( 'MetaMask - updated state is missing', ); }); it('should throw an error if passed a valid argument but metadata has not yet been set', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); await expect(() => localStore.set({ appState: { test: true } }), ).rejects.toThrow( @@ -61,7 +68,7 @@ describe('LocalStore', () => { }); it('should not throw if passed a valid argument and metadata has been set', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); localStore.setMetadata({ version: 74 }); await expect(async function () { localStore.set({ appState: { test: true } }); @@ -71,9 +78,39 @@ describe('LocalStore', () => { describe('get', () => { it('should return undefined if called in a browser that does not support local storage', async () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); const result = await localStore.get(); expect(result).toStrictEqual(undefined); }); + + it('should update mostRecentRetrievedState', async () => { + const localStore = setup({ + localMock: { + get: jest + .fn() + .mockImplementation(() => + Promise.resolve({ appState: { test: true } }), + ), + }, + }); + + await localStore.get(); + + expect(localStore.mostRecentRetrievedState).toStrictEqual({ + appState: { test: true }, + }); + }); + + it('should reset mostRecentRetrievedState to null if storage.local is empty', async () => { + const localStore = setup({ + localMock: { + get: jest.fn().mockImplementation(() => Promise.resolve({})), + }, + }); + + await localStore.get(); + + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + }); }); }); diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index ea6ba5876..2f4c0a1b0 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -15,6 +15,7 @@ export default class ReadOnlyNetworkStore { this._initialized = false; this._initializing = this._init(); this._state = undefined; + this.mostRecentRetrievedState = null; } /** @@ -30,6 +31,7 @@ export default class ReadOnlyNetworkStore { const response = await fetchWithTimeout(FIXTURE_SERVER_URL); if (response.ok) { this._state = await response.json(); + this.mostRecentRetrievedState = this._state; } } catch (error) { log.debug(`Error loading network state: '${error.message}'`); diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js new file mode 100644 index 000000000..fa02987a7 --- /dev/null +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -0,0 +1,61 @@ +import { maskObject } from '../../../shared/modules/object.utils'; +import ExtensionPlatform from '../platforms/extension'; +import LocalStore from './local-store'; +import ReadOnlyNetworkStore from './network-store'; +import { SENTRY_BACKGROUND_STATE } from './setupSentry'; + +const platform = new ExtensionPlatform(); +const localStore = process.env.IN_TEST + ? new ReadOnlyNetworkStore() + : new LocalStore(); + +/** + * Get the persisted wallet state. + * + * @returns The persisted wallet state. + */ +globalThis.stateHooks.getPersistedState = async function () { + return await localStore.get(); +}; + +const persistedStateMask = { + data: SENTRY_BACKGROUND_STATE, + meta: { + version: true, + }, +}; + +/** + * Get a state snapshot to include with Sentry error reports. This uses the + * persisted state pre-initialization, and the in-memory state post- + * initialization. In both cases the state is anonymized. + * + * @returns A Sentry state snapshot. + */ +globalThis.stateHooks.getSentryState = function () { + const sentryState = { + browser: window.navigator.userAgent, + version: platform.getVersion(), + }; + if (globalThis.stateHooks.getSentryAppState) { + return { + ...sentryState, + state: globalThis.stateHooks.getSentryAppState(), + }; + } else if (globalThis.stateHooks.getMostRecentPersistedState) { + const persistedState = globalThis.stateHooks.getMostRecentPersistedState(); + if (persistedState) { + return { + ...sentryState, + persistedState: maskObject( + // `getMostRecentPersistedState` is used here instead of + // `getPersistedState` to avoid making this an asynchronous function. + globalThis.stateHooks.getMostRecentPersistedState(), + persistedStateMask, + ), + }; + } + return sentryState; + } + return sentryState; +}; diff --git a/app/scripts/lib/setup-persisted-state-hook.js b/app/scripts/lib/setup-persisted-state-hook.js deleted file mode 100644 index 9b29fad26..000000000 --- a/app/scripts/lib/setup-persisted-state-hook.js +++ /dev/null @@ -1,10 +0,0 @@ -import LocalStore from './local-store'; -import ReadOnlyNetworkStore from './network-store'; - -const localStore = process.env.IN_TEST - ? new ReadOnlyNetworkStore() - : new LocalStore(); - -globalThis.stateHooks.getPersistedState = async function () { - return await localStore.get(); -}; diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 50f37c430..6be5c7758 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -1,10 +1,12 @@ +// Disabled to allow setting up initial state hooks first + +// This import sets up global functions required for Sentry to function. +// It must be run first in case an error is thrown later during initialization. +import './lib/setup-initial-state-hooks'; + // dev only, "react-devtools" import is skipped in prod builds import 'react-devtools'; -// This import sets up a global function required for Sentry to function. -// It must be run first in case an error is thrown later during initialization. -import './lib/setup-persisted-state-hook'; - import PortStream from 'extension-port-stream'; import browser from 'webextension-polyfill'; @@ -31,6 +33,14 @@ import ExtensionPlatform from './platforms/extension'; import { setupMultiplex } from './lib/stream-utils'; import { getEnvironmentType, getPlatform } from './lib/util'; import metaRPCClientFactory from './lib/metaRPCClientFactory'; +import LocalStore from './lib/local-store'; +import ReadOnlyNetworkStore from './lib/network-store'; + +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; const container = document.getElementById('app-content'); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index ff3b2e56f..36f1921b3 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -195,9 +195,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Wait for Sentry request @@ -284,8 +284,23 @@ describe('Sentry errors', function () { const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'version', + 'persistedState', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); await matchesSnapshot({ - data: transformBackgroundState(appState), + data: transformBackgroundState(appState.persistedState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -309,9 +324,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Trigger error @@ -352,9 +367,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Trigger error @@ -369,8 +384,23 @@ describe('Sentry errors', function () { const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'version', + 'persistedState', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); await matchesSnapshot({ - data: transformUiState(appState), + data: transformBackgroundState(appState.persistedState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -479,7 +509,7 @@ describe('Sentry errors', function () { const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; const { participateInMetaMetrics } = - extra.appState.store.MetaMetricsController; + extra.appState.state.MetaMetricsController; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -523,8 +553,8 @@ describe('Sentry errors', function () { const appState = mockJsonBody?.extra?.appState; assert.deepStrictEqual(Object.keys(appState), [ 'browser', - 'store', 'version', + 'state', ]); assert.ok( typeof appState?.browser === 'string' && @@ -537,7 +567,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundState(appState.store), + data: transformBackgroundState(appState.state), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -575,7 +605,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; - const { participateInMetaMetrics } = extra.appState.store.metamask; + const { participateInMetaMetrics } = extra.appState.state.metamask; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -617,8 +647,8 @@ describe('Sentry errors', function () { const appState = mockJsonBody?.extra?.appState; assert.deepStrictEqual(Object.keys(appState), [ 'browser', - 'store', 'version', + 'state', ]); assert.ok( typeof appState?.browser === 'string' && @@ -631,7 +661,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformUiState(appState.store), + data: transformUiState(appState.state), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 0967ef424..422bcb0e2 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -1 +1,111 @@ -{} +{ + "data": { + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppStateController": { + "browserEnvironment": "object", + "nftsDropdownState": "object", + "connectedStatusPopoverHasBeenShown": true, + "termsOfUseLastAgreed": "number", + "defaultHomeActiveTabName": null, + "fullScreenGasPollTokens": "object", + "notificationGasPollTokens": "object", + "popupGasPollTokens": "object", + "qrHardware": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "showTestnetMessageInDropdown": "boolean", + "trezorModel": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean" + }, + "CachedBalancesController": "object", + "CurrencyController": { + "conversionDate": 1665507600, + "conversionRate": 1300, + "currentCurrency": "usd", + "nativeCurrency": "ETH", + "usdConversionRate": "number" + }, + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { "vault": "string" }, + "MetaMetricsController": { + "eventsBeforeMetricsOptIn": "object", + "fragments": "object", + "metaMetricsId": "fake-metrics-id", + "participateInMetaMetrics": true, + "traits": "object" + }, + "NetworkController": { + "networkId": "1337", + "selectedNetworkClientId": "string", + "networksMetadata": "object", + "providerConfig": { + "chainId": "string", + "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", + "ticker": "ETH", + "type": "rpc", + "id": "string" + }, + "networkConfigurations": "object" + }, + "OnboardingController": { + "completedOnboarding": true, + "firstTimeFlowType": "import", + "onboardingTabs": "object", + "seedPhraseBackedUp": true + }, + "PermissionController": "object", + "PreferencesController": { + "advancedGasFee": "object", + "currentLocale": "en", + "dismissSeedBackUpReminder": "boolean", + "featureFlags": { "showIncomingTransactions": true }, + "forgottenPassword": false, + "identities": "object", + "infuraBlocked": "boolean", + "ipfsGateway": "dweb.link", + "knownMethodData": "object", + "ledgerTransportType": "string", + "lostIdentities": "object", + "openSeaEnabled": "boolean", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "selectedAddress": "string", + "theme": "string", + "useBlockie": false, + "useNftDetection": "boolean", + "useNonceField": false, + "usePhishDetect": true, + "useTokenDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "useMultiAccountBalanceChecker": "boolean" + }, + "SmartTransactionsController": "object", + "SubjectMetadataController": "object", + "TokensController": "object", + "TransactionController": "object", + "config": "object", + "firstTimeInfo": "object" + } +} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 0967ef424..0a9fac1d0 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -1 +1,112 @@ -{} +{ + "data": { + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppStateController": { + "browserEnvironment": "object", + "nftsDropdownState": "object", + "connectedStatusPopoverHasBeenShown": true, + "termsOfUseLastAgreed": "number", + "defaultHomeActiveTabName": null, + "fullScreenGasPollTokens": "object", + "notificationGasPollTokens": "object", + "popupGasPollTokens": "object", + "qrHardware": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "showTestnetMessageInDropdown": "boolean", + "trezorModel": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean" + }, + "CachedBalancesController": "object", + "CurrencyController": { + "conversionDate": 1665507600, + "conversionRate": 1300, + "currentCurrency": "usd", + "nativeCurrency": "ETH", + "usdConversionRate": "number" + }, + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { "vault": "string" }, + "MetaMetricsController": { + "eventsBeforeMetricsOptIn": "object", + "fragments": "object", + "metaMetricsId": "fake-metrics-id", + "participateInMetaMetrics": true, + "traits": "object" + }, + "NetworkController": { + "networkId": "1337", + "selectedNetworkClientId": "string", + "networksMetadata": "object", + "providerConfig": { + "chainId": "string", + "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", + "ticker": "ETH", + "type": "rpc", + "id": "string" + }, + "networkConfigurations": "object" + }, + "OnboardingController": { + "completedOnboarding": true, + "firstTimeFlowType": "import", + "onboardingTabs": "object", + "seedPhraseBackedUp": true + }, + "PermissionController": "object", + "PreferencesController": { + "advancedGasFee": "object", + "currentLocale": "en", + "dismissSeedBackUpReminder": "boolean", + "featureFlags": { "showIncomingTransactions": true }, + "forgottenPassword": false, + "identities": "object", + "infuraBlocked": "boolean", + "ipfsGateway": "dweb.link", + "knownMethodData": "object", + "ledgerTransportType": "string", + "lostIdentities": "object", + "openSeaEnabled": "boolean", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "selectedAddress": "string", + "theme": "string", + "useBlockie": false, + "useNftDetection": "boolean", + "useNonceField": false, + "usePhishDetect": true, + "useTokenDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "useMultiAccountBalanceChecker": "boolean" + }, + "SmartTransactionsController": "object", + "SubjectMetadataController": "object", + "TokensController": "object", + "TransactionController": "object", + "config": "object", + "firstTimeInfo": "object" + }, + "meta": { "version": 74 } +} diff --git a/ui/index.js b/ui/index.js index 6f9a148ed..de1432a80 100644 --- a/ui/index.js +++ b/ui/index.js @@ -233,14 +233,9 @@ function setupStateHooks(store) { }); return state; }; - window.stateHooks.getSentryState = function () { + window.stateHooks.getSentryAppState = function () { const reduxState = store.getState(); - const maskedReduxState = maskObject(reduxState, SENTRY_UI_STATE); - return { - browser: window.navigator.userAgent, - store: maskedReduxState, - version: global.platform.getVersion(), - }; + return maskObject(reduxState, SENTRY_UI_STATE); }; } From 486ade85f3a3ce5195049423869d97eb9c6d6399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Thu, 17 Aug 2023 14:50:40 +0200 Subject: [PATCH 038/102] =?UTF-8?q?[MMI]=C2=A0Fixed=20connect=20mmi=20butt?= =?UTF-8?q?on=20several=20issues=20(#20455)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed connect mmi button several issues * Fixed snapshot * Improved multiple things * running yarn dedupe * Update LavaMoat policies * For some reason, this.mmiConfigurationController.store.mmiConfiguration?.portfolio sometimes is undefined, added [] if null. Moved the || {} outside the find method * minor improvements --------- Co-authored-by: MetaMask Bot --- app/scripts/controllers/mmi-controller.js | 2 +- lavamoat/browserify/mmi/policy.json | 18 +- package.json | 2 +- .../custody-confirm-link-modal.js | 5 +- .../ui/metafox-logo/horizontal-logo.js | 3 +- .../institutional-home-footer.js | 6 +- .../__snapshots__/custody.test.js.snap | 1 + ui/pages/institutional/custody/custody.js | 168 ++++++++++-------- ui/selectors/selectors.js | 3 +- yarn.lock | 64 ++++--- 10 files changed, 164 insertions(+), 108 deletions(-) diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index bfc44a796..6aec42962 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -569,7 +569,7 @@ export default class MMIController extends EventEmitter { const mmiDashboardData = await this.handleMmiDashboardData(); const cookieSetUrls = this.mmiConfigurationController.store.mmiConfiguration?.portfolio - ?.cookieSetUrls; + ?.cookieSetUrls || []; setDashboardCookie(mmiDashboardData, cookieSetUrls); } catch (error) { console.error(error); diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index db6f4bba9..b59f78086 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -790,10 +790,26 @@ "@metamask-institutional/extension>@metamask-institutional/custody-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring": true, + "@metamask-institutional/extension>@metamask-institutional/custody-keyring": true, "@metamask/obs-store": true } }, + "@metamask-institutional/extension>@metamask-institutional/custody-keyring": { + "globals": { + "console.log": true, + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/sdk>@metamask-institutional/types": true, + "@metamask/obs-store": true, + "browserify>crypto-browserify": true, + "browserify>events": true, + "gulp-sass>lodash.clonedeep": true + } + }, "@metamask-institutional/institutional-features": { "globals": { "chrome.runtime.id": true, diff --git a/package.json b/package.json index 7ab36645e..dd4acae94 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "@material-ui/core": "^4.11.0", "@metamask-institutional/custody-controller": "0.2.6", "@metamask-institutional/custody-keyring": "^0.0.25", - "@metamask-institutional/extension": "^0.2.1", + "@metamask-institutional/extension": "0.3.2", "@metamask-institutional/institutional-features": "^1.1.8", "@metamask-institutional/portfolio-dashboard": "^1.1.3", "@metamask-institutional/rpc-allowlist": "^1.0.0", diff --git a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js index a71f06580..430f1d4b9 100644 --- a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js +++ b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js @@ -48,9 +48,8 @@ const CustodyConfirmLink = ({ hideModal }) => { const { custodians } = useSelector(getMMIConfiguration); const { custodianName } = custodyAccountDetails[toChecksumHexAddress(address)] || {}; - const { displayName, iconUrl } = custodians.find( - (item) => item.name === custodianName || {}, - ); + const { displayName, iconUrl } = + custodians.find((item) => item.name === custodianName) || {}; const { url, ethereum, text, action } = useSelector( (state) => state.appState.modal.modalState.props.link || {}, ); diff --git a/ui/components/ui/metafox-logo/horizontal-logo.js b/ui/components/ui/metafox-logo/horizontal-logo.js index f9b9fe830..129870248 100644 --- a/ui/components/ui/metafox-logo/horizontal-logo.js +++ b/ui/components/ui/metafox-logo/horizontal-logo.js @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; +import { ThemeType } from '../../../../shared/constants/preferences'; const LOGO_WIDTH = 162; const LOGO_HEIGHT = 30; @@ -994,6 +995,6 @@ export default function MetaFoxHorizontalLogo({ } MetaFoxHorizontalLogo.propTypes = { - theme: PropTypes.oneOf(['light', 'dark']), + theme: PropTypes.oneOf([ThemeType.light, ThemeType.dark, ThemeType.os]), className: PropTypes.string, }; diff --git a/ui/pages/home/institutional/institutional-home-footer.js b/ui/pages/home/institutional/institutional-home-footer.js index 46e9c07a9..f9873e8a6 100644 --- a/ui/pages/home/institutional/institutional-home-footer.js +++ b/ui/pages/home/institutional/institutional-home-footer.js @@ -50,9 +50,9 @@ const InstitutionalHomeFooter = ({ activitySupportDisplayStyle }) => { InstitutionalHomeFooter.propTypes = { activitySupportDisplayStyle: PropTypes.shape({ justifyContent: PropTypes.string.isRequired, - paddingLeft: PropTypes.string, - marginBottom: PropTypes.string, - marginTop: PropTypes.string, + paddingLeft: PropTypes.number, + marginBottom: PropTypes.number, + marginTop: PropTypes.number, }).isRequired, }; diff --git a/ui/pages/institutional/custody/__snapshots__/custody.test.js.snap b/ui/pages/institutional/custody/__snapshots__/custody.test.js.snap index c8666db9b..525078513 100644 --- a/ui/pages/institutional/custody/__snapshots__/custody.test.js.snap +++ b/ui/pages/institutional/custody/__snapshots__/custody.test.js.snap @@ -90,6 +90,7 @@ exports[`CustodyPage renders CustodyPage 2`] = ` + `; diff --git a/ui/pages/institutional/custody/custody.js b/ui/pages/institutional/custody/custody.js index 2c62d8df6..1d9b1116e 100644 --- a/ui/pages/institutional/custody/custody.js +++ b/ui/pages/institutional/custody/custody.js @@ -8,6 +8,7 @@ import React, { import { useSelector, useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { v4 as uuidv4 } from 'uuid'; +import { isEqual } from 'lodash'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { mmiActionsFactory } from '../../../store/institutional/institution-background'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -42,6 +43,7 @@ import { } from '../../../helpers/constants/routes'; import { getCurrentChainId, getSelectedAddress } from '../../../selectors'; import { getMMIConfiguration } from '../../../selectors/institutional/selectors'; +import { getInstitutionalConnectRequests } from '../../../ducks/institutional/institutional'; import CustodyAccountList from '../connect-custody/account-list'; import JwtUrlForm from '../../../components/institutional/jwt-url-form'; import { @@ -74,14 +76,15 @@ const CustodyPage = () => { const [apiUrl, setApiUrl] = useState(''); const [addNewTokenClicked, setAddNewTokenClicked] = useState(false); const [chainId, setChainId] = useState(parseInt(currentChainId, 16)); - const [connectRequest, setConnectRequest] = useState(undefined); + const connectRequests = useSelector(getInstitutionalConnectRequests, isEqual); const [accounts, setAccounts] = useState(); const address = useSelector(getSelectedAddress); + const connectRequest = connectRequests ? connectRequests[0] : undefined; const custodianButtons = useMemo(() => { const custodianItems = []; - const sortedCustodians = custodians.sort(function (a, b) { + const sortedCustodians = [...custodians].sort(function (a, b) { const nameA = a.name.toLowerCase(); const nameB = b.name.toLowerCase(); @@ -94,14 +97,34 @@ const CustodyPage = () => { return 0; }); + function shouldShowInProduction(custodian) { + return ( + custodian && + 'production' in custodian && + !custodian.production && + process.env.METAMASK_ENVIRONMENT === 'production' + ); + } + + function isHidden(custodian) { + return custodian && 'hidden' in custodian && custodian.hidden; + } + + function isNotSelectedCustodian(custodian) { + return ( + custodian && + 'name' in custodian && + connectRequest && + Object.keys(connectRequest).length && + custodian.name !== selectedCustodianName + ); + } + sortedCustodians.forEach((custodian) => { if ( - (!custodian.production && - process.env.METAMASK_ENVIRONMENT === 'production') || - custodian.hidden || - (connectRequest && - Object.keys(connectRequest).length && - custodian.name !== selectedCustodianName) + shouldShowInProduction(custodian) || + isHidden(custodian) || + isNotSelectedCustodian(custodian) ) { return; } @@ -134,23 +157,27 @@ const CustodyPage = () => { size={BUTTON_SIZES.SM} data-testid="custody-connect-button" onClick={async () => { - const jwtListValue = await dispatch( - mmiActions.getCustodianJWTList(custodian.name), - ); - setSelectedCustodianName(custodian.name); - setSelectedCustodianType(custodian.type); - setSelectedCustodianImage(custodian.iconUrl); - setSelectedCustodianDisplayName(custodian.displayName); - setApiUrl(custodian.apiUrl); - setCurrentJwt(jwtListValue[0] || ''); - setJwtList(jwtListValue); - trackEvent({ - category: MetaMetricsEventCategory.MMI, - event: MetaMetricsEventName.CustodianSelected, - properties: { - custodian: custodian.name, - }, - }); + try { + const jwtListValue = await dispatch( + mmiActions.getCustodianJWTList(custodian.name), + ); + setSelectedCustodianName(custodian.name); + setSelectedCustodianType(custodian.type); + setSelectedCustodianImage(custodian.iconUrl); + setSelectedCustodianDisplayName(custodian.displayName); + setApiUrl(custodian.apiUrl); + setCurrentJwt(jwtListValue[0] || ''); + setJwtList(jwtListValue); + trackEvent({ + category: MetaMetricsEventCategory.MMI, + event: MetaMetricsEventName.CustodianSelected, + properties: { + custodian: custodian.name, + }, + }); + } catch (error) { + console.error('Error:', error); + } }} > {t('select')} @@ -208,58 +235,54 @@ const CustodyPage = () => { useEffect(() => { const fetchConnectRequest = async () => { - const connectRequestValue = await dispatch( - mmiActions.getCustodianConnectRequest(), - ); + try { + if (connectRequest && Object.keys(connectRequest).length) { + const { + token, + environment: custodianName, + service: custodianType, + apiUrl: custodianApiUrl, + } = connectRequest; - if (Object.keys(connectRequestValue).length) { - const { - token, - custodianName, - custodianType, - apiUrl: custodianApiUrl, - } = connectRequestValue; + const custodianToken = + token || (await dispatch(mmiActions.getCustodianToken(address))); - const jwt = - token || (await dispatch(mmiActions.getCustodianToken(address))); - const accountsValue = await dispatch( - mmiActions.getCustodianAccounts( - jwt, - custodianApiUrl, - custodianType, - true, - ), - ); + setCurrentJwt(custodianToken); + setSelectedCustodianType(custodianType); + setSelectedCustodianName(custodianName); + setApiUrl(custodianApiUrl); + setConnectError(''); - setConnectRequest(connectRequestValue); - setCurrentJwt(jwt); - setSelectedCustodianType(custodianType); - setSelectedCustodianName(custodianName); - setApiUrl(custodianApiUrl); - setConnectError(''); - setAccounts(accountsValue); + const accountsValue = await dispatch( + mmiActions.getCustodianAccounts( + custodianToken, + custodianApiUrl, + custodianType, + true, + ), + ); - trackEvent({ - category: MetaMetricsEventCategory.MMI, - event: MetaMetricsEventName.CustodianConnected, - properties: { - custodian: custodianName, - apiUrl, - rpc: Boolean(connectRequest), - }, - }); + setAccounts(accountsValue); + + trackEvent({ + category: MetaMetricsEventCategory.MMI, + event: MetaMetricsEventName.CustodianConnected, + properties: { + custodian: custodianName, + apiUrl, + rpc: Boolean(connectRequest), + }, + }); + } + } catch (error) { + console.error(error); + handleConnectError(error); } }; - const handleFetchConnectRequest = async () => { - try { - setLoading(true); - await fetchConnectRequest(); - setLoading(false); - } catch (error) { - console.error(error); - setLoading(false); - } + const handleFetchConnectRequest = () => { + setLoading(true); + fetchConnectRequest().finally(() => setLoading(false)); }; handleFetchConnectRequest(); @@ -343,8 +366,7 @@ const CustodyPage = () => { {selectError} )} - - {!accounts && !selectedCustodianType ? ( + {!accounts && !selectedCustodianType && ( {
    {custodianButtons}
- ) : null} + )} {!accounts && selectedCustodianType && ( <> Date: Thu, 17 Aug 2023 14:58:26 +0200 Subject: [PATCH 039/102] =?UTF-8?q?[MMI]=C2=A0Fix=20signed=20messages=20fr?= =?UTF-8?q?om=20non=20custodial=20accounts=20(#20506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Alter behaviour in MMI controller to use the original code path for signed messages that are from non custodial accounts * Improved readability * Removed unused import --------- Co-authored-by: Shane Terence Odlum --- app/scripts/controllers/mmi-controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index 6aec42962..f5fcc339c 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -578,7 +578,12 @@ export default class MMIController extends EventEmitter { } async newUnsignedMessage(msgParams, req, version) { - const updatedMsgParams = { ...msgParams, deferSetAsSigned: true }; + // The code path triggered by deferSetAsSigned: true is for custodial accounts + const accountDetails = this.custodyController.getAccountDetails( + msgParams.from, + ); + const isCustodial = Boolean(accountDetails); + const updatedMsgParams = { ...msgParams, deferSetAsSigned: isCustodial }; if (req.method.includes('eth_signTypedData')) { return await this.signatureController.newUnsignedTypedMessage( From 8f178bcc2627dc49da1eb2766989558b5e4d4847 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 17 Aug 2023 15:43:01 +0200 Subject: [PATCH 040/102] Enable snaps in stable (#19134) * Enable Snaps feature flag in stable * Run snaps E2Es in stable * Fix CI config indentation * Fix CI paths * Update LavaMoat policies * Update iframe URL * Exclude some tests from running in stable e2e * Disable another test on stable * Bump to 1.0.1 * Fix config.yml issue due to staleness * Stop running newly added test * Update snapshots used for E2E * Use shallow-git-clone --- .circleci/config.yml | 84 +++- builds.yml | 4 + lavamoat/browserify/main/policy.json | 421 ++++++++++++++++++ package.json | 8 +- test/e2e/run-all.js | 20 +- .../errors-after-init-opt-in-ui-state.json | 7 + yarn.lock | 64 +-- 7 files changed, 569 insertions(+), 39 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ffc188eb2..95a6db4ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,8 +152,14 @@ workflows: - prep-build-test - test-e2e-chrome-snaps: requires: - - prep-build-test-flask + - prep-build-test - test-e2e-firefox-snaps: + requires: + - prep-build-test + - test-e2e-chrome-snaps-flask: + requires: + - prep-build-test-flask + - test-e2e-firefox-snaps-flask: requires: - prep-build-test-flask - test-e2e-chrome-mv3: @@ -847,6 +853,80 @@ jobs: path: test/test-results/e2e.xml test-e2e-firefox-snaps: + executor: node-browsers + parallelism: 4 + steps: + - run: *shallow-git-clone + - run: + name: Install Firefox + command: ./.circleci/scripts/firefox-install.sh + - attach_workspace: + at: . + - run: + name: Move test build to dist + command: mv ./dist-test ./dist + - run: + name: Move test zips to builds + command: mv ./builds-test ./builds + - run: + name: test:e2e:firefox:snaps + command: | + if .circleci/scripts/test-run-e2e.sh + then + yarn test:e2e:firefox:snaps --retries 2 --debug --build-type=main + fi + no_output_timeout: 20m + - run: + name: Merge JUnit report + command: | + if [ "$(ls -A test/test-results/e2e)" ]; then + yarn test:e2e:report + fi + when: always + - store_artifacts: + path: test-artifacts + destination: test-artifacts + - store_test_results: + path: test/test-results/e2e.xml + + test-e2e-chrome-snaps: + executor: node-browsers + parallelism: 4 + steps: + - run: *shallow-git-clone + - run: + name: Re-Install Chrome + command: ./.circleci/scripts/chrome-install.sh + - attach_workspace: + at: . + - run: + name: Move test build to dist + command: mv ./dist-test ./dist + - run: + name: Move test zips to builds + command: mv ./builds-test ./builds + - run: + name: test:e2e:chrome:snaps + command: | + if .circleci/scripts/test-run-e2e.sh + then + yarn test:e2e:chrome:snaps --retries 2 --debug --build-type=main + fi + no_output_timeout: 20m + - run: + name: Merge JUnit report + command: | + if [ "$(ls -A test/test-results/e2e)" ]; then + yarn test:e2e:report + fi + when: always + - store_artifacts: + path: test-artifacts + destination: test-artifacts + - store_test_results: + path: test/test-results/e2e.xml + + test-e2e-firefox-snaps-flask: executor: node-browsers parallelism: 4 steps: @@ -883,7 +963,7 @@ jobs: - store_test_results: path: test/test-results/e2e.xml - test-e2e-chrome-snaps: + test-e2e-chrome-snaps-flask: executor: node-browsers parallelism: 4 steps: diff --git a/builds.yml b/builds.yml index 4283eb26e..74f60894d 100644 --- a/builds.yml +++ b/builds.yml @@ -16,12 +16,16 @@ buildTypes: main: features: - build-main + - snaps # Additional env variables that are specific to this build env: - INFURA_PROD_PROJECT_ID - SEGMENT_PROD_WRITE_KEY - INFURA_ENV_KEY_REF: INFURA_PROD_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY + - ALLOW_LOCAL_SNAPS: false + - REQUIRE_SNAPS_ALLOWLIST: true + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/1.0.1/index.html # Main build uses the default browser manifest manifestOverrides: false diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 12cd90e66..1a6c48c6d 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1751,6 +1751,13 @@ "browserify>events": true } }, + "@metamask/notification-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1807,6 +1814,52 @@ "eslint>optionator>fast-levenshtein": true } }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true + } + }, + "@metamask/post-message-stream>readable-stream": { + "packages": { + "@metamask/post-message-stream>readable-stream>process-nextick-args": true, + "@metamask/post-message-stream>readable-stream>safe-buffer": true, + "@metamask/post-message-stream>readable-stream>string_decoder": true, + "browserify>browser-resolve": true, + "browserify>events": true, + "browserify>process": true, + "browserify>timers-browserify": true, + "pumpify>inherits": true, + "readable-stream>core-util-is": true, + "readable-stream>isarray": true, + "readable-stream>util-deprecate": true + } + }, + "@metamask/post-message-stream>readable-stream>process-nextick-args": { + "packages": { + "browserify>process": true + } + }, + "@metamask/post-message-stream>readable-stream>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask/post-message-stream>readable-stream>string_decoder": { + "packages": { + "@metamask/post-message-stream>readable-stream>safe-buffer": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -1834,6 +1887,25 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, + "@metamask/providers>@metamask/object-multiplex": { + "globals": { + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true + } + }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, "@metamask/rpc-methods": { "packages": { "@metamask/browser-passworder": true, @@ -1950,16 +2022,203 @@ "define": true } }, + "@metamask/snaps-controllers": { + "globals": { + "URL": true, + "chrome.offscreen.createDocument": true, + "chrome.offscreen.hasDocument": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/providers>@metamask/object-multiplex": true, + "@metamask/rpc-methods": true, + "@metamask/snaps-controllers>@metamask/utils": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>gunzip-maybe": true, + "@metamask/snaps-controllers>nanoid": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-middleware-stream": true, + "pump": true + } + }, "@metamask/snaps-controllers-flask>nanoid": { "globals": { "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers>concat-stream": { + "packages": { + "@metamask/snaps-controllers>concat-stream>readable-stream": true, + "browserify>buffer": true, + "browserify>concat-stream>typedarray": true, + "pumpify>inherits": true, + "terser>source-map-support>buffer-from": true + } + }, + "@metamask/snaps-controllers>concat-stream>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe": { + "packages": { + "@metamask/snaps-controllers>gunzip-maybe>browserify-zlib": true, + "@metamask/snaps-controllers>gunzip-maybe>is-deflate": true, + "@metamask/snaps-controllers>gunzip-maybe>is-gzip": true, + "@metamask/snaps-controllers>gunzip-maybe>peek-stream": true, + "@metamask/snaps-controllers>gunzip-maybe>pumpify": true, + "@metamask/snaps-controllers>gunzip-maybe>through2": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>browserify-zlib": { + "packages": { + "@metamask/snaps-controllers>gunzip-maybe>browserify-zlib>pako": true, + "browserify>assert": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>util": true, + "readable-stream": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>peek-stream": { + "packages": { + "@metamask/snaps-controllers>gunzip-maybe>peek-stream>duplexify": true, + "@metamask/snaps-controllers>gunzip-maybe>peek-stream>through2": true, + "browserify>buffer": true, + "terser>source-map-support>buffer-from": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>peek-stream>duplexify": { + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "duplexify>stream-shift": true, + "end-of-stream": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>peek-stream>through2": { + "packages": { + "browserify>process": true, + "browserify>util": true, + "readable-stream": true, + "watchify>xtend": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>pumpify": { + "packages": { + "@metamask/snaps-controllers>gunzip-maybe>pumpify>duplexify": true, + "@metamask/snaps-controllers>gunzip-maybe>pumpify>pump": true, + "pumpify>inherits": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>pumpify>duplexify": { + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "duplexify>stream-shift": true, + "end-of-stream": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>pumpify>pump": { + "packages": { + "browserify>browser-resolve": true, + "end-of-stream": true, + "pump>once": true + } + }, + "@metamask/snaps-controllers>gunzip-maybe>through2": { + "packages": { + "browserify>process": true, + "browserify>util": true, + "readable-stream": true, + "watchify>xtend": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>readable-web-to-node-stream": { + "packages": { + "@metamask/snaps-controllers>readable-web-to-node-stream>readable-stream": true + } + }, + "@metamask/snaps-controllers>readable-web-to-node-stream>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, + "@metamask/snaps-controllers>tar-stream": { + "packages": { + "@metamask/snaps-controllers>tar-stream>fs-constants": true, + "@metamask/snaps-controllers>tar-stream>readable-stream": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>string_decoder": true, + "browserify>util": true, + "end-of-stream": true, + "madge>ora>bl": true, + "pumpify>inherits": true + } + }, + "@metamask/snaps-controllers>tar-stream>fs-constants": { + "packages": { + "browserify>constants-browserify": true + } + }, + "@metamask/snaps-controllers>tar-stream>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, @@ -2012,6 +2271,26 @@ "@metamask/snaps-utils>@metamask/utils": true } }, + "@metamask/snaps-utils>@metamask/snaps-registry": { + "packages": { + "@metamask/key-tree>@noble/secp256k1": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/snaps-utils>@metamask/utils": { "globals": { "TextDecoder": true, @@ -3998,6 +4277,24 @@ "Intl": true } }, + "madge>ora>bl": { + "packages": { + "browserify>buffer": true, + "madge>ora>bl>readable-stream": true, + "pumpify>inherits": true + } + }, + "madge>ora>bl>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "mocha>serialize-javascript>randombytes": { "globals": { "crypto": true, @@ -4262,6 +4559,115 @@ "react": true } }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "prop-types": true, + "react": true, + "react-markdown>comma-separated-tokens": true, + "react-markdown>property-information": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>remark-parse": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown": true + } + }, + "react-markdown>remark-parse>mdast-util-from-markdown": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, + "react-syntax-highlighter>refractor>parse-entities": true + } + }, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "packages": { + "react-syntax-highlighter>refractor>parse-entities": true + } + }, + "react-markdown>remark-rehype": { + "packages": { + "react-markdown>remark-rehype>mdast-util-to-hast": true + } + }, + "react-markdown>remark-rehype>mdast-util-to-hast": { + "globals": { + "console.warn": true + }, + "packages": { + "react-markdown>remark-rehype>mdast-util-to-hast>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true + } + }, + "react-markdown>remark-rehype>mdast-util-to-hast>mdast-util-definitions": { + "packages": { + "react-markdown>unist-util-visit": true + } + }, + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true + } + }, + "react-markdown>unified": { + "packages": { + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true + } + }, + "react-markdown>unist-util-visit": { + "packages": { + "react-markdown>unist-util-visit>unist-util-visit-parents": true + } + }, + "react-markdown>unist-util-visit>unist-util-visit-parents": { + "packages": { + "react-markdown>unist-util-visit>unist-util-is": true + } + }, + "react-markdown>vfile": { + "packages": { + "browserify>path-browserify": true, + "browserify>process": true, + "react-markdown>vfile>is-buffer": true, + "react-markdown>vfile>vfile-message": true, + "vinyl>replace-ext": true + } + }, + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true + } + }, "react-popper": { "globals": { "document": true @@ -4416,6 +4822,11 @@ "react": true } }, + "react-syntax-highlighter>refractor>parse-entities": { + "globals": { + "document.createElement": true + } + }, "react-tippy": { "globals": { "Element": true, @@ -4579,12 +4990,22 @@ "define": true } }, + "terser>source-map-support>buffer-from": { + "packages": { + "browserify>buffer": true + } + }, "uuid": { "globals": { "crypto": true, "msCrypto": true } }, + "vinyl>replace-ext": { + "packages": { + "browserify>path-browserify": true + } + }, "web3": { "globals": { "XMLHttpRequest": true diff --git a/package.json b/package.json index dd4acae94..9b4589258 100644 --- a/package.json +++ b/package.json @@ -261,18 +261,18 @@ "@metamask/ppom-validator": "^0.2.0", "@metamask/providers": "^11.1.0", "@metamask/rate-limit-controller": "^3.0.0", - "@metamask/rpc-methods": "^1.0.0", + "@metamask/rpc-methods": "^1.0.1", "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.37.2-flask.1", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", "@metamask/signature-controller": "^5.3.0", "@metamask/slip44": "^3.0.0", "@metamask/smart-transactions-controller": "^4.0.0", - "@metamask/snaps-controllers": "^1.0.0", + "@metamask/snaps-controllers": "^1.0.1", "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.38.0-flask.1", - "@metamask/snaps-ui": "^1.0.0", + "@metamask/snaps-ui": "^1.0.1", "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.37.3-flask.1", - "@metamask/snaps-utils": "^1.0.0", + "@metamask/snaps-utils": "^1.0.1", "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.38.0-flask.1", "@metamask/subject-metadata-controller": "^2.0.0", "@metamask/utils": "^5.0.0", diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index 28c43da28..2556330e0 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -4,6 +4,7 @@ const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const { runInShell } = require('../../development/lib/run-command'); const { exitWithError } = require('../../development/lib/exit-with-error'); +const { loadBuildTypesConfig } = require('../../development/lib/build-type'); const getTestPathsForTestDir = async (testDir) => { const testFilenames = await fs.readdir(testDir, { withFileTypes: true }); @@ -64,6 +65,11 @@ async function main() { description: `run json-rpc specific e2e tests`, type: 'boolean', }) + .option('build-type', { + description: `Sets the build-type to test for. This may filter out tests.`, + type: 'string', + choices: Object.keys(loadBuildTypesConfig().buildTypes), + }) .option('retries', { description: 'Set how many times the test should be retried upon failure.', @@ -73,13 +79,25 @@ async function main() { .strict() .help('help'); - const { browser, debug, retries, snaps, mv3, rpc } = argv; + const { browser, debug, retries, snaps, mv3, rpc, buildType } = argv; let testPaths; if (snaps) { const testDir = path.join(__dirname, 'snaps'); testPaths = await getTestPathsForTestDir(testDir); + + if (buildType && buildType !== 'flask') { + // These tests should only be ran on Flask for now + const filteredTests = [ + 'test-snap-manageAccount.spec.js', + 'test-snap-rpc.spec.js', + 'test-snap-lifecycle.spec.js', + ]; + testPaths = testPaths.filter((p) => + filteredTests.every((filteredTest) => !p.endsWith(filteredTest)), + ); + } } else if (rpc) { const testDir = path.join(__dirname, 'json-rpc'); testPaths = await getTestPathsForTestDir(testDir); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 89bb21722..24f10128f 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -142,6 +142,13 @@ "allNftContracts": "object", "allNfts": "object", "ignoredNfts": "object", + "snapErrors": "object", + "snaps": "object", + "snapStates": "object", + "jobs": "object", + "database": "object", + "lastUpdated": "object", + "notifications": "object", "accounts": "object", "currentNetworkTxList": "object", "unapprovedDecryptMsgs": "object", diff --git a/yarn.lock b/yarn.lock index 126f606d0..c79fe20af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4707,22 +4707,22 @@ __metadata: languageName: node linkType: hard -"@metamask/rpc-methods@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/rpc-methods@npm:1.0.0" +"@metamask/rpc-methods@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/rpc-methods@npm:1.0.1" dependencies: "@metamask/browser-passworder": "npm:^4.0.2" "@metamask/key-tree": "npm:^7.1.1" "@metamask/permission-controller": "npm:^4.0.0" - "@metamask/snaps-ui": "npm:^1.0.0" - "@metamask/snaps-utils": "npm:^1.0.0" + "@metamask/snaps-ui": "npm:^1.0.1" + "@metamask/snaps-utils": "npm:^1.0.1" "@metamask/types": "npm:^1.1.0" "@metamask/utils": "npm:^6.0.1" "@noble/hashes": "npm:^1.1.3" eth-rpc-errors: "npm:^4.0.2" nanoid: "npm:^3.1.31" superstruct: "npm:^1.0.3" - checksum: 7e5f2900f9a54bcc112d9861eeb461de5a7803fdaa4e1bfee1c1c9f68a659dc42f56a7dbbc4f8147f66927c7192d1b5314cc32ca5d8985b969694582127b8fa8 + checksum: 2e88d739780361901820ae6ba683b14898dafaee8bb05b169f6074265a3c2aa60b9f66fdbc5003e65ed656bce3a2090c6f6d1ea800c1ac7f035dfa21adcccddd languageName: node linkType: hard @@ -4872,19 +4872,19 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/snaps-controllers@npm:1.0.0" +"@metamask/snaps-controllers@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-controllers@npm:1.0.1" dependencies: "@metamask/approval-controller": "npm:^3.0.0" "@metamask/base-controller": "npm:^3.0.0" "@metamask/object-multiplex": "npm:^1.2.0" "@metamask/permission-controller": "npm:^4.0.0" "@metamask/post-message-stream": "npm:^6.1.2" - "@metamask/rpc-methods": "npm:^1.0.0" - "@metamask/snaps-execution-environments": "npm:^1.0.0" + "@metamask/rpc-methods": "npm:^1.0.1" + "@metamask/snaps-execution-environments": "npm:^1.0.1" "@metamask/snaps-registry": "npm:^1.2.1" - "@metamask/snaps-utils": "npm:^1.0.0" + "@metamask/snaps-utils": "npm:^1.0.1" "@metamask/utils": "npm:^6.0.1" "@xstate/fsm": "npm:^2.0.0" concat-stream: "npm:^2.0.0" @@ -4898,7 +4898,7 @@ __metadata: pump: "npm:^3.0.0" readable-web-to-node-stream: "npm:^3.0.2" tar-stream: "npm:^2.2.0" - checksum: f9ab5a5f593d5d0e971e682d3b32758d30e4bb444ba48f2f66dcf662305ed5c38394fadab32c625d9d171025736637c15765e443d01ef6be247ab75875e0e2e5 + checksum: 68b779618e97e5b55c4899c08a48230a41bc64b5bc86330f75167396afe65e0f5eefc8db0a70a08866d713144fe02457dd2705e97d600f403c785ebcb5e12ef8 languageName: node linkType: hard @@ -4944,15 +4944,15 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/snaps-execution-environments@npm:1.0.0" +"@metamask/snaps-execution-environments@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-execution-environments@npm:1.0.1" dependencies: "@metamask/object-multiplex": "npm:^1.2.0" "@metamask/post-message-stream": "npm:^6.1.1" "@metamask/providers": "npm:^10.2.0" - "@metamask/rpc-methods": "npm:^1.0.0" - "@metamask/snaps-utils": "npm:^1.0.0" + "@metamask/rpc-methods": "npm:^1.0.1" + "@metamask/snaps-utils": "npm:^1.0.1" "@metamask/utils": "npm:^6.0.1" eth-rpc-errors: "npm:^4.0.3" json-rpc-engine: "npm:^6.1.0" @@ -4960,7 +4960,7 @@ __metadata: ses: "npm:^0.18.1" stream-browserify: "npm:^3.0.0" superstruct: "npm:^1.0.3" - checksum: 744af06aab2952da69efa6922eb886a6cdbbec0368b35d3d253ecedcc940001e08b2496aa87acfbfe88d7e38955c8e807e942a4c86fc6c01ed86ce44f2106180 + checksum: 69a7d94c3d9b4838f72330bb7956c1bf27eb856d481d66669ac2461294e1e8491108385b510a83ff02edb08eca3236889ef74482a3eaefc6e8bc540b3f7880c0 languageName: node linkType: hard @@ -5005,13 +5005,13 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-ui@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/snaps-ui@npm:1.0.0" +"@metamask/snaps-ui@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-ui@npm:1.0.1" dependencies: "@metamask/utils": "npm:^6.0.1" superstruct: "npm:^1.0.3" - checksum: 805d23c43eb9a5d7ed7d332c9f98187b755142aeb37129d29a5153d2c9bd995beb5508a4d7f26b9d958d403768decded133d8b0c9935d3ac691f6e26fa81c285 + checksum: c3eb4808ffeb94b4d7a7f52b5827ee866ebf488c2abab8599f1f002f85eb5b39d1e2ca639258f75e8b9cc850f969bddbdb2de5c491a8d4d7f3912de8ad26ec27 languageName: node linkType: hard @@ -5104,9 +5104,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/snaps-utils@npm:1.0.0" +"@metamask/snaps-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-utils@npm:1.0.1" dependencies: "@babel/core": "npm:^7.18.6" "@babel/types": "npm:^7.18.7" @@ -5115,7 +5115,7 @@ __metadata: "@metamask/permission-controller": "npm:^4.0.0" "@metamask/providers": "npm:^10.2.1" "@metamask/snaps-registry": "npm:^1.2.1" - "@metamask/snaps-ui": "npm:^1.0.0" + "@metamask/snaps-ui": "npm:^1.0.1" "@metamask/utils": "npm:^6.0.1" "@noble/hashes": "npm:^1.1.3" "@scure/base": "npm:^1.1.1" @@ -5128,7 +5128,7 @@ __metadata: ses: "npm:^0.18.7" superstruct: "npm:^1.0.3" validate-npm-package-name: "npm:^5.0.0" - checksum: daf2ff95c7fbd3c68ef47b3816aba9fbbe7363adc780500fe03b3b0b0ba23ca382e16feeb6deb909d458e08c035214e5819a48d8f7456499934299224f980b8f + checksum: 45ef36809b41c71b79bd49c19450efdb999c5b738ce0989b6880ae117b172d44519967334ade650ae6239c5068e71c019e096f6ac6155aa23bb26d98fe5294af languageName: node linkType: hard @@ -24287,18 +24287,18 @@ __metadata: "@metamask/ppom-validator": "npm:^0.2.0" "@metamask/providers": "npm:^11.1.0" "@metamask/rate-limit-controller": "npm:^3.0.0" - "@metamask/rpc-methods": "npm:^1.0.0" + "@metamask/rpc-methods": "npm:^1.0.1" "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.37.2-flask.1" "@metamask/safe-event-emitter": "npm:^2.0.0" "@metamask/scure-bip39": "npm:^2.0.3" "@metamask/signature-controller": "npm:^5.3.0" "@metamask/slip44": "npm:^3.0.0" "@metamask/smart-transactions-controller": "npm:^4.0.0" - "@metamask/snaps-controllers": "npm:^1.0.0" + "@metamask/snaps-controllers": "npm:^1.0.1" "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.38.0-flask.1" - "@metamask/snaps-ui": "npm:^1.0.0" + "@metamask/snaps-ui": "npm:^1.0.1" "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.37.3-flask.1" - "@metamask/snaps-utils": "npm:^1.0.0" + "@metamask/snaps-utils": "npm:^1.0.1" "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.38.0-flask.1" "@metamask/subject-metadata-controller": "npm:^2.0.0" "@metamask/test-dapp": "npm:^7.0.1" From 2529b360bef7f9ac114167d2a29ef5c165c4a7e9 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 17 Aug 2023 16:13:40 +0200 Subject: [PATCH 041/102] Track usage of snap exports (#20503) * Track usage of snap exports * Fix fencing * Small change to event name * Use MetaMetricsEventName --- app/scripts/metamask-controller.js | 54 ++++++++++++++++++++++++------ shared/constants/metametrics.ts | 5 +++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8daf37320..6bc0d9531 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -5,7 +5,12 @@ import { storeAsStream } from '@metamask/obs-store/dist/asStream'; import { JsonRpcEngine } from 'json-rpc-engine'; import { createEngineStream } from 'json-rpc-middleware-stream'; import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware'; -import { debounce } from 'lodash'; +import { + debounce, + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + throttle, + ///: END:ONLY_INCLUDE_IN +} from 'lodash'; import { keyringBuilderFactory } from '@metamask/eth-keyring-controller'; import { KeyringController } from '@metamask/keyring-controller'; import createFilterMiddleware from 'eth-json-rpc-filters'; @@ -1848,6 +1853,39 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(snaps) + /** + * Tracks snaps export usage. Note: This function is throttled to 1 call per 60 seconds. + * + * @param {string} handler - The handler to trigger on the snap for the request. + */ + _trackSnapExportUsage = throttle( + (handler) => + this.metaMetricsController.trackEvent({ + event: MetaMetricsEventName.SnapExportUsed, + category: MetaMetricsEventCategory.Snaps, + properties: { + export: handler, + }, + }), + SECOND * 60, + ); + + /** + * Passes a JSON-RPC request object to the SnapController for execution. + * + * @param {object} args - A bag of options. + * @param {string} args.snapId - The ID of the recipient snap. + * @param {string} args.origin - The origin of the RPC request. + * @param {string} args.handler - The handler to trigger on the snap for the request. + * @param {object} args.request - The JSON-RPC request object. + * @returns The result of the JSON-RPC request. + */ + handleSnapRequest(args) { + this._trackSnapExportUsage(args.handler); + + return this.controllerMessenger.call('SnapController:handleRequest', args); + } + /** * Constructor helper for getting Snap permission specifications. */ @@ -1869,10 +1907,7 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:get', ), - handleSnapRpcRequest: this.controllerMessenger.call.bind( - this.controllerMessenger, - 'SnapController:handleRequest', - ), + handleSnapRpcRequest: this.handleSnapRequest.bind(this), getSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:getSnapState', @@ -2024,7 +2059,7 @@ export default class MetamaskController extends EventEmitter { `${this.snapController.name}:snapInstalled`, (truncatedSnap) => { this.metaMetricsController.trackEvent({ - event: 'Snap Installed', + event: MetaMetricsEventName.SnapInstalled, category: MetaMetricsEventCategory.Snaps, properties: { snap_id: truncatedSnap.id, @@ -2038,7 +2073,7 @@ export default class MetamaskController extends EventEmitter { `${this.snapController.name}:snapUpdated`, (newSnap, oldVersion) => { this.metaMetricsController.trackEvent({ - event: 'Snap Updated', + event: MetaMetricsEventName.SnapUpdated, category: MetaMetricsEventCategory.Snaps, properties: { snap_id: newSnap.id, @@ -2607,10 +2642,7 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:remove', ), - handleSnapRequest: this.controllerMessenger.call.bind( - this.controllerMessenger, - 'SnapController:handleRequest', - ), + handleSnapRequest: this.handleSnapRequest.bind(this), revokeDynamicSnapPermissions: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:revokeDynamicPermissions', diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index cbbca92ca..3b2ed005b 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -625,6 +625,11 @@ export enum MetaMetricsEventName { TransactionFinalized = 'Transaction Finalized', ExitedSwaps = 'Exited Swaps', SwapError = 'Swap Error', + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + SnapInstalled = 'Snap Installed', + SnapUpdated = 'Snap Updated', + SnapExportUsed = 'Snap Export Used', + ///: END:ONLY_INCLUDE_IN } export enum MetaMetricsEventAccountType { From 4cf886f710c5cb16e5dc9d0504330bf404594307 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 12:22:37 -0230 Subject: [PATCH 042/102] Initialize composable observable store after update (#20468) * Initialize composable observable store after update The composable observable store now updates state immediately when the structure is updated. Previously each store would only be updated after the first state change. This ensures that the composable observable store state is always complete. * SUpport falsy controller state We now use the nullish coalescing operator when checkint store.state, so that we don't accidentally ignore falsy state. Co-authored-by: Frederik Bolding * Add test for falsy controller state * Update state snapshots A change on `develop` required another state update. --------- Co-authored-by: Frederik Bolding --- app/scripts/lib/ComposableObservableStore.js | 4 + .../lib/ComposableObservableStore.test.js | 40 ++++++++++ ...rs-after-init-opt-in-background-state.json | 80 +++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js index ff722a82d..80d9c483d 100644 --- a/app/scripts/lib/ComposableObservableStore.js +++ b/app/scripts/lib/ComposableObservableStore.js @@ -51,6 +51,7 @@ export default class ComposableObservableStore extends ObservableStore { updateStructure(config) { this.config = config; this.removeAllListeners(); + const initialState = {}; for (const key of Object.keys(config)) { if (!config[key]) { throw new Error(`Undefined '${key}'`); @@ -72,7 +73,10 @@ export default class ComposableObservableStore extends ObservableStore { }, ); } + + initialState[key] = store.state ?? store.getState?.(); } + this.updateState(initialState); } /** diff --git a/app/scripts/lib/ComposableObservableStore.test.js b/app/scripts/lib/ComposableObservableStore.test.js index b6a58f1e1..bb3dd48fd 100644 --- a/app/scripts/lib/ComposableObservableStore.test.js +++ b/app/scripts/lib/ComposableObservableStore.test.js @@ -120,6 +120,46 @@ describe('ComposableObservableStore', () => { }); }); + it('should initialize state with all three types of stores', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + const exampleController = new ExampleController({ + messenger: controllerMessenger, + }); + const oldExampleController = new OldExampleController(); + exampleStore.putState('state'); + exampleController.updateBar('state'); + oldExampleController.updateBaz('state'); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleController, + OldExample: oldExampleController, + Store: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: { bar: 'state' }, + OldExample: { baz: 'state' }, + Store: 'state', + }); + }); + + it('should initialize falsy state', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + exampleStore.putState(false); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: false, + }); + }); + it('should return flattened state', () => { const controllerMessenger = new ControllerMessenger(); const fooStore = new ObservableStore({ foo: 'foo' }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 2f29cc98c..583be2020 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,5 +1,18 @@ { "AccountTracker": { "accounts": "object" }, + "AddressBookController": "object", + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppMetadataController": { + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94 + }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, @@ -24,6 +37,7 @@ }, "ApprovalController": "object", "CachedBalancesController": "object", + "CronjobController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, @@ -42,6 +56,22 @@ "unapprovedEncryptionPublicKeyMsgCount": 0 }, "EnsController": "object", + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { + "isUnlocked": false, + "keyringTypes": "object", + "keyrings": "object" + }, "MetaMetricsController": { "participateInMetaMetrics": true, "metaMetricsId": "fake-metrics-id", @@ -66,6 +96,51 @@ "networksMetadata": "object", "networkConfigurations": "object" }, + "NftController": "object", + "NotificationController": "object", + "OnboardingController": { + "seedPhraseBackedUp": true, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "onboardingTabs": "object" + }, + "PermissionController": "object", + "PermissionLogController": "object", + "PreferencesController": { + "useBlockie": false, + "useNonceField": false, + "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "use4ByteResolution": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "featureFlags": { "showIncomingTransactions": true }, + "knownMethodData": "object", + "currentLocale": "en", + "identities": "object", + "lostIdentities": "object", + "forgottenPassword": false, + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string" + }, "SignatureController": { "unapprovedMsgs": "object", "unapprovedPersonalMsgs": "object", @@ -74,7 +149,12 @@ "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 }, + "SmartTransactionsController": "object", + "SnapController": "object", + "SnapsRegistry": "object", + "SubjectMetadataController": "object", "SwapsController": "object", + "TokenListController": "object", "TokenRatesController": "object", "TokensController": "object", "TxController": "object" From 861c30de299cfe5d3c43c043a588ace37e1d0a6a Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Thu, 17 Aug 2023 20:50:07 +0530 Subject: [PATCH 043/102] Updating blockaid related packages (#20480) --- lavamoat/browserify/flask/policy.json | 1 + package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index e725d3af4..d56789b6a 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -2013,6 +2013,7 @@ }, "@metamask/ppom-validator": { "globals": { + "URL": true, "clearInterval": true, "console.error": true, "setInterval": true diff --git a/package.json b/package.json index 9b4589258..5e7113b42 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ }, "dependencies": { "@babel/runtime": "^7.18.9", - "@blockaid/ppom": "^1.0.2", + "@blockaid/ppom": "^1.1.1", "@download/blockies": "^1.0.3", "@ensdomains/content-hash": "^2.5.6", "@ethereumjs/common": "^3.1.1", @@ -258,7 +258,7 @@ "@metamask/permission-controller": "^4.0.0", "@metamask/phishing-controller": "^6.0.0", "@metamask/post-message-stream": "^6.0.0", - "@metamask/ppom-validator": "^0.2.0", + "@metamask/ppom-validator": "^0.3.0", "@metamask/providers": "^11.1.0", "@metamask/rate-limit-controller": "^3.0.0", "@metamask/rpc-methods": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index c79fe20af..cd2739145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1619,10 +1619,10 @@ __metadata: languageName: node linkType: hard -"@blockaid/ppom@npm:^1.0.2": - version: 1.0.2 - resolution: "@blockaid/ppom@npm:1.0.2" - checksum: d288117f19400108b0177df63d6accc3e06a60f203d36b9e3b1d8cabc55e167674cb000d1fe50971a7fe73662ca55659211682a0d20e676c66ccba112043d9fb +"@blockaid/ppom@npm:^1.1.1": + version: 1.1.1 + resolution: "@blockaid/ppom@npm:1.1.1" + checksum: d12ae3ee9e91ce8719381f828834b9dc438671482608fc39a82c854e203be327b5d7feaaac378b7eb8132e0596e202a699f889bbf863ae26f45fa4d44512b4b3 languageName: node linkType: hard @@ -4589,16 +4589,16 @@ __metadata: languageName: node linkType: hard -"@metamask/ppom-validator@npm:^0.2.0": - version: 0.2.0 - resolution: "@metamask/ppom-validator@npm:0.2.0" +"@metamask/ppom-validator@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ppom-validator@npm:0.3.0" dependencies: "@metamask/base-controller": "npm:^3.0.0" "@metamask/controller-utils": "npm:^4.0.0" await-semaphore: "npm:^0.1.3" elliptic: "npm:^6.5.4" json-rpc-random-id: "npm:^1.0.1" - checksum: c86e3d2aa8321347ffaf45290d9c8b0d4f3a594c88dc2090933b6159a604c2f574360462faba7cbda7fa1f32f730d159f90847bdd47badb0dc0cd027be0db9e8 + checksum: 669d92799d1f143e48a887e4a6213d25f1fd0cc64445a7445ac2c04ac9ef92f62f954e7323a0ebd5276e8752d10d83c174328f412edfe09e8b33cf21fb5325c9 languageName: node linkType: hard @@ -24221,7 +24221,7 @@ __metadata: "@babel/preset-typescript": "npm:^7.16.7" "@babel/register": "npm:^7.5.5" "@babel/runtime": "npm:^7.18.9" - "@blockaid/ppom": "npm:^1.0.2" + "@blockaid/ppom": "npm:^1.1.1" "@download/blockies": "npm:^1.0.3" "@ensdomains/content-hash": "npm:^2.5.6" "@ethereumjs/common": "npm:^3.1.1" @@ -24284,7 +24284,7 @@ __metadata: "@metamask/phishing-controller": "npm:^6.0.0" "@metamask/phishing-warning": "npm:^2.1.0" "@metamask/post-message-stream": "npm:^6.0.0" - "@metamask/ppom-validator": "npm:^0.2.0" + "@metamask/ppom-validator": "npm:^0.3.0" "@metamask/providers": "npm:^11.1.0" "@metamask/rate-limit-controller": "npm:^3.0.0" "@metamask/rpc-methods": "npm:^1.0.1" From 6c505878787fca0ebf3bc15e158aef66ab0b3f48 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 13:32:45 -0230 Subject: [PATCH 044/102] Add flag to update E2E snapshots (#20514) The E2E test scripts now have a flag for updating E2E test snapshots. The flag has been documented as well. This makes it easier to update snapshots and raises visbility of this feature. --- README.md | 20 +++++++++++--------- test/e2e/run-all.js | 20 +++++++++++++++++++- test/e2e/run-e2e-test.js | 11 +++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b8e257d1d..fcd7cadd7 100644 --- a/README.md +++ b/README.md @@ -88,15 +88,17 @@ These test scripts all support additional options, which might be helpful for de Single e2e tests can be run with `yarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js` along with the options below. ```console - --browser Set the browser used; either 'chrome' or 'firefox'. - [string] [choices: "chrome", "firefox"] - --debug Run tests in debug mode, logging each driver interaction - [boolean] [default: false] - --retries Set how many times the test should be retried upon failure. - [number] [default: 0] - --leave-running Leaves the browser running after a test fails, along with - anything else that the test used (ganache, the test dapp, - etc.) [boolean] [default: false] + --browser Set the browser used; either 'chrome' or 'firefox'. + [string] [choices: "chrome", "firefox"] + --debug Run tests in debug mode, logging each driver interaction + [boolean] [default: false] + --retries Set how many times the test should be retried upon failure. + [number] [default: 0] + --leave-running Leaves the browser running after a test fails, along with + anything else that the test used (ganache, the test dapp, + etc.) [boolean] [default: false] + --update-snapshot Update E2E test snapshots + [alias: -u] [boolean] [default: false] ``` For example, to run the `account-details` tests using Chrome, with debug logging and with the browser set to remain open upon failure, you would use: diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index 2556330e0..811f6b8fb 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -74,12 +74,27 @@ async function main() { description: 'Set how many times the test should be retried upon failure.', type: 'number', + }) + .option('update-snapshot', { + alias: 'u', + default: false, + description: 'Update E2E snapshots', + type: 'boolean', }), ) .strict() .help('help'); - const { browser, debug, retries, snaps, mv3, rpc, buildType } = argv; + const { + browser, + debug, + retries, + snaps, + mv3, + rpc, + buildType, + updateSnapshot, + } = argv; let testPaths; @@ -130,6 +145,9 @@ async function main() { if (debug) { args.push('--debug'); } + if (updateSnapshot) { + args.push('--update-snapshot'); + } // For running E2Es in parallel in CI const currentChunkIndex = process.env.CIRCLE_NODE_INDEX ?? 0; diff --git a/test/e2e/run-e2e-test.js b/test/e2e/run-e2e-test.js index 1c2c60362..a7b8f8602 100644 --- a/test/e2e/run-e2e-test.js +++ b/test/e2e/run-e2e-test.js @@ -42,6 +42,12 @@ async function main() { 'Leaves the browser running after a test fails, along with anything else that the test used (ganache, the test dapp, etc.)', type: 'boolean', }) + .option('update-snapshot', { + alias: 'u', + default: false, + description: 'Update E2E snapshots', + type: 'boolean', + }) .positional('e2e-test-path', { describe: 'The path for the E2E test to run.', type: 'string', @@ -58,6 +64,7 @@ async function main() { retries, retryUntilFailure, leaveRunning, + updateSnapshot, } = argv; if (!browser) { @@ -103,6 +110,10 @@ async function main() { exit = '--no-exit'; } + if (updateSnapshot) { + process.env.UPDATE_SNAPSHOTS = 'true'; + } + const configFile = path.join(__dirname, '.mocharc.js'); const extraArgs = process.env.E2E_ARGS?.split(' ') || []; From 80746e67b50219f12d736f276c94063b79043d25 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 09:29:05 -0230 Subject: [PATCH 045/102] Improve Sentry state pre-initialization (#20491) * Improve Sentry state pre-initialization Previously the masked state snapshot sent to Sentry would be blank for errors that occured during initialization. Instead we'll now include some basic information in all cases, and a masked copy of the persisted state if it happens after the first time the persisted state is read. * Add test * Fix crash when persisted state not yet fetched * Add descriptions for initial state hooks * Update comments to reflect recent changes * Re-order imports to follow conventions * Move initial state hooks back to module-level The initial state hooks are now setup at the top-level of their module. This ensures that they're setup prior to later imports. Calling a function to setup these hooks in the entrypoint module wouldn't accomplish this even if it was run "before" the imports because ES6 imports always get hoisted to the top of the file. The `localStore` instance wasn't available statically, so a new state hook was introduced for retrieving the most recent retrieved persisted state. * Fix error e2e tests --- app/scripts/background.js | 27 ++--- app/scripts/lib/local-store.js | 3 + app/scripts/lib/local-store.test.js | 57 +++++++-- app/scripts/lib/network-store.js | 2 + app/scripts/lib/setup-initial-state-hooks.js | 61 ++++++++++ app/scripts/lib/setup-persisted-state-hook.js | 10 -- app/scripts/ui.js | 18 ++- test/e2e/tests/errors.spec.js | 58 ++++++--- ...s-before-init-opt-in-background-state.json | 112 ++++++++++++++++- .../errors-before-init-opt-in-ui-state.json | 113 +++++++++++++++++- ui/index.js | 9 +- 11 files changed, 408 insertions(+), 62 deletions(-) create mode 100644 app/scripts/lib/setup-initial-state-hooks.js delete mode 100644 app/scripts/lib/setup-persisted-state-hook.js diff --git a/app/scripts/background.js b/app/scripts/background.js index f4e46c9f0..7e89ce1c3 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -2,9 +2,11 @@ * @file The entry point for the web extension singleton process. */ -// This import sets up a global function required for Sentry to function. +// Disabled to allow setting up initial state hooks first + +// This import sets up global functions required for Sentry to function. // It must be run first in case an error is thrown later during initialization. -import './lib/setup-persisted-state-hook'; +import './lib/setup-initial-state-hooks'; import EventEmitter from 'events'; import endOfStream from 'end-of-stream'; @@ -69,6 +71,12 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; + const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -92,9 +100,6 @@ const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; -// state persistence -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { @@ -889,17 +894,9 @@ browser.runtime.onInstalled.addListener(({ reason }) => { }); function setupSentryGetStateGlobal(store) { - global.stateHooks.getSentryState = function () { + global.stateHooks.getSentryAppState = function () { const backgroundState = store.memStore.getState(); - const maskedBackgroundState = maskObject( - backgroundState, - SENTRY_BACKGROUND_STATE, - ); - return { - browser: window.navigator.userAgent, - store: maskedBackgroundState, - version: platform.getVersion(), - }; + return maskObject(backgroundState, SENTRY_BACKGROUND_STATE); }; } diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index b0a223561..40908af5e 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -16,6 +16,7 @@ export default class ExtensionStore { // once data persistence fails once and it flips true we don't send further // data persistence errors to sentry this.dataPersistenceFailing = false; + this.mostRecentRetrievedState = null; } setMetadata(initMetaData) { @@ -66,8 +67,10 @@ export default class ExtensionStore { // extension.storage.local always returns an obj // if the object is empty, treat it as undefined if (isEmpty(result)) { + this.mostRecentRetrievedState = null; return undefined; } + this.mostRecentRetrievedState = result; return result; } diff --git a/app/scripts/lib/local-store.test.js b/app/scripts/lib/local-store.test.js index 2c3cea405..8b786ca81 100644 --- a/app/scripts/lib/local-store.test.js +++ b/app/scripts/lib/local-store.test.js @@ -2,11 +2,12 @@ import browser from 'webextension-polyfill'; import LocalStore from './local-store'; jest.mock('webextension-polyfill', () => ({ + runtime: { lastError: null }, storage: { local: true }, })); -const setup = ({ isSupported }) => { - browser.storage.local = isSupported; +const setup = ({ localMock = jest.fn() } = {}) => { + browser.storage.local = localMock; return new LocalStore(); }; describe('LocalStore', () => { @@ -15,21 +16,27 @@ describe('LocalStore', () => { }); describe('contructor', () => { it('should set isSupported property to false when browser does not support local storage', () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); expect(localStore.isSupported).toBe(false); }); it('should set isSupported property to true when browser supports local storage', () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); expect(localStore.isSupported).toBe(true); }); + + it('should initialize mostRecentRetrievedState to null', () => { + const localStore = setup({ localMock: false }); + + expect(localStore.mostRecentRetrievedState).toBeNull(); + }); }); describe('setMetadata', () => { it('should set the metadata property on LocalStore', () => { const metadata = { version: 74 }; - const localStore = setup({ isSupported: true }); + const localStore = setup(); localStore.setMetadata(metadata); expect(localStore.metadata).toStrictEqual(metadata); @@ -38,21 +45,21 @@ describe('LocalStore', () => { describe('set', () => { it('should throw an error if called in a browser that does not support local storage', async () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); await expect(() => localStore.set()).rejects.toThrow( 'Metamask- cannot persist state to local store as this browser does not support this action', ); }); it('should throw an error if not passed a truthy value as an argument', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); await expect(() => localStore.set()).rejects.toThrow( 'MetaMask - updated state is missing', ); }); it('should throw an error if passed a valid argument but metadata has not yet been set', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); await expect(() => localStore.set({ appState: { test: true } }), ).rejects.toThrow( @@ -61,7 +68,7 @@ describe('LocalStore', () => { }); it('should not throw if passed a valid argument and metadata has been set', async () => { - const localStore = setup({ isSupported: true }); + const localStore = setup(); localStore.setMetadata({ version: 74 }); await expect(async function () { localStore.set({ appState: { test: true } }); @@ -71,9 +78,39 @@ describe('LocalStore', () => { describe('get', () => { it('should return undefined if called in a browser that does not support local storage', async () => { - const localStore = setup({ isSupported: false }); + const localStore = setup({ localMock: false }); const result = await localStore.get(); expect(result).toStrictEqual(undefined); }); + + it('should update mostRecentRetrievedState', async () => { + const localStore = setup({ + localMock: { + get: jest + .fn() + .mockImplementation(() => + Promise.resolve({ appState: { test: true } }), + ), + }, + }); + + await localStore.get(); + + expect(localStore.mostRecentRetrievedState).toStrictEqual({ + appState: { test: true }, + }); + }); + + it('should reset mostRecentRetrievedState to null if storage.local is empty', async () => { + const localStore = setup({ + localMock: { + get: jest.fn().mockImplementation(() => Promise.resolve({})), + }, + }); + + await localStore.get(); + + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + }); }); }); diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index ea6ba5876..2f4c0a1b0 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -15,6 +15,7 @@ export default class ReadOnlyNetworkStore { this._initialized = false; this._initializing = this._init(); this._state = undefined; + this.mostRecentRetrievedState = null; } /** @@ -30,6 +31,7 @@ export default class ReadOnlyNetworkStore { const response = await fetchWithTimeout(FIXTURE_SERVER_URL); if (response.ok) { this._state = await response.json(); + this.mostRecentRetrievedState = this._state; } } catch (error) { log.debug(`Error loading network state: '${error.message}'`); diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js new file mode 100644 index 000000000..fa02987a7 --- /dev/null +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -0,0 +1,61 @@ +import { maskObject } from '../../../shared/modules/object.utils'; +import ExtensionPlatform from '../platforms/extension'; +import LocalStore from './local-store'; +import ReadOnlyNetworkStore from './network-store'; +import { SENTRY_BACKGROUND_STATE } from './setupSentry'; + +const platform = new ExtensionPlatform(); +const localStore = process.env.IN_TEST + ? new ReadOnlyNetworkStore() + : new LocalStore(); + +/** + * Get the persisted wallet state. + * + * @returns The persisted wallet state. + */ +globalThis.stateHooks.getPersistedState = async function () { + return await localStore.get(); +}; + +const persistedStateMask = { + data: SENTRY_BACKGROUND_STATE, + meta: { + version: true, + }, +}; + +/** + * Get a state snapshot to include with Sentry error reports. This uses the + * persisted state pre-initialization, and the in-memory state post- + * initialization. In both cases the state is anonymized. + * + * @returns A Sentry state snapshot. + */ +globalThis.stateHooks.getSentryState = function () { + const sentryState = { + browser: window.navigator.userAgent, + version: platform.getVersion(), + }; + if (globalThis.stateHooks.getSentryAppState) { + return { + ...sentryState, + state: globalThis.stateHooks.getSentryAppState(), + }; + } else if (globalThis.stateHooks.getMostRecentPersistedState) { + const persistedState = globalThis.stateHooks.getMostRecentPersistedState(); + if (persistedState) { + return { + ...sentryState, + persistedState: maskObject( + // `getMostRecentPersistedState` is used here instead of + // `getPersistedState` to avoid making this an asynchronous function. + globalThis.stateHooks.getMostRecentPersistedState(), + persistedStateMask, + ), + }; + } + return sentryState; + } + return sentryState; +}; diff --git a/app/scripts/lib/setup-persisted-state-hook.js b/app/scripts/lib/setup-persisted-state-hook.js deleted file mode 100644 index 9b29fad26..000000000 --- a/app/scripts/lib/setup-persisted-state-hook.js +++ /dev/null @@ -1,10 +0,0 @@ -import LocalStore from './local-store'; -import ReadOnlyNetworkStore from './network-store'; - -const localStore = process.env.IN_TEST - ? new ReadOnlyNetworkStore() - : new LocalStore(); - -globalThis.stateHooks.getPersistedState = async function () { - return await localStore.get(); -}; diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 565e8187a..aeb93cacb 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -1,13 +1,15 @@ +// Disabled to allow setting up initial state hooks first + +// This import sets up global functions required for Sentry to function. +// It must be run first in case an error is thrown later during initialization. +import './lib/setup-initial-state-hooks'; + // polyfills import '@formatjs/intl-relativetimeformat/polyfill'; // dev only, "react-devtools" import is skipped in prod builds import 'react-devtools'; -// This import sets up a global function required for Sentry to function. -// It must be run first in case an error is thrown later during initialization. -import './lib/setup-persisted-state-hook'; - import PortStream from 'extension-port-stream'; import browser from 'webextension-polyfill'; @@ -34,6 +36,14 @@ import ExtensionPlatform from './platforms/extension'; import { setupMultiplex } from './lib/stream-utils'; import { getEnvironmentType, getPlatform } from './lib/util'; import metaRPCClientFactory from './lib/metaRPCClientFactory'; +import LocalStore from './lib/local-store'; +import ReadOnlyNetworkStore from './lib/network-store'; + +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; const container = document.getElementById('app-content'); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 3098a5083..5f2128d32 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -197,9 +197,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Wait for Sentry request @@ -286,8 +286,23 @@ describe('Sentry errors', function () { const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'version', + 'persistedState', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); await matchesSnapshot({ - data: transformBackgroundState(appState), + data: transformBackgroundState(appState.persistedState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -311,9 +326,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Trigger error @@ -354,9 +369,9 @@ describe('Sentry errors', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await driver.findElement('#password'); - // Erase `getSentryState` hook, simulating a "before initialization" state + // Erase `getSentryAppState` hook, simulating a "before initialization" state await driver.executeScript( - 'window.stateHooks.getSentryState = undefined', + 'window.stateHooks.getSentryAppState = undefined', ); // Trigger error @@ -371,8 +386,23 @@ describe('Sentry errors', function () { const mockTextBody = mockedRequest.body.text.split('\n'); const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; + assert.deepStrictEqual(Object.keys(appState), [ + 'browser', + 'version', + 'persistedState', + ]); + assert.ok( + typeof appState?.browser === 'string' && + appState?.browser.length > 0, + 'Invalid browser state', + ); + assert.ok( + typeof appState?.version === 'string' && + appState?.version.length > 0, + 'Invalid version state', + ); await matchesSnapshot({ - data: transformUiState(appState), + data: transformBackgroundState(appState.persistedState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -481,7 +511,7 @@ describe('Sentry errors', function () { const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; const { participateInMetaMetrics } = - extra.appState.store.MetaMetricsController; + extra.appState.state.MetaMetricsController; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -525,8 +555,8 @@ describe('Sentry errors', function () { const appState = mockJsonBody?.extra?.appState; assert.deepStrictEqual(Object.keys(appState), [ 'browser', - 'store', 'version', + 'state', ]); assert.ok( typeof appState?.browser === 'string' && @@ -539,7 +569,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundState(appState.store), + data: transformBackgroundState(appState.state), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -577,7 +607,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; - const { participateInMetaMetrics } = extra.appState.store.metamask; + const { participateInMetaMetrics } = extra.appState.state.metamask; // Verify request assert.equal(type, 'TestError'); assert.equal(value, 'Test Error'); @@ -619,8 +649,8 @@ describe('Sentry errors', function () { const appState = mockJsonBody?.extra?.appState; assert.deepStrictEqual(Object.keys(appState), [ 'browser', - 'store', 'version', + 'state', ]); assert.ok( typeof appState?.browser === 'string' && @@ -633,7 +663,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformUiState(appState.store), + data: transformUiState(appState.state), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 0967ef424..422bcb0e2 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -1 +1,111 @@ -{} +{ + "data": { + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppStateController": { + "browserEnvironment": "object", + "nftsDropdownState": "object", + "connectedStatusPopoverHasBeenShown": true, + "termsOfUseLastAgreed": "number", + "defaultHomeActiveTabName": null, + "fullScreenGasPollTokens": "object", + "notificationGasPollTokens": "object", + "popupGasPollTokens": "object", + "qrHardware": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "showTestnetMessageInDropdown": "boolean", + "trezorModel": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean" + }, + "CachedBalancesController": "object", + "CurrencyController": { + "conversionDate": 1665507600, + "conversionRate": 1300, + "currentCurrency": "usd", + "nativeCurrency": "ETH", + "usdConversionRate": "number" + }, + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { "vault": "string" }, + "MetaMetricsController": { + "eventsBeforeMetricsOptIn": "object", + "fragments": "object", + "metaMetricsId": "fake-metrics-id", + "participateInMetaMetrics": true, + "traits": "object" + }, + "NetworkController": { + "networkId": "1337", + "selectedNetworkClientId": "string", + "networksMetadata": "object", + "providerConfig": { + "chainId": "string", + "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", + "ticker": "ETH", + "type": "rpc", + "id": "string" + }, + "networkConfigurations": "object" + }, + "OnboardingController": { + "completedOnboarding": true, + "firstTimeFlowType": "import", + "onboardingTabs": "object", + "seedPhraseBackedUp": true + }, + "PermissionController": "object", + "PreferencesController": { + "advancedGasFee": "object", + "currentLocale": "en", + "dismissSeedBackUpReminder": "boolean", + "featureFlags": { "showIncomingTransactions": true }, + "forgottenPassword": false, + "identities": "object", + "infuraBlocked": "boolean", + "ipfsGateway": "dweb.link", + "knownMethodData": "object", + "ledgerTransportType": "string", + "lostIdentities": "object", + "openSeaEnabled": "boolean", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "selectedAddress": "string", + "theme": "string", + "useBlockie": false, + "useNftDetection": "boolean", + "useNonceField": false, + "usePhishDetect": true, + "useTokenDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "useMultiAccountBalanceChecker": "boolean" + }, + "SmartTransactionsController": "object", + "SubjectMetadataController": "object", + "TokensController": "object", + "TransactionController": "object", + "config": "object", + "firstTimeInfo": "object" + } +} diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 0967ef424..0a9fac1d0 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -1 +1,112 @@ -{} +{ + "data": { + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppStateController": { + "browserEnvironment": "object", + "nftsDropdownState": "object", + "connectedStatusPopoverHasBeenShown": true, + "termsOfUseLastAgreed": "number", + "defaultHomeActiveTabName": null, + "fullScreenGasPollTokens": "object", + "notificationGasPollTokens": "object", + "popupGasPollTokens": "object", + "qrHardware": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "showTestnetMessageInDropdown": "boolean", + "trezorModel": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean" + }, + "CachedBalancesController": "object", + "CurrencyController": { + "conversionDate": 1665507600, + "conversionRate": 1300, + "currentCurrency": "usd", + "nativeCurrency": "ETH", + "usdConversionRate": "number" + }, + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { "vault": "string" }, + "MetaMetricsController": { + "eventsBeforeMetricsOptIn": "object", + "fragments": "object", + "metaMetricsId": "fake-metrics-id", + "participateInMetaMetrics": true, + "traits": "object" + }, + "NetworkController": { + "networkId": "1337", + "selectedNetworkClientId": "string", + "networksMetadata": "object", + "providerConfig": { + "chainId": "string", + "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", + "ticker": "ETH", + "type": "rpc", + "id": "string" + }, + "networkConfigurations": "object" + }, + "OnboardingController": { + "completedOnboarding": true, + "firstTimeFlowType": "import", + "onboardingTabs": "object", + "seedPhraseBackedUp": true + }, + "PermissionController": "object", + "PreferencesController": { + "advancedGasFee": "object", + "currentLocale": "en", + "dismissSeedBackUpReminder": "boolean", + "featureFlags": { "showIncomingTransactions": true }, + "forgottenPassword": false, + "identities": "object", + "infuraBlocked": "boolean", + "ipfsGateway": "dweb.link", + "knownMethodData": "object", + "ledgerTransportType": "string", + "lostIdentities": "object", + "openSeaEnabled": "boolean", + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "selectedAddress": "string", + "theme": "string", + "useBlockie": false, + "useNftDetection": "boolean", + "useNonceField": false, + "usePhishDetect": true, + "useTokenDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "useMultiAccountBalanceChecker": "boolean" + }, + "SmartTransactionsController": "object", + "SubjectMetadataController": "object", + "TokensController": "object", + "TransactionController": "object", + "config": "object", + "firstTimeInfo": "object" + }, + "meta": { "version": 74 } +} diff --git a/ui/index.js b/ui/index.js index 6f9a148ed..de1432a80 100644 --- a/ui/index.js +++ b/ui/index.js @@ -233,14 +233,9 @@ function setupStateHooks(store) { }); return state; }; - window.stateHooks.getSentryState = function () { + window.stateHooks.getSentryAppState = function () { const reduxState = store.getState(); - const maskedReduxState = maskObject(reduxState, SENTRY_UI_STATE); - return { - browser: window.navigator.userAgent, - store: maskedReduxState, - version: global.platform.getVersion(), - }; + return maskObject(reduxState, SENTRY_UI_STATE); }; } From 2cd60d94e8a3c947cb2ff97d0acb9789c6ffa654 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 09:04:30 -0230 Subject: [PATCH 046/102] Remove invalid `tokensChainsCache` state (#20495) Migration #77 would set the `TokenListController.tokensChainsCache` state to `undefined` if it wasn't already set to anything when that migration was run. This is probably harmless except that it results in Sentry errors during migrations, and it results in that property having a value (at least temporarily) that doesn't match its type. Migration #77 has been updated to prevent this property from being set to `undefined` going forward. A new migration has been added to delete this value for any users already affected by this problem. The new migration was named "92.1" so that it could run after 92 but before 93, to make backporting this to v10.34.x easier (v10.34.x is currently on migration 92). "92.1" is still a valid number so this should work just as well as a whole number. --- app/scripts/migrations/077.js | 21 ++- app/scripts/migrations/077.test.js | 70 +++++++++ app/scripts/migrations/092.1.test.ts | 139 ++++++++++++++++++ app/scripts/migrations/092.1.ts | 53 +++++++ app/scripts/migrations/index.js | 2 + test/e2e/tests/errors.spec.js | 2 - .../errors-after-init-opt-in-ui-state.json | 1 + 7 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 app/scripts/migrations/092.1.test.ts create mode 100644 app/scripts/migrations/092.1.ts diff --git a/app/scripts/migrations/077.js b/app/scripts/migrations/077.js index 5a5d4f1ac..9adb293ba 100644 --- a/app/scripts/migrations/077.js +++ b/app/scripts/migrations/077.js @@ -1,4 +1,6 @@ import { cloneDeep } from 'lodash'; +import log from 'loglevel'; +import { hasProperty, isObject } from '@metamask/utils'; import transformState077For082 from './077-supplements/077-supplement-for-082'; import transformState077For084 from './077-supplements/077-supplement-for-084'; import transformState077For086 from './077-supplements/077-supplement-for-086'; @@ -29,8 +31,23 @@ export default { }; function transformState(state) { - const TokenListController = state?.TokenListController || {}; - + if (!hasProperty(state, 'TokenListController')) { + log.warn('Skipping migration, TokenListController state is missing'); + return state; + } else if (!isObject(state.TokenListController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); + return state; + } else if (!hasProperty(state.TokenListController, 'tokensChainsCache')) { + log.warn( + 'Skipping migration, TokenListController.tokensChainsCache state is missing', + ); + return state; + } + const { TokenListController } = state; const { tokensChainsCache } = TokenListController; let dataCache; diff --git a/app/scripts/migrations/077.test.js b/app/scripts/migrations/077.test.js index 53efb5cd5..67de1a8fa 100644 --- a/app/scripts/migrations/077.test.js +++ b/app/scripts/migrations/077.test.js @@ -1,11 +1,22 @@ +import { cloneDeep } from 'lodash'; import migration77 from './077'; +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + describe('migration #77', () => { it('should update the version metadata', async () => { const oldStorage = { meta: { version: 76, }, + data: {}, }; const newStorage = await migration77.migrate(oldStorage); @@ -13,6 +24,65 @@ describe('migration #77', () => { version: 77, }); }); + + it('should return state unchanged if token list controller is missing', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + Foo: { + bar: 'baz', + }, + }, + }; + + const newStorage = await migration77.migrate(cloneDeep(oldStorage)); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('should capture an exception if the TokenListController state is invalid', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + TokenListController: 'test', + }, + }; + + await migration77.migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is string`), + ); + }); + + it('should return state unchanged if tokenChainsCache is missing', async () => { + const oldStorage = { + meta: { + version: 76, + }, + data: { + TokenListController: { + tokenList: { + '0x514910771af9ca656af840dff83e8264ecf986ca': { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + symbol: 'LINK', + decimals: 18, + }, + }, + }, + }, + }; + + const newStorage = await migration77.migrate(cloneDeep(oldStorage)); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + it('should change the data from array to object for a single network', async () => { const oldStorage = { meta: { diff --git a/app/scripts/migrations/092.1.test.ts b/app/scripts/migrations/092.1.test.ts new file mode 100644 index 000000000..7ab1045ca --- /dev/null +++ b/app/scripts/migrations/092.1.test.ts @@ -0,0 +1,139 @@ +import { cloneDeep } from 'lodash'; +import { migrate, version } from './092.1'; + +const PREVIOUS_VERSION = 92; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + startSession: jest.fn(), + endSession: jest.fn(), + toggleSession: jest.fn(), + captureException: sentryCaptureExceptionMock, +}; + +describe('migration #92.1', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should update the version metadata', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ + version, + }); + }); + + it('should return state unaltered if there is no TokenListController state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('captures an exception if the TokenListController state is invalid', async () => { + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: { TokenListController: 'this is not valid' }, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.TokenListController is string`), + ); + }); + + it('should return state unaltered if there is no TokenListController tokensChainsCache state', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokenList: {}, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should return state unaltered if the tokensChainsCache state is an unexpected type', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: 'unexpected string', + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should return state unaltered if the tokensChainsCache state is valid', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: {}, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should remove undefined tokensChainsCache state', async () => { + const oldData = { + other: 'data', + TokenListController: { + tokensChainsCache: undefined, + }, + }; + const oldStorage = { + meta: { + version: PREVIOUS_VERSION, + }, + data: oldData, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + expect(newStorage.data).toStrictEqual({ + other: 'data', + TokenListController: {}, + }); + }); +}); diff --git a/app/scripts/migrations/092.1.ts b/app/scripts/migrations/092.1.ts new file mode 100644 index 000000000..62b47fb04 --- /dev/null +++ b/app/scripts/migrations/092.1.ts @@ -0,0 +1,53 @@ +import { cloneDeep } from 'lodash'; +import { hasProperty, isObject } from '@metamask/utils'; +import log from 'loglevel'; + +export const version = 92.1; + +/** + * Check whether the `TokenListController.tokensChainsCache` state is + * `undefined`, and delete it if so. + * + * This property was accidentally set to `undefined` by an earlier revision of + * migration #77 in some cases. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate(originalVersionedData: { + meta: { version: number }; + data: Record; +}) { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + versionedData.data = transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if (!hasProperty(state, 'TokenListController')) { + log.warn('Skipping migration, TokenListController state is missing'); + return state; + } else if (!isObject(state.TokenListController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.TokenListController is ${typeof state.TokenListController}`, + ), + ); + return state; + } else if (!hasProperty(state.TokenListController, 'tokensChainsCache')) { + log.warn( + 'Skipping migration, TokenListController.tokensChainsCache state is missing', + ); + return state; + } + + if (state.TokenListController.tokensChainsCache === undefined) { + delete state.TokenListController.tokensChainsCache; + } + + return state; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index dc4fcd4e0..f5c169a1f 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -96,6 +96,7 @@ import * as m089 from './089'; import * as m090 from './090'; import * as m091 from './091'; import * as m092 from './092'; +import * as m092point1 from './092.1'; const migrations = [ m002, @@ -189,6 +190,7 @@ const migrations = [ m090, m091, m092, + m092point1, ]; export default migrations; diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 5f2128d32..36f1921b3 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -20,7 +20,6 @@ const removedBackgroundFields = [ // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'AppStateController.currentPopupId', 'AppStateController.timeoutMinutes', - 'TokenListController.tokensChainsCache', ]; const removedUiFields = [ @@ -29,7 +28,6 @@ const removedUiFields = [ // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'metamask.currentPopupId', 'metamask.timeoutMinutes', - 'metamask.tokensChainsCache', ]; /** diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 129ae885a..89bb21722 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -130,6 +130,7 @@ "estimatedGasFeeTimeBounds": "object", "gasEstimateType": "string", "tokenList": "object", + "tokensChainsCache": "object", "preventPollingOnNetworkRestart": "boolean", "tokens": "object", "ignoredTokens": "object", From 01b009c9ada46cc40d4429e8106173f8ce270e13 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Thu, 17 Aug 2023 14:55:15 -0230 Subject: [PATCH 047/102] Run yarn dedupe to deal with unused ses dependency in yarn.lock, for v10.34.5 --- yarn.lock | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 944abfeba..17d9856dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31349,14 +31349,7 @@ __metadata: languageName: node linkType: hard -"ses@npm:^0.18.1": - version: 0.18.4 - resolution: "ses@npm:0.18.4" - checksum: 9afd6edcf390a693926ef728ebb5a435994bbb0f915009ad524c6588cf62e2f66f6d4b4b2694f093b2af2e92c003947ad55404750d756ba75ce70c8636a7ba02 - languageName: node - linkType: hard - -"ses@npm:^0.18.7": +"ses@npm:^0.18.1, ses@npm:^0.18.7": version: 0.18.7 resolution: "ses@npm:0.18.7" dependencies: From 88212a7c82ce20f37b5949246d3751a0be605cf4 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 17:56:10 -0230 Subject: [PATCH 048/102] Require `test-deps-depcheck` to pass CI (#20386) The CI job `test-deps-depcheck` was optional, allowing PRs to be merged that included dependency errors. It is now a required job, ensuring that no such errors are introduced in the future. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95a6db4ef..9ea2e9072 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,6 +221,7 @@ workflows: - prep-build-flask - all-tests-pass: requires: + - test-deps-depcheck - validate-lavamoat-allow-scripts - validate-lavamoat-policy-build - validate-lavamoat-policy-webapp From 65c13d3490b52f377c1ff31664f12f6c5b3b3a67 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 05:47:23 -0230 Subject: [PATCH 049/102] Fix Sentry breadcrumbs collection during initialization (#20521) * Fix Sentry MetaMetrics detection The refactor of the Sentry state in #20491 accidentally broke our opt- in detection. The opt-in detection has been updated to look for both types of application state (during and after initialization). * Continue suppressing breadcrumbs during onboarding * Fix how onboarding status is retrieved The check for whether the user had completed onboarding assumed that the application state was post-initialization UI state. It has been updated to handle background state and pre-initialization state as well. * Remove unnecessary optional chain operators * Add missing optional chain operator * Fix JSDoc description parameter type --- app/scripts/lib/setupSentry.js | 102 ++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 384f246db..e17481f80 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -127,6 +127,71 @@ export const SENTRY_UI_STATE = { unconnectedAccount: true, }; +/** + * Returns whether MetaMetrics is enabled, given the application state. + * + * @param {{ state: unknown} | { persistedState: unknown }} appState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getMetaMetricsEnabledFromAppState(appState) { + // during initialization after loading persisted state + if (appState.persistedState) { + return getMetaMetricsEnabledFromPersistedState(appState.persistedState); + // After initialization + } else if (appState.state) { + // UI + if (appState.state.metamask) { + return Boolean(appState.state.metamask.participateInMetaMetrics); + } + // background + return Boolean( + appState.state.MetaMetricsController?.participateInMetaMetrics, + ); + } + // during initialization, before first persisted state is read + return false; +} + +/** + * Returns whether MetaMetrics is enabled, given the persisted state. + * + * @param {unknown} persistedState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getMetaMetricsEnabledFromPersistedState(persistedState) { + return Boolean( + persistedState?.data?.MetaMetricsController?.participateInMetaMetrics, + ); +} + +/** + * Returns whether onboarding has completed, given the application state. + * + * @param {Record} appState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getOnboardingCompleteFromAppState(appState) { + // during initialization after loading persisted state + if (appState.persistedState) { + return Boolean( + appState.persistedState.data?.OnboardingController?.completedOnboarding, + ); + // After initialization + } else if (appState.state) { + // UI + if (appState.state.metamask) { + return Boolean(appState.state.metamask.completedOnboarding); + } + // background + return Boolean(appState.state.OnboardingController?.completedOnboarding); + } + // during initialization, before first persisted state is read + return false; +} + export default function setupSentry({ release, getState }) { if (!release) { throw new Error('Missing release'); @@ -164,22 +229,21 @@ export default function setupSentry({ release, getState }) { } /** - * A function that returns whether MetaMetrics is enabled. This should also - * return `false` if state has not yet been initialzed. + * Returns whether MetaMetrics is enabled. If the application hasn't yet + * been initialized, the persisted state will be used (if any). * - * @returns `true` if MetaMask's state has been initialized, and MetaMetrics - * is enabled, `false` otherwise. + * @returns `true` if MetaMetrics is enabled, `false` otherwise. */ async function getMetaMetricsEnabled() { const appState = getState(); - if (Object.keys(appState) > 0) { - return Boolean(appState?.store?.metamask?.participateInMetaMetrics); + if (appState.state || appState.persistedState) { + return getMetaMetricsEnabledFromAppState(appState); } + // If we reach here, it means the error was thrown before initialization + // completed, and before we loaded the persisted state for the first time. try { const persistedState = await globalThis.stateHooks.getPersistedState(); - return Boolean( - persistedState?.data?.MetaMetricsController?.participateInMetaMetrics, - ); + return getMetaMetricsEnabledFromPersistedState(persistedState); } catch (error) { console.error(error); return false; @@ -321,17 +385,15 @@ function hideUrlIfNotInternal(url) { */ export function beforeBreadcrumb(getState) { return (breadcrumb) => { - if (getState) { - const appState = getState(); - if ( - Object.values(appState).length && - (!appState?.store?.metamask?.participateInMetaMetrics || - !appState?.store?.metamask?.completedOnboarding || - breadcrumb?.category === 'ui.input') - ) { - return null; - } - } else { + if (!getState) { + return null; + } + const appState = getState(); + if ( + !getMetaMetricsEnabledFromAppState(appState) || + !getOnboardingCompleteFromAppState(appState) || + breadcrumb?.category === 'ui.input' + ) { return null; } const newBreadcrumb = removeUrlsFromBreadCrumb(breadcrumb); From 1d130081214b23e1838a028270c5aa686f399fe8 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 05:47:23 -0230 Subject: [PATCH 050/102] Fix Sentry breadcrumbs collection during initialization (#20521) * Fix Sentry MetaMetrics detection The refactor of the Sentry state in #20491 accidentally broke our opt- in detection. The opt-in detection has been updated to look for both types of application state (during and after initialization). * Continue suppressing breadcrumbs during onboarding * Fix how onboarding status is retrieved The check for whether the user had completed onboarding assumed that the application state was post-initialization UI state. It has been updated to handle background state and pre-initialization state as well. * Remove unnecessary optional chain operators * Add missing optional chain operator * Fix JSDoc description parameter type --- app/scripts/lib/setupSentry.js | 102 ++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 1838598d0..c6d29995e 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -127,6 +127,71 @@ export const SENTRY_UI_STATE = { unconnectedAccount: true, }; +/** + * Returns whether MetaMetrics is enabled, given the application state. + * + * @param {{ state: unknown} | { persistedState: unknown }} appState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getMetaMetricsEnabledFromAppState(appState) { + // during initialization after loading persisted state + if (appState.persistedState) { + return getMetaMetricsEnabledFromPersistedState(appState.persistedState); + // After initialization + } else if (appState.state) { + // UI + if (appState.state.metamask) { + return Boolean(appState.state.metamask.participateInMetaMetrics); + } + // background + return Boolean( + appState.state.MetaMetricsController?.participateInMetaMetrics, + ); + } + // during initialization, before first persisted state is read + return false; +} + +/** + * Returns whether MetaMetrics is enabled, given the persisted state. + * + * @param {unknown} persistedState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getMetaMetricsEnabledFromPersistedState(persistedState) { + return Boolean( + persistedState?.data?.MetaMetricsController?.participateInMetaMetrics, + ); +} + +/** + * Returns whether onboarding has completed, given the application state. + * + * @param {Record} appState - Application state + * @returns `true` if MetaMask's state has been initialized, and MetaMetrics + * is enabled, `false` otherwise. + */ +function getOnboardingCompleteFromAppState(appState) { + // during initialization after loading persisted state + if (appState.persistedState) { + return Boolean( + appState.persistedState.data?.OnboardingController?.completedOnboarding, + ); + // After initialization + } else if (appState.state) { + // UI + if (appState.state.metamask) { + return Boolean(appState.state.metamask.completedOnboarding); + } + // background + return Boolean(appState.state.OnboardingController?.completedOnboarding); + } + // during initialization, before first persisted state is read + return false; +} + export default function setupSentry({ release, getState }) { if (!release) { throw new Error('Missing release'); @@ -164,22 +229,21 @@ export default function setupSentry({ release, getState }) { } /** - * A function that returns whether MetaMetrics is enabled. This should also - * return `false` if state has not yet been initialzed. + * Returns whether MetaMetrics is enabled. If the application hasn't yet + * been initialized, the persisted state will be used (if any). * - * @returns `true` if MetaMask's state has been initialized, and MetaMetrics - * is enabled, `false` otherwise. + * @returns `true` if MetaMetrics is enabled, `false` otherwise. */ async function getMetaMetricsEnabled() { const appState = getState(); - if (Object.keys(appState) > 0) { - return Boolean(appState?.store?.metamask?.participateInMetaMetrics); + if (appState.state || appState.persistedState) { + return getMetaMetricsEnabledFromAppState(appState); } + // If we reach here, it means the error was thrown before initialization + // completed, and before we loaded the persisted state for the first time. try { const persistedState = await globalThis.stateHooks.getPersistedState(); - return Boolean( - persistedState?.data?.MetaMetricsController?.participateInMetaMetrics, - ); + return getMetaMetricsEnabledFromPersistedState(persistedState); } catch (error) { console.error(error); return false; @@ -227,17 +291,15 @@ function hideUrlIfNotInternal(url) { */ export function beforeBreadcrumb(getState) { return (breadcrumb) => { - if (getState) { - const appState = getState(); - if ( - Object.values(appState).length && - (!appState?.store?.metamask?.participateInMetaMetrics || - !appState?.store?.metamask?.completedOnboarding || - breadcrumb?.category === 'ui.input') - ) { - return null; - } - } else { + if (!getState) { + return null; + } + const appState = getState(); + if ( + !getMetaMetricsEnabledFromAppState(appState) || + !getOnboardingCompleteFromAppState(appState) || + breadcrumb?.category === 'ui.input' + ) { return null; } const newBreadcrumb = removeUrlsFromBreadCrumb(breadcrumb); From 8807d06535b414b7b7b8abbef5917ce5d42ec627 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Fri, 18 Aug 2023 14:06:46 +0200 Subject: [PATCH 051/102] Security Alert Blockaid: fix propType and add instance to ConfirmApprove page (#20494) * fix: SecurityProviderBannerAlert propTypes * feat: add ConfirmApprove BlockaidBannerAlert --- .../security-provider-banner-alert.js | 4 ++-- .../confirm-approve-content.component.js | 10 ++++++++++ .../confirm-approve-content.component.test.js | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js index 5e115c193..d79b6eb35 100644 --- a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js +++ b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js @@ -83,7 +83,7 @@ SecurityProviderBannerAlert.propTypes = { .isRequired, /** Severity level */ - severity: PropTypes.oneOfType([Severity.Danger, Severity.Warning]).isRequired, + severity: PropTypes.oneOf([Severity.Danger, Severity.Warning]).isRequired, /** Title to be passed as param */ title: PropTypes.string.isRequired, @@ -96,7 +96,7 @@ SecurityProviderBannerAlert.propTypes = { details: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), /** Name of the security provider */ - provider: PropTypes.oneOfType(Object.values(SecurityProvider)), + provider: PropTypes.oneOf(Object.values(SecurityProvider)), }; export default SecurityProviderBannerAlert; diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index b25933c8b..817450231 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -22,6 +22,9 @@ import { } from '../../../helpers/constants/design-system'; import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content'; import LedgerInstructionField from '../../../components/app/ledger-instruction-field'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; import { TokenStandard } from '../../../../shared/constants/transaction'; @@ -551,6 +554,13 @@ export default class ConfirmApproveContent extends Component { 'confirm-approve-content--full': showFullTxDetails, })} > + { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {isSuspiciousResponse(txData?.securityProviderResponse) && ( { @@ -343,4 +344,21 @@ describe('ConfirmApproveContent Component', () => { expect(getByText(securityProviderResponse.reason)).toBeInTheDocument(); }); + + it('should render security alert if provided', () => { + const mockSecurityAlertResponse = { + result_type: BlockaidResultType.Malicious, + reason: 'blur_farming', + }; + + const { getByText } = renderComponent({ + ...props, + txData: { + ...props.txData, + securityAlertResponse: mockSecurityAlertResponse, + }, + }); + + expect(getByText('This is a deceptive request')).toBeInTheDocument(); + }); }); From 60f149178cd7b9f92e6f506dfc9424b618addc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Fri, 18 Aug 2023 14:38:51 +0200 Subject: [PATCH 052/102] [MMI] Set approved status in tx controller in MMI logic (#20507) * fix(custodial-signing): set approved status in tx controller in MMI logic * Fixed the issue that was not showing custody-confirm-link --------- Co-authored-by: Shane Terence Odlum --- app/scripts/controllers/transactions/index.js | 4 ++++ .../confirm-transaction-base.component.js | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index bf6e7be45..e8fa7dd93 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -1779,7 +1779,11 @@ export default class TransactionController extends EventEmitter { // MMI does not broadcast transactions, as that is the responsibility of the custodian if (txMeta.custodyStatus) { this.inProcessOfSigning.delete(txId); + // Custodial nonces and gas params are set by the custodian, so MMI follows the approve + // workflow before the transaction parameters are sent to the keyring + this.txStateManager.setTxStatusApproved(txId); await this._signTransaction(txId); + // MMI relies on custodian to publish transactions so exits this code path early return; } ///: END:ONLY_INCLUDE_IN diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js index 82b98637f..0ee18509f 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -746,9 +746,6 @@ export default class ConfirmTransactionBase extends Component { sendTransaction(txData) .then(() => { - if (!this._isMounted) { - return; - } if (txData.custodyStatus) { showCustodianDeepLink({ fromAddress, @@ -762,6 +759,9 @@ export default class ConfirmTransactionBase extends Component { }, onDeepLinkShown: () => { clearConfirmTransaction(); + if (!this._isMounted) { + return; + } this.setState({ submitting: false }, () => { history.push(mostRecentOverviewPage); updateCustomNonce(''); @@ -769,6 +769,9 @@ export default class ConfirmTransactionBase extends Component { }, }); } else { + if (!this._isMounted) { + return; + } this.setState( { submitting: false, From ca1ddeb59bb84c7189bc672f7305b73bcca03b3e Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 18 Aug 2023 11:15:45 -0230 Subject: [PATCH 053/102] Fix and test log.info calls run for each migration (#20517) * Fix and test log.info calls run for each migration In migrator/index.js, log.info is called before an after each migration. These calls are intended to produce breadcrumbs to be captured by sentry in cases where errors happen during or shortly after migrations are run. These calls were not causing any output to the console because the log.setLevel calls in ui/index.js were setting a 'warn value in local storage that was being used by logLevel in the background. This commit fixes the problem by setting the `persist` param of setLevel to false, so that the background no longer reads the ui's log level. Tests are added to verify that these logs are captured in sentry breadcrumbs when there is a migration error due to an invariant state. * Improve breadcrumb message matching The test modified in this commit asserts eqaulity of messages from breadcrumbs and hard coded expected results. This could cause failures, as sometimes the messages contain whitespace characters. This commit ensures the assertions only check that the expected string is within the message string, ignoring extra characters. --- app/scripts/background.js | 2 +- test/e2e/fixture-builder.js | 7 ++++ test/e2e/tests/errors.spec.js | 60 +++++++++++++++++++++++++++++++++++ ui/index.js | 2 +- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 7b45cdfd5..69cf65964 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -88,7 +88,7 @@ const metamaskInternalProcessHash = { const metamaskBlockedPorts = ['trezor-connect']; -log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info'); +log.setLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info', false); const platform = new ExtensionPlatform(); const notificationManager = new NotificationManager(); diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 3c1415e53..12de298bd 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -744,6 +744,13 @@ class FixtureBuilder { return this; } + withBadPreferencesControllerState() { + merge(this.fixture.data, { + PreferencesController: 5, + }); + return this; + } + withTokensControllerERC20() { merge(this.fixture.data.TokensController, { tokens: [ diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 36f1921b3..5c557b256 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -124,6 +124,18 @@ describe('Sentry errors', function () { }); } + async function mockSentryInvariantMigrationError(mockServer) { + return await mockServer + .forPost('https://sentry.io/api/0000000/envelope/') + .withBodyIncluding('typeof state.PreferencesController is number') + .thenCallback(() => { + return { + statusCode: 200, + json: {}, + }; + }); + } + async function mockSentryTestError(mockServer) { return await mockServer .forPost('https://sentry.io/api/0000000/envelope/') @@ -307,6 +319,54 @@ describe('Sentry errors', function () { ); }); + it('should capture migration log breadcrumbs when there is an invariant state error in a migration', async function () { + await withFixtures( + { + fixtures: { + ...new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .withBadPreferencesControllerState() + .build(), + }, + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryInvariantMigrationError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const breadcrumbs = mockJsonBody?.breadcrumbs ?? []; + const migrationLogBreadcrumbs = breadcrumbs.filter((breadcrumb) => { + return breadcrumb.message?.match(/Running migration \d+/u); + }); + const migrationLogMessages = migrationLogBreadcrumbs.map( + (breadcrumb) => + breadcrumb.message.match(/(Running migration \d+)/u)[1], + ); + const firstMigrationLog = migrationLogMessages[0]; + const lastMigrationLog = + migrationLogMessages[migrationLogMessages.length - 1]; + + assert.equal(migrationLogMessages.length, 8); + assert.equal(firstMigrationLog, 'Running migration 75'); + assert.equal(lastMigrationLog, 'Running migration 82'); + }, + ); + }); + it('should send error events in UI', async function () { await withFixtures( { diff --git a/ui/index.js b/ui/index.js index de1432a80..208006de1 100644 --- a/ui/index.js +++ b/ui/index.js @@ -27,7 +27,7 @@ import Root from './pages'; import txHelper from './helpers/utils/tx-helper'; import { _setBackgroundConnection } from './store/action-queue'; -log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn'); +log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn', false); let reduxStore; From 3e26da493f68ff5ea9ca7f6da63a8648d2f7bd0b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 12:22:37 -0230 Subject: [PATCH 054/102] Initialize composable observable store after update (#20468) * Initialize composable observable store after update The composable observable store now updates state immediately when the structure is updated. Previously each store would only be updated after the first state change. This ensures that the composable observable store state is always complete. * SUpport falsy controller state We now use the nullish coalescing operator when checkint store.state, so that we don't accidentally ignore falsy state. Co-authored-by: Frederik Bolding * Add test for falsy controller state * Update state snapshots A change on `develop` required another state update. --------- Co-authored-by: Frederik Bolding --- app/scripts/lib/ComposableObservableStore.js | 4 + .../lib/ComposableObservableStore.test.js | 40 ++++++++++ ...rs-after-init-opt-in-background-state.json | 80 +++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js index ff722a82d..80d9c483d 100644 --- a/app/scripts/lib/ComposableObservableStore.js +++ b/app/scripts/lib/ComposableObservableStore.js @@ -51,6 +51,7 @@ export default class ComposableObservableStore extends ObservableStore { updateStructure(config) { this.config = config; this.removeAllListeners(); + const initialState = {}; for (const key of Object.keys(config)) { if (!config[key]) { throw new Error(`Undefined '${key}'`); @@ -72,7 +73,10 @@ export default class ComposableObservableStore extends ObservableStore { }, ); } + + initialState[key] = store.state ?? store.getState?.(); } + this.updateState(initialState); } /** diff --git a/app/scripts/lib/ComposableObservableStore.test.js b/app/scripts/lib/ComposableObservableStore.test.js index b6a58f1e1..bb3dd48fd 100644 --- a/app/scripts/lib/ComposableObservableStore.test.js +++ b/app/scripts/lib/ComposableObservableStore.test.js @@ -120,6 +120,46 @@ describe('ComposableObservableStore', () => { }); }); + it('should initialize state with all three types of stores', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + const exampleController = new ExampleController({ + messenger: controllerMessenger, + }); + const oldExampleController = new OldExampleController(); + exampleStore.putState('state'); + exampleController.updateBar('state'); + oldExampleController.updateBaz('state'); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleController, + OldExample: oldExampleController, + Store: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: { bar: 'state' }, + OldExample: { baz: 'state' }, + Store: 'state', + }); + }); + + it('should initialize falsy state', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + exampleStore.putState(false); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: false, + }); + }); + it('should return flattened state', () => { const controllerMessenger = new ControllerMessenger(); const fooStore = new ObservableStore({ foo: 'foo' }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 2f29cc98c..583be2020 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,5 +1,18 @@ { "AccountTracker": { "accounts": "object" }, + "AddressBookController": "object", + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppMetadataController": { + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94 + }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, @@ -24,6 +37,7 @@ }, "ApprovalController": "object", "CachedBalancesController": "object", + "CronjobController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, @@ -42,6 +56,22 @@ "unapprovedEncryptionPublicKeyMsgCount": 0 }, "EnsController": "object", + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { + "isUnlocked": false, + "keyringTypes": "object", + "keyrings": "object" + }, "MetaMetricsController": { "participateInMetaMetrics": true, "metaMetricsId": "fake-metrics-id", @@ -66,6 +96,51 @@ "networksMetadata": "object", "networkConfigurations": "object" }, + "NftController": "object", + "NotificationController": "object", + "OnboardingController": { + "seedPhraseBackedUp": true, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "onboardingTabs": "object" + }, + "PermissionController": "object", + "PermissionLogController": "object", + "PreferencesController": { + "useBlockie": false, + "useNonceField": false, + "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "use4ByteResolution": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "featureFlags": { "showIncomingTransactions": true }, + "knownMethodData": "object", + "currentLocale": "en", + "identities": "object", + "lostIdentities": "object", + "forgottenPassword": false, + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string" + }, "SignatureController": { "unapprovedMsgs": "object", "unapprovedPersonalMsgs": "object", @@ -74,7 +149,12 @@ "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 }, + "SmartTransactionsController": "object", + "SnapController": "object", + "SnapsRegistry": "object", + "SubjectMetadataController": "object", "SwapsController": "object", + "TokenListController": "object", "TokenRatesController": "object", "TokensController": "object", "TxController": "object" From c2163434dbbc8fc791b99e69a05f95af7cbf5c48 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 18 Aug 2023 11:15:45 -0230 Subject: [PATCH 055/102] Fix and test log.info calls run for each migration (#20517) * Fix and test log.info calls run for each migration In migrator/index.js, log.info is called before an after each migration. These calls are intended to produce breadcrumbs to be captured by sentry in cases where errors happen during or shortly after migrations are run. These calls were not causing any output to the console because the log.setLevel calls in ui/index.js were setting a 'warn value in local storage that was being used by logLevel in the background. This commit fixes the problem by setting the `persist` param of setLevel to false, so that the background no longer reads the ui's log level. Tests are added to verify that these logs are captured in sentry breadcrumbs when there is a migration error due to an invariant state. * Improve breadcrumb message matching The test modified in this commit asserts eqaulity of messages from breadcrumbs and hard coded expected results. This could cause failures, as sometimes the messages contain whitespace characters. This commit ensures the assertions only check that the expected string is within the message string, ignoring extra characters. --- app/scripts/background.js | 2 +- test/e2e/fixture-builder.js | 7 ++++ test/e2e/tests/errors.spec.js | 60 +++++++++++++++++++++++++++++++++++ ui/index.js | 2 +- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 7e89ce1c3..aa655e5a4 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -88,7 +88,7 @@ const metamaskInternalProcessHash = { const metamaskBlockedPorts = ['trezor-connect']; -log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info'); +log.setLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info', false); const platform = new ExtensionPlatform(); const notificationManager = new NotificationManager(); diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index ed3e3aaac..3528ec5aa 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -724,6 +724,13 @@ class FixtureBuilder { return this; } + withBadPreferencesControllerState() { + merge(this.fixture.data, { + PreferencesController: 5, + }); + return this; + } + withTokensControllerERC20() { merge(this.fixture.data.TokensController, { tokens: [ diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 36f1921b3..5c557b256 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -124,6 +124,18 @@ describe('Sentry errors', function () { }); } + async function mockSentryInvariantMigrationError(mockServer) { + return await mockServer + .forPost('https://sentry.io/api/0000000/envelope/') + .withBodyIncluding('typeof state.PreferencesController is number') + .thenCallback(() => { + return { + statusCode: 200, + json: {}, + }; + }); + } + async function mockSentryTestError(mockServer) { return await mockServer .forPost('https://sentry.io/api/0000000/envelope/') @@ -307,6 +319,54 @@ describe('Sentry errors', function () { ); }); + it('should capture migration log breadcrumbs when there is an invariant state error in a migration', async function () { + await withFixtures( + { + fixtures: { + ...new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .withBadPreferencesControllerState() + .build(), + }, + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + testSpecificMock: mockSentryInvariantMigrationError, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + + // Wait for Sentry request + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 3000); + + const [mockedRequest] = await mockedEndpoint.getSeenRequests(); + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); + const breadcrumbs = mockJsonBody?.breadcrumbs ?? []; + const migrationLogBreadcrumbs = breadcrumbs.filter((breadcrumb) => { + return breadcrumb.message?.match(/Running migration \d+/u); + }); + const migrationLogMessages = migrationLogBreadcrumbs.map( + (breadcrumb) => + breadcrumb.message.match(/(Running migration \d+)/u)[1], + ); + const firstMigrationLog = migrationLogMessages[0]; + const lastMigrationLog = + migrationLogMessages[migrationLogMessages.length - 1]; + + assert.equal(migrationLogMessages.length, 8); + assert.equal(firstMigrationLog, 'Running migration 75'); + assert.equal(lastMigrationLog, 'Running migration 82'); + }, + ); + }); + it('should send error events in UI', async function () { await withFixtures( { diff --git a/ui/index.js b/ui/index.js index de1432a80..208006de1 100644 --- a/ui/index.js +++ b/ui/index.js @@ -27,7 +27,7 @@ import Root from './pages'; import txHelper from './helpers/utils/tx-helper'; import { _setBackgroundConnection } from './store/action-queue'; -log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn'); +log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn', false); let reduxStore; From fa778d5af9783b62ef083b355bd45885b32cb8b7 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 18 Aug 2023 12:55:33 -0230 Subject: [PATCH 056/102] Update snapshot tests for errors.spec.js --- ...ors-after-init-opt-in-background-state.json | 18 +++++++----------- .../errors-after-init-opt-in-ui-state.json | 11 +++++------ ...rs-before-init-opt-in-background-state.json | 3 +-- .../errors-before-init-opt-in-ui-state.json | 3 +-- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 583be2020..b682921e7 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -8,10 +8,10 @@ }, "AnnouncementController": "object", "AppMetadataController": { - "currentAppVersion": "10.34.4", + "currentAppVersion": "10.34.5", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 94 + "currentMigrationVersion": 92.1 }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, @@ -36,8 +36,8 @@ "serviceWorkerLastActiveTime": "number" }, "ApprovalController": "object", + "BackupController": "undefined", "CachedBalancesController": "object", - "CronjobController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, @@ -70,7 +70,8 @@ "KeyringController": { "isUnlocked": false, "keyringTypes": "object", - "keyrings": "object" + "keyrings": "object", + "encryptionKey": "object" }, "MetaMetricsController": { "participateInMetaMetrics": true, @@ -82,8 +83,8 @@ "previousUserTraits": "object" }, "NetworkController": { - "selectedNetworkClientId": "string", "networkId": "1337", + "networkStatus": "available", "providerConfig": { "chainId": "string", "nickname": "Localhost 8545", @@ -93,11 +94,10 @@ "type": "rpc", "id": "string" }, - "networksMetadata": "object", + "networkDetails": "object", "networkConfigurations": "object" }, "NftController": "object", - "NotificationController": "object", "OnboardingController": { "seedPhraseBackedUp": true, "firstTimeFlowType": "import", @@ -115,7 +115,6 @@ "useMultiAccountBalanceChecker": "boolean", "useTokenDetection": "boolean", "useNftDetection": "boolean", - "use4ByteResolution": "boolean", "useCurrencyRateCheck": "boolean", "openSeaEnabled": "boolean", "advancedGasFee": "object", @@ -132,7 +131,6 @@ "useNativeCurrencyAsPrimaryCurrency": true }, "ipfsGateway": "dweb.link", - "useAddressBarEnsResolution": "boolean", "infuraBlocked": "boolean", "ledgerTransportType": "string", "snapRegistryList": "object", @@ -150,8 +148,6 @@ "unapprovedTypedMessagesCount": 0 }, "SmartTransactionsController": "object", - "SnapController": "object", - "SnapsRegistry": "object", "SubjectMetadataController": "object", "SwapsController": "object", "TokenListController": "object", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 89bb21722..af2f71406 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -32,7 +32,6 @@ "firstTimeFlowType": "import", "completedOnboarding": true, "knownMethodData": "object", - "use4ByteResolution": "boolean", "participateInMetaMetrics": true, "nextNonce": null, "conversionRate": 1300, @@ -57,12 +56,12 @@ "usedNetworks": "object", "snapsInstallPrivacyWarningShown": "boolean", "serviceWorkerLastActiveTime": "number", - "currentAppVersion": "10.34.4", + "currentAppVersion": "10.34.5", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 94, - "selectedNetworkClientId": "string", + "currentMigrationVersion": 92.1, "networkId": "1337", + "networkStatus": "available", "providerConfig": { "chainId": "string", "nickname": "Localhost 8545", @@ -72,10 +71,11 @@ "type": "rpc", "id": "string" }, - "networksMetadata": "object", + "networkDetails": "object", "cachedBalances": "object", "keyringTypes": "object", "keyrings": "object", + "encryptionKey": "object", "useNonceField": false, "usePhishDetect": true, "dismissSeedBackUpReminder": "boolean", @@ -89,7 +89,6 @@ "lostIdentities": "object", "forgottenPassword": false, "ipfsGateway": "dweb.link", - "useAddressBarEnsResolution": "boolean", "infuraBlocked": "boolean", "ledgerTransportType": "string", "snapRegistryList": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 422bcb0e2..f3adc0cd9 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -52,8 +52,7 @@ }, "NetworkController": { "networkId": "1337", - "selectedNetworkClientId": "string", - "networksMetadata": "object", + "networkStatus": "available", "providerConfig": { "chainId": "string", "nickname": "Localhost 8545", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 0a9fac1d0..704026b0b 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -52,8 +52,7 @@ }, "NetworkController": { "networkId": "1337", - "selectedNetworkClientId": "string", - "networksMetadata": "object", + "networkStatus": "available", "providerConfig": { "chainId": "string", "nickname": "Localhost 8545", From 016a1ef4e4f4412824c5b723b9703a2279dcf935 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 18 Aug 2023 12:58:08 -0230 Subject: [PATCH 057/102] Remove GHSA-h755-8qp9-cq8 from advisory exclusions because yarn audit output no longer flags that advisory --- .iyarc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.iyarc b/.iyarc index 9e16de044..cea1e59eb 100644 --- a/.iyarc +++ b/.iyarc @@ -4,8 +4,3 @@ GHSA-257v-vj4p-3w2h # request library is subject to SSRF. # addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch GHSA-p8p7-x288-28g6 - -# Prototype pollution -# Not easily patched -# Minimal risk to us because we're using lockdown which also prevents this case of prototype pollution -GHSA-h755-8qp9-cq85 From 23249b68b7e5b497fd75189ab786effd997b984e Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Fri, 18 Aug 2023 12:57:04 -0300 Subject: [PATCH 058/102] feat: github actions to automatically create and close bug report issue (#20391) * feat(action): github action to create bug report issue at RC cut * feat(action): github action to close bug report issue once release is ready * fix(action): replace main by master * fix(action): create event does not support branch filter * fix(action): indentation * fix(action): actionlint erors * fix(action): actionlint erors 2 * fix(action): actionlint erors 3 * fix(action): replace npm by yarn for consistency --- .../scripts/close-release-bug-report-issue.ts | 120 ++++++++++++++++++ .github/workflows/add-release-label.yml | 2 +- .github/workflows/check-pr-labels.yml | 2 +- .github/workflows/close-bug-report.yml | 34 +++++ .github/workflows/create-bug-report.yml | 47 +++++++ package.json | 1 + 6 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/close-release-bug-report-issue.ts create mode 100644 .github/workflows/close-bug-report.yml create mode 100644 .github/workflows/create-bug-report.yml diff --git a/.github/scripts/close-release-bug-report-issue.ts b/.github/scripts/close-release-bug-report-issue.ts new file mode 100644 index 000000000..8f1cf8ed9 --- /dev/null +++ b/.github/scripts/close-release-bug-report-issue.ts @@ -0,0 +1,120 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions are scoped to the repository where the action is running. + // "GITHUB_TOKEN" does not have access to other repositories, even when they belong to the same organization. + // As we want to update bug report issues which are not located in the same repository, + // we need to create our own "BUG_REPORT_TOKEN" with "repo" permissions. + // Such a token allows to access other repositories of the MetaMask organisation. + const personalAccessToken = process.env.BUG_REPORT_TOKEN; + if (!personalAccessToken) { + core.setFailed('BUG_REPORT_TOKEN not found'); + process.exit(1); + } + + const repoOwner = context.repo.owner; // MetaMask + + const bugReportRepo = process.env.BUG_REPORT_REPO; + if (!bugReportRepo) { + core.setFailed('BUG_REPORT_REPO not found'); + process.exit(1); + } + + // Extract branch name from the context + const branchName: string = context.payload.pull_request?.head.ref || ""; + + // Extract semver version number from the branch name + const releaseVersionNumberMatch = branchName.match(/^Version-v(\d+\.\d+\.\d+)$/); + + if (!releaseVersionNumberMatch) { + core.setFailed(`Failed to extract version number from branch name: ${branchName}`); + process.exit(1); + } + + const releaseVersionNumber = releaseVersionNumberMatch[1]; + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken); + + const bugReportIssue = await retrieveOpenBugReportIssue(octokit, repoOwner, bugReportRepo, releaseVersionNumber); + + if (!bugReportIssue) { + throw new Error(`No open bug report issue was found for release ${releaseVersionNumber} on ${repoOwner}/${bugReportRepo} repo`); + } + + if (bugReportIssue.title?.toLocaleLowerCase() !== `v${releaseVersionNumber} Bug Report`.toLocaleLowerCase()) { + throw new Error(`Unexpected bug report title: "${bugReportIssue.title}" instead of "v${releaseVersionNumber} Bug Report"`); + } + + console.log(`Closing bug report issue with title "${bugReportIssue.title}" and id: ${bugReportIssue.id}`); + + await closeIssue(octokit, bugReportIssue.id); + + console.log(`Issue with id: ${bugReportIssue.id} successfully closed`); +} + +// This function retrieves the issue titled "vx.y.z Bug Report" on a specific repo +async function retrieveOpenBugReportIssue(octokit: InstanceType, repoOwner: string, repoName: string, releaseVersionNumber: string): Promise<{ + id: string; + title: string; +} | undefined> { + + const retrieveOpenBugReportIssueQuery = ` + query RetrieveOpenBugReportIssue { + search(query: "repo:${repoOwner}/${repoName} type:issue is:open in:title v${releaseVersionNumber} Bug Report", type: ISSUE, first: 1) { + nodes { + ... on Issue { + id + title + } + } + } + } +`; + + const retrieveOpenBugReportIssueQueryResult: { + search: { + nodes: { + id: string; + title: string; + }[]; + }; + } = await octokit.graphql(retrieveOpenBugReportIssueQuery); + + const bugReportIssues = retrieveOpenBugReportIssueQueryResult?.search?.nodes; + + return bugReportIssues?.length > 0 ? bugReportIssues[0] : undefined; +} + + +// This function closes a Github issue, based on its ID +async function closeIssue(octokit: InstanceType, issueId: string): Promise { + + const closeIssueMutation = ` + mutation CloseIssue($issueId: ID!) { + updateIssue(input: {id: $issueId, state: CLOSED}) { + clientMutationId + } + } + `; + + const closeIssueMutationResult: { + updateIssue: { + clientMutationId: string; + }; + } = await octokit.graphql(closeIssueMutation, { + issueId, + }); + + const clientMutationId = closeIssueMutationResult?.updateIssue?.clientMutationId; + + return clientMutationId; +} diff --git a/.github/workflows/add-release-label.yml b/.github/workflows/add-release-label.yml index 8a9bf087f..3acc4e22a 100644 --- a/.github/workflows/add-release-label.yml +++ b/.github/workflows/add-release-label.yml @@ -37,4 +37,4 @@ jobs: env: RELEASE_LABEL_TOKEN: ${{ secrets.RELEASE_LABEL_TOKEN }} NEXT_SEMVER_VERSION: ${{ env.NEXT_SEMVER_VERSION }} - run: npm run add-release-label-to-pr-and-linked-issues + run: yarn run add-release-label-to-pr-and-linked-issues diff --git a/.github/workflows/check-pr-labels.yml b/.github/workflows/check-pr-labels.yml index 18e3304a1..cc1cb37db 100644 --- a/.github/workflows/check-pr-labels.yml +++ b/.github/workflows/check-pr-labels.yml @@ -35,4 +35,4 @@ jobs: id: check-pr-has-required-labels env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run check-pr-has-required-labels + run: yarn run check-pr-has-required-labels diff --git a/.github/workflows/close-bug-report.yml b/.github/workflows/close-bug-report.yml new file mode 100644 index 000000000..7d520c30f --- /dev/null +++ b/.github/workflows/close-bug-report.yml @@ -0,0 +1,34 @@ +name: Close release bug report issue when release branch gets merged + +on: + pull_request: + branches: + - master + types: + - closed + +jobs: + close-bug-report: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'Version-v') + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 # This retrieves only the latest commit. + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: yarn + + - name: Install dependencies + run: yarn --immutable + + - name: Close release bug report issue + id: close-release-bug-report-issue + env: + BUG_REPORT_REPO: MetaMask-planning + BUG_REPORT_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} + run: yarn run close-release-bug-report-issue diff --git a/.github/workflows/create-bug-report.yml b/.github/workflows/create-bug-report.yml new file mode 100644 index 000000000..f46296bea --- /dev/null +++ b/.github/workflows/create-bug-report.yml @@ -0,0 +1,47 @@ +name: Create release bug report issue when release branch gets created + +on: create + +jobs: + create-bug-report: + runs-on: ubuntu-latest + steps: + - name: Extract version from branch name if release branch + id: extract_version + run: | + if [[ "$GITHUB_REF" == "refs/heads/Version-v"* ]]; then + version="${GITHUB_REF#refs/heads/Version-v}" + echo "New release branch($version), continue next steps" + echo "version=$version" >> "$GITHUB_ENV" + else + echo "Not a release branch, skip next steps" + fi + + - name: Create bug report issue on planning repo + if: env.version + uses: octokit/request-action@v2.x + with: + route: POST /repos/MetaMask/MetaMask-planning/issues + owner: MetaMask + title: v${{ env.version }} Bug Report + body: | + This bug report was automatically created by a GitHub action upon the creation of release branch `Version-v${{ env.version }}` (release cut). + + + **Expected actions for release engineers:** + + 1. Convert this issue into a Zenhub epic and link all bugs identified during the release regression testing phase to this epic. + + 2. After completing the first regression run, move this epic to "Regression Completed" on the [Extension Release Regression board](https://app.zenhub.com/workspaces/extension-release-regression-6478c62d937eaa15e95c33c5/board?filterLogic=any&labels=release-${{ env.version }},release-task). + + + Note that once the release is prepared for store submission, meaning the `Version-v${{ env.version }}` branch merges into `master`, another GitHub action will automatically close this epic. + + labels: | + [ + "type-bug", + "regression-RC", + "release-${{ env.version }}" + ] + env: + GITHUB_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} diff --git a/package.json b/package.json index 5e7113b42..b0988abf3 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "validate-branch-name": "validate-branch-name", "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts", "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", + "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate" }, "resolutions": { From 8df77ed93d6a61c74061611abb22f107ef015f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Fri, 18 Aug 2023 18:44:23 +0200 Subject: [PATCH 059/102] [MMI] Always redirect to MMI portfolio dashboard upon the user clicking on swaps (#20527) --- .../app/wallet-overview/eth-overview.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index 21127d10e..4c10a8edd 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -14,39 +14,44 @@ import { getMmiPortfolioEnabled, getMmiPortfolioUrl, } from '../../../selectors/institutional/selectors'; -///: END:ONLY_INCLUDE_IN import { MMI_SWAPS_URL } from '../../../../shared/constants/swaps'; +///: END:ONLY_INCLUDE_IN import { I18nContext } from '../../../contexts/i18n'; import { SEND_ROUTE, + ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) BUILD_QUOTE_ROUTE, + ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/routes'; import Tooltip from '../../ui/tooltip'; import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { - getAccountType, isBalanceCached, getShouldShowFiat, - getCurrentKeyring, - getSwapsDefaultToken, getIsSwapsChain, getSelectedAccountCachedBalance, getCurrentChainId, ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) + getSwapsDefaultToken, + getCurrentKeyring, getIsBridgeChain, getIsBuyableChain, getMetaMetricsId, ///: END:ONLY_INCLUDE_IN } from '../../../selectors'; +///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; -import IconButton from '../../ui/icon-button'; import { isHardwareKeyring } from '../../../helpers/utils/hardware'; +///: END:ONLY_INCLUDE_IN +import IconButton from '../../ui/icon-button'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsEventCategory, MetaMetricsEventName, + ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) MetaMetricsSwapsEventSource, + ///: END:ONLY_INCLUDE_IN } from '../../../../shared/constants/metametrics'; import Spinner from '../../ui/spinner'; import { startNewDraftTransaction } from '../../../ducks/send'; @@ -69,16 +74,15 @@ const EthOverview = ({ className, showAddress }) => { const isBridgeChain = useSelector(getIsBridgeChain); const isBuyableChain = useSelector(getIsBuyableChain); const metaMetricsId = useSelector(getMetaMetricsId); - ///: END:ONLY_INCLUDE_IN const keyring = useSelector(getCurrentKeyring); const usingHardwareWallet = isHardwareKeyring(keyring?.type); + const defaultSwapsToken = useSelector(getSwapsDefaultToken); + ///: END:ONLY_INCLUDE_IN const balanceIsCached = useSelector(isBalanceCached); const showFiat = useSelector(getShouldShowFiat); const balance = useSelector(getSelectedAccountCachedBalance); const isSwapsChain = useSelector(getIsSwapsChain); - const defaultSwapsToken = useSelector(getSwapsDefaultToken); const chainId = useSelector(getCurrentChainId); - const accountType = useSelector(getAccountType); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled); @@ -256,14 +260,13 @@ const EthOverview = ({ className, showAddress }) => { /> } onClick={() => { - if (accountType === 'custody') { - global.platform.openTab({ - url: MMI_SWAPS_URL, - }); - - return; - } + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + global.platform.openTab({ + url: MMI_SWAPS_URL, + }); + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) if (isSwapsChain) { trackEvent({ event: MetaMetricsEventName.NavSwapButtonClicked, @@ -282,6 +285,7 @@ const EthOverview = ({ className, showAddress }) => { history.push(BUILD_QUOTE_ROUTE); } } + ///: END:ONLY_INCLUDE_IN }} label={t('swap')} data-testid="token-overview-button-swap" From 7f157cabf975f532bead00b0b75664c5e5db488f Mon Sep 17 00:00:00 2001 From: Srirag <77497081+strawhatrag@users.noreply.github.com> Date: Fri, 18 Aug 2023 22:24:12 +0530 Subject: [PATCH 060/102] Deprecation message has been added above @mixin H1 (#20500) --- ui/css/design-system/typography.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/css/design-system/typography.scss b/ui/css/design-system/typography.scss index a7b16b46c..a70f745e2 100644 --- a/ui/css/design-system/typography.scss +++ b/ui/css/design-system/typography.scss @@ -111,6 +111,15 @@ $font-size-h9: map-get($typography-variants, 'h9'); font-weight: normal; } +/** +* @deprecated The typography mixins `@include H1` - `@include H9` have been deprecated in favor of the `` component from the component-library. +* Please update your code to use the `` component instead, which can be found in ui/components/component-library/text/text.tsx. +* You can find documentation for the new `` component in the MetaMask Storybook: +* {@link https://metamask.github.io/metamask-storybook/?path=/docs/components-componentlibrary-text--docs} +* If you would like to help with the replacement of the old mixins `@include H1` - `@include H9`, please submit a pull request against this GitHub issue: +* {@link https://github.com/MetaMask/metamask-extension/issues/19533} +*/ + // Typography @mixin H1 { @include typography('h1'); From 3d9457e51716f4f03b2ae92571e592bc646b1bd3 Mon Sep 17 00:00:00 2001 From: jainex Date: Fri, 18 Aug 2023 22:24:50 +0530 Subject: [PATCH 061/102] Replacing deprecated Popover with Modal (#20413) * Replacing deprecated Popover with Modal * Replacing deprecated Popover with Modal in detected-token-ignored-popover * Remove unused code * fix hover problem * update footerButton size to large * Lint fix * UI updates and removing unused CSS * reset chnages in edit-gas-popover.component.js --------- Co-authored-by: georgewrmarshall --- ui/components/app/app-components.scss | 1 - .../detected-token-ignored-popover.js | 103 ++++++++++-------- .../detected-token-ignored-popover.stories.js | 21 +++- .../detected-token-ignored-popover/index.scss | 21 ---- .../app/detected-token/detected-token.js | 1 + 5 files changed, 78 insertions(+), 69 deletions(-) delete mode 100644 ui/components/app/detected-token/detected-token-ignored-popover/index.scss diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 017e7deb6..c4dc26714 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -91,7 +91,6 @@ @import 'detected-token/detected-token-aggregators/index'; @import 'detected-token/detected-token-values/index'; @import 'detected-token/detected-token-details/index'; -@import 'detected-token/detected-token-ignored-popover/index'; @import 'detected-token/detected-token-selection-popover/index'; @import 'network-account-balance-header/index'; @import 'approve-content-card/index'; diff --git a/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.js b/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.js index 708b078db..6e520dbb5 100644 --- a/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.js +++ b/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.js @@ -3,64 +3,78 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import Popover from '../../../ui/popover'; -import Button from '../../../ui/button'; -import { TextVariant } from '../../../../helpers/constants/design-system'; -import { Text } from '../../../component-library'; +import { + Display, + JustifyContent, +} from '../../../../helpers/constants/design-system'; +import { + Text, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + Button, + BUTTON_VARIANT, + Box, + BUTTON_SIZES, +} from '../../../component-library'; const DetectedTokenIgnoredPopover = ({ partiallyIgnoreDetectedTokens, onCancelIgnore, handleClearTokensSelection, + isOpen, }) => { const t = useI18nContext(); - - const footer = ( - <> - - - - ); - return ( - - - {partiallyIgnoreDetectedTokens - ? t('importSelectedTokensDescription') - : t('ignoreTokenWarning')} - - + + + + {partiallyIgnoreDetectedTokens + ? t('importSelectedTokens') + : t('areYouSure')} + + + {partiallyIgnoreDetectedTokens + ? t('importSelectedTokensDescription') + : t('ignoreTokenWarning')} + + + + + + + ); }; @@ -68,6 +82,7 @@ DetectedTokenIgnoredPopover.propTypes = { partiallyIgnoreDetectedTokens: PropTypes.bool.isRequired, onCancelIgnore: PropTypes.func.isRequired, handleClearTokensSelection: PropTypes.func.isRequired, + isOpen: PropTypes.bool.isRequired, }; export default DetectedTokenIgnoredPopover; diff --git a/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.stories.js b/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.stories.js index 2bfc6b7db..b8cfd9be3 100644 --- a/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.stories.js +++ b/ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.stories.js @@ -4,14 +4,23 @@ import DetectedTokenIgnoredPopover from './detected-token-ignored-popover'; export default { title: 'Components/App/DetectedToken/DetectedTokenIgnoredPopover', - argTypes: { onCancelIgnore: { - control: 'func', + action: 'onCancelIgnore', }, handleClearTokensSelection: { - control: 'func', + action: 'handleClearTokensSelection', }, + partiallyIgnoreDetectedTokens: { + control: 'boolean', + }, + isOpen: { + control: 'boolean', + }, + }, + args: { + partiallyIgnoreDetectedTokens: false, + isOpen: true, }, }; @@ -22,3 +31,9 @@ const Template = (args) => { export const DefaultStory = Template.bind({}); DefaultStory.storyName = 'Default'; + +export const PartiallyIgnoreDetectedTokens = Template.bind({}); + +PartiallyIgnoreDetectedTokens.args = { + partiallyIgnoreDetectedTokens: true, +}; diff --git a/ui/components/app/detected-token/detected-token-ignored-popover/index.scss b/ui/components/app/detected-token/detected-token-ignored-popover/index.scss deleted file mode 100644 index 69fa05637..000000000 --- a/ui/components/app/detected-token/detected-token-ignored-popover/index.scss +++ /dev/null @@ -1,21 +0,0 @@ -.detected-token-ignored-popover { - &__ignore-button { - margin-inline-end: 8px; - } - - &__import-button { - margin-inline-start: 8px; - } - - &--ignore { - .popover-header { - margin-inline-start: 85px; - } - } - - &--import { - .popover-header { - margin-inline-start: 50px; - } - } -} diff --git a/ui/components/app/detected-token/detected-token.js b/ui/components/app/detected-token/detected-token.js index af64ee00e..582befda8 100644 --- a/ui/components/app/detected-token/detected-token.js +++ b/ui/components/app/detected-token/detected-token.js @@ -161,6 +161,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => { <> {showDetectedTokenIgnoredPopover && ( Date: Fri, 18 Aug 2023 16:32:28 -0230 Subject: [PATCH 062/102] Fix pre-initialization UI error state capture (#20529) In the case where an error is thrown in the UI before initialization has finished, we aren't capturing the application state correctly for Sentry errors. We had a test case for this, but the test case was broken due to a mistake in how the `network-store` was setup (it was not matching the behavior of the real `local-tstore` module). The pre-initialization state capture logic was updated to rely solely upon the `localStore` instance used by Sentry to determine whether the user had opted-in to metrics or not. This simplifies the logic a great deal, removing the need for the `getMostRecentPersistedState` state hook. It also ensures that state is captured corretly pre- initialization in both the background and UI. --- app/scripts/background.js | 9 ++--- app/scripts/lib/network-store.js | 6 ++- app/scripts/lib/setup-initial-state-hooks.js | 39 ++++++++++++-------- app/scripts/ui.js | 8 ---- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 69cf65964..eb4ac613b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -71,12 +71,6 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ -// Setup global hook for improved Sentry state snapshots during initialization -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); -global.stateHooks.getMostRecentPersistedState = () => - localStore.mostRecentRetrievedState; - const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -100,6 +94,9 @@ const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; +// state persistence +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index 2f4c0a1b0..3a0326a2b 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -31,7 +31,6 @@ export default class ReadOnlyNetworkStore { const response = await fetchWithTimeout(FIXTURE_SERVER_URL); if (response.ok) { this._state = await response.json(); - this.mostRecentRetrievedState = this._state; } } catch (error) { log.debug(`Error loading network state: '${error.message}'`); @@ -49,6 +48,11 @@ export default class ReadOnlyNetworkStore { if (!this._initialized) { await this._initializing; } + // Delay setting this until after the first read, to match the + // behavior of the local store. + if (!this.mostRecentRetrievedState) { + this.mostRecentRetrievedState = this._state; + } return this._state; } diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js index fa02987a7..f65bb5257 100644 --- a/app/scripts/lib/setup-initial-state-hooks.js +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -5,7 +5,9 @@ import ReadOnlyNetworkStore from './network-store'; import { SENTRY_BACKGROUND_STATE } from './setupSentry'; const platform = new ExtensionPlatform(); -const localStore = process.env.IN_TEST + +// This instance of `localStore` is used by Sentry to get the persisted state +const sentryLocalStore = process.env.IN_TEST ? new ReadOnlyNetworkStore() : new LocalStore(); @@ -15,7 +17,7 @@ const localStore = process.env.IN_TEST * @returns The persisted wallet state. */ globalThis.stateHooks.getPersistedState = async function () { - return await localStore.get(); + return await sentryLocalStore.get(); }; const persistedStateMask = { @@ -37,25 +39,30 @@ globalThis.stateHooks.getSentryState = function () { browser: window.navigator.userAgent, version: platform.getVersion(), }; + // If `getSentryAppState` is set, it implies that initialization has completed if (globalThis.stateHooks.getSentryAppState) { return { ...sentryState, state: globalThis.stateHooks.getSentryAppState(), }; - } else if (globalThis.stateHooks.getMostRecentPersistedState) { - const persistedState = globalThis.stateHooks.getMostRecentPersistedState(); - if (persistedState) { - return { - ...sentryState, - persistedState: maskObject( - // `getMostRecentPersistedState` is used here instead of - // `getPersistedState` to avoid making this an asynchronous function. - globalThis.stateHooks.getMostRecentPersistedState(), - persistedStateMask, - ), - }; - } - return sentryState; + } else if ( + // This is truthy if Sentry has retrieved state at least once already. This + // should always be true because Sentry calls `getPersistedState` during + // error processing (before this function is called) if `getSentryAppState` + // hasn't been set yet. + sentryLocalStore.mostRecentRetrievedState + ) { + return { + ...sentryState, + persistedState: maskObject( + sentryLocalStore.mostRecentRetrievedState, + persistedStateMask, + ), + }; } + // This branch means that local storage has not yet been read, so we have + // no choice but to omit the application state. + // This should be unreachable, unless an error was encountered during error + // processing. return sentryState; }; diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 6be5c7758..570b02c0b 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -33,14 +33,6 @@ import ExtensionPlatform from './platforms/extension'; import { setupMultiplex } from './lib/stream-utils'; import { getEnvironmentType, getPlatform } from './lib/util'; import metaRPCClientFactory from './lib/metaRPCClientFactory'; -import LocalStore from './lib/local-store'; -import ReadOnlyNetworkStore from './lib/network-store'; - -// Setup global hook for improved Sentry state snapshots during initialization -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); -global.stateHooks.getMostRecentPersistedState = () => - localStore.mostRecentRetrievedState; const container = document.getElementById('app-content'); From 0a3241e30db934083709994af95b6d2f1e236b75 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 16:32:28 -0230 Subject: [PATCH 063/102] Fix pre-initialization UI error state capture (#20529) In the case where an error is thrown in the UI before initialization has finished, we aren't capturing the application state correctly for Sentry errors. We had a test case for this, but the test case was broken due to a mistake in how the `network-store` was setup (it was not matching the behavior of the real `local-tstore` module). The pre-initialization state capture logic was updated to rely solely upon the `localStore` instance used by Sentry to determine whether the user had opted-in to metrics or not. This simplifies the logic a great deal, removing the need for the `getMostRecentPersistedState` state hook. It also ensures that state is captured corretly pre- initialization in both the background and UI. --- app/scripts/background.js | 9 ++--- app/scripts/lib/network-store.js | 6 ++- app/scripts/lib/setup-initial-state-hooks.js | 39 ++++++++++++-------- app/scripts/ui.js | 8 ---- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index aa655e5a4..830452033 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -71,12 +71,6 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ -// Setup global hook for improved Sentry state snapshots during initialization -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); -global.stateHooks.getMostRecentPersistedState = () => - localStore.mostRecentRetrievedState; - const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -100,6 +94,9 @@ const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; +// state persistence +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index 2f4c0a1b0..3a0326a2b 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -31,7 +31,6 @@ export default class ReadOnlyNetworkStore { const response = await fetchWithTimeout(FIXTURE_SERVER_URL); if (response.ok) { this._state = await response.json(); - this.mostRecentRetrievedState = this._state; } } catch (error) { log.debug(`Error loading network state: '${error.message}'`); @@ -49,6 +48,11 @@ export default class ReadOnlyNetworkStore { if (!this._initialized) { await this._initializing; } + // Delay setting this until after the first read, to match the + // behavior of the local store. + if (!this.mostRecentRetrievedState) { + this.mostRecentRetrievedState = this._state; + } return this._state; } diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js index fa02987a7..f65bb5257 100644 --- a/app/scripts/lib/setup-initial-state-hooks.js +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -5,7 +5,9 @@ import ReadOnlyNetworkStore from './network-store'; import { SENTRY_BACKGROUND_STATE } from './setupSentry'; const platform = new ExtensionPlatform(); -const localStore = process.env.IN_TEST + +// This instance of `localStore` is used by Sentry to get the persisted state +const sentryLocalStore = process.env.IN_TEST ? new ReadOnlyNetworkStore() : new LocalStore(); @@ -15,7 +17,7 @@ const localStore = process.env.IN_TEST * @returns The persisted wallet state. */ globalThis.stateHooks.getPersistedState = async function () { - return await localStore.get(); + return await sentryLocalStore.get(); }; const persistedStateMask = { @@ -37,25 +39,30 @@ globalThis.stateHooks.getSentryState = function () { browser: window.navigator.userAgent, version: platform.getVersion(), }; + // If `getSentryAppState` is set, it implies that initialization has completed if (globalThis.stateHooks.getSentryAppState) { return { ...sentryState, state: globalThis.stateHooks.getSentryAppState(), }; - } else if (globalThis.stateHooks.getMostRecentPersistedState) { - const persistedState = globalThis.stateHooks.getMostRecentPersistedState(); - if (persistedState) { - return { - ...sentryState, - persistedState: maskObject( - // `getMostRecentPersistedState` is used here instead of - // `getPersistedState` to avoid making this an asynchronous function. - globalThis.stateHooks.getMostRecentPersistedState(), - persistedStateMask, - ), - }; - } - return sentryState; + } else if ( + // This is truthy if Sentry has retrieved state at least once already. This + // should always be true because Sentry calls `getPersistedState` during + // error processing (before this function is called) if `getSentryAppState` + // hasn't been set yet. + sentryLocalStore.mostRecentRetrievedState + ) { + return { + ...sentryState, + persistedState: maskObject( + sentryLocalStore.mostRecentRetrievedState, + persistedStateMask, + ), + }; } + // This branch means that local storage has not yet been read, so we have + // no choice but to omit the application state. + // This should be unreachable, unless an error was encountered during error + // processing. return sentryState; }; diff --git a/app/scripts/ui.js b/app/scripts/ui.js index aeb93cacb..6f923ce39 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -36,14 +36,6 @@ import ExtensionPlatform from './platforms/extension'; import { setupMultiplex } from './lib/stream-utils'; import { getEnvironmentType, getPlatform } from './lib/util'; import metaRPCClientFactory from './lib/metaRPCClientFactory'; -import LocalStore from './lib/local-store'; -import ReadOnlyNetworkStore from './lib/network-store'; - -// Setup global hook for improved Sentry state snapshots during initialization -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); -global.stateHooks.getMostRecentPersistedState = () => - localStore.mostRecentRetrievedState; const container = document.getElementById('app-content'); From dc6069a3abec6378bf882ffe1c991c6733989fec Mon Sep 17 00:00:00 2001 From: George Marshall Date: Fri, 18 Aug 2023 13:27:10 -0700 Subject: [PATCH 064/102] Deprecating ErrorMessage in favor of BannerAlert (#20461) --- ui/components/ui/error-message/README.mdx | 15 --------------- .../ui/error-message/error-message.component.js | 13 +++++++------ .../ui/error-message/error-message.stories.js | 9 +-------- 3 files changed, 8 insertions(+), 29 deletions(-) delete mode 100644 ui/components/ui/error-message/README.mdx diff --git a/ui/components/ui/error-message/README.mdx b/ui/components/ui/error-message/README.mdx deleted file mode 100644 index 777ee8927..000000000 --- a/ui/components/ui/error-message/README.mdx +++ /dev/null @@ -1,15 +0,0 @@ -import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; - -import ErrorMessage from '.'; - -# Error Message - -This component highlights error messages - - - - - -## Props - - diff --git a/ui/components/ui/error-message/error-message.component.js b/ui/components/ui/error-message/error-message.component.js index 53f50f90f..64ff8fc80 100644 --- a/ui/components/ui/error-message/error-message.component.js +++ b/ui/components/ui/error-message/error-message.component.js @@ -4,13 +4,14 @@ import { Icon, IconName, IconSize } from '../../component-library'; import { IconColor } from '../../../helpers/constants/design-system'; /** - * @deprecated - Please use ActionableMessage type danger - * @see ActionableMessage - * @param {object} props - * @param {string} props.errorMessage - * @param {string} props.errorKey - * @param {object} context + * @deprecated The `` component has been deprecated in favor of the new `` component from the component-library. + * Please update your code to use the new `` component instead, which can be found at ui/components/component-library/banner-alert/banner-alert.js. + * You can find documentation for the new BannerAlert component in the MetaMask Storybook: + * {@link https://metamask.github.io/metamask-storybook/?path=/docs/components-componentlibrary-banneralert--docs} + * If you would like to help with the replacement of the old ErrorMessage component, please submit a pull request against this GitHub issue: + * {@link https://github.com/MetaMask/metamask-extension/issues/20394} */ + const ErrorMessage = (props, context) => { const { errorMessage, errorKey } = props; const error = errorKey ? context.t(errorKey) : errorMessage; diff --git a/ui/components/ui/error-message/error-message.stories.js b/ui/components/ui/error-message/error-message.stories.js index e800104ad..6f9add283 100644 --- a/ui/components/ui/error-message/error-message.stories.js +++ b/ui/components/ui/error-message/error-message.stories.js @@ -1,16 +1,9 @@ import React from 'react'; -import README from './README.mdx'; import ErrorMessage from '.'; export default { - title: 'Components/UI/ErrorMessage', - + title: 'Components/UI/ErrorMessage(deprecated)', component: ErrorMessage, - parameters: { - docs: { - page: README, - }, - }, argTypes: { errorMessage: { control: 'text' }, errorKey: { control: 'text' }, From 885a8ce256e27c105299ab28747784682153c587 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 18:26:27 -0230 Subject: [PATCH 065/102] Fix Sentry breadcrumb collection during initialization (again) (#20532) Sentry breadcrumb collection during initialization was broken in #20529 because we failed to consider that the `getSentryState` check was also used for an opt-in check in the `beforeBreadcrumb` hook. I had assumed that `getSentryState` was only used to get state to add additional context to an error report. But the function has a second purpose: to get state for the purposes of checking whether the user has opted into MetaMetrics. In this second case, `mostRecentRetrievedState` is sometimes unset (which violates an assumption made in #20529) The `getMostRecentPersistedState` hook removed in #20529 has been restored, ensuring that the `getSentryState` function returns Sentry state after loading state for the first time, but before the first error has occurred. This mistake didn't cause e2e tests to fail because multiple errors are currently thrown in the background upon initialization on `develop` (relating to Snow scuttling). These errors were early enough that they happened before the console logs that our breadcrumb test was testing for. When #20529 was ported onto the v10.34.5 RC, these errors were not present so the test failed correctly. --- app/scripts/background.js | 10 +++-- app/scripts/lib/setup-initial-state-hooks.js | 40 ++++++++++++-------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index eb4ac613b..b11170de3 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -71,6 +71,12 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; + const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -93,10 +99,6 @@ let uiIsTriggering = false; const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; - -// state persistence -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js index f65bb5257..9b3f18e09 100644 --- a/app/scripts/lib/setup-initial-state-hooks.js +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -28,9 +28,12 @@ const persistedStateMask = { }; /** - * Get a state snapshot to include with Sentry error reports. This uses the - * persisted state pre-initialization, and the in-memory state post- - * initialization. In both cases the state is anonymized. + * Get a state snapshot for Sentry. This is used to add additional context to + * error reports, and it's used when processing errors and breadcrumbs to + * determine whether the user has opted into Metametrics. + * + * This uses the persisted state pre-initialization, and the in-memory state + * post-initialization. In both cases the state is anonymized. * * @returns A Sentry state snapshot. */ @@ -47,22 +50,27 @@ globalThis.stateHooks.getSentryState = function () { }; } else if ( // This is truthy if Sentry has retrieved state at least once already. This - // should always be true because Sentry calls `getPersistedState` during - // error processing (before this function is called) if `getSentryAppState` - // hasn't been set yet. - sentryLocalStore.mostRecentRetrievedState + // should always be true when getting context for an error report, but can + // be unset when Sentry is performing the opt-in check. + sentryLocalStore.mostRecentRetrievedState || + // This is only set in the background process. + globalThis.stateHooks.getMostRecentPersistedState ) { - return { - ...sentryState, - persistedState: maskObject( - sentryLocalStore.mostRecentRetrievedState, - persistedStateMask, - ), - }; + const persistedState = + sentryLocalStore.mostRecentRetrievedState || + globalThis.stateHooks.getMostRecentPersistedState(); + // This can be unset when this method is called in the background for an + // opt-in check, but the state hasn't been loaded yet. + if (persistedState) { + return { + ...sentryState, + persistedState: maskObject(persistedState, persistedStateMask), + }; + } } // This branch means that local storage has not yet been read, so we have // no choice but to omit the application state. - // This should be unreachable, unless an error was encountered during error - // processing. + // This should be unreachable when getting context for an error report, but + // can be false when Sentry is performing the opt-in check. return sentryState; }; From ddeaeb5ba51715b15e10e2631f2c92d3b8ee4a83 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 18:26:27 -0230 Subject: [PATCH 066/102] Fix Sentry breadcrumb collection during initialization (again) (#20532) Sentry breadcrumb collection during initialization was broken in #20529 because we failed to consider that the `getSentryState` check was also used for an opt-in check in the `beforeBreadcrumb` hook. I had assumed that `getSentryState` was only used to get state to add additional context to an error report. But the function has a second purpose: to get state for the purposes of checking whether the user has opted into MetaMetrics. In this second case, `mostRecentRetrievedState` is sometimes unset (which violates an assumption made in #20529) The `getMostRecentPersistedState` hook removed in #20529 has been restored, ensuring that the `getSentryState` function returns Sentry state after loading state for the first time, but before the first error has occurred. This mistake didn't cause e2e tests to fail because multiple errors are currently thrown in the background upon initialization on `develop` (relating to Snow scuttling). These errors were early enough that they happened before the console logs that our breadcrumb test was testing for. When #20529 was ported onto the v10.34.5 RC, these errors were not present so the test failed correctly. --- app/scripts/background.js | 10 +++-- app/scripts/lib/setup-initial-state-hooks.js | 40 ++++++++++++-------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 830452033..44f9d177d 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -71,6 +71,12 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IN /* eslint-enable import/order */ +// Setup global hook for improved Sentry state snapshots during initialization +const inTest = process.env.IN_TEST; +const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); +global.stateHooks.getMostRecentPersistedState = () => + localStore.mostRecentRetrievedState; + const { sentry } = global; const firstTimeState = { ...rawFirstTimeState }; @@ -93,10 +99,6 @@ let uiIsTriggering = false; const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; let controller; - -// state persistence -const inTest = process.env.IN_TEST; -const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); let versionedData; if (inTest || process.env.METAMASK_DEBUG) { diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js index f65bb5257..9b3f18e09 100644 --- a/app/scripts/lib/setup-initial-state-hooks.js +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -28,9 +28,12 @@ const persistedStateMask = { }; /** - * Get a state snapshot to include with Sentry error reports. This uses the - * persisted state pre-initialization, and the in-memory state post- - * initialization. In both cases the state is anonymized. + * Get a state snapshot for Sentry. This is used to add additional context to + * error reports, and it's used when processing errors and breadcrumbs to + * determine whether the user has opted into Metametrics. + * + * This uses the persisted state pre-initialization, and the in-memory state + * post-initialization. In both cases the state is anonymized. * * @returns A Sentry state snapshot. */ @@ -47,22 +50,27 @@ globalThis.stateHooks.getSentryState = function () { }; } else if ( // This is truthy if Sentry has retrieved state at least once already. This - // should always be true because Sentry calls `getPersistedState` during - // error processing (before this function is called) if `getSentryAppState` - // hasn't been set yet. - sentryLocalStore.mostRecentRetrievedState + // should always be true when getting context for an error report, but can + // be unset when Sentry is performing the opt-in check. + sentryLocalStore.mostRecentRetrievedState || + // This is only set in the background process. + globalThis.stateHooks.getMostRecentPersistedState ) { - return { - ...sentryState, - persistedState: maskObject( - sentryLocalStore.mostRecentRetrievedState, - persistedStateMask, - ), - }; + const persistedState = + sentryLocalStore.mostRecentRetrievedState || + globalThis.stateHooks.getMostRecentPersistedState(); + // This can be unset when this method is called in the background for an + // opt-in check, but the state hasn't been loaded yet. + if (persistedState) { + return { + ...sentryState, + persistedState: maskObject(persistedState, persistedStateMask), + }; + } } // This branch means that local storage has not yet been read, so we have // no choice but to omit the application state. - // This should be unreachable, unless an error was encountered during error - // processing. + // This should be unreachable when getting context for an error report, but + // can be false when Sentry is performing the opt-in check. return sentryState; }; From f83c7ff3ef21503797723fa30b086e6938a8a5e5 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 18 Aug 2023 18:30:40 -0230 Subject: [PATCH 067/102] Update changelog for v10.34.5 (#20533) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f1393ca..dc05b3ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [10.34.5] +### Changed +- Improve error diagnostic information + - Add additional logging for state migrations ([#20424](https://github.com/MetaMask/metamask-extension/pull/20424), [#20517](https://github.com/MetaMask/metamask-extension/pull/20517), [#20521](https://github.com/MetaMask/metamask-extension/pull/20521)) + - Improve diagnostic state snapshot ([#20457](https://github.com/MetaMask/metamask-extension/pull/20457), [#20491](https://github.com/MetaMask/metamask-extension/pull/20491), [#20428](https://github.com/MetaMask/metamask-extension/pull/20428), [#20458](https://github.com/MetaMask/metamask-extension/pull/20458)) + - Capture additional errors when state migrations fail ([#20427](https://github.com/MetaMask/metamask-extension/pull/20427)) + +### Fixed +- Fix bug where state was temporarily incomplete upon initial load ([#20468](https://github.com/MetaMask/metamask-extension/pull/20468)) + - In rare circumstances, this bug may have resulted in data loss (of preferences, permissions, or tracked NFTs/tokens) or UI crashes. ## [10.34.4] ### Changed From 07abc53cce5d69466017fa01795f67f94d479fc2 Mon Sep 17 00:00:00 2001 From: Dhruv <79097544+dhruvv173@users.noreply.github.com> Date: Sat, 19 Aug 2023 03:22:40 +0530 Subject: [PATCH 068/102] fix/BannerBase to TS (#20421) * BannerBase to TS * snapshot updates * more snapshot updates * addressing type definition error * updating eth-sign-modal snapshot * Updates to stories, types and adding data-testid * Updating snapshots * updating snapshot of blockaid-banner-alert and adding unit test for childrenWrapperProps * BannerBase updates to stories, adding locale for close button, removing static data-testid in favor of using it at the instance, updating snapshots associated with those changes * Removing incorrect arg from storybook file * Updating html element in security provider e2e test to match BannerBase. Also updating snapshot of ConfirmTransaction page * Fixing e2e tests --------- Co-authored-by: georgewrmarshall --- test/e2e/swaps/shared.js | 2 +- test/e2e/tests/security-provider.spec.js | 8 +- .../ledger-instruction-field.test.js.snap | 2 +- .../__snapshots__/eth-sign-modal.test.js.snap | 2 +- .../export-private-key-modal.test.js.snap | 2 +- ...ecurity-provider-banner-alert.test.js.snap | 9 +- .../blockaid-banner-alert.test.js.snap | 36 ++--- .../__snapshots__/banner-alert.test.js.snap | 9 +- .../banner-alert/banner-alert.test.js | 4 +- .../component-library/banner-base/README.mdx | 16 +- .../__snapshots__/banner-base.test.js.snap | 24 --- .../__snapshots__/banner-base.test.tsx.snap | 23 +++ .../banner-base/banner-base.js | 141 ------------------ ...ase.stories.js => banner-base.stories.tsx} | 107 +++++-------- .../banner-base/banner-base.test.js | 101 ------------- .../banner-base/banner-base.test.tsx | 129 ++++++++++++++++ .../banner-base/banner-base.tsx | 94 ++++++++++++ .../banner-base/banner-base.types.ts | 81 ++++++++++ .../component-library/banner-base/index.js | 1 - .../component-library/banner-base/index.ts | 2 + .../__snapshots__/banner-tip.test.js.snap | 9 +- .../banner-tip/banner-tip.test.js | 4 +- .../component-library/box/box.types.ts | 6 + .../detected-token-banner.test.js.snap | 2 +- .../confirm-send-ether.test.js.snap | 2 +- .../confirm-transaction-base.test.js.snap | 2 +- .../__snapshots__/reveal-seed.test.js.snap | 2 +- .../prepare-swap-page/prepare-swap-page.js | 3 + .../swaps/prepare-swap-page/review-quote.js | 1 + .../view-quote-price-difference.js | 1 + .../swaps-banner-alert/swaps-banner-alert.js | 6 +- .../transaction-settings.js | 1 + 32 files changed, 429 insertions(+), 403 deletions(-) delete mode 100644 ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap create mode 100644 ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap delete mode 100644 ui/components/component-library/banner-base/banner-base.js rename ui/components/component-library/banner-base/{banner-base.stories.js => banner-base.stories.tsx} (58%) delete mode 100644 ui/components/component-library/banner-base/banner-base.test.js create mode 100644 ui/components/component-library/banner-base/banner-base.test.tsx create mode 100644 ui/components/component-library/banner-base/banner-base.tsx create mode 100644 ui/components/component-library/banner-base/banner-base.types.ts delete mode 100644 ui/components/component-library/banner-base/index.js create mode 100644 ui/components/component-library/banner-base/index.ts diff --git a/test/e2e/swaps/shared.js b/test/e2e/swaps/shared.js index f8f149385..0d961a654 100644 --- a/test/e2e/swaps/shared.js +++ b/test/e2e/swaps/shared.js @@ -157,7 +157,7 @@ const checkActivityTransaction = async (driver, options) => { const checkNotification = async (driver, options) => { const boxTitle = await driver.findElement( - '[data-testid="mm-banner-base-title"]', + '[data-testid="swaps-banner-title"]', ); assert.equal(await boxTitle.getText(), options.title, 'Invalid box title'); const boxContent = await driver.findElement( diff --git a/test/e2e/tests/security-provider.spec.js b/test/e2e/tests/security-provider.spec.js index bfbfc443f..9ffb5a352 100644 --- a/test/e2e/tests/security-provider.spec.js +++ b/test/e2e/tests/security-provider.spec.js @@ -106,7 +106,7 @@ describe('Transaction security provider', function () { ); const warningHeader = await driver.isElementPresent({ text: 'This could be a scam', - tag: 'h5', + tag: 'p', }); assert.equal(warningHeader, true); }, @@ -146,7 +146,7 @@ describe('Transaction security provider', function () { ); const warningHeader = await driver.isElementPresent({ text: 'Request may not be safe', - tag: 'h5', + tag: 'p', }); assert.equal(warningHeader, true); }, @@ -186,7 +186,7 @@ describe('Transaction security provider', function () { ); const warningHeader = await driver.isElementPresent({ text: 'Request may not be safe', - tag: 'h5', + tag: 'p', }); assert.equal(warningHeader, false); }, @@ -226,7 +226,7 @@ describe('Transaction security provider', function () { ); const warningHeader = await driver.isElementPresent({ text: 'Request not verified', - tag: 'h5', + tag: 'p', }); assert.equal(warningHeader, true); }, diff --git a/ui/components/app/ledger-instruction-field/__snapshots__/ledger-instruction-field.test.js.snap b/ui/components/app/ledger-instruction-field/__snapshots__/ledger-instruction-field.test.js.snap index b1dad7808..046b5f148 100644 --- a/ui/components/app/ledger-instruction-field/__snapshots__/ledger-instruction-field.test.js.snap +++ b/ui/components/app/ledger-instruction-field/__snapshots__/ledger-instruction-field.test.js.snap @@ -7,7 +7,7 @@ exports[`LedgerInstructionField Component rendering should render properly with class="confirm-detail-row" >

-
Malicious third party detected -
+

diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap index fb17c6870..d6c4f3e5d 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap @@ -2,19 +2,18 @@ exports[`Blockaid Banner Alert should render 'danger' UI when securityAlertResponse.result_type is 'Malicious 1`] = `

-
This is a deceptive request -
+

@@ -48,19 +47,18 @@ exports[`Blockaid Banner Alert should render 'danger' UI when securityAlertRespo exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Failed 1`] = `

-
This is a deceptive request -
+

@@ -72,19 +70,18 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Warning 1`] = `

-
This is a deceptive request -
+

@@ -119,19 +116,18 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp exports[`Blockaid Banner Alert should render details when provided 1`] = `

-
This is a deceptive request -
+

diff --git a/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.js.snap b/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.js.snap index 28da219ef..2224365df 100644 --- a/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.js.snap +++ b/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.js.snap @@ -3,7 +3,7 @@ exports[`BannerAlert should render BannerAlert element correctly 1`] = `

-
BannerAlert test -
+

diff --git a/ui/components/component-library/banner-alert/banner-alert.test.js b/ui/components/component-library/banner-alert/banner-alert.test.js index 5c612a0c3..f131e9872 100644 --- a/ui/components/component-library/banner-alert/banner-alert.test.js +++ b/ui/components/component-library/banner-alert/banner-alert.test.js @@ -75,9 +75,7 @@ describe('BannerAlert', () => { const { getByText } = render( , ); - expect(getByText('BannerAlert title test')).toHaveClass( - 'mm-banner-base__title', - ); + expect(getByText('BannerAlert title test')).toBeDefined(); }); it('should render BannerAlert description', () => { diff --git a/ui/components/component-library/banner-base/README.mdx b/ui/components/component-library/banner-base/README.mdx index a6711ee97..ca427b962 100644 --- a/ui/components/component-library/banner-base/README.mdx +++ b/ui/components/component-library/banner-base/README.mdx @@ -7,21 +7,19 @@ import { BannerBase } from './banner-base'; `BannerBase` serves as a base for all banner variants. It contains standard props such as information and related actions. - + ## Props -The `BannerBase` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props - ### Title Use the `title` prop to pass a string that is sentence case no period. Use the `titleProps` prop to pass additional props to the `Text` component. - + @@ -39,7 +37,7 @@ The `description` is the content area of the `BannerBase` that must be a string. If content requires more than a string, see `children` prop below. - + @@ -56,7 +54,7 @@ import { BannerBase } from '../../component-library'; The `children` prop is an alternative to `description` for `BannerBase` when more than a string is needed. Children content shouldn't repeat title and only 1-3 lines. - + @@ -76,7 +74,7 @@ import { BannerBase } from '../../component-library'; Use the `actionButtonLabel` prop to pass text, `actionButtonOnClick` prop to pass an onClick handler, and `actionButtonProps` prop to pass an object of [ButtonLink props](/docs/components-componentlibrary-buttonlink--default-story) for the action - + @@ -103,7 +101,7 @@ Use the `onClose` prop to pass a function to the close button. The close button Additional props can be passed to the close button with `closeButtonProps` - + @@ -122,7 +120,7 @@ import { BannerBase } from '../../component-library'; Use the `startAccessory` prop to add components such as icons or fox image to the start (default: left) of the `BannerBase` content - + diff --git a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap deleted file mode 100644 index d155bd2c9..000000000 --- a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BannerBase should render bannerbase element correctly 1`] = ` -

-
-
-
- Bannerbase test -
-

- should render bannerbase element correctly -

-
-
-
-`; diff --git a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap new file mode 100644 index 000000000..b7186d2ac --- /dev/null +++ b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BannerBase should render BannerBase element correctly 1`] = ` +
+
+
+

+ BannerBase test +

+

+ should render BannerBase element correctly +

+
+
+
+`; diff --git a/ui/components/component-library/banner-base/banner-base.js b/ui/components/component-library/banner-base/banner-base.js deleted file mode 100644 index fc861c1e5..000000000 --- a/ui/components/component-library/banner-base/banner-base.js +++ /dev/null @@ -1,141 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; - -import { - BackgroundColor, - BorderRadius, - Display, - Size, - TextVariant, -} from '../../../helpers/constants/design-system'; - -import Box from '../../ui/box'; - -import { ButtonLink, IconName, ButtonIcon, Text } from '..'; - -export const BannerBase = ({ - className, - title, - titleProps, - description, - descriptionProps, - children, - actionButtonLabel, - actionButtonOnClick, - actionButtonProps, - startAccessory, - onClose, - closeButtonProps, - ...props -}) => { - return ( - - {startAccessory && <>{startAccessory}} - -
- {title && ( - - {title} - - )} - {description && {description}} - {children && typeof children === 'object' ? ( - children - ) : ( - {children} - )} - {actionButtonLabel && ( - - {actionButtonLabel} - - )} -
- {onClose && ( - - )} -
- ); -}; - -BannerBase.propTypes = { - /** - * The title of the BannerBase - */ - title: PropTypes.string, - /** - * Additional props to pass to the `Text` component used for the `title` text - */ - titleProps: PropTypes.object, - /** - * The description is the content area below BannerBase title - */ - description: PropTypes.string, - /** - * Additional props to pass to the `Text` component used for the `description` text - */ - descriptionProps: PropTypes.object, - /** - * The children is an alternative to using the description prop for BannerBase content below the title - */ - children: PropTypes.node, - /** - * Label for action button (ButtonLink) of the BannerBase below the children - */ - actionButtonLabel: PropTypes.string, - /** - * Props for action button (ButtonLink) of the BannerBase below the children - */ - actionButtonProps: PropTypes.object, - /** - * The onClick handler for the action button (ButtonLink) - */ - actionButtonOnClick: PropTypes.func, - /** - * The start(defualt left) content area of BannerBase - */ - startAccessory: PropTypes.node, - /** - * The onClick handler for the close button - * When passed this will allow for the close button to show - */ - onClose: PropTypes.func, - /** - * The props to pass to the close button - */ - closeButtonProps: PropTypes.object, - /** - * An additional className to apply to the BannerBase - */ - className: PropTypes.string, - /** - * BannerBase accepts all the props from Box - */ - ...Box.propTypes, -}; diff --git a/ui/components/component-library/banner-base/banner-base.stories.js b/ui/components/component-library/banner-base/banner-base.stories.tsx similarity index 58% rename from ui/components/component-library/banner-base/banner-base.stories.js rename to ui/components/component-library/banner-base/banner-base.stories.tsx index a964c80e8..f5df17ca3 100644 --- a/ui/components/component-library/banner-base/banner-base.stories.js +++ b/ui/components/component-library/banner-base/banner-base.stories.tsx @@ -1,28 +1,17 @@ import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; import { useState } from '@storybook/addons'; -import { Size } from '../../../helpers/constants/design-system'; -import { ButtonLink, ButtonPrimary, Icon, IconName, IconSize } from '..'; +import { + ButtonLink, + ButtonLinkSize, + ButtonPrimary, + Icon, + IconName, + IconSize, +} from '..'; import { BannerBase } from './banner-base'; import README from './README.mdx'; -const marginSizeControlOptions = [ - undefined, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 'auto', -]; - export default { title: 'Components/ComponentLibrary/BannerBase', component: BannerBase, @@ -39,20 +28,26 @@ export default { title: { control: 'text', }, + titleProps: { + control: 'object', + }, description: { control: 'text', }, + descriptionProps: { + control: 'object', + }, children: { control: 'text', }, - action: { - control: 'func', + childrenProps: { + control: 'object', }, actionButtonLabel: { control: 'text', }, actionButtonOnClick: { - control: 'func', + action: 'actionButtonOnClick', }, actionButtonProps: { control: 'object', @@ -63,33 +58,12 @@ export default { onClose: { action: 'onClose', }, - marginTop: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginRight: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginBottom: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, - marginLeft: { - options: marginSizeControlOptions, - control: 'select', - table: { category: 'box props' }, - }, }, -}; +} as Meta; -export const DefaultStory = (args) => { - const onClose = () => console.log('BannerBase onClose trigger'); - return ; -}; +const Template: StoryFn = (args) => ; + +export const DefaultStory = Template.bind({}); DefaultStory.args = { title: 'Title is sentence case no period', @@ -100,18 +74,14 @@ DefaultStory.args = { DefaultStory.storyName = 'Default'; -export const Title = (args) => { - return ; -}; +export const Title = Template.bind({}); Title.args = { title: 'Title is sentence case no period', children: 'Pass only a string through the title prop', }; -export const Description = (args) => { - return ; -}; +export const Description = Template.bind({}); Description.args = { title: 'Description vs children', @@ -119,29 +89,27 @@ Description.args = { 'Pass only a string through the description prop or you can use children if the contents require more', }; -export const Children = (args) => { +export const Children: StoryFn = (args) => { return ( - {`Description shouldn't repeat title. 1-3 lines. Can contain a `} + Description shouldn't repeat title. 1-3 lines. Can contain a{' '} - hyperlink. + hyperlink + . ); }; -export const ActionButton = (args) => { - return ; -}; +export const ActionButton = Template.bind({}); ActionButton.args = { title: 'Action prop demo', actionButtonLabel: 'Action', - actionButtonOnClick: () => console.log('ButtonLink actionButtonOnClick demo'), actionButtonProps: { endIconName: IconName.Arrow2Right, }, @@ -149,14 +117,9 @@ ActionButton.args = { 'Use actionButtonLabel for action text, actionButtonOnClick for the onClick handler, and actionButtonProps to pass any ButtonLink prop types such as iconName', }; -export const OnClose = (args) => { +export const OnClose: StoryFn = (args) => { const [isShown, setShown] = useState(true); - const bannerToggle = () => { - if (isShown) { - console.log('close button clicked'); - } - setShown(!isShown); - }; + const bannerToggle = () => setShown(!isShown); return ( <> {isShown ? ( @@ -173,9 +136,7 @@ OnClose.args = { children: 'Click the close button icon to hide this notifcation', }; -export const StartAccessory = (args) => { - return ; -}; +export const StartAccessory = Template.bind({}); StartAccessory.args = { title: 'Start accessory demo', diff --git a/ui/components/component-library/banner-base/banner-base.test.js b/ui/components/component-library/banner-base/banner-base.test.js deleted file mode 100644 index 17f4643b3..000000000 --- a/ui/components/component-library/banner-base/banner-base.test.js +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-disable jest/require-top-level-describe */ -import { render } from '@testing-library/react'; -import React from 'react'; - -import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; - -import { Icon, IconName } from '..'; -import { BannerBase } from './banner-base'; - -describe('BannerBase', () => { - it('should render bannerbase element correctly', () => { - const { getByTestId, container } = render( - - should render bannerbase element correctly - , - ); - expect(getByTestId('banner-base')).toHaveClass('mm-banner-base'); - expect(container).toMatchSnapshot(); - }); - - it('should render with added classname', () => { - const { getByTestId } = render( - - should render bannerbase element correctly - , - ); - expect(getByTestId('banner-base')).toHaveClass('mm-banner-base--test'); - }); - - it('should render bannerbase title', () => { - const { getByText } = render(); - expect(getByText('Bannerbase title test')).toHaveClass( - 'mm-banner-base__title', - ); - }); - - it('should render bannerbase description', () => { - const { getByText } = render( - , - ); - expect(getByText('Bannerbase description test')).toBeDefined(); - }); - - it('should render bannerbase children', () => { - const { getByText } = render( - Bannerbase children test, - ); - expect(getByText('Bannerbase children test')).toBeDefined(); - }); - - it('should render bannerbase action button', () => { - const { getByTestId } = render( - - console.log('ButtonLink actionButtonOnClick demo') - } - > - Use actionButtonLabel for action text, actionButtonOnClick for the - onClick handler, and actionButtonProps to pass any ButtonLink prop types - such as iconName - , - ); - expect(getByTestId('action')).toHaveClass('mm-banner-base__action'); - }); - - it('should render bannerbase startAccessory', () => { - const { getByTestId } = render( - - } - />, - ); - - expect(getByTestId('start-accessory')).toBeDefined(); - }); - - it('should render and fire onClose event', async () => { - const onClose = jest.fn(); - const { user, getByTestId } = renderWithUserEvent( - , - ); - await user.click(getByTestId('close-button')); - expect(onClose).toHaveBeenCalledTimes(1); - }); -}); diff --git a/ui/components/component-library/banner-base/banner-base.test.tsx b/ui/components/component-library/banner-base/banner-base.test.tsx new file mode 100644 index 000000000..09eaf3943 --- /dev/null +++ b/ui/components/component-library/banner-base/banner-base.test.tsx @@ -0,0 +1,129 @@ +/* eslint-disable jest/require-top-level-describe */ +import { render } from '@testing-library/react'; +import React from 'react'; + +import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; + +import { Icon, IconName } from '..'; +import { BannerBase } from './banner-base'; + +describe('BannerBase', () => { + it('should render BannerBase element correctly', () => { + const { getByTestId, container } = render( + + should render BannerBase element correctly + , + ); + expect(getByTestId('banner-base')).toHaveClass('mm-banner-base'); + expect(container).toMatchSnapshot(); + }); + + it('should render with added classname', () => { + const { getByTestId } = render( + + should render BannerBase element correctly + , + ); + expect(getByTestId('banner-base')).toHaveClass('mm-banner-base--test'); + }); + + it('should render BannerBase title', () => { + const { getByText, getByTestId } = render( + , + ); + + expect(getByText('BannerBase title test')).toBeDefined(); + expect(getByTestId('title')).toBeDefined(); + }); + + it('should render BannerBase description', () => { + const { getByText, getByTestId } = render( + , + ); + expect(getByText('BannerBase description test')).toBeDefined(); + expect(getByTestId('description')).toBeDefined(); + }); + + it('should render BannerBase children with props', () => { + const { getByText, getByTestId } = render( + + BannerBase children + , + ); + expect(getByTestId('children-wrapper')).toBeDefined(); + expect(getByText('BannerBase children')).toBeDefined(); + }); + + it('should render BannerBase children without wrapper when not a string', () => { + const { getByText, queryByTestId } = render( + +
BannerBase children
+
, + ); + + expect(queryByTestId('children-wrapper')).not.toBeInTheDocument(); + expect(getByText('BannerBase children')).toBeDefined(); + }); + + it('should render BannerBase action button', () => { + const fn = jest.fn(); + const { getByTestId } = render( + + BannerBase children + , + ); + expect(getByTestId('action')).toHaveClass('mm-banner-base__action'); + }); + + it('should render BannerBase startAccessory', () => { + const { getByTestId } = render( + + } + />, + ); + + expect(getByTestId('start-accessory')).toBeDefined(); + }); + + it('should render and fire onClose event', async () => { + const onClose = jest.fn(); + const { user, getByTestId } = renderWithUserEvent( + , + ); + await user.click(getByTestId('close-button')); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/ui/components/component-library/banner-base/banner-base.tsx b/ui/components/component-library/banner-base/banner-base.tsx new file mode 100644 index 000000000..9947d7a6e --- /dev/null +++ b/ui/components/component-library/banner-base/banner-base.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import classnames from 'classnames'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +import { + BackgroundColor, + BorderRadius, + Display, + TextVariant, +} from '../../../helpers/constants/design-system'; + +import { + ButtonLink, + IconName, + ButtonIcon, + Text, + Box, + ButtonLinkSize, + ButtonIconSize, +} from '..'; +import { BoxProps, PolymorphicRef } from '../box'; +import { BannerBaseComponent, BannerBaseProps } from './banner-base.types'; + +export const BannerBase: BannerBaseComponent = React.forwardRef( + ( + { + className = '', + title, + titleProps, + description, + descriptionProps, + children, + childrenWrapperProps, + actionButtonLabel, + actionButtonOnClick, + actionButtonProps, + startAccessory, + onClose, + closeButtonProps, + ...props + }: BannerBaseProps, + ref?: PolymorphicRef, + ) => { + const t = useI18nContext(); + return ( + )} + > + {startAccessory && <>{startAccessory}} + +
+ {title && ( + + {title} + + )} + {description && {description}} + {children && typeof children === 'object' ? ( + children + ) : ( + {children} + )} + {actionButtonLabel && ( + + {actionButtonLabel} + + )} +
+ {onClose && ( + + )} +
+ ); + }, +); diff --git a/ui/components/component-library/banner-base/banner-base.types.ts b/ui/components/component-library/banner-base/banner-base.types.ts new file mode 100644 index 000000000..8ed90d71f --- /dev/null +++ b/ui/components/component-library/banner-base/banner-base.types.ts @@ -0,0 +1,81 @@ +import React from 'react'; + +import type { + PolymorphicComponentPropWithRef, + StyleUtilityProps, +} from '../box'; + +import type { TextProps } from '../text'; +import type { ButtonLinkProps } from '../button-link'; +import type { ButtonIconProps } from '../button-icon'; + +/** + * Makes all props optional so that if a prop object is used not ALL required props need to be passed + * TODO: Move to appropriate place in app as this will be highly reusable + */ +type MakePropsOptional = { + [K in keyof T]?: T[K]; +}; + +export interface BannerBaseStyleUtilityProps extends StyleUtilityProps { + /** + * The title of the BannerBase + */ + title?: string; + /** + * Additional props to pass to the `Text` component used for the `title` text + */ + titleProps?: MakePropsOptional>; + /** + * The description is the content area below BannerBase title + */ + description?: string; + /** + * Additional props to pass to the `Text` component used for the `description` text + */ + descriptionProps?: MakePropsOptional>; + /** + * The children is an alternative to using the description prop for BannerBase content below the title + */ + children?: React.ReactNode; + /** + * Additional props to pass to the `Text` component used to wrap the `children` if `children` is type `string` + */ + childrenWrapperProps?: MakePropsOptional>; + /** + * Label for action button (ButtonLink) of the BannerBase below the children + */ + actionButtonLabel?: string; + /** + * Props for action button (ButtonLink) of the BannerBase below the children + */ + actionButtonProps?: MakePropsOptional>; + /** + * The onClick handler for the action button (ButtonLink) + */ + actionButtonOnClick?: (event: React.MouseEvent) => void; + /** + * The start(default left) content area of BannerBase + */ + startAccessory?: React.ReactNode; + /** + * The onClick handler for the close button + * When passed this will allow for the close button to show + */ + onClose?: () => void; + /** + * The props to pass to the close button + */ + closeButtonProps?: MakePropsOptional>; + /** + * An additional className to apply to the BannerBase + */ + className?: string; +} + +export type BannerBaseProps = + PolymorphicComponentPropWithRef; + +export type BannerBaseComponent = ( + props: BannerBaseProps, +) => React.ReactElement | null; diff --git a/ui/components/component-library/banner-base/index.js b/ui/components/component-library/banner-base/index.js deleted file mode 100644 index e286559ae..000000000 --- a/ui/components/component-library/banner-base/index.js +++ /dev/null @@ -1 +0,0 @@ -export { BannerBase } from './banner-base'; diff --git a/ui/components/component-library/banner-base/index.ts b/ui/components/component-library/banner-base/index.ts new file mode 100644 index 000000000..4989b34ea --- /dev/null +++ b/ui/components/component-library/banner-base/index.ts @@ -0,0 +1,2 @@ +export { BannerBase } from './banner-base'; +export type { BannerBaseProps } from './banner-base.types'; diff --git a/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.js.snap b/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.js.snap index 8658c76c7..b44382369 100644 --- a/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.js.snap +++ b/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.js.snap @@ -3,7 +3,7 @@ exports[`BannerTip should render BannerTip element correctly 1`] = `
-
BannerTip test -
+

diff --git a/ui/components/component-library/banner-tip/banner-tip.test.js b/ui/components/component-library/banner-tip/banner-tip.test.js index 772acf722..edfad87a1 100644 --- a/ui/components/component-library/banner-tip/banner-tip.test.js +++ b/ui/components/component-library/banner-tip/banner-tip.test.js @@ -57,9 +57,7 @@ describe('BannerTip', () => { it('should render BannerTip title', () => { const { getByText } = render(); - expect(getByText('BannerTip title test')).toHaveClass( - 'mm-banner-base__title', - ); + expect(getByText('BannerTip title test')).toBeDefined(); }); it('should render BannerTip description', () => { diff --git a/ui/components/component-library/box/box.types.ts b/ui/components/component-library/box/box.types.ts index badb3f5ce..49a2c894e 100644 --- a/ui/components/component-library/box/box.types.ts +++ b/ui/components/component-library/box/box.types.ts @@ -410,6 +410,12 @@ export interface StyleUtilityProps { * Accepts responsive props in the form of an array. */ color?: TextColor | TextColorArray | IconColor | IconColorArray; + /** + * An optional data-testid to apply to the component. + * TypeScript is complaining about data- attributes which means we need to explicitly define this as a prop. + * TODO: Allow data- attributes. + */ + 'data-testid'?: string; } /** * Box component props. diff --git a/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap b/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap index d07c02c5e..014cd0d77 100644 --- a/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap +++ b/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap @@ -3,7 +3,7 @@ exports[`DetectedTokensBanner should render correctly 1`] = `

diff --git a/ui/pages/swaps/prepare-swap-page/review-quote.js b/ui/pages/swaps/prepare-swap-page/review-quote.js index 5c815f2a5..9bca5d8c6 100644 --- a/ui/pages/swaps/prepare-swap-page/review-quote.js +++ b/ui/pages/swaps/prepare-swap-page/review-quote.js @@ -1073,6 +1073,7 @@ export default function ReviewQuote({ setReceiveToAmount }) { {(showInsufficientWarning || tokenBalanceUnavailable) && ( + {description} ); diff --git a/ui/pages/swaps/transaction-settings/transaction-settings.js b/ui/pages/swaps/transaction-settings/transaction-settings.js index a0d213eac..51291ec45 100644 --- a/ui/pages/swaps/transaction-settings/transaction-settings.js +++ b/ui/pages/swaps/transaction-settings/transaction-settings.js @@ -337,6 +337,7 @@ export default function TransactionSettings({ Date: Sat, 19 Aug 2023 00:07:38 +0200 Subject: [PATCH 069/102] Use KeyringController messenger events (#20341) * refactor: use keyring-controller events and unlock methods * test: add tests for setLocked * rollback: call trezor dispose directly * Update app/scripts/metamask-controller.js Co-authored-by: Mark Stacey --------- Co-authored-by: Mark Stacey --- .../metamask-controller.actions.test.js | 8 ++++ app/scripts/metamask-controller.js | 39 ++++++++++--------- app/scripts/metamask-controller.test.js | 16 +++++++- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 8c015c9c5..a0dba444b 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -220,6 +220,14 @@ describe('MetaMaskController', function () { }); }); + describe('#setLocked', function () { + it('should lock the wallet', async function () { + const { isUnlocked, keyrings } = await metamaskController.setLocked(); + assert(!isUnlocked); + assert.deepEqual(keyrings, []); + }); + }); + describe('#addToken', function () { const address = '0x514910771af9ca656af840dff83e8264ecf986ca'; const symbol = 'LINK'; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6bc0d9531..daf2a74e1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -877,15 +877,21 @@ export default class MetamaskController extends EventEmitter { ), }); - this.keyringController = - this.coreKeyringController.getEthKeyringController(); - - this.keyringController.memStore.subscribe((state) => - this._onKeyringControllerUpdate(state), + this.controllerMessenger.subscribe('KeyringController:unlock', () => + this._onUnlock(), + ); + this.controllerMessenger.subscribe('KeyringController:lock', () => + this._onLock(), + ); + this.controllerMessenger.subscribe( + 'KeyringController:stateChange', + (state) => { + this._onKeyringControllerUpdate(state); + }, ); - this.keyringController.on('unlock', () => this._onUnlock()); - this.keyringController.on('lock', () => this._onLock()); + this.keyringController = + this.coreKeyringController.getEthKeyringController(); const getIdentities = () => this.preferencesController.store.getState().identities; @@ -3060,10 +3066,9 @@ export default class MetamaskController extends EventEmitter { * is up to date with known accounts once the vault is decrypted. * * @param {string} password - The user's password - * @returns {Promise} The keyringController update. */ async submitPassword(password) { - await this.keyringController.submitPassword(password); + await this.coreKeyringController.submitPassword(password); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.mmiController.onSubmitPassword(); @@ -3083,8 +3088,6 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.getLedgerTransportPreference(); this.setLedgerTransportPreference(transportPreference); - - return this.keyringController.fullUpdate(); } async _loginUser() { @@ -3123,7 +3126,7 @@ export default class MetamaskController extends EventEmitter { const { loginToken, loginSalt } = await this.extension.storage.session.get(['loginToken', 'loginSalt']); if (loginToken && loginSalt) { - const { vault } = this.keyringController.store.getState(); + const { vault } = this.coreKeyringController.state; const jsonVault = JSON.parse(vault); @@ -3135,7 +3138,10 @@ export default class MetamaskController extends EventEmitter { return; } - await this.keyringController.submitEncryptionKey(loginToken, loginSalt); + await this.coreKeyringController.submitEncryptionKey( + loginToken, + loginSalt, + ); } } catch (e) { // If somehow this login token doesn't work properly, @@ -4709,16 +4715,11 @@ export default class MetamaskController extends EventEmitter { trezorKeyring.dispose(); } - const [ledgerKeyring] = this.coreKeyringController.getKeyringsByType( - KeyringType.ledger, - ); - ledgerKeyring?.destroy?.(); - if (isManifestV3) { this.clearLoginArtifacts(); } - return this.keyringController.setLocked(); + return this.coreKeyringController.setLocked(); } removePermissionsFor = (subjects) => { diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 20f496aba..ec7f40643 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -370,7 +370,7 @@ describe('MetaMaskController', function () { metamaskController.preferencesController.store.getState().identities, ); const addresses = - await metamaskController.keyringController.getAccounts(); + await metamaskController.coreKeyringController.getAccounts(); identities.forEach((identity) => { assert.ok( @@ -388,6 +388,20 @@ describe('MetaMaskController', function () { }); }); + describe('setLocked', function () { + it('should lock KeyringController', async function () { + sandbox.spy(metamaskController.coreKeyringController, 'setLocked'); + + await metamaskController.setLocked(); + + assert(metamaskController.coreKeyringController.setLocked.called); + assert.equal( + metamaskController.coreKeyringController.state.isUnlocked, + false, + ); + }); + }); + describe('#createNewVaultAndKeychain', function () { it('can only create new vault on keyringController once', async function () { const selectStub = sandbox.stub( From ba31f87014ec1146b8d5a77eb57b53c0fce1ffca Mon Sep 17 00:00:00 2001 From: Dhruv <79097544+dhruvv173@users.noreply.github.com> Date: Sat, 19 Aug 2023 06:29:02 +0530 Subject: [PATCH 070/102] fix/TagUrl to TS (#20519) --- .../signature-request.test.js.snap | 4 +- .../avatar-favicon/avatar-favicon.test.tsx | 3 +- .../avatar-favicon/avatar-favicon.types.ts | 2 +- .../component-library/tag-url/README.mdx | 26 ++-- ...url.test.js.snap => tag-url.test.tsx.snap} | 2 +- .../component-library/tag-url/index.js | 1 - .../component-library/tag-url/index.ts | 2 + .../component-library/tag-url/tag-url.js | 118 ------------------ ...tag-url.stories.js => tag-url.stories.tsx} | 39 +++--- .../{tag-url.test.js => tag-url.test.tsx} | 0 .../component-library/tag-url/tag-url.tsx | 87 +++++++++++++ .../tag-url/tag-url.types.ts | 55 ++++++++ .../__snapshots__/index.test.js.snap | 2 +- 13 files changed, 189 insertions(+), 152 deletions(-) rename ui/components/component-library/tag-url/__snapshots__/{tag-url.test.js.snap => tag-url.test.tsx.snap} (76%) delete mode 100644 ui/components/component-library/tag-url/index.js create mode 100644 ui/components/component-library/tag-url/index.ts delete mode 100644 ui/components/component-library/tag-url/tag-url.js rename ui/components/component-library/tag-url/{tag-url.stories.js => tag-url.stories.tsx} (64%) rename ui/components/component-library/tag-url/{tag-url.test.js => tag-url.test.tsx} (100%) create mode 100644 ui/components/component-library/tag-url/tag-url.tsx create mode 100644 ui/components/component-library/tag-url/tag-url.types.ts diff --git a/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap index af2a54c5c..055cde8c0 100644 --- a/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap +++ b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap @@ -179,7 +179,7 @@ exports[`Signature Request Component render should match snapshot when we are us class="signature-request__origin" >
{ const { getByTestId } = render( , @@ -108,7 +107,7 @@ describe('AvatarFavicon', () => { }); it('should forward a ref to the root html element', () => { const ref = React.createRef(); - render(); + render(); expect(ref.current).not.toBeNull(); expect(ref.current.nodeName).toBe('DIV'); }); diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts b/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts index 72316b5c2..35b28ebc4 100644 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.types.ts @@ -12,7 +12,7 @@ export enum AvatarFaviconSize { } export interface AvatarFaviconStyleUtilityProps - extends Omit { + extends Omit { /** * The src accepts the string of the image to be rendered */ diff --git a/ui/components/component-library/tag-url/README.mdx b/ui/components/component-library/tag-url/README.mdx index ace2e64d8..a5cc123f2 100644 --- a/ui/components/component-library/tag-url/README.mdx +++ b/ui/components/component-library/tag-url/README.mdx @@ -12,13 +12,11 @@ The `TagUrl` is a component used to display dApp information ## Props -The `TagUrl` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props. - ### Action Button Label -If we want to have a button in `TagUrl` component. You can update the `ButtonLink` component props used for the action button using `actionButtonProps` +Use the `actionButtonLabel` to add a `ButtonLink` inside of `TagUrl`. Use the `actionButtonProps` prop object to add the necessary `href` or `onClick`. @@ -32,18 +30,30 @@ import { TagUrl } from '../../ui/component-library/tag-url'; src="https://uniswap.org/favicon.ico" showLockIcon actionButtonLabel="Permissions" + actionButtonProps={{ + externalLink: true, + href: 'https://metamask.io', + }} /> ``` @@ -51,7 +61,7 @@ import { TagUrl } from '../../ui/component-library/tag-url'; Use the `showLockIcon` prop to render a lock icon next to the `label`. It's intended use is to display if the url uses `https`. This logic should be added during implementation and is not included in the component. -Off(`undefined`) by default. +`false` by default. Props for the lock icon can be changed using the `lockIconProps` @@ -89,8 +99,8 @@ import { TagUrl } from '../../ui/component-library/tag-url'; showLockIcon />
{ - return ( - - - {showLockIcon && ( - - )} - - {label} - - {actionButtonLabel && ( - - {actionButtonLabel} - - )} - - ); -}; - -TagUrl.propTypes = { - /** - * The src accepts the string of the image to be rendered - */ - src: PropTypes.string, - /** - * The showLockIcon accepts a boolean prop to render the lock icon instead of https in label - */ - showLockIcon: PropTypes.bool, - /** - * It accepts all the props from Avatar Favicon - */ - avatarFaviconProps: PropTypes.object, - /** - * It accepts all the props from Icon - */ - lockIconProps: PropTypes.object, - /** - * The text content of the TagUrl component - */ - label: PropTypes.string.isRequired, - /** - * It accepts all the props from Text Component - */ - labelProps: PropTypes.object, - /** - * If we want a button in TagUrl component. - */ - actionButtonLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - /** - * It accepts all the props from ButtonLink - */ - actionButtonProps: PropTypes.object, - /** - * Additional classNames to be added to the TagUrl component - */ - className: PropTypes.string, - /** - * TagUrl accepts all the props from Box - */ - ...Box.propTypes, -}; - -export default TagUrl; diff --git a/ui/components/component-library/tag-url/tag-url.stories.js b/ui/components/component-library/tag-url/tag-url.stories.tsx similarity index 64% rename from ui/components/component-library/tag-url/tag-url.stories.js rename to ui/components/component-library/tag-url/tag-url.stories.tsx index 488c446ca..aa8163281 100644 --- a/ui/components/component-library/tag-url/tag-url.stories.js +++ b/ui/components/component-library/tag-url/tag-url.stories.tsx @@ -1,16 +1,15 @@ import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; import { - DISPLAY, - FLEX_DIRECTION, + Display, + FlexDirection, } from '../../../helpers/constants/design-system'; - -import Box from '../../ui/box'; +import { Box } from '..'; import README from './README.mdx'; import { TagUrl } from './tag-url'; export default { title: 'Components/ComponentLibrary/TagUrl', - component: TagUrl, parameters: { docs: { @@ -36,7 +35,7 @@ export default { src: 'https://uniswap.org/favicon.ico', showLockIcon: true, }, -}; +} as Meta; const Template = (args) => ; @@ -47,8 +46,8 @@ DefaultStory.args = { actionButtonLabel: 'Permissions', }; -export const ActionButtonLabel = (args) => ( - +export const ActionButtonLabel: StoryFn = (args) => ( + @@ -57,10 +56,14 @@ export const ActionButtonLabel = (args) => ( ActionButtonLabel.args = { actionButtonLabel: 'Permissions', + actionButtonProps: { + externalLink: true, + href: 'https://metamask.io', + }, }; -export const ShowLockIcon = (args) => ( - +export const ShowLockIcon: StoryFn = (args) => ( + ( ); -export const Src = (args) => ( - +export const Src: StoryFn = (args) => ( + ( /> ( ); -export const Label = (args) => ( - +export const Label: StoryFn = (args) => ( + diff --git a/ui/components/component-library/tag-url/tag-url.test.js b/ui/components/component-library/tag-url/tag-url.test.tsx similarity index 100% rename from ui/components/component-library/tag-url/tag-url.test.js rename to ui/components/component-library/tag-url/tag-url.test.tsx diff --git a/ui/components/component-library/tag-url/tag-url.tsx b/ui/components/component-library/tag-url/tag-url.tsx new file mode 100644 index 000000000..15e3d3dbc --- /dev/null +++ b/ui/components/component-library/tag-url/tag-url.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import classnames from 'classnames'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + Display, + IconColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { + AvatarFavicon, + ButtonLink, + Box, + IconName, + Icon, + IconSize, + Text, + ButtonLinkSize, +} from '..'; +import { BoxProps, PolymorphicRef } from '../box'; +import { TagUrlComponent, TagUrlProps } from './tag-url.types'; + +export const TagUrl: TagUrlComponent = React.forwardRef( + ( + { + label, + labelProps, + actionButtonLabel, + actionButtonProps, + src, + showLockIcon, + avatarFaviconProps, + lockIconProps, + className = '', + ...props + }: TagUrlProps, + ref?: PolymorphicRef, + ) => { + return ( + )} + > + + {showLockIcon && ( + + )} + + {label} + + {actionButtonLabel && ( + + {actionButtonLabel} + + )} + + ); + }, +); diff --git a/ui/components/component-library/tag-url/tag-url.types.ts b/ui/components/component-library/tag-url/tag-url.types.ts new file mode 100644 index 000000000..90bc173d9 --- /dev/null +++ b/ui/components/component-library/tag-url/tag-url.types.ts @@ -0,0 +1,55 @@ +import React from 'react'; +import type { + PolymorphicComponentPropWithRef, + StyleUtilityProps, +} from '../box'; +import { AvatarFaviconProps } from '../avatar-favicon'; +import { IconProps } from '../icon'; +import { TextProps } from '../text'; +import { ButtonLinkProps } from '../button-link'; + +export interface TagUrlStyleUtilityProps extends StyleUtilityProps { + /** + * The src accepts the string of the image to be rendered + */ + src?: string; + /** + * The showLockIcon accepts a boolean prop to render the lock icon instead of https in label + */ + showLockIcon?: boolean; + /** + * It accepts all the props from Avatar Favicon + */ + avatarFaviconProps?: AvatarFaviconProps<'span'>; + /** + * It accepts all the props from Icon + */ + lockIconProps?: IconProps<'span'>; + /** + * The text content of the TagUrl component + */ + label: string; + /** + * It accepts all the props from Text Component + */ + labelProps?: TextProps<'p'>; + /** + * If we want a button in TagUrl component. + */ + actionButtonLabel?: string | React.ReactNode; + /** + * It accepts all the props from ButtonLink + */ + actionButtonProps?: ButtonLinkProps<'button'>; + /** + * Additional classNames to be added to the TagUrl component + */ + className?: string; +} + +export type TagUrlProps = + PolymorphicComponentPropWithRef; + +export type TagUrlComponent = ( + props: TagUrlProps, +) => React.ReactElement | null; diff --git a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap index 8a3e10304..81000b0b0 100644 --- a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap +++ b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap @@ -178,7 +178,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] = class="signature-request__origin" >
Date: Mon, 21 Aug 2023 12:03:55 +0200 Subject: [PATCH 071/102] [FLASK] Fix regression in transaction confirmation tabs (#20267) * Fix regression in transaction confirmation tabs * Fix transaction insights e2e test --- test/e2e/snaps/test-snap-txinsights.spec.js | 5 +++++ .../confirm-page-container-content.component.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js index 4130abb50..637dab89e 100644 --- a/test/e2e/snaps/test-snap-txinsights.spec.js +++ b/test/e2e/snaps/test-snap-txinsights.spec.js @@ -102,6 +102,11 @@ describe('Test Snap TxInsights', function () { 'MetaMask Notification', windowHandles, ); + await driver.delay(1000); + await driver.clickElement({ + text: 'Insights Example Snap', + tag: 'button', + }); // check that txinsightstest tab contains the right info await driver.delay(1000); diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index 4630cc5f9..f881c9029 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -120,7 +120,7 @@ export default class ConfirmPageContainerContent extends Component { } = this.props; return ( - + Date: Mon, 21 Aug 2023 07:45:25 -0230 Subject: [PATCH 072/102] Use primary transaction to get token value in useTransactionDisplayData (#20536) Co-authored-by: Jyoti Puri --- ui/hooks/useTransactionDisplayData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/hooks/useTransactionDisplayData.js b/ui/hooks/useTransactionDisplayData.js index 8d5f7bc3d..6ce9b11c5 100644 --- a/ui/hooks/useTransactionDisplayData.js +++ b/ui/hooks/useTransactionDisplayData.js @@ -198,7 +198,7 @@ export function useTransactionDisplayData(transactionGroup) { ); const tokenDisplayValue = useTokenDisplayValue( - initialTransaction?.txParams?.data, + primaryTransaction?.txParams?.data, token, isTokenCategory, ); From 8afa75e1f1064dacf18bdaffbb31754d7e8ad81f Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 21 Aug 2023 10:04:01 -0230 Subject: [PATCH 073/102] Update version in e2e state snapshot --- .../errors-after-init-opt-in-background-state.json | 2 +- .../state-snapshots/errors-after-init-opt-in-ui-state.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 583be2020..37927c291 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -8,7 +8,7 @@ }, "AnnouncementController": "object", "AppMetadataController": { - "currentAppVersion": "10.34.4", + "currentAppVersion": "10.34.5", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": 94 diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 24f10128f..37a9dc691 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -57,7 +57,7 @@ "usedNetworks": "object", "snapsInstallPrivacyWarningShown": "boolean", "serviceWorkerLastActiveTime": "number", - "currentAppVersion": "10.34.4", + "currentAppVersion": "10.34.5", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": 94, From a712298f184fa43f0b37ec0aed7efaf1c58fe46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Mon, 21 Aug 2023 15:49:54 +0200 Subject: [PATCH 074/102] updated mmi packages version (#20545) * updated mmi packages version * Update LavaMoat policies --------- Co-authored-by: MetaMask Bot --- lavamoat/browserify/mmi/policy.json | 111 +--------------------- package.json | 14 +-- yarn.lock | 137 +++++++++------------------- 3 files changed, 53 insertions(+), 209 deletions(-) diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index b59f78086..45b960760 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -733,26 +733,10 @@ "@metamask-institutional/custody-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-controller>@metamask-institutional/custody-keyring": true, + "@metamask-institutional/custody-keyring": true, "@metamask/obs-store": true } }, - "@metamask-institutional/custody-controller>@metamask-institutional/custody-keyring": { - "globals": { - "console.log": true, - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/sdk>@metamask-institutional/types": true, - "@metamask/obs-store": true, - "browserify>crypto-browserify": true, - "browserify>events": true, - "gulp-sass>lodash.clonedeep": true - } - }, "@metamask-institutional/custody-keyring": { "globals": { "console.log": true, @@ -782,102 +766,15 @@ }, "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/extension>@metamask-institutional/custody-controller": true, + "@metamask-institutional/custody-controller": true, "@metamask-institutional/sdk": true, "@metamask-institutional/sdk>@metamask-institutional/types": true } }, - "@metamask-institutional/extension>@metamask-institutional/custody-controller": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/extension>@metamask-institutional/custody-keyring": true, - "@metamask/obs-store": true - } - }, - "@metamask-institutional/extension>@metamask-institutional/custody-keyring": { - "globals": { - "console.log": true, - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/sdk>@metamask-institutional/types": true, - "@metamask/obs-store": true, - "browserify>crypto-browserify": true, - "browserify>events": true, - "gulp-sass>lodash.clonedeep": true - } - }, "@metamask-institutional/institutional-features": { - "globals": { - "chrome.runtime.id": true, - "console.log": true, - "fetch": true, - "setInterval": true - }, "packages": { - "@metamask-institutional/institutional-features>@auth0/auth0-spa-js": true, - "@metamask-institutional/institutional-features>@metamask-institutional/custody-keyring": true, - "@metamask-institutional/sdk>@metamask-institutional/simplecache": true, - "@metamask/obs-store": true, - "browserify>events": true - } - }, - "@metamask-institutional/institutional-features>@auth0/auth0-spa-js": { - "globals": { - "AbortController": true, - "Blob": true, - "MessageChannel": true, - "TextEncoder": true, - "URL.createObjectURL": true, - "URLSearchParams": true, - "Worker": true, - "addEventListener": true, - "atob": true, - "btoa": true, - "clearInterval": true, - "clearTimeout": true, - "console.warn": true, - "crossOriginIsolated": true, - "crypto": true, - "document.body.appendChild": true, - "document.body.contains": true, - "document.body.removeChild": true, - "document.cookie": "write", - "document.createElement": true, - "fetch": true, - "innerHeight": true, - "innerWidth": true, - "localStorage": true, - "location.assign": true, - "location.href": true, - "location.origin": true, - "location.protocol": true, - "open": true, - "removeEventListener": true, - "screenX": true, - "screenY": true, - "sessionStorage": true, - "setInterval": true, - "setTimeout": true - } - }, - "@metamask-institutional/institutional-features>@metamask-institutional/custody-keyring": { - "globals": { - "console.log": true, - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/sdk>@metamask-institutional/types": true, - "@metamask/obs-store": true, - "browserify>crypto-browserify": true, - "browserify>events": true, - "gulp-sass>lodash.clonedeep": true + "@metamask-institutional/custody-keyring": true, + "@metamask/obs-store": true } }, "@metamask-institutional/portfolio-dashboard": { diff --git a/package.json b/package.json index bd0727d02..680305809 100644 --- a/package.json +++ b/package.json @@ -221,14 +221,14 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@lavamoat/snow": "^1.5.0", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "0.2.6", - "@metamask-institutional/custody-keyring": "^0.0.25", - "@metamask-institutional/extension": "0.3.2", - "@metamask-institutional/institutional-features": "^1.1.8", - "@metamask-institutional/portfolio-dashboard": "^1.1.3", + "@metamask-institutional/custody-controller": "^0.2.10", + "@metamask-institutional/custody-keyring": "^0.0.27", + "@metamask-institutional/extension": "^0.3.3", + "@metamask-institutional/institutional-features": "^1.2.2", + "@metamask-institutional/portfolio-dashboard": "^1.4.0", "@metamask-institutional/rpc-allowlist": "^1.0.0", - "@metamask-institutional/sdk": "^0.1.17", - "@metamask-institutional/transaction-update": "^0.1.21", + "@metamask-institutional/sdk": "^0.1.18", + "@metamask-institutional/transaction-update": "^0.1.25", "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^4.0.0", "@metamask/approval-controller": "^3.4.0", diff --git a/yarn.lock b/yarn.lock index cd2739145..604271ab0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,13 +62,6 @@ __metadata: languageName: node linkType: hard -"@auth0/auth0-spa-js@npm:^2.0.4": - version: 2.0.5 - resolution: "@auth0/auth0-spa-js@npm:2.0.5" - checksum: f5c21f0adbc06abf9b737b6c8893e1d15f166b5de857f34cae6ee3d4f8ee61b9e6b0c119d1687c4a715dd5021690287e607acda54fefb2cf77e30c544834a08d - languageName: node - linkType: hard - "@aw-web-design/x-default-browser@npm:1.4.88": version: 1.4.88 resolution: "@aw-web-design/x-default-browser@npm:1.4.88" @@ -3635,51 +3628,22 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/custody-controller@npm:0.2.6": - version: 0.2.6 - resolution: "@metamask-institutional/custody-controller@npm:0.2.6" +"@metamask-institutional/custody-controller@npm:^0.2.10": + version: 0.2.10 + resolution: "@metamask-institutional/custody-controller@npm:0.2.10" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-keyring": "npm:^0.0.22" - "@metamask-institutional/sdk": "npm:^0.1.15" - "@metamask-institutional/types": "npm:^1.0.2" - "@metamask/obs-store": "npm:^8.0.0" - checksum: fe4a6e15ddac94eac0ea69712ac87eb594895d5a6ea057aa6d57dc8fb27e083a062cc431c0011e0f65323287bc7b849aeec67412f897db5e8e04059ec357cfec - languageName: node - linkType: hard - -"@metamask-institutional/custody-controller@npm:^0.2.9": - version: 0.2.9 - resolution: "@metamask-institutional/custody-controller@npm:0.2.9" - dependencies: - "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-keyring": "npm:^0.0.26" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" "@metamask-institutional/sdk": "npm:^0.1.18" "@metamask-institutional/types": "npm:^1.0.3" "@metamask/obs-store": "npm:^8.0.0" - checksum: b59a49b42d51c8b8eced77c06e4398afacb0e6c47b7757b3df7c0cacbb77656f12c0e681cf3e71760363ea95e7d8c167a9146b5573c0bed220a466572e739080 + checksum: 4ab4c26649f7e7e6e37308b00b2aa21cf40c0a861435a54c49bf0e092b49cbef71189774bd8ca038ace57d561facb8f79a0d98ad55ef0196101a857508e447d9 languageName: node linkType: hard -"@metamask-institutional/custody-keyring@npm:^0.0.22": - version: 0.0.22 - resolution: "@metamask-institutional/custody-keyring@npm:0.0.22" - dependencies: - "@ethereumjs/tx": "npm:^4.1.1" - "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/configuration-client": "npm:^1.0.6" - "@metamask-institutional/sdk": "npm:^0.1.14" - "@metamask-institutional/types": "npm:^1.0.1" - "@metamask/obs-store": "npm:^8.0.0" - crypto: "npm:^1.0.1" - lodash.clonedeep: "npm:^4.5.0" - checksum: d8ab6956670a2694aa3cfcce1ea76d0e742a3dd8a2ab9c3f5cc9dd476b482cef1274ff70f6dfbcb1921c06e733f7d768d4a270ccc3bdd95bae8516066ce5e594 - languageName: node - linkType: hard - -"@metamask-institutional/custody-keyring@npm:^0.0.25": - version: 0.0.25 - resolution: "@metamask-institutional/custody-keyring@npm:0.0.25" +"@metamask-institutional/custody-keyring@npm:^0.0.27": + version: 0.0.27 + resolution: "@metamask-institutional/custody-keyring@npm:0.0.27" dependencies: "@ethereumjs/tx": "npm:^4.1.1" "@ethereumjs/util": "npm:^8.0.5" @@ -3689,55 +3653,38 @@ __metadata: "@metamask/obs-store": "npm:^8.0.0" crypto: "npm:^1.0.1" lodash.clonedeep: "npm:^4.5.0" - checksum: c086f99e35c720eee9c609e5c18657de40cd0355daea68b0921b18f14f3d10088a898cff3df0376915f90f8559dc32709c761b9b8c728eda0534935ae35cd276 + checksum: f006671892e9abc3f72105a5276193f9194e54c51dd732affb632d1cf5d4cbfdc2c0b323e65b8265f1dedfb81dabd9ab2ce1e2c177ea5e791bd9af238b187336 languageName: node linkType: hard -"@metamask-institutional/custody-keyring@npm:^0.0.26": - version: 0.0.26 - resolution: "@metamask-institutional/custody-keyring@npm:0.0.26" - dependencies: - "@ethereumjs/tx": "npm:^4.1.1" - "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/configuration-client": "npm:^1.0.6" - "@metamask-institutional/sdk": "npm:^0.1.18" - "@metamask-institutional/types": "npm:^1.0.3" - "@metamask/obs-store": "npm:^8.0.0" - crypto: "npm:^1.0.1" - lodash.clonedeep: "npm:^4.5.0" - checksum: c2163fc6b83b3c0dd5c5a7c69c71f5a2563a28ffb2df069b4ed16c56692872fb52b40aed911e1af30a0fee733a61d0239677df04d5da945c7f4cf5e003eff8d9 - languageName: node - linkType: hard - -"@metamask-institutional/extension@npm:0.3.2": - version: 0.3.2 - resolution: "@metamask-institutional/extension@npm:0.3.2" +"@metamask-institutional/extension@npm:^0.3.3": + version: 0.3.3 + resolution: "@metamask-institutional/extension@npm:0.3.3" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-controller": "npm:^0.2.9" - "@metamask-institutional/custody-keyring": "npm:^0.0.26" + "@metamask-institutional/custody-controller": "npm:^0.2.10" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/sdk": "npm:^0.1.18" - "@metamask-institutional/transaction-update": "npm:^0.1.24" + "@metamask-institutional/transaction-update": "npm:^0.1.25" "@metamask-institutional/types": "npm:^1.0.3" jest-create-mock-instance: "npm:^2.0.0" jest-fetch-mock: "npm:3.0.3" - checksum: f3fe93a1f2872e2f27b56af3e03b0aba90bb290cb7b800bed086db16e56bd9251e99eb7f46fd142e1ccb588f13d24a9379a23dbd778e8a0e83e5dbf7c11692a7 + checksum: cb19b44b686dfa08bee0764c7ac8ff782f4ada8439e17d63b96700e043f07c406c727d1abe1c5d32ba97ecdb394748c8ed93b8d6ee9d5fafa9c3d66f13aa108d languageName: node linkType: hard -"@metamask-institutional/institutional-features@npm:^1.1.8": - version: 1.1.8 - resolution: "@metamask-institutional/institutional-features@npm:1.1.8" +"@metamask-institutional/institutional-features@npm:^1.2.2": + version: 1.2.2 + resolution: "@metamask-institutional/institutional-features@npm:1.2.2" dependencies: - "@auth0/auth0-spa-js": "npm:^2.0.4" - "@metamask-institutional/custody-keyring": "npm:^0.0.22" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" "@metamask/obs-store": "npm:^8.0.0" - checksum: 549ab6890f94024904fdd3f8a6ebe3109dc97c147223ed446da332598b65fb8ae89a2094f3b75bbd0420fc5722176fc794ae1c6d08ca19780f71b22a67382a7b + checksum: 2eb4cd7d36e38ba89a11e22c8522e1a091572e171abec71b6fa7b28b0f54c7fec0cbeb238d1066112dfbaf70075529f37f5aae55d65fa2639a73597a222b65a6 languageName: node linkType: hard -"@metamask-institutional/portfolio-dashboard@npm:^1.1.3, @metamask-institutional/portfolio-dashboard@npm:^1.4.0": +"@metamask-institutional/portfolio-dashboard@npm:^1.4.0": version: 1.4.0 resolution: "@metamask-institutional/portfolio-dashboard@npm:1.4.0" checksum: c973305d5b4088d5ae45300fc016f3cbc15232d1c8b734201a352b2296de8f21d0c1e5d9bfc6d1d164e5784e5128aaf2f6ca4370a9f6e99db3f5685a8266a5a0 @@ -3751,7 +3698,7 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/sdk@npm:^0.1.14, @metamask-institutional/sdk@npm:^0.1.15, @metamask-institutional/sdk@npm:^0.1.17, @metamask-institutional/sdk@npm:^0.1.18": +"@metamask-institutional/sdk@npm:^0.1.18": version: 0.1.18 resolution: "@metamask-institutional/sdk@npm:0.1.18" dependencies: @@ -3772,36 +3719,36 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/transaction-update@npm:^0.1.21, @metamask-institutional/transaction-update@npm:^0.1.24": - version: 0.1.24 - resolution: "@metamask-institutional/transaction-update@npm:0.1.24" +"@metamask-institutional/transaction-update@npm:^0.1.25": + version: 0.1.25 + resolution: "@metamask-institutional/transaction-update@npm:0.1.25" dependencies: - "@metamask-institutional/custody-keyring": "npm:^0.0.26" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" "@metamask-institutional/sdk": "npm:^0.1.18" "@metamask-institutional/types": "npm:^1.0.3" - "@metamask-institutional/websocket-client": "npm:^0.1.26" + "@metamask-institutional/websocket-client": "npm:^0.1.27" "@metamask/obs-store": "npm:^8.0.0" ethereumjs-util: "npm:^7.1.5" - checksum: d767d61600101ca3c580e3eeb9133d1a9d0753fd6190a830f798142074ff0e69142c567094dfb46062a299ee53b69db47021014e015db8c0fa55038bcdca7ca1 + checksum: ee1c597a3e3ec3d226eb1a0cdda6ed3e098faab776eaeb30c5abbcf72efa4eceadbceefed51f6fc7fa92b45f02c0f0da732e2d600c13832d93f8ba449e4a31b5 languageName: node linkType: hard -"@metamask-institutional/types@npm:^1.0.1, @metamask-institutional/types@npm:^1.0.2, @metamask-institutional/types@npm:^1.0.3": +"@metamask-institutional/types@npm:^1.0.3": version: 1.0.3 resolution: "@metamask-institutional/types@npm:1.0.3" checksum: dc9e1b3b65a8cc2700ae8be51dcf334875d8713b0c7e6234a392ce0f2ec28fda205488287536846c072276883d0ab07e290b1d01d6b5bef8b72e08cc2689e734 languageName: node linkType: hard -"@metamask-institutional/websocket-client@npm:^0.1.26": - version: 0.1.26 - resolution: "@metamask-institutional/websocket-client@npm:0.1.26" +"@metamask-institutional/websocket-client@npm:^0.1.27": + version: 0.1.27 + resolution: "@metamask-institutional/websocket-client@npm:0.1.27" dependencies: - "@metamask-institutional/custody-keyring": "npm:^0.0.26" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" "@metamask-institutional/sdk": "npm:^0.1.18" "@metamask-institutional/types": "npm:^1.0.3" mock-socket: "npm:^9.2.1" - checksum: ae2b5146c4922a76100d8348a7b22dacc5d43cda3f2be92dda24251082061462dab327059793b7b61b4d7c4270e7b7fd40c0da8feac0bcf7805e18c5f2bc6db9 + checksum: e35e36f0ceae33596848539f8a6dff7bb5867124b7bceece61abc21e603ef81db4d0d3f182edcda8e62552d4870e1f3f527bc489e6412a5ae8d88909e5c64af5 languageName: node linkType: hard @@ -24238,14 +24185,14 @@ __metadata: "@lavamoat/lavapack": "npm:^5.2.0" "@lavamoat/snow": "npm:^1.5.0" "@material-ui/core": "npm:^4.11.0" - "@metamask-institutional/custody-controller": "npm:0.2.6" - "@metamask-institutional/custody-keyring": "npm:^0.0.25" - "@metamask-institutional/extension": "npm:0.3.2" - "@metamask-institutional/institutional-features": "npm:^1.1.8" - "@metamask-institutional/portfolio-dashboard": "npm:^1.1.3" + "@metamask-institutional/custody-controller": "npm:^0.2.10" + "@metamask-institutional/custody-keyring": "npm:^0.0.27" + "@metamask-institutional/extension": "npm:^0.3.3" + "@metamask-institutional/institutional-features": "npm:^1.2.2" + "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/rpc-allowlist": "npm:^1.0.0" - "@metamask-institutional/sdk": "npm:^0.1.17" - "@metamask-institutional/transaction-update": "npm:^0.1.21" + "@metamask-institutional/sdk": "npm:^0.1.18" + "@metamask-institutional/transaction-update": "npm:^0.1.25" "@metamask/address-book-controller": "npm:^3.0.0" "@metamask/announcement-controller": "npm:^4.0.0" "@metamask/approval-controller": "npm:^3.4.0" From 702ee233e7f244ffcbbde9858ec6a750c4c59167 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 21 Aug 2023 12:53:04 -0230 Subject: [PATCH 075/102] Remove snapshot update from release process (#20546) The Sentry e2e state snapshots now mask the application version and migration version, ensuring that the snapshots don't need a extra update in each release candidate branch and post-release sync branch. The values are masked rather than removed so that the test still shows they are present in error reports, where they can be quite useful for diagnostic purposes. --- test/e2e/tests/errors.spec.js | 8 ++++++++ .../errors-after-init-opt-in-background-state.json | 4 ++-- .../errors-after-init-opt-in-ui-state.json | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 5c557b256..7d7e12424 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -9,9 +9,17 @@ const FixtureBuilder = require('../fixture-builder'); const maskedBackgroundFields = [ 'CurrencyController.conversionDate', // This is a timestamp that changes each run + // App metadata is masked so that we don't have to update the snapshot as + // part of the release process + 'AppMetadataController.currentAppVersion', + 'AppMetadataController.currentMigrationVersion', ]; const maskedUiFields = [ 'metamask.conversionDate', // This is a timestamp that changes each run + // App metadata is masked so that we don't have to update the snapshot as + // part of the release process + 'metamask.currentAppVersion', + 'metamask.currentMigrationVersion', ]; const removedBackgroundFields = [ diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 37927c291..5a4012071 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -8,10 +8,10 @@ }, "AnnouncementController": "object", "AppMetadataController": { - "currentAppVersion": "10.34.5", + "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 94 + "currentMigrationVersion": "number" }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 37a9dc691..7843dcd7f 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -57,10 +57,10 @@ "usedNetworks": "object", "snapsInstallPrivacyWarningShown": "boolean", "serviceWorkerLastActiveTime": "number", - "currentAppVersion": "10.34.5", + "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 94, + "currentMigrationVersion": "number", "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { From 8ca0b762ad71912778ae9dfd21bd71fd0f6b013a Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Mon, 21 Aug 2023 18:06:12 +0200 Subject: [PATCH 076/102] Don't call "toPrecisionWithoutTrailingZeros" if a destination value is not a number (#20525) --- ui/pages/swaps/swaps.util.test.js | 6 ++++++ ui/pages/swaps/swaps.util.ts | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js index 05f06f7d1..642879484 100644 --- a/ui/pages/swaps/swaps.util.test.js +++ b/ui/pages/swaps/swaps.util.test.js @@ -401,6 +401,12 @@ describe('Swaps Util', () => { '39.6493201125', ); }); + + it('gets swaps value for display when the value contains three dots', () => { + expect(formatSwapsValueForDisplay('33680099000000000000...')).toBe( + '33680099000000000000...', + ); + }); }); describe('getSwapsTokensReceivedFromTxMeta', () => { diff --git a/ui/pages/swaps/swaps.util.ts b/ui/pages/swaps/swaps.util.ts index 566faa93f..7782f0df8 100644 --- a/ui/pages/swaps/swaps.util.ts +++ b/ui/pages/swaps/swaps.util.ts @@ -545,8 +545,18 @@ export function quotesToRenderableData({ }); } -export function formatSwapsValueForDisplay(destinationAmount: string): string { - let amountToDisplay = toPrecisionWithoutTrailingZeros(destinationAmount, 12); +export function formatSwapsValueForDisplay( + destinationAmount: string | BigNumber, +): string { + let amountToDisplay; + if ( + typeof destinationAmount === 'string' && + destinationAmount.includes('...') + ) { + amountToDisplay = destinationAmount; + } else { + amountToDisplay = toPrecisionWithoutTrailingZeros(destinationAmount, 12); + } if (amountToDisplay.match(/e[+-]/u)) { amountToDisplay = new BigNumber(amountToDisplay).toFixed(); } From 37209a7d2e9529e242f800863598197727589fb7 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 22 Aug 2023 10:17:07 +0100 Subject: [PATCH 077/102] Replace IncomingTransactionsController with helper (#20378) Remove the IncomingTransactionController and replace it with an internal helper class. Move incoming transactions into the central transactions object. Create a new RemoteTransactionSource interface to decouple incoming transaction support from Etherscan. Split the incoming transaction logic into multiple files for easier maintenance. --- .../controllers/incoming-transactions.js | 320 ---- .../controllers/incoming-transactions.test.js | 1448 ----------------- .../EtherscanRemoteTransactionSource.test.ts | 219 +++ .../EtherscanRemoteTransactionSource.ts | 156 ++ .../IncomingTransactionHelper.test.ts | 585 +++++++ .../transactions/IncomingTransactionHelper.ts | 282 ++++ .../transactions/etherscan.test.ts | 153 ++ .../controllers/transactions/etherscan.ts | 205 +++ app/scripts/controllers/transactions/index.js | 81 +- .../controllers/transactions/index.test.js | 103 +- app/scripts/controllers/transactions/types.ts | 49 + app/scripts/lib/setupSentry.js | 3 - app/scripts/metamask-controller.js | 113 +- app/scripts/metamask-controller.test.js | 83 + app/scripts/migrations/095.test.ts | 364 +++++ app/scripts/migrations/095.ts | 94 ++ app/scripts/migrations/index.js | 2 + jest.config.js | 6 + shared/constants/transaction.ts | 3 +- test/e2e/fixture-builder.js | 85 +- test/e2e/tests/clear-activity.spec.js | 3 +- ...rs-after-init-opt-in-background-state.json | 10 - .../errors-after-init-opt-in-ui-state.json | 9 +- ...s-before-init-opt-in-background-state.json | 10 - .../errors-before-init-opt-in-ui-state.json | 10 - .../account-details-modal.test.js | 1 - ...nonce-sorted-transactions-selector.test.js | 2 +- ui/selectors/transactions.js | 5 +- ui/store/actions.ts | 4 +- 29 files changed, 2491 insertions(+), 1917 deletions(-) delete mode 100644 app/scripts/controllers/incoming-transactions.js delete mode 100644 app/scripts/controllers/incoming-transactions.test.js create mode 100644 app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts create mode 100644 app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts create mode 100644 app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts create mode 100644 app/scripts/controllers/transactions/IncomingTransactionHelper.ts create mode 100644 app/scripts/controllers/transactions/etherscan.test.ts create mode 100644 app/scripts/controllers/transactions/etherscan.ts create mode 100644 app/scripts/controllers/transactions/types.ts create mode 100644 app/scripts/migrations/095.test.ts create mode 100644 app/scripts/migrations/095.ts diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js deleted file mode 100644 index 9b0b85f33..000000000 --- a/app/scripts/controllers/incoming-transactions.js +++ /dev/null @@ -1,320 +0,0 @@ -import { ObservableStore } from '@metamask/obs-store'; -import log from 'loglevel'; -import BN from 'bn.js'; -import createId from '../../../shared/modules/random-id'; -import { previousValueComparator } from '../lib/util'; -import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; - -import { - TransactionType, - TransactionStatus, -} from '../../../shared/constants/transaction'; -import { ETHERSCAN_SUPPORTED_NETWORKS } from '../../../shared/constants/network'; -import { bnToHex } from '../../../shared/modules/conversion.utils'; - -const fetchWithTimeout = getFetchWithTimeout(); - -/** - * @typedef {import('../../../shared/constants/transaction').TransactionMeta} TransactionMeta - */ - -/** - * A transaction object in the format returned by the Etherscan API. - * - * Note that this is not an exhaustive type definiton; only the properties we use are defined - * - * @typedef {object} EtherscanTransaction - * @property {string} blockNumber - The number of the block this transaction was found in, in decimal - * @property {string} from - The hex-prefixed address of the sender - * @property {string} gas - The gas limit, in decimal GWEI - * @property {string} [gasPrice] - The gas price, in decimal WEI - * @property {string} [maxFeePerGas] - The maximum fee per gas, inclusive of tip, in decimal WEI - * @property {string} [maxPriorityFeePerGas] - The maximum tip per gas in decimal WEI - * @property {string} hash - The hex-prefixed transaction hash - * @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed) - * @property {string} nonce - The transaction nonce, in decimal - * @property {string} timeStamp - The timestamp for the transaction, in seconds - * @property {string} to - The hex-prefixed address of the recipient - * @property {string} value - The amount of ETH sent in this transaction, in decimal WEI - */ - -/** - * This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check - * for new incoming transactions for the current selected account on the current network - * - * Note that only Etherscan-compatible networks are supported. We will not attempt to retrieve incoming transactions - * on non-compatible custom RPC endpoints. - */ -export default class IncomingTransactionsController { - constructor(opts = {}) { - const { - blockTracker, - onNetworkDidChange, - getCurrentChainId, - preferencesController, - onboardingController, - } = opts; - this.blockTracker = blockTracker; - this.getCurrentChainId = getCurrentChainId; - this.preferencesController = preferencesController; - this.onboardingController = onboardingController; - - this._onLatestBlock = async (newBlockNumberHex) => { - const selectedAddress = this.preferencesController.getSelectedAddress(); - const newBlockNumberDec = parseInt(newBlockNumberHex, 16); - await this._update(selectedAddress, newBlockNumberDec); - }; - - const incomingTxLastFetchedBlockByChainId = Object.keys( - ETHERSCAN_SUPPORTED_NETWORKS, - ).reduce((network, chainId) => { - network[chainId] = null; - return network; - }, {}); - - const initState = { - incomingTransactions: {}, - incomingTxLastFetchedBlockByChainId, - ...opts.initState, - }; - this.store = new ObservableStore(initState); - - this.preferencesController.store.subscribe( - previousValueComparator((prevState, currState) => { - const { - featureFlags: { - showIncomingTransactions: prevShowIncomingTransactions, - } = {}, - } = prevState; - const { - featureFlags: { - showIncomingTransactions: currShowIncomingTransactions, - } = {}, - } = currState; - - if (currShowIncomingTransactions === prevShowIncomingTransactions) { - return; - } - - if (prevShowIncomingTransactions && !currShowIncomingTransactions) { - this.stop(); - return; - } - - this.start(); - }, this.preferencesController.store.getState()), - ); - - this.preferencesController.store.subscribe( - previousValueComparator(async (prevState, currState) => { - const { selectedAddress: prevSelectedAddress } = prevState; - const { selectedAddress: currSelectedAddress } = currState; - - if (currSelectedAddress === prevSelectedAddress) { - return; - } - await this._update(currSelectedAddress); - }, this.preferencesController.store.getState()), - ); - - this.onboardingController.store.subscribe( - previousValueComparator(async (prevState, currState) => { - const { completedOnboarding: prevCompletedOnboarding } = prevState; - const { completedOnboarding: currCompletedOnboarding } = currState; - if (!prevCompletedOnboarding && currCompletedOnboarding) { - const address = this.preferencesController.getSelectedAddress(); - await this._update(address); - } - }, this.onboardingController.store.getState()), - ); - - onNetworkDidChange(async () => { - const address = this.preferencesController.getSelectedAddress(); - await this._update(address); - }); - } - - start() { - const chainId = this.getCurrentChainId(); - - if (this._allowedToMakeFetchIncomingTx(chainId)) { - this.blockTracker.removeListener('latest', this._onLatestBlock); - this.blockTracker.addListener('latest', this._onLatestBlock); - } - } - - stop() { - this.blockTracker.removeListener('latest', this._onLatestBlock); - } - - /** - * Determines the correct block number to begin looking for new transactions - * from, fetches the transactions and then saves them and the next block - * number to begin fetching from in state. Block numbers and transactions are - * stored per chainId. - * - * @private - * @param {string} address - address to lookup transactions for - * @param {number} [newBlockNumberDec] - block number to begin fetching from - */ - async _update(address, newBlockNumberDec) { - const chainId = this.getCurrentChainId(); - - if (!address || !this._allowedToMakeFetchIncomingTx(chainId)) { - return; - } - try { - const currentState = this.store.getState(); - const currentBlock = parseInt(this.blockTracker.getCurrentBlock(), 16); - - const mostRecentlyFetchedBlock = - currentState.incomingTxLastFetchedBlockByChainId[chainId]; - const blockToFetchFrom = - mostRecentlyFetchedBlock ?? newBlockNumberDec ?? currentBlock; - - const newIncomingTxs = await this._getNewIncomingTransactions( - address, - blockToFetchFrom, - chainId, - ); - - let newMostRecentlyFetchedBlock = blockToFetchFrom; - - newIncomingTxs.forEach((tx) => { - if ( - tx.blockNumber && - parseInt(newMostRecentlyFetchedBlock, 10) < - parseInt(tx.blockNumber, 10) - ) { - newMostRecentlyFetchedBlock = parseInt(tx.blockNumber, 10); - } - }); - - this.store.updateState({ - incomingTxLastFetchedBlockByChainId: { - ...currentState.incomingTxLastFetchedBlockByChainId, - [chainId]: newMostRecentlyFetchedBlock + 1, - }, - incomingTransactions: newIncomingTxs.reduce( - (transactions, tx) => { - transactions[tx.hash] = tx; - return transactions; - }, - { - ...currentState.incomingTransactions, - }, - ), - }); - } catch (err) { - log.error(err); - } - } - - /** - * fetches transactions for the given address and chain, via etherscan, then - * processes the data into the necessary shape for usage in this controller. - * - * @private - * @param {string} [address] - Address to fetch transactions for - * @param {number} [fromBlock] - Block to look for transactions at - * @param {string} [chainId] - The chainId for the current network - * @returns {TransactionMeta[]} - */ - async _getNewIncomingTransactions(address, fromBlock, chainId) { - const etherscanDomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].domain; - const etherscanSubdomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].subdomain; - - const apiUrl = `https://${etherscanSubdomain}.${etherscanDomain}`; - let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`; - - if (fromBlock) { - url += `&startBlock=${parseInt(fromBlock, 10)}`; - } - const response = await fetchWithTimeout(url); - const { status, result } = await response.json(); - let newIncomingTxs = []; - if (status === '1' && Array.isArray(result) && result.length > 0) { - const remoteTxList = {}; - const remoteTxs = []; - result.forEach((tx) => { - if (!remoteTxList[tx.hash]) { - remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId)); - remoteTxList[tx.hash] = 1; - } - }); - - newIncomingTxs = remoteTxs.filter( - (tx) => tx.txParams?.to?.toLowerCase() === address.toLowerCase(), - ); - newIncomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1)); - } - return newIncomingTxs; - } - - /** - * Transmutes a EtherscanTransaction into a TransactionMeta - * - * @param {EtherscanTransaction} etherscanTransaction - the transaction to normalize - * @param {string} chainId - The chainId of the current network - * @returns {TransactionMeta} - */ - _normalizeTxFromEtherscan(etherscanTransaction, chainId) { - const time = parseInt(etherscanTransaction.timeStamp, 10) * 1000; - const status = - etherscanTransaction.isError === '0' - ? TransactionStatus.confirmed - : TransactionStatus.failed; - const txParams = { - from: etherscanTransaction.from, - gas: bnToHex(new BN(etherscanTransaction.gas)), - nonce: bnToHex(new BN(etherscanTransaction.nonce)), - to: etherscanTransaction.to, - value: bnToHex(new BN(etherscanTransaction.value)), - }; - - if (etherscanTransaction.gasPrice) { - txParams.gasPrice = bnToHex(new BN(etherscanTransaction.gasPrice)); - } else if (etherscanTransaction.maxFeePerGas) { - txParams.maxFeePerGas = bnToHex( - new BN(etherscanTransaction.maxFeePerGas), - ); - txParams.maxPriorityFeePerGas = bnToHex( - new BN(etherscanTransaction.maxPriorityFeePerGas), - ); - } - - return { - blockNumber: etherscanTransaction.blockNumber, - id: createId(), - chainId, - metamaskNetworkId: ETHERSCAN_SUPPORTED_NETWORKS[chainId].networkId, - status, - time, - txParams, - hash: etherscanTransaction.hash, - type: TransactionType.incoming, - }; - } - - /** - * @param chainId - {string} The chainId of the current network - * @returns {boolean} Whether or not the user has consented to show incoming transactions - */ - _allowedToMakeFetchIncomingTx(chainId) { - const { featureFlags = {} } = this.preferencesController.store.getState(); - const { completedOnboarding } = this.onboardingController.store.getState(); - - const hasIncomingTransactionsFeatureEnabled = Boolean( - featureFlags.showIncomingTransactions, - ); - - const isEtherscanSupportedNetwork = Boolean( - ETHERSCAN_SUPPORTED_NETWORKS[chainId], - ); - return ( - completedOnboarding && - isEtherscanSupportedNetwork && - hasIncomingTransactionsFeatureEnabled - ); - } -} diff --git a/app/scripts/controllers/incoming-transactions.test.js b/app/scripts/controllers/incoming-transactions.test.js deleted file mode 100644 index c46c3190b..000000000 --- a/app/scripts/controllers/incoming-transactions.test.js +++ /dev/null @@ -1,1448 +0,0 @@ -import { strict as assert } from 'assert'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; -import nock from 'nock'; -import { cloneDeep } from 'lodash'; - -import waitUntilCalled from '../../../test/lib/wait-until-called'; -import { - ETHERSCAN_SUPPORTED_NETWORKS, - CHAIN_IDS, - NETWORK_TYPES, - NETWORK_IDS, -} from '../../../shared/constants/network'; -import { - TransactionType, - TransactionStatus, -} from '../../../shared/constants/transaction'; -import { MILLISECOND } from '../../../shared/constants/time'; - -const IncomingTransactionsController = proxyquire('./incoming-transactions', { - '../../../shared/modules/random-id': { default: () => 54321 }, -}).default; - -const FAKE_CHAIN_ID = '0x1338'; -const MOCK_SELECTED_ADDRESS = '0x0101'; -const SET_STATE_TIMEOUT = MILLISECOND * 10; - -const EXISTING_INCOMING_TX = { id: 777, hash: '0x123456' }; -const PREPOPULATED_INCOMING_TXS_BY_HASH = { - [EXISTING_INCOMING_TX.hash]: EXISTING_INCOMING_TX, -}; -const PREPOPULATED_BLOCKS_BY_NETWORK = { - [CHAIN_IDS.GOERLI]: 1, - [CHAIN_IDS.MAINNET]: 3, - [CHAIN_IDS.SEPOLIA]: 6, -}; -const EMPTY_BLOCKS_BY_NETWORK = Object.keys( - ETHERSCAN_SUPPORTED_NETWORKS, -).reduce((network, chainId) => { - network[chainId] = null; - return network; -}, {}); - -function getEmptyInitState() { - return { - incomingTransactions: {}, - incomingTxLastFetchedBlockByChainId: EMPTY_BLOCKS_BY_NETWORK, - }; -} - -function getNonEmptyInitState() { - return { - incomingTransactions: PREPOPULATED_INCOMING_TXS_BY_HASH, - incomingTxLastFetchedBlockByChainId: PREPOPULATED_BLOCKS_BY_NETWORK, - }; -} - -function getMockNetworkControllerMethods(chainId = FAKE_CHAIN_ID) { - return { - getCurrentChainId: () => chainId, - onNetworkDidChange: sinon.spy(), - }; -} - -function getMockPreferencesController({ - showIncomingTransactions = true, -} = {}) { - return { - getSelectedAddress: sinon.stub().returns(MOCK_SELECTED_ADDRESS), - store: { - getState: sinon.stub().returns({ - featureFlags: { - showIncomingTransactions, - }, - }), - subscribe: sinon.spy(), - }, - }; -} - -function getMockOnboardingController({ completedOnboarding = true } = {}) { - return { - store: { - getState: sinon.stub().returns({ - completedOnboarding, - }), - subscribe: sinon.spy(), - }, - }; -} - -function getMockBlockTracker() { - return { - addListener: sinon.stub().callsArgWithAsync(1, '0xa'), - removeListener: sinon.spy(), - testProperty: 'fakeBlockTracker', - getCurrentBlock: () => '0xa', - }; -} - -function getDefaultControllerOpts() { - return { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getEmptyInitState(), - }; -} - -/** - * @typedef {import( - * '../../../../app/scripts/controllers/incoming-transactions' - * ).EtherscanTransaction} EtherscanTransaction - */ - -/** - * Returns a transaction object matching the expected format returned - * by the Etherscan API - * - * @param {object} [params] - options bag - * @param {string} [params.toAddress] - The hex-prefixed address of the recipient - * @param {number} [params.blockNumber] - The block number for the transaction - * @param {boolean} [params.useEIP1559] - Use EIP-1559 gas fields - * @param params.hash - * @returns {EtherscanTransaction} - */ -const getFakeEtherscanTransaction = ({ - toAddress = MOCK_SELECTED_ADDRESS, - blockNumber = 10, - useEIP1559 = false, - hash = '0xfake', -} = {}) => { - if (useEIP1559) { - return { - blockNumber: blockNumber.toString(), - from: '0xfake', - gas: '0', - maxFeePerGas: '10', - maxPriorityFeePerGas: '1', - hash, - isError: '0', - nonce: '100', - timeStamp: '16000000000000', - to: toAddress, - value: '0', - }; - } - return { - blockNumber: blockNumber.toString(), - from: '0xfake', - gas: '0', - gasPrice: '0', - hash: '0xfake', - isError: '0', - nonce: '100', - timeStamp: '16000000000000', - to: toAddress, - value: '0', - }; -}; - -function nockEtherscanApiForAllChains(mockResponse) { - Object.values(ETHERSCAN_SUPPORTED_NETWORKS).forEach( - ({ domain, subdomain }) => { - nock(`https://${domain}.${subdomain}`) - .get(/api.+/u) - .reply(200, JSON.stringify(mockResponse)); - }, - ); -} - -describe('IncomingTransactionsController', function () { - afterEach(function () { - sinon.restore(); - nock.cleanAll(); - }); - - describe('constructor', function () { - it('should set up correct store, listeners and properties in the constructor', function () { - const mockedNetworkMethods = getMockNetworkControllerMethods(); - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...mockedNetworkMethods, - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: {}, - }, - ); - sinon.spy(incomingTransactionsController, '_update'); - - assert.deepStrictEqual( - incomingTransactionsController.store.getState(), - getEmptyInitState(), - ); - - assert(mockedNetworkMethods.onNetworkDidChange.calledOnce); - const networkControllerListenerCallback = - mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0]; - assert.strictEqual(incomingTransactionsController._update.callCount, 0); - networkControllerListenerCallback('testNetworkType'); - assert.strictEqual(incomingTransactionsController._update.callCount, 1); - assert.deepStrictEqual( - incomingTransactionsController._update.getCall(0).args[0], - '0x0101', - ); - - incomingTransactionsController._update.resetHistory(); - }); - - it('should set the store to a provided initial state', function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - assert.deepStrictEqual( - incomingTransactionsController.store.getState(), - getNonEmptyInitState(), - ); - }); - }); - - describe('update events', function () { - it('should set up a listener for the latest block', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: {}, - getCurrentChainId: () => CHAIN_IDS.GOERLI, - }, - ); - - incomingTransactionsController.start(); - - assert( - incomingTransactionsController.blockTracker.addListener.calledOnce, - ); - assert.strictEqual( - incomingTransactionsController.blockTracker.addListener.getCall(0) - .args[0], - 'latest', - ); - }); - - it('should update upon latest block when started and on supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - const startBlock = - getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - CHAIN_IDS.GOERLI - ]; - nock('https://api-goerli.etherscan.io') - .get( - `/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, - ) - .reply( - 200, - JSON.stringify({ - status: '1', - result: [ - getFakeEtherscanTransaction(), - getFakeEtherscanTransaction({ - hash: '0xfakeeip1559', - useEIP1559: true, - }), - ], - }), - ); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - - incomingTransactionsController.start(); - await updateStateCalled(); - - const actualState = incomingTransactionsController.store.getState(); - const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id; - - const actualStateWithoutGenerated = cloneDeep(actualState); - delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id; - delete actualStateWithoutGenerated?.incomingTransactions?.[ - '0xfakeeip1559' - ]?.id; - - assert.ok( - typeof generatedTxId === 'number' && generatedTxId > 0, - 'Generated transaction ID should be a positive number', - ); - assert.deepStrictEqual( - actualStateWithoutGenerated, - { - incomingTransactions: { - ...getNonEmptyInitState().incomingTransactions, - '0xfake': { - blockNumber: '10', - hash: '0xfake', - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 16000000000000000, - type: TransactionType.incoming, - txParams: { - from: '0xfake', - gas: '0x0', - gasPrice: '0x0', - nonce: '0x64', - to: '0x0101', - value: '0x0', - }, - }, - '0xfakeeip1559': { - blockNumber: '10', - hash: '0xfakeeip1559', - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 16000000000000000, - type: TransactionType.incoming, - txParams: { - from: '0xfake', - gas: '0x0', - maxFeePerGas: '0xa', - maxPriorityFeePerGas: '0x1', - nonce: '0x64', - to: '0x0101', - value: '0x0', - }, - }, - }, - incomingTxLastFetchedBlockByChainId: { - ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [CHAIN_IDS.GOERLI]: 11, - }, - }, - 'State should have been updated after first block was received', - ); - }); - - it('should not update upon latest block when started and not on supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [getFakeEtherscanTransaction()], - }); - - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - incomingTransactionsController.start(); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - - it('should not update upon latest block when started and incoming transactions disabled', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(), - preferencesController: getMockPreferencesController({ - showIncomingTransactions: false, - }), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [getFakeEtherscanTransaction()], - }); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - incomingTransactionsController.start(); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - - it('should not update upon latest block when not started', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [getFakeEtherscanTransaction()], - }); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - - it('should not update upon latest block when stopped', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [getFakeEtherscanTransaction()], - }); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - incomingTransactionsController.stop(); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - - it('should update when the selected address changes and on supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`; - const startBlock = - getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - CHAIN_IDS.GOERLI - ]; - nock('https://api-goerli.etherscan.io') - .get( - `/api?module=account&action=txlist&address=${NEW_MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, - ) - .reply( - 200, - JSON.stringify({ - status: '1', - result: [ - getFakeEtherscanTransaction({ - toAddress: NEW_MOCK_SELECTED_ADDRESS, - }), - ], - }), - ); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - - const subscription = - incomingTransactionsController.preferencesController.store.subscribe.getCall( - 1, - ).args[0]; - // The incoming transactions controller will always skip the first event - // We need to call subscription twice to test the event handling - // TODO: stop skipping the first event - await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }); - await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS }); - await updateStateCalled(); - - const actualState = incomingTransactionsController.store.getState(); - const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id; - - const actualStateWithoutGenerated = cloneDeep(actualState); - delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id; - - assert.ok( - typeof generatedTxId === 'number' && generatedTxId > 0, - 'Generated transaction ID should be a positive number', - ); - assert.deepStrictEqual( - actualStateWithoutGenerated, - { - incomingTransactions: { - ...getNonEmptyInitState().incomingTransactions, - '0xfake': { - blockNumber: '10', - hash: '0xfake', - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 16000000000000000, - type: TransactionType.incoming, - txParams: { - from: '0xfake', - gas: '0x0', - gasPrice: '0x0', - nonce: '0x64', - to: '0x01019', - value: '0x0', - }, - }, - }, - incomingTxLastFetchedBlockByChainId: { - ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [CHAIN_IDS.GOERLI]: 11, - }, - }, - 'State should have been updated after first block was received', - ); - }); - - it('should not update when the selected address changes and not on supported network', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: { ...getMockBlockTracker() }, - ...getMockNetworkControllerMethods(), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`; - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [ - getFakeEtherscanTransaction({ toAddress: NEW_MOCK_SELECTED_ADDRESS }), - ], - }); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - const subscription = - incomingTransactionsController.preferencesController.store.subscribe.getCall( - 1, - ).args[0]; - // The incoming transactions controller will always skip the first event - // We need to call subscription twice to test the event handling - // TODO: stop skipping the first event - await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }); - await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS }); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - - it('should update when switching to a supported network', async function () { - const mockedNetworkMethods = getMockNetworkControllerMethods( - CHAIN_IDS.GOERLI, - ); - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...mockedNetworkMethods, - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - const startBlock = - getNonEmptyInitState().incomingTxLastFetchedBlockByChainId[ - CHAIN_IDS.GOERLI - ]; - nock('https://api-goerli.etherscan.io') - .get( - `/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`, - ) - .reply( - 200, - JSON.stringify({ - status: '1', - result: [getFakeEtherscanTransaction()], - }), - ); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - - const subscription = - mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0]; - await subscription(CHAIN_IDS.GOERLI); - await updateStateCalled(); - - const actualState = incomingTransactionsController.store.getState(); - const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id; - - const actualStateWithoutGenerated = cloneDeep(actualState); - delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id; - - assert.ok( - typeof generatedTxId === 'number' && generatedTxId > 0, - 'Generated transaction ID should be a positive number', - ); - assert.deepStrictEqual( - actualStateWithoutGenerated, - { - incomingTransactions: { - ...getNonEmptyInitState().incomingTransactions, - '0xfake': { - blockNumber: '10', - hash: '0xfake', - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 16000000000000000, - type: TransactionType.incoming, - txParams: { - from: '0xfake', - gas: '0x0', - gasPrice: '0x0', - nonce: '0x64', - to: '0x0101', - value: '0x0', - }, - }, - }, - incomingTxLastFetchedBlockByChainId: { - ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, - [CHAIN_IDS.GOERLI]: 11, - }, - }, - 'State should have been updated after first block was received', - ); - }); - - it('should not update when switching to an unsupported network', async function () { - const mockedNetworkMethods = getMockNetworkControllerMethods( - CHAIN_IDS.GOERLI, - ); - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...mockedNetworkMethods, - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - // reply with a valid request for any supported network, so that this test has every opportunity to fail - nockEtherscanApiForAllChains({ - status: '1', - result: [getFakeEtherscanTransaction()], - }); - const updateStateStub = sinon.stub( - incomingTransactionsController.store, - 'updateState', - ); - const updateStateCalled = waitUntilCalled( - updateStateStub, - incomingTransactionsController.store, - ); - const putStateStub = sinon.stub( - incomingTransactionsController.store, - 'putState', - ); - const putStateCalled = waitUntilCalled( - putStateStub, - incomingTransactionsController.store, - ); - - const subscription = - mockedNetworkMethods.onNetworkDidChange.getCall(0).args[0]; - - incomingTransactionsController.getCurrentChainId = () => FAKE_CHAIN_ID; - await subscription(); - - try { - await Promise.race([ - updateStateCalled(), - putStateCalled(), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT); - }), - ]); - assert.fail('Update state should not have been called'); - } catch (error) { - assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown'); - } - }); - }); - - describe('block explorer lookup', function () { - let sandbox; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - }); - - afterEach(function () { - sandbox.restore(); - }); - - function stubFetch() { - return sandbox.stub(window, 'fetch'); - } - - function assertStubNotCalled(stub) { - assert(stub.callCount === 0); - } - - async function triggerUpdate(incomingTransactionsController) { - const subscription = - incomingTransactionsController.preferencesController.store.subscribe.getCall( - 1, - ).args[0]; - - // Sets address causing a call to _update - await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }); - } - - it('should not happen when incoming transactions feature is disabled', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - ...getDefaultControllerOpts(), - preferencesController: getMockPreferencesController({ - showIncomingTransactions: false, - }), - }, - ); - const fetchStub = stubFetch(); - await triggerUpdate(incomingTransactionsController); - assertStubNotCalled(fetchStub); - }); - - it('should not happen when onboarding is in progress', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - ...getDefaultControllerOpts(), - onboardingController: getMockOnboardingController({ - completedOnboarding: false, - }), - }, - ); - - const fetchStub = stubFetch(); - await triggerUpdate(incomingTransactionsController); - assertStubNotCalled(fetchStub); - }); - - it('should not happen when chain id is not supported', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - ...getDefaultControllerOpts(), - getCurrentChainId: () => FAKE_CHAIN_ID, - }, - ); - - const fetchStub = stubFetch(); - await triggerUpdate(incomingTransactionsController); - assertStubNotCalled(fetchStub); - }); - - it('should make api call when chain id, incoming features, and onboarding status are ok', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - ...getDefaultControllerOpts(), - getCurrentChainId: () => CHAIN_IDS.GOERLI, - onboardingController: getMockOnboardingController({ - completedOnboarding: true, - }), - preferencesController: getMockPreferencesController({ - showIncomingTransactions: true, - }), - }, - ); - - const fetchStub = stubFetch(); - await triggerUpdate(incomingTransactionsController); - assert(fetchStub.callCount === 1); - }); - }); - - describe('_update', function () { - describe('when state is empty (initialized)', function () { - it('should use provided block number and update the latest block seen', async function () { - const incomingTransactionsController = - new IncomingTransactionsController({ - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getEmptyInitState(), - getCurrentChainId: () => CHAIN_IDS.GOERLI, - }); - sinon.spy(incomingTransactionsController.store, 'updateState'); - - incomingTransactionsController._getNewIncomingTransactions = sinon - .stub() - .returns([]); - - await incomingTransactionsController._update('fakeAddress', 999); - assert( - incomingTransactionsController._getNewIncomingTransactions.calledOnce, - ); - assert.deepStrictEqual( - incomingTransactionsController._getNewIncomingTransactions.getCall(0) - .args, - ['fakeAddress', 999, CHAIN_IDS.GOERLI], - ); - assert.deepStrictEqual( - incomingTransactionsController.store.updateState.getCall(0).args[0], - { - incomingTxLastFetchedBlockByChainId: { - ...EMPTY_BLOCKS_BY_NETWORK, - [CHAIN_IDS.GOERLI]: 1000, - }, - incomingTransactions: {}, - }, - ); - }); - - it('should update the last fetched block for network to highest block seen in incoming txs', async function () { - const incomingTransactionsController = - new IncomingTransactionsController({ - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getEmptyInitState(), - getCurrentChainId: () => CHAIN_IDS.GOERLI, - }); - - const NEW_TRANSACTION_ONE = { - id: 555, - hash: '0xfff', - blockNumber: 444, - }; - const NEW_TRANSACTION_TWO = { - id: 556, - hash: '0xffa', - blockNumber: 443, - }; - - sinon.spy(incomingTransactionsController.store, 'updateState'); - - incomingTransactionsController._getNewIncomingTransactions = sinon - .stub() - .returns([NEW_TRANSACTION_ONE, NEW_TRANSACTION_TWO]); - await incomingTransactionsController._update('fakeAddress', 10); - - assert(incomingTransactionsController.store.updateState.calledOnce); - - assert.deepStrictEqual( - incomingTransactionsController._getNewIncomingTransactions.getCall(0) - .args, - ['fakeAddress', 10, CHAIN_IDS.GOERLI], - ); - - assert.deepStrictEqual( - incomingTransactionsController.store.updateState.getCall(0).args[0], - { - incomingTxLastFetchedBlockByChainId: { - ...EMPTY_BLOCKS_BY_NETWORK, - [CHAIN_IDS.GOERLI]: 445, - }, - incomingTransactions: { - [NEW_TRANSACTION_ONE.hash]: NEW_TRANSACTION_ONE, - [NEW_TRANSACTION_TWO.hash]: NEW_TRANSACTION_TWO, - }, - }, - ); - }); - }); - - describe('when state is populated with prior data for network', function () { - it('should use the last fetched block for the current network and increment by 1 in state', async function () { - const incomingTransactionsController = - new IncomingTransactionsController({ - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - getCurrentChainId: () => CHAIN_IDS.GOERLI, - }); - sinon.spy(incomingTransactionsController.store, 'updateState'); - incomingTransactionsController._getNewIncomingTransactions = sinon - .stub() - .returns([]); - - await incomingTransactionsController._update('fakeAddress', 999); - - assert( - incomingTransactionsController._getNewIncomingTransactions.calledOnce, - ); - - assert.deepStrictEqual( - incomingTransactionsController._getNewIncomingTransactions.getCall(0) - .args, - ['fakeAddress', 1, CHAIN_IDS.GOERLI], - ); - - assert.deepStrictEqual( - incomingTransactionsController.store.updateState.getCall(0).args[0], - { - incomingTxLastFetchedBlockByChainId: { - ...PREPOPULATED_BLOCKS_BY_NETWORK, - [CHAIN_IDS.GOERLI]: - PREPOPULATED_BLOCKS_BY_NETWORK[CHAIN_IDS.GOERLI] + 1, - }, - incomingTransactions: PREPOPULATED_INCOMING_TXS_BY_HASH, - }, - ); - }); - }); - - it('should update the last fetched block for network to highest block seen in incoming txs', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - getCurrentChainId: () => CHAIN_IDS.GOERLI, - }, - ); - - const NEW_TRANSACTION_ONE = { - id: 555, - hash: '0xfff', - blockNumber: 444, - }; - const NEW_TRANSACTION_TWO = { - id: 556, - hash: '0xffa', - blockNumber: 443, - }; - - sinon.spy(incomingTransactionsController.store, 'updateState'); - - incomingTransactionsController._getNewIncomingTransactions = sinon - .stub() - .returns([NEW_TRANSACTION_ONE, NEW_TRANSACTION_TWO]); - await incomingTransactionsController._update('fakeAddress', 10); - - assert(incomingTransactionsController.store.updateState.calledOnce); - - assert.deepStrictEqual( - incomingTransactionsController._getNewIncomingTransactions.getCall(0) - .args, - ['fakeAddress', 1, CHAIN_IDS.GOERLI], - ); - - assert.deepStrictEqual( - incomingTransactionsController.store.updateState.getCall(0).args[0], - { - incomingTxLastFetchedBlockByChainId: { - ...PREPOPULATED_BLOCKS_BY_NETWORK, - [CHAIN_IDS.GOERLI]: 445, - }, - incomingTransactions: { - ...PREPOPULATED_INCOMING_TXS_BY_HASH, - [NEW_TRANSACTION_ONE.hash]: NEW_TRANSACTION_ONE, - [NEW_TRANSACTION_TWO.hash]: NEW_TRANSACTION_TWO, - }, - }, - ); - }); - }); - - describe('_getNewIncomingTransactions', function () { - const ADDRESS_TO_FETCH_FOR = '0xfakeaddress'; - const FETCHED_TX = getFakeEtherscanTransaction({ - toAddress: ADDRESS_TO_FETCH_FOR, - }); - const mockFetch = sinon.stub().returns( - Promise.resolve({ - json: () => Promise.resolve({ status: '1', result: [FETCHED_TX] }), - }), - ); - let tempFetch; - beforeEach(function () { - tempFetch = window.fetch; - window.fetch = mockFetch; - }); - - afterEach(function () { - window.fetch = tempFetch; - mockFetch.resetHistory(); - }); - - it('should call fetch with the expected url when passed an address, block number and supported chainId', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - '789', - CHAIN_IDS.GOERLI, - ); - - assert(mockFetch.calledOnce); - assert.strictEqual( - mockFetch.getCall(0).args[0], - `https://api-${NETWORK_TYPES.GOERLI}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, - ); - }); - - it('should call fetch with the expected url when passed an address, block number and MAINNET chainId', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.MAINNET), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - '789', - CHAIN_IDS.MAINNET, - ); - - assert(mockFetch.calledOnce); - assert.strictEqual( - mockFetch.getCall(0).args[0], - `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`, - ); - }); - - it('should call fetch with the expected url when passed an address and supported chainId, but a falsy block number', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - null, - CHAIN_IDS.GOERLI, - ); - - assert(mockFetch.calledOnce); - assert.strictEqual( - mockFetch.getCall(0).args[0], - `https://api-${NETWORK_TYPES.GOERLI}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`, - ); - }); - - it('should return an array of normalized transactions', async function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - '789', - CHAIN_IDS.GOERLI, - ); - - assert(mockFetch.calledOnce); - assert.deepStrictEqual(result, [ - incomingTransactionsController._normalizeTxFromEtherscan( - FETCHED_TX, - CHAIN_IDS.GOERLI, - ), - ]); - }); - - it('should return empty tx array if status is 0', async function () { - const mockFetchStatusZero = sinon.stub().returns( - Promise.resolve({ - json: () => Promise.resolve({ status: '0', result: [FETCHED_TX] }), - }), - ); - const tempFetchStatusZero = window.fetch; - window.fetch = mockFetchStatusZero; - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - '789', - CHAIN_IDS.GOERLI, - ); - assert.deepStrictEqual(result, []); - window.fetch = tempFetchStatusZero; - mockFetchStatusZero.reset(); - }); - - it('should return empty tx array if result array is empty', async function () { - const mockFetchEmptyResult = sinon.stub().returns( - Promise.resolve({ - json: () => Promise.resolve({ status: '1', result: [] }), - }), - ); - const tempFetchEmptyResult = window.fetch; - window.fetch = mockFetchEmptyResult; - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = - await incomingTransactionsController._getNewIncomingTransactions( - ADDRESS_TO_FETCH_FOR, - '789', - CHAIN_IDS.GOERLI, - ); - assert.deepStrictEqual(result, []); - window.fetch = tempFetchEmptyResult; - mockFetchEmptyResult.reset(); - }); - }); - - describe('_normalizeTxFromEtherscan', function () { - it('should return the expected data when the tx is in error', function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = incomingTransactionsController._normalizeTxFromEtherscan( - { - timeStamp: '4444', - isError: '1', - blockNumber: 333, - from: '0xa', - gas: '11', - gasPrice: '12', - nonce: '13', - to: '0xe', - value: '15', - hash: '0xg', - }, - CHAIN_IDS.GOERLI, - ); - - assert.deepStrictEqual(result, { - blockNumber: 333, - id: 54321, - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.failed, - time: 4444000, - txParams: { - from: '0xa', - gas: '0xb', - gasPrice: '0xc', - nonce: '0xd', - to: '0xe', - value: '0xf', - }, - hash: '0xg', - type: TransactionType.incoming, - }); - }); - - it('should return the expected data when the tx is not in error', function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = incomingTransactionsController._normalizeTxFromEtherscan( - { - timeStamp: '4444', - isError: '0', - blockNumber: 333, - from: '0xa', - gas: '11', - gasPrice: '12', - nonce: '13', - to: '0xe', - value: '15', - hash: '0xg', - }, - CHAIN_IDS.GOERLI, - ); - - assert.deepStrictEqual(result, { - blockNumber: 333, - id: 54321, - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 4444000, - txParams: { - from: '0xa', - gas: '0xb', - gasPrice: '0xc', - nonce: '0xd', - to: '0xe', - value: '0xf', - }, - hash: '0xg', - type: TransactionType.incoming, - }); - }); - - it('should return the expected data when the tx uses EIP-1559 fields', function () { - const incomingTransactionsController = new IncomingTransactionsController( - { - blockTracker: getMockBlockTracker(), - ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), - preferencesController: getMockPreferencesController(), - onboardingController: getMockOnboardingController(), - initState: getNonEmptyInitState(), - }, - ); - - const result = incomingTransactionsController._normalizeTxFromEtherscan( - { - timeStamp: '4444', - isError: '0', - blockNumber: 333, - from: '0xa', - gas: '11', - maxFeePerGas: '12', - maxPriorityFeePerGas: '1', - nonce: '13', - to: '0xe', - value: '15', - hash: '0xg', - }, - CHAIN_IDS.GOERLI, - ); - - assert.deepStrictEqual(result, { - blockNumber: 333, - id: 54321, - metamaskNetworkId: NETWORK_IDS.GOERLI, - chainId: CHAIN_IDS.GOERLI, - status: TransactionStatus.confirmed, - time: 4444000, - txParams: { - from: '0xa', - gas: '0xb', - maxFeePerGas: '0xc', - maxPriorityFeePerGas: '0x1', - nonce: '0xd', - to: '0xe', - value: '0xf', - }, - hash: '0xg', - type: TransactionType.incoming, - }); - }); - }); -}); diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts new file mode 100644 index 000000000..2e7effd2c --- /dev/null +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts @@ -0,0 +1,219 @@ +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { + TransactionStatus, + TransactionType, +} from '../../../../shared/constants/transaction'; +import createRandomId from '../../../../shared/modules/random-id'; +import type { + EtherscanTokenTransactionMeta, + EtherscanTransactionMeta, + EtherscanTransactionMetaBase, + EtherscanTransactionResponse, +} from './etherscan'; +import { + fetchEtherscanTokenTransactions, + fetchEtherscanTransactions, +} from './etherscan'; +import { EtherscanRemoteTransactionSource } from './EtherscanRemoteTransactionSource'; + +jest.mock('./etherscan', () => ({ + fetchEtherscanTransactions: jest.fn(), + fetchEtherscanTokenTransactions: jest.fn(), +})); + +jest.mock('../../../../shared/modules/random-id'); + +const ID_MOCK = 123; + +const ETHERSCAN_TRANSACTION_BASE_MOCK: EtherscanTransactionMetaBase = { + blockNumber: '4535105', + confirmations: '4', + contractAddress: '', + cumulativeGasUsed: '693910', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + gas: '335208', + gasPrice: '20000000000', + gasUsed: '21000', + hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff91', + nonce: '1', + timeStamp: '1543596356', + transactionIndex: '13', + value: '50000000000000000', + blockHash: '0x0000000001', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', +}; + +const ETHERSCAN_TRANSACTION_SUCCESS_MOCK: EtherscanTransactionMeta = { + ...ETHERSCAN_TRANSACTION_BASE_MOCK, + functionName: 'testFunction', + input: '0x', + isError: '0', + methodId: 'testId', + txreceipt_status: '1', +}; + +const ETHERSCAN_TRANSACTION_ERROR_MOCK: EtherscanTransactionMeta = { + ...ETHERSCAN_TRANSACTION_SUCCESS_MOCK, + isError: '1', +}; + +const ETHERSCAN_TOKEN_TRANSACTION_MOCK: EtherscanTokenTransactionMeta = { + ...ETHERSCAN_TRANSACTION_BASE_MOCK, + tokenDecimal: '456', + tokenName: 'TestToken', + tokenSymbol: 'ABC', +}; + +const ETHERSCAN_TRANSACTION_RESPONSE_MOCK: EtherscanTransactionResponse = + { + result: [ + ETHERSCAN_TRANSACTION_SUCCESS_MOCK, + ETHERSCAN_TRANSACTION_ERROR_MOCK, + ], + }; + +const ETHERSCAN_TOKEN_TRANSACTION_RESPONSE_MOCK: EtherscanTransactionResponse = + { + result: [ + ETHERSCAN_TOKEN_TRANSACTION_MOCK, + ETHERSCAN_TOKEN_TRANSACTION_MOCK, + ], + }; + +const ETHERSCAN_TRANSACTION_RESPONSE_EMPTY_MOCK: EtherscanTransactionResponse = + { + result: [], + }; + +const ETHERSCAN_TOKEN_TRANSACTION_RESPONSE_EMPTY_MOCK: EtherscanTransactionResponse = + ETHERSCAN_TRANSACTION_RESPONSE_EMPTY_MOCK as any; + +const EXPECTED_NORMALISED_TRANSACTION_BASE = { + blockNumber: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.blockNumber, + chainId: undefined, + hash: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.hash, + id: ID_MOCK, + metamaskNetworkId: undefined, + status: TransactionStatus.confirmed, + time: 1543596356000, + txParams: { + from: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.from, + gas: '0x51d68', + gasPrice: '0x4a817c800', + nonce: '0x1', + to: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.to, + value: '0xb1a2bc2ec50000', + }, + type: TransactionType.incoming, +}; + +const EXPECTED_NORMALISED_TRANSACTION_SUCCESS = { + ...EXPECTED_NORMALISED_TRANSACTION_BASE, + txParams: { + ...EXPECTED_NORMALISED_TRANSACTION_BASE.txParams, + data: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.input, + }, +}; + +const EXPECTED_NORMALISED_TRANSACTION_ERROR = { + ...EXPECTED_NORMALISED_TRANSACTION_SUCCESS, + status: TransactionStatus.failed, +}; + +const EXPECTED_NORMALISED_TOKEN_TRANSACTION = { + ...EXPECTED_NORMALISED_TRANSACTION_BASE, +}; + +describe('EtherscanRemoteTransactionSource', () => { + const fetchEtherscanTransactionsMock = + fetchEtherscanTransactions as jest.MockedFn< + typeof fetchEtherscanTransactions + >; + + const fetchEtherscanTokenTransactionsMock = + fetchEtherscanTokenTransactions as jest.MockedFn< + typeof fetchEtherscanTokenTransactions + >; + + const createIdMock = createRandomId as jest.MockedFn; + + beforeEach(() => { + jest.resetAllMocks(); + + fetchEtherscanTransactionsMock.mockResolvedValue( + ETHERSCAN_TRANSACTION_RESPONSE_EMPTY_MOCK, + ); + + fetchEtherscanTokenTransactionsMock.mockResolvedValue( + ETHERSCAN_TOKEN_TRANSACTION_RESPONSE_EMPTY_MOCK, + ); + + createIdMock.mockReturnValue(ID_MOCK); + }); + + describe('isSupportedNetwork', () => { + it('returns true if chain ID in constant', () => { + expect( + new EtherscanRemoteTransactionSource().isSupportedNetwork( + CHAIN_IDS.MAINNET, + '1', + ), + ).toBe(true); + }); + + it('returns false if chain ID not in constant', () => { + expect( + new EtherscanRemoteTransactionSource().isSupportedNetwork( + CHAIN_IDS.LOCALHOST, + '1', + ), + ).toBe(false); + }); + }); + + describe('fetchTransactions', () => { + it('returns normalized transactions fetched from Etherscan', async () => { + fetchEtherscanTransactionsMock.mockResolvedValueOnce( + ETHERSCAN_TRANSACTION_RESPONSE_MOCK, + ); + + const transactions = + await new EtherscanRemoteTransactionSource().fetchTransactions( + {} as any, + ); + + expect(transactions).toStrictEqual([ + EXPECTED_NORMALISED_TRANSACTION_SUCCESS, + EXPECTED_NORMALISED_TRANSACTION_ERROR, + ]); + }); + + it('returns normalized token transactions fetched from Etherscan', async () => { + fetchEtherscanTokenTransactionsMock.mockResolvedValueOnce( + ETHERSCAN_TOKEN_TRANSACTION_RESPONSE_MOCK, + ); + + const transactions = + await new EtherscanRemoteTransactionSource().fetchTransactions( + {} as any, + ); + + expect(transactions).toStrictEqual([ + EXPECTED_NORMALISED_TOKEN_TRANSACTION, + EXPECTED_NORMALISED_TOKEN_TRANSACTION, + ]); + }); + + it('returns no normalized token transactions if flag disabled', async () => { + fetchEtherscanTokenTransactionsMock.mockResolvedValueOnce( + ETHERSCAN_TOKEN_TRANSACTION_RESPONSE_MOCK, + ); + + const transactions = await new EtherscanRemoteTransactionSource({ + includeTokenTransfers: false, + }).fetchTransactions({} as any); + + expect(transactions).toStrictEqual([]); + }); + }); +}); diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts new file mode 100644 index 000000000..ee37d0d4b --- /dev/null +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts @@ -0,0 +1,156 @@ +import { BNToHex } from '@metamask/controller-utils'; +import type { Hex } from '@metamask/utils'; +import { BN } from 'ethereumjs-util'; +import createId from '../../../../shared/modules/random-id'; + +import { + TransactionMeta, + TransactionStatus, + TransactionType, +} from '../../../../shared/constants/transaction'; +import { ETHERSCAN_SUPPORTED_NETWORKS } from '../../../../shared/constants/network'; +import type { + EtherscanTokenTransactionMeta, + EtherscanTransactionMeta, + EtherscanTransactionMetaBase, + EtherscanTransactionRequest, + EtherscanTransactionResponse, +} from './etherscan'; +import { + fetchEtherscanTokenTransactions, + fetchEtherscanTransactions, +} from './etherscan'; +import { + RemoteTransactionSource, + RemoteTransactionSourceRequest, +} from './types'; + +/** + * A RemoteTransactionSource that fetches transaction data from Etherscan. + */ +export class EtherscanRemoteTransactionSource + implements RemoteTransactionSource +{ + #apiKey?: string; + + #includeTokenTransfers: boolean; + + constructor({ + apiKey, + includeTokenTransfers, + }: { apiKey?: string; includeTokenTransfers?: boolean } = {}) { + this.#apiKey = apiKey; + this.#includeTokenTransfers = includeTokenTransfers ?? true; + } + + isSupportedNetwork(chainId: Hex, _networkId: string): boolean { + return Object.keys(ETHERSCAN_SUPPORTED_NETWORKS).includes(chainId); + } + + async fetchTransactions( + request: RemoteTransactionSourceRequest, + ): Promise { + const etherscanRequest: EtherscanTransactionRequest = { + ...request, + apiKey: this.#apiKey, + chainId: request.currentChainId, + }; + + const transactionPromise = fetchEtherscanTransactions(etherscanRequest); + + const tokenTransactionPromise = this.#includeTokenTransfers + ? fetchEtherscanTokenTransactions(etherscanRequest) + : Promise.resolve({ + result: [] as EtherscanTokenTransactionMeta[], + } as EtherscanTransactionResponse); + + const [etherscanTransactions, etherscanTokenTransactions] = + await Promise.all([transactionPromise, tokenTransactionPromise]); + + const transactions = etherscanTransactions.result.map((tx) => + this.#normalizeTransaction( + tx, + request.currentNetworkId, + request.currentChainId, + ), + ); + + const tokenTransactions = etherscanTokenTransactions.result.map((tx) => + this.#normalizeTokenTransaction( + tx, + request.currentNetworkId, + request.currentChainId, + ), + ); + + return [...transactions, ...tokenTransactions]; + } + + #normalizeTransaction( + txMeta: EtherscanTransactionMeta, + currentNetworkId: string, + currentChainId: Hex, + ): TransactionMeta { + const base = this.#normalizeTransactionBase( + txMeta, + currentNetworkId, + currentChainId, + ); + + return { + ...base, + txParams: { + ...base.txParams, + data: txMeta.input, + }, + ...(txMeta.isError === '0' + ? { status: TransactionStatus.confirmed } + : { + status: TransactionStatus.failed, + }), + }; + } + + #normalizeTokenTransaction( + txMeta: EtherscanTokenTransactionMeta, + currentNetworkId: string, + currentChainId: Hex, + ): TransactionMeta { + const base = this.#normalizeTransactionBase( + txMeta, + currentNetworkId, + currentChainId, + ); + + return { + ...base, + }; + } + + #normalizeTransactionBase( + txMeta: EtherscanTransactionMetaBase, + currentNetworkId: string, + currentChainId: Hex, + ): TransactionMeta { + const time = parseInt(txMeta.timeStamp, 10) * 1000; + + return { + blockNumber: txMeta.blockNumber, + chainId: currentChainId, + hash: txMeta.hash, + id: createId(), + metamaskNetworkId: currentNetworkId, + status: TransactionStatus.confirmed, + time, + txParams: { + from: txMeta.from, + gas: BNToHex(new BN(txMeta.gas)), + gasPrice: BNToHex(new BN(txMeta.gasPrice)), + nonce: BNToHex(new BN(txMeta.nonce)), + to: txMeta.to, + value: BNToHex(new BN(txMeta.value)), + }, + type: TransactionType.incoming, + } as TransactionMeta; + } +} diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts new file mode 100644 index 000000000..f20675215 --- /dev/null +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts @@ -0,0 +1,585 @@ +import { NetworkType } from '@metamask/controller-utils'; +import type { BlockTracker, NetworkState } from '@metamask/network-controller'; + +import { + TransactionMeta, + TransactionStatus, +} from '../../../../shared/constants/transaction'; +import { IncomingTransactionHelper } from './IncomingTransactionHelper'; +import { RemoteTransactionSource } from './types'; + +jest.mock('@metamask/controller-utils', () => ({ + ...jest.requireActual('@metamask/controller-utils'), + isSmartContractCode: jest.fn(), + query: () => Promise.resolve({}), +})); + +const NETWORK_STATE_MOCK: NetworkState = { + providerConfig: { + chainId: '0x1', + type: NetworkType.mainnet, + }, + networkId: '1', +} as unknown as NetworkState; + +const ADDERSS_MOCK = '0x1'; +const FROM_BLOCK_HEX_MOCK = '0x20'; +const FROM_BLOCK_DECIMAL_MOCK = 32; + +const BLOCK_TRACKER_MOCK = { + addListener: jest.fn(), + removeListener: jest.fn(), + getLatestBlock: jest.fn(() => FROM_BLOCK_HEX_MOCK), +} as unknown as jest.Mocked; + +const CONTROLLER_ARGS_MOCK = { + blockTracker: BLOCK_TRACKER_MOCK, + getCurrentAccount: () => ADDERSS_MOCK, + getNetworkState: () => NETWORK_STATE_MOCK, + remoteTransactionSource: {} as RemoteTransactionSource, + transactionLimit: 1, +}; + +const TRANSACTION_MOCK: TransactionMeta = { + blockNumber: '123', + chainId: '0x1', + status: TransactionStatus.submitted, + time: 0, + txParams: { to: '0x1' }, +} as unknown as TransactionMeta; + +const TRANSACTION_MOCK_2: TransactionMeta = { + blockNumber: '234', + chainId: '0x1', + hash: '0x2', + time: 1, + txParams: { to: '0x1' }, +} as unknown as TransactionMeta; + +const createRemoteTransactionSourceMock = ( + remoteTransactions: TransactionMeta[], + { + isSupportedNetwork, + error, + }: { isSupportedNetwork?: boolean; error?: boolean } = {}, +): RemoteTransactionSource => ({ + isSupportedNetwork: jest.fn(() => isSupportedNetwork ?? true), + fetchTransactions: jest.fn(() => + error + ? Promise.reject(new Error('Test Error')) + : Promise.resolve(remoteTransactions), + ), +}); + +async function emitBlockTrackerLatestEvent( + helper: IncomingTransactionHelper, + { start, error }: { start?: boolean; error?: boolean } = {}, +) { + const transactionsListener = jest.fn(); + const blockNumberListener = jest.fn(); + + if (error) { + transactionsListener.mockImplementation(() => { + throw new Error('Test Error'); + }); + } + + helper.hub.addListener('transactions', transactionsListener); + helper.hub.addListener('updatedLastFetchedBlockNumbers', blockNumberListener); + + if (start !== false) { + helper.start(); + } + + await BLOCK_TRACKER_MOCK.addListener.mock.calls[0]?.[1]?.( + FROM_BLOCK_HEX_MOCK, + ); + + return { + transactions: transactionsListener.mock.calls[0]?.[0], + lastFetchedBlockNumbers: + blockNumberListener.mock.calls[0]?.[0].lastFetchedBlockNumbers, + transactionsListener, + blockNumberListener, + }; +} + +describe('IncomingTransactionHelper', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('on block tracker latest event', () => { + it('handles errors', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK_2, + ]), + }); + + await emitBlockTrackerLatestEvent(helper, { error: true }); + }); + + describe('fetches remote transactions', () => { + it('using remote transaction source', async () => { + const remoteTransactionSource = createRemoteTransactionSourceMock([]); + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource, + }); + + await emitBlockTrackerLatestEvent(helper); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledTimes( + 1, + ); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledWith({ + address: ADDERSS_MOCK, + currentChainId: NETWORK_STATE_MOCK.providerConfig.chainId, + currentNetworkId: NETWORK_STATE_MOCK.networkId, + fromBlock: expect.any(Number), + limit: CONTROLLER_ARGS_MOCK.transactionLimit, + }); + }); + + it('using from block as latest block minus ten if no last fetched data', async () => { + const remoteTransactionSource = createRemoteTransactionSourceMock([]); + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource, + }); + + await emitBlockTrackerLatestEvent(helper); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledTimes( + 1, + ); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledWith( + expect.objectContaining({ + fromBlock: FROM_BLOCK_DECIMAL_MOCK - 10, + }), + ); + }); + + it('using from block as last fetched value plus one', async () => { + const remoteTransactionSource = createRemoteTransactionSourceMock([]); + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource, + lastFetchedBlockNumbers: { + [`${NETWORK_STATE_MOCK.providerConfig.chainId}#${ADDERSS_MOCK}`]: + FROM_BLOCK_DECIMAL_MOCK, + }, + }); + + await emitBlockTrackerLatestEvent(helper); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledTimes( + 1, + ); + + expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledWith( + expect.objectContaining({ + fromBlock: FROM_BLOCK_DECIMAL_MOCK + 1, + }), + ); + }); + }); + + describe('emits transactions event', () => { + it('if new transaction fetched', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK_2, + ]), + }); + + const { transactions } = await emitBlockTrackerLatestEvent(helper); + + expect(transactions).toStrictEqual({ + added: [TRANSACTION_MOCK_2], + updated: [], + }); + }); + + it('if new outgoing transaction fetched and update transactions enabled', async () => { + const outgoingTransaction = { + ...TRANSACTION_MOCK_2, + txParams: { + ...TRANSACTION_MOCK_2.txParams, + from: '0x1', + to: '0x2', + }, + }; + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + outgoingTransaction, + ]), + updateTransactions: true, + }); + + const { transactions } = await emitBlockTrackerLatestEvent(helper); + + expect(transactions).toStrictEqual({ + added: [outgoingTransaction], + updated: [], + }); + }); + + it('if existing transaction fetched with different status and update transactions enabled', async () => { + const updatedTransaction = { + ...TRANSACTION_MOCK, + status: TransactionStatus.confirmed, + } as TransactionMeta; + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + updatedTransaction, + ]), + getLocalTransactions: () => [TRANSACTION_MOCK], + updateTransactions: true, + }); + + const { transactions } = await emitBlockTrackerLatestEvent(helper); + + expect(transactions).toStrictEqual({ + added: [], + updated: [updatedTransaction], + }); + }); + + it('sorted by time in ascending order', async () => { + const firstTransaction = { ...TRANSACTION_MOCK, time: 5 }; + const secondTransaction = { ...TRANSACTION_MOCK, time: 6 }; + const thirdTransaction = { ...TRANSACTION_MOCK, time: 7 }; + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + firstTransaction, + thirdTransaction, + secondTransaction, + ]), + }); + + const { transactions } = await emitBlockTrackerLatestEvent(helper); + + expect(transactions).toStrictEqual({ + added: [firstTransaction, secondTransaction, thirdTransaction], + updated: [], + }); + }); + + it('does not if identical transaction fetched and update transactions enabled', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK, + ]), + getLocalTransactions: () => [TRANSACTION_MOCK], + updateTransactions: true, + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if disabled', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK, + ]), + isEnabled: jest + .fn() + .mockReturnValueOnce(true) + .mockReturnValueOnce(false), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if current network is not supported by remote transaction source', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock( + [TRANSACTION_MOCK], + { isSupportedNetwork: false }, + ), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if no remote transactions', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if update transactions disabled and no incoming transactions', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + { + ...TRANSACTION_MOCK, + txParams: { to: '0x2' }, + } as TransactionMeta, + { + ...TRANSACTION_MOCK, + txParams: { to: undefined } as any, + } as TransactionMeta, + ]), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if error fetching transactions', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock( + [TRANSACTION_MOCK], + { error: true }, + ), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + + it('does not if not started', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK, + ]), + }); + + const { transactionsListener } = await emitBlockTrackerLatestEvent( + helper, + { start: false }, + ); + + expect(transactionsListener).not.toHaveBeenCalled(); + }); + }); + + describe('emits updatedLastFetchedBlockNumbers event', () => { + it('if fetched transaction has higher block number', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK_2, + ]), + }); + + const { lastFetchedBlockNumbers } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(lastFetchedBlockNumbers).toStrictEqual({ + [`${NETWORK_STATE_MOCK.providerConfig.chainId}#${ADDERSS_MOCK}`]: + parseInt(TRANSACTION_MOCK_2.blockNumber as string, 10), + }); + }); + + it('does not if no fetched transactions', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + const { blockNumberListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(blockNumberListener).not.toHaveBeenCalled(); + }); + + it('does not if no block number on fetched transaction', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + { ...TRANSACTION_MOCK_2, blockNumber: undefined }, + ]), + }); + + const { blockNumberListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(blockNumberListener).not.toHaveBeenCalled(); + }); + + it('does not if fetch transaction not to current account', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + { + ...TRANSACTION_MOCK_2, + txParams: { to: '0x2' }, + } as TransactionMeta, + ]), + }); + + const { blockNumberListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(blockNumberListener).not.toHaveBeenCalled(); + }); + + it('does not if fetched transaction has same block number', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK_2, + ]), + lastFetchedBlockNumbers: { + [`${NETWORK_STATE_MOCK.providerConfig.chainId}#${ADDERSS_MOCK}`]: + parseInt(TRANSACTION_MOCK_2.blockNumber as string, 10), + }, + }); + + const { blockNumberListener } = await emitBlockTrackerLatestEvent( + helper, + ); + + expect(blockNumberListener).not.toHaveBeenCalled(); + }); + }); + }); + + describe('start', () => { + it('adds listener to block tracker', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + helper.start(); + + expect( + CONTROLLER_ARGS_MOCK.blockTracker.addListener, + ).toHaveBeenCalledTimes(1); + }); + + it('does nothing if already started', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + helper.start(); + helper.start(); + + expect( + CONTROLLER_ARGS_MOCK.blockTracker.addListener, + ).toHaveBeenCalledTimes(1); + }); + + it('does nothing if disabled', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + isEnabled: () => false, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + helper.start(); + + expect( + CONTROLLER_ARGS_MOCK.blockTracker.addListener, + ).not.toHaveBeenCalled(); + }); + + it('does nothing if network not supported by remote transaction source', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([], { + isSupportedNetwork: false, + }), + }); + + helper.start(); + + expect( + CONTROLLER_ARGS_MOCK.blockTracker.addListener, + ).not.toHaveBeenCalled(); + }); + }); + + describe('stop', () => { + it('removes listener from block tracker', async () => { + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([]), + }); + + helper.start(); + helper.stop(); + + expect( + CONTROLLER_ARGS_MOCK.blockTracker.removeListener, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('update', () => { + it('emits transactions event', async () => { + const listener = jest.fn(); + + const helper = new IncomingTransactionHelper({ + ...CONTROLLER_ARGS_MOCK, + remoteTransactionSource: createRemoteTransactionSourceMock([ + TRANSACTION_MOCK_2, + ]), + }); + + helper.hub.on('transactions', listener); + + await helper.update(); + + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledWith({ + added: [TRANSACTION_MOCK_2], + updated: [], + }); + }); + }); +}); diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts new file mode 100644 index 000000000..3acb63726 --- /dev/null +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts @@ -0,0 +1,282 @@ +import EventEmitter from 'events'; +import type { BlockTracker, NetworkState } from '@metamask/network-controller'; +import type { Hex } from '@metamask/utils'; + +import log from 'loglevel'; +import { TransactionMeta } from '../../../../shared/constants/transaction'; +import { RemoteTransactionSource } from './types'; + +const UPDATE_CHECKS: ((txMeta: TransactionMeta) => any)[] = [ + (txMeta) => txMeta.status, +]; + +export class IncomingTransactionHelper { + hub: EventEmitter; + + #blockTracker: BlockTracker; + + #getCurrentAccount: () => string; + + #getLocalTransactions: () => TransactionMeta[]; + + #getNetworkState: () => NetworkState; + + #isEnabled: () => boolean; + + #isRunning: boolean; + + #isUpdating: boolean; + + #lastFetchedBlockNumbers: Record; + + #onLatestBlock: (blockNumberHex: Hex) => Promise; + + #remoteTransactionSource: RemoteTransactionSource; + + #transactionLimit?: number; + + #updateTransactions: boolean; + + constructor({ + blockTracker, + getCurrentAccount, + getLocalTransactions, + getNetworkState, + isEnabled, + lastFetchedBlockNumbers, + remoteTransactionSource, + transactionLimit, + updateTransactions, + }: { + blockTracker: BlockTracker; + getCurrentAccount: () => string; + getNetworkState: () => NetworkState; + getLocalTransactions?: () => TransactionMeta[]; + isEnabled?: () => boolean; + lastFetchedBlockNumbers?: Record; + remoteTransactionSource: RemoteTransactionSource; + transactionLimit?: number; + updateTransactions?: boolean; + }) { + this.hub = new EventEmitter(); + + this.#blockTracker = blockTracker; + this.#getCurrentAccount = getCurrentAccount; + this.#getLocalTransactions = getLocalTransactions || (() => []); + this.#getNetworkState = getNetworkState; + this.#isEnabled = isEnabled ?? (() => true); + this.#isRunning = false; + this.#isUpdating = false; + this.#lastFetchedBlockNumbers = lastFetchedBlockNumbers ?? {}; + this.#remoteTransactionSource = remoteTransactionSource; + this.#transactionLimit = transactionLimit; + this.#updateTransactions = updateTransactions ?? false; + + // Using a property instead of a method to provide a listener reference + // with the correct scope that we can remove later if stopped. + this.#onLatestBlock = async (blockNumberHex: Hex) => { + await this.update(blockNumberHex); + }; + } + + start() { + if (this.#isRunning) { + return; + } + + if (!this.#canStart()) { + return; + } + + this.#blockTracker.addListener('latest', this.#onLatestBlock); + this.#isRunning = true; + } + + stop() { + this.#blockTracker.removeListener('latest', this.#onLatestBlock); + this.#isRunning = false; + } + + async update(latestBlockNumberHex?: Hex): Promise { + if (this.#isUpdating) { + return; + } + + this.#isUpdating = true; + + try { + if (!this.#canStart()) { + return; + } + + const latestBlockNumber = parseInt( + latestBlockNumberHex || (await this.#blockTracker.getLatestBlock()), + 16, + ); + + const fromBlock = this.#getFromBlock(latestBlockNumber); + const address = this.#getCurrentAccount(); + const currentChainId = this.#getCurrentChainId(); + const currentNetworkId = this.#getCurrentNetworkId(); + + let remoteTransactions = []; + + try { + remoteTransactions = + await this.#remoteTransactionSource.fetchTransactions({ + address, + currentChainId, + currentNetworkId, + fromBlock, + limit: this.#transactionLimit, + }); + } catch (error: any) { + return; + } + + if (!this.#updateTransactions) { + remoteTransactions = remoteTransactions.filter( + (tx) => tx.txParams.to?.toLowerCase() === address.toLowerCase(), + ); + } + + const localTransactions = this.#updateTransactions + ? this.#getLocalTransactions() + : []; + + const newTransactions = this.#getNewTransactions( + remoteTransactions, + localTransactions, + ); + + const updatedTransactions = this.#getUpdatedTransactions( + remoteTransactions, + localTransactions, + ); + + if (newTransactions.length > 0 || updatedTransactions.length > 0) { + this.#sortTransactionsByTime(newTransactions); + this.#sortTransactionsByTime(updatedTransactions); + + this.hub.emit('transactions', { + added: newTransactions, + updated: updatedTransactions, + }); + } + + this.#updateLastFetchedBlockNumber(remoteTransactions); + } catch (error) { + log.error('Error while checking incoming transactions', error); + } finally { + this.#isUpdating = false; + } + } + + #sortTransactionsByTime(transactions: TransactionMeta[]) { + transactions.sort((a, b) => (a.time < b.time ? -1 : 1)); + } + + #getNewTransactions( + remoteTxs: TransactionMeta[], + localTxs: TransactionMeta[], + ): TransactionMeta[] { + return remoteTxs.filter( + (tx) => !localTxs.some(({ hash }) => hash === tx.hash), + ); + } + + #getUpdatedTransactions( + remoteTxs: TransactionMeta[], + localTxs: TransactionMeta[], + ): TransactionMeta[] { + return remoteTxs.filter((remoteTx) => + localTxs.some( + (localTx) => + remoteTx.hash === localTx.hash && + this.#isTransactionOutdated(remoteTx, localTx), + ), + ); + } + + #isTransactionOutdated( + remoteTx: TransactionMeta, + localTx: TransactionMeta, + ): boolean { + return UPDATE_CHECKS.some( + (getValue) => getValue(remoteTx) !== getValue(localTx), + ); + } + + #getFromBlock(latestBlockNumber: number): number { + const lastFetchedKey = this.#getBlockNumberKey(); + + const lastFetchedBlockNumber = + this.#lastFetchedBlockNumbers[lastFetchedKey]; + + if (lastFetchedBlockNumber) { + return lastFetchedBlockNumber + 1; + } + + // Avoid using latest block as remote transaction source + // may not have indexed it yet + return Math.max(latestBlockNumber - 10, 0); + } + + #updateLastFetchedBlockNumber(remoteTxs: TransactionMeta[]) { + let lastFetchedBlockNumber = -1; + + for (const tx of remoteTxs) { + const currentBlockNumberValue = tx.blockNumber + ? parseInt(tx.blockNumber, 10) + : -1; + + lastFetchedBlockNumber = Math.max( + lastFetchedBlockNumber, + currentBlockNumberValue, + ); + } + + if (lastFetchedBlockNumber === -1) { + return; + } + + const lastFetchedKey = this.#getBlockNumberKey(); + const previousValue = this.#lastFetchedBlockNumbers[lastFetchedKey]; + + if (previousValue === lastFetchedBlockNumber) { + return; + } + + this.#lastFetchedBlockNumbers[lastFetchedKey] = lastFetchedBlockNumber; + + this.hub.emit('updatedLastFetchedBlockNumbers', { + lastFetchedBlockNumbers: this.#lastFetchedBlockNumbers, + blockNumber: lastFetchedBlockNumber, + }); + } + + #getBlockNumberKey(): string { + return `${this.#getCurrentChainId()}#${this.#getCurrentAccount().toLowerCase()}`; + } + + #canStart(): boolean { + const isEnabled = this.#isEnabled(); + const currentChainId = this.#getCurrentChainId(); + const currentNetworkId = this.#getCurrentNetworkId(); + + const isSupportedNetwork = this.#remoteTransactionSource.isSupportedNetwork( + currentChainId, + currentNetworkId, + ); + + return isEnabled && isSupportedNetwork; + } + + #getCurrentChainId(): Hex { + return this.#getNetworkState().providerConfig.chainId; + } + + #getCurrentNetworkId(): string { + return this.#getNetworkState().networkId as string; + } +} diff --git a/app/scripts/controllers/transactions/etherscan.test.ts b/app/scripts/controllers/transactions/etherscan.test.ts new file mode 100644 index 000000000..4b18ffa86 --- /dev/null +++ b/app/scripts/controllers/transactions/etherscan.test.ts @@ -0,0 +1,153 @@ +import { handleFetch } from '@metamask/controller-utils'; + +import { + CHAIN_IDS, + ETHERSCAN_SUPPORTED_NETWORKS, +} from '../../../../shared/constants/network'; +import type { + EtherscanTransactionMeta, + EtherscanTransactionRequest, + EtherscanTransactionResponse, +} from './etherscan'; +import * as Etherscan from './etherscan'; + +jest.mock('@metamask/controller-utils', () => ({ + ...jest.requireActual('@metamask/controller-utils'), + handleFetch: jest.fn(), +})); + +const ADDERSS_MOCK = '0x2A2D72308838A6A46a0B5FDA3055FE915b5D99eD'; + +const REQUEST_MOCK: EtherscanTransactionRequest = { + address: ADDERSS_MOCK, + chainId: CHAIN_IDS.GOERLI, + limit: 3, + fromBlock: 2, + apiKey: 'testApiKey', +}; + +const RESPONSE_MOCK: EtherscanTransactionResponse = { + result: [ + { from: ADDERSS_MOCK, nonce: '0x1' } as EtherscanTransactionMeta, + { from: ADDERSS_MOCK, nonce: '0x2' } as EtherscanTransactionMeta, + ], +}; + +describe('Etherscan', () => { + const handleFetchMock = handleFetch as jest.MockedFunction< + typeof handleFetch + >; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe.each([ + ['fetchEtherscanTransactions', 'txlist'], + ['fetchEtherscanTokenTransactions', 'tokentx'], + ])('%s', (method, action) => { + it('returns fetched response', async () => { + handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); + + const result = await (Etherscan as any)[method](REQUEST_MOCK); + + expect(result).toStrictEqual(RESPONSE_MOCK); + }); + + it('fetches from Etherscan URL', async () => { + handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); + + await (Etherscan as any)[method](REQUEST_MOCK); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith( + `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].subdomain}.${ + ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].domain + }/api?` + + `module=account` + + `&address=${REQUEST_MOCK.address}` + + `&startBlock=${REQUEST_MOCK.fromBlock}` + + `&apikey=${REQUEST_MOCK.apiKey}` + + `&offset=${REQUEST_MOCK.limit}` + + `&order=desc` + + `&action=${action}` + + `&tag=latest` + + `&page=1`, + ); + }); + + it('supports alternate networks', async () => { + handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); + + await (Etherscan as any)[method]({ + ...REQUEST_MOCK, + chainId: CHAIN_IDS.MAINNET, + }); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith( + `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.MAINNET].subdomain}.${ + ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.MAINNET].domain + }/api?` + + `module=account` + + `&address=${REQUEST_MOCK.address}` + + `&startBlock=${REQUEST_MOCK.fromBlock}` + + `&apikey=${REQUEST_MOCK.apiKey}` + + `&offset=${REQUEST_MOCK.limit}` + + `&order=desc` + + `&action=${action}` + + `&tag=latest` + + `&page=1`, + ); + }); + + it('throws if message is not ok', async () => { + handleFetchMock.mockResolvedValueOnce({ + status: '0', + message: 'NOTOK', + result: 'test error', + }); + + await expect((Etherscan as any)[method](REQUEST_MOCK)).rejects.toThrow( + 'Etherscan request failed - test error', + ); + }); + + it('throws if chain is not supported', async () => { + const unsupportedChainId = '0x11111111111111111111'; + + await expect( + (Etherscan as any)[method]({ + ...REQUEST_MOCK, + chainId: unsupportedChainId, + }), + ).rejects.toThrow( + `Etherscan does not support chain with ID: ${unsupportedChainId}`, + ); + }); + + it('does not include empty values in fetched URL', async () => { + handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); + + await (Etherscan as any)[method]({ + ...REQUEST_MOCK, + fromBlock: undefined, + apiKey: undefined, + }); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith( + `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].subdomain}.${ + ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].domain + }/api?` + + `module=account` + + `&address=${REQUEST_MOCK.address}` + + `&offset=${REQUEST_MOCK.limit}` + + `&order=desc` + + `&action=${action}` + + `&tag=latest` + + `&page=1`, + ); + }); + }); +}); diff --git a/app/scripts/controllers/transactions/etherscan.ts b/app/scripts/controllers/transactions/etherscan.ts new file mode 100644 index 000000000..b01f94fa4 --- /dev/null +++ b/app/scripts/controllers/transactions/etherscan.ts @@ -0,0 +1,205 @@ +import { handleFetch } from '@metamask/controller-utils'; +import { Hex } from '@metamask/utils'; +import { ETHERSCAN_SUPPORTED_NETWORKS } from '../../../../shared/constants/network'; + +export interface EtherscanTransactionMetaBase { + blockNumber: string; + blockHash: string; + confirmations: string; + contractAddress: string; + cumulativeGasUsed: string; + from: string; + gas: string; + gasPrice: string; + gasUsed: string; + hash: string; + nonce: string; + timeStamp: string; + to: string; + transactionIndex: string; + value: string; +} + +export interface EtherscanTransactionMeta extends EtherscanTransactionMetaBase { + functionName: string; + input: string; + isError: string; + methodId: string; + txreceipt_status: string; +} + +export interface EtherscanTokenTransactionMeta + extends EtherscanTransactionMetaBase { + tokenDecimal: string; + tokenName: string; + tokenSymbol: string; +} + +export interface EtherscanTransactionResponse< + T extends EtherscanTransactionMetaBase, +> { + result: T[]; +} + +export interface EtherscanTransactionRequest { + address: string; + apiKey?: string; + chainId: Hex; + fromBlock?: number; + limit?: number; +} + +interface RawEtherscanResponse { + status: '0' | '1'; + message: string; + result: string | T[]; +} + +/** + * Retrieves transaction data from Etherscan. + * + * @param request - Configuration required to fetch transactions. + * @param request.address - Address to retrieve transactions for. + * @param request.apiKey - Etherscan API key. + * @param request.chainId - Current chain ID used to determine subdomain and domain. + * @param request.fromBlock - Block number to start fetching transactions from. + * @param request.limit - Number of transactions to retrieve. + * @returns An Etherscan response object containing the request status and an array of token transaction data. + */ +export async function fetchEtherscanTransactions({ + address, + apiKey, + chainId, + fromBlock, + limit, +}: EtherscanTransactionRequest): Promise< + EtherscanTransactionResponse +> { + return await fetchTransactions('txlist', { + address, + apiKey, + chainId, + fromBlock, + limit, + }); +} + +/** + * Retrieves token transaction data from Etherscan. + * + * @param request - Configuration required to fetch token transactions. + * @param request.address - Address to retrieve token transactions for. + * @param request.apiKey - Etherscan API key. + * @param request.chainId - Current chain ID used to determine subdomain and domain. + * @param request.fromBlock - Block number to start fetching token transactions from. + * @param request.limit - Number of token transactions to retrieve. + * @returns An Etherscan response object containing the request status and an array of token transaction data. + */ +export async function fetchEtherscanTokenTransactions({ + address, + apiKey, + chainId, + fromBlock, + limit, +}: EtherscanTransactionRequest): Promise< + EtherscanTransactionResponse +> { + return await fetchTransactions('tokentx', { + address, + apiKey, + chainId, + fromBlock, + limit, + }); +} + +/** + * Retrieves transaction data from Etherscan from a specific endpoint. + * + * @param action - The Etherscan endpoint to use. + * @param options - Options bag. + * @param options.address - Address to retrieve transactions for. + * @param options.apiKey - Etherscan API key. + * @param options.chainId - Current chain ID used to determine subdomain and domain. + * @param options.fromBlock - Block number to start fetching transactions from. + * @param options.limit - Number of transactions to retrieve. + * @returns An object containing the request status and an array of transaction data. + */ +async function fetchTransactions( + action: string, + { + address, + apiKey, + chainId, + fromBlock, + limit, + }: { + address: string; + apiKey?: string; + chainId: Hex; + fromBlock?: number; + limit?: number; + }, +): Promise> { + const urlParams = { + module: 'account', + address, + startBlock: fromBlock?.toString(), + apikey: apiKey, + offset: limit?.toString(), + order: 'desc', + }; + + const etherscanTxUrl = getEtherscanApiUrl(chainId, { + ...urlParams, + action, + }); + + const response = (await handleFetch( + etherscanTxUrl, + )) as RawEtherscanResponse; + + if (response.status === '0' && response.message === 'NOTOK') { + throw new Error(`Etherscan request failed - ${response.result}`); + } + + return { result: response.result as T[] }; +} + +/** + * Return a URL that can be used to fetch data from Etherscan. + * + * @param chainId - Current chain ID used to determine subdomain and domain. + * @param urlParams - The parameters used to construct the URL. + * @returns URL to access Etherscan data. + */ +function getEtherscanApiUrl( + chainId: Hex, + urlParams: Record, +): string { + type SupportedChainId = keyof typeof ETHERSCAN_SUPPORTED_NETWORKS; + + const networkInfo = ETHERSCAN_SUPPORTED_NETWORKS[chainId as SupportedChainId]; + + if (!networkInfo) { + throw new Error(`Etherscan does not support chain with ID: ${chainId}`); + } + + const apiUrl = `https://${networkInfo.subdomain}.${networkInfo.domain}`; + let url = `${apiUrl}/api?`; + + // eslint-disable-next-line guard-for-in + for (const paramKey in urlParams) { + const value = urlParams[paramKey]; + + if (!value) { + continue; + } + + url += `${paramKey}=${value}&`; + } + + url += 'tag=latest&page=1'; + + return url; +} diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index e8fa7dd93..8e30f4c5d 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -72,6 +72,8 @@ import TransactionStateManager from './tx-state-manager'; import TxGasUtil from './tx-gas-utils'; import PendingTransactionTracker from './pending-tx-tracker'; import * as txUtils from './lib/util'; +import { IncomingTransactionHelper } from './IncomingTransactionHelper'; +import { EtherscanRemoteTransactionSource } from './EtherscanRemoteTransactionSource'; const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonces) to keep in memory const UPDATE_POST_TX_BALANCE_TIMEOUT = 5000; @@ -127,6 +129,7 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; * @param {object} opts.initState - initial transaction list default is an empty array * @param {Function} opts.getNetworkId - Get the current network ID. * @param {Function} opts.getNetworkStatus - Get the current network status. + * @param {Function} opts.getNetworkState - Get the network state. * @param {Function} opts.onNetworkStateChange - Subscribe to network state change events. * @param {object} opts.blockTracker - An instance of eth-blocktracker * @param {object} opts.provider - A network provider. @@ -134,6 +137,7 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; * @param {object} opts.getPermittedAccounts - get accounts that an origin has permissions for * @param {Function} opts.signTransaction - ethTx signer that returns a rawTx * @param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state + * @param {Function} opts.hasCompletedOnboarding - Returns whether or not the user has completed the onboarding flow * @param {object} opts.preferencesStore */ @@ -142,6 +146,7 @@ export default class TransactionController extends EventEmitter { super(); this.getNetworkId = opts.getNetworkId; this.getNetworkStatus = opts.getNetworkStatus; + this._getNetworkState = opts.getNetworkState; this._getCurrentChainId = opts.getCurrentChainId; this.getProviderConfig = opts.getProviderConfig; this._getCurrentNetworkEIP1559Compatibility = @@ -166,6 +171,7 @@ export default class TransactionController extends EventEmitter { this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails; this.securityProviderRequest = opts.securityProviderRequest; this.messagingSystem = opts.messenger; + this._hasCompletedOnboarding = opts.hasCompletedOnboarding; this.memStore = new ObservableStore({}); @@ -216,6 +222,32 @@ export default class TransactionController extends EventEmitter { this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }); + this.incomingTransactionHelper = new IncomingTransactionHelper({ + blockTracker: this.blockTracker, + getCurrentAccount: () => this.getSelectedAddress(), + getNetworkState: () => this._getNetworkState(), + isEnabled: () => + Boolean( + this.preferencesStore.getState().featureFlags + ?.showIncomingTransactions && this._hasCompletedOnboarding(), + ), + lastFetchedBlockNumbers: opts.initState?.lastFetchedBlockNumbers || {}, + remoteTransactionSource: new EtherscanRemoteTransactionSource({ + includeTokenTransfers: false, + }), + updateTransactions: false, + }); + + this.incomingTransactionHelper.hub.on( + 'transactions', + this._onIncomingTransactions.bind(this), + ); + + this.incomingTransactionHelper.hub.on( + 'updatedLastFetchedBlockNumbers', + this._onUpdatedLastFetchedBlockNumbers.bind(this), + ); + this.txStateManager.store.subscribe(() => this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE), ); @@ -759,6 +791,18 @@ export default class TransactionController extends EventEmitter { ); } + startIncomingTransactionPolling() { + this.incomingTransactionHelper.start(); + } + + stopIncomingTransactionPolling() { + this.incomingTransactionHelper.stop(); + } + + async updateIncomingTransactions() { + await this.incomingTransactionHelper.update(); + } + // // PRIVATE METHODS // @@ -2086,11 +2130,18 @@ export default class TransactionController extends EventEmitter { * Updates the memStore in transaction controller */ _updateMemstore() { + const { transactions } = this.store.getState(); const unapprovedTxs = this.txStateManager.getUnapprovedTxList(); + const currentNetworkTxList = this.txStateManager.getTransactions({ limit: MAX_MEMSTORE_TX_LIST_SIZE, }); - this.memStore.updateState({ unapprovedTxs, currentNetworkTxList }); + + this.memStore.updateState({ + unapprovedTxs, + currentNetworkTxList, + transactions, + }); } _calculateTransactionsCost(txMeta, approvalTxMeta) { @@ -2734,6 +2785,34 @@ export default class TransactionController extends EventEmitter { ); } + _onIncomingTransactions({ added: transactions }) { + log.debug('Detected new incoming transactions', transactions); + + const currentTransactions = this.store.getState().transactions || {}; + + const incomingTransactions = transactions + .filter((tx) => !this._hasTransactionHash(tx.hash, currentTransactions)) + .reduce((result, tx) => { + result[tx.id] = tx; + return result; + }, {}); + + const updatedTransactions = { + ...currentTransactions, + ...incomingTransactions, + }; + + this.store.updateState({ transactions: updatedTransactions }); + } + + _onUpdatedLastFetchedBlockNumbers({ lastFetchedBlockNumbers }) { + this.store.updateState({ lastFetchedBlockNumbers }); + } + + _hasTransactionHash(hash, transactions) { + return Object.values(transactions).some((tx) => tx.hash === hash); + } + // Approvals async _requestTransactionApproval( diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index a94e331b1..77150f34a 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -38,6 +38,7 @@ import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import TxGasUtil from './tx-gas-utils'; +import * as IncomingTransactionHelperClass from './IncomingTransactionHelper'; import TransactionController from '.'; const noop = () => true; @@ -51,6 +52,16 @@ const actionId = 'DUMMY_ACTION_ID'; const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; +const TRANSACTION_META_MOCK = { + hash: '0x1', + id: 1, + status: TransactionStatus.confirmed, + transaction: { + from: VALID_ADDRESS, + }, + time: 123456789, +}; + async function flushPromises() { await new Promise((resolve) => setImmediate(resolve)); } @@ -65,7 +76,9 @@ describe('Transaction Controller', function () { getCurrentChainId, messengerMock, resultCallbacksMock, - updateSpy; + updateSpy, + incomingTransactionHelperClassMock, + incomingTransactionHelperEventMock; beforeEach(function () { fragmentExists = false; @@ -101,6 +114,16 @@ describe('Transaction Controller', function () { call: sinon.stub(), }; + incomingTransactionHelperEventMock = sinon.spy(); + + incomingTransactionHelperClassMock = sinon + .stub(IncomingTransactionHelperClass, 'IncomingTransactionHelper') + .returns({ + hub: { + on: incomingTransactionHelperEventMock, + }, + }); + txController = new TransactionController({ provider, getGasPrice() { @@ -148,6 +171,10 @@ describe('Transaction Controller', function () { ); }); + afterEach(function () { + incomingTransactionHelperClassMock.restore(); + }); + function getLastTxMeta() { return updateSpy.lastCall.args[0]; } @@ -3374,4 +3401,78 @@ describe('Transaction Controller', function () { assert.deepEqual(transaction1, transaction2); }); }); + + describe('on incoming transaction helper transactions event', function () { + it('adds new transactions to state', async function () { + const existingTransaction = TRANSACTION_META_MOCK; + + const incomingTransaction1 = { + ...TRANSACTION_META_MOCK, + id: 2, + hash: '0x2', + }; + + const incomingTransaction2 = { + ...TRANSACTION_META_MOCK, + id: 3, + hash: '0x3', + }; + + txController.store.getState().transactions = { + [existingTransaction.id]: existingTransaction, + }; + + await incomingTransactionHelperEventMock.firstCall.args[1]({ + added: [incomingTransaction1, incomingTransaction2], + updated: [], + }); + + assert.deepEqual(txController.store.getState().transactions, { + [existingTransaction.id]: existingTransaction, + [incomingTransaction1.id]: incomingTransaction1, + [incomingTransaction2.id]: incomingTransaction2, + }); + }); + + it('ignores new transactions if hash matches existing transaction', async function () { + const existingTransaction = TRANSACTION_META_MOCK; + const incomingTransaction1 = { ...TRANSACTION_META_MOCK, id: 2 }; + const incomingTransaction2 = { ...TRANSACTION_META_MOCK, id: 3 }; + + txController.store.getState().transactions = { + [existingTransaction.id]: existingTransaction, + }; + + await incomingTransactionHelperEventMock.firstCall.args[1]({ + added: [incomingTransaction1, incomingTransaction2], + updated: [], + }); + + assert.deepEqual(txController.store.getState().transactions, { + [existingTransaction.id]: existingTransaction, + }); + }); + }); + + describe('on incoming transaction helper updatedLastFetchedBlockNumbers event', function () { + it('updates state', async function () { + const lastFetchedBlockNumbers = { + key: 234, + }; + + assert.deepEqual( + txController.store.getState().lastFetchedBlockNumbers, + undefined, + ); + + await incomingTransactionHelperEventMock.secondCall.args[1]({ + lastFetchedBlockNumbers, + }); + + assert.deepEqual( + txController.store.getState().lastFetchedBlockNumbers, + lastFetchedBlockNumbers, + ); + }); + }); }); diff --git a/app/scripts/controllers/transactions/types.ts b/app/scripts/controllers/transactions/types.ts new file mode 100644 index 000000000..e59205e1b --- /dev/null +++ b/app/scripts/controllers/transactions/types.ts @@ -0,0 +1,49 @@ +import { Hex } from '@metamask/utils'; +import { TransactionMeta } from '../../../../shared/constants/transaction'; + +/** + * The configuration required to fetch transaction data from a RemoteTransactionSource. + */ +export interface RemoteTransactionSourceRequest { + /** + * The address of the account to fetch transactions for. + */ + address: string; + + /** + * API key if required by the remote source. + */ + apiKey?: string; + + /** + * The chainId of the current network. + */ + currentChainId: Hex; + + /** + * The networkId of the current network. + */ + currentNetworkId: string; + + /** + * Block number to start fetching transactions from. + */ + fromBlock?: number; + + /** + * Maximum number of transactions to retrieve. + */ + limit?: number; +} + +/** + * An object capable of fetching transaction data from a remote source. + * Used by the IncomingTransactionHelper to retrieve remote transaction data. + */ +export interface RemoteTransactionSource { + isSupportedNetwork: (chainId: Hex, networkId: string) => boolean; + + fetchTransactions: ( + request: RemoteTransactionSourceRequest, + ) => Promise; +} diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index e17481f80..5a0e95a80 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -58,9 +58,6 @@ export const SENTRY_BACKGROUND_STATE = { EncryptionPublicKeyController: { unapprovedEncryptionPublicKeyMsgCount: true, }, - IncomingTransactionsController: { - incomingTxLastFetchedBlockByChainId: true, - }, KeyringController: { isUnlocked: true, }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index daf2a74e1..183de1fb0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -190,7 +190,6 @@ import CachedBalancesController from './controllers/cached-balances'; import AlertController from './controllers/alert'; import OnboardingController from './controllers/onboarding'; import Backup from './lib/backup'; -import IncomingTransactionsController from './controllers/incoming-transactions'; import DecryptMessageController from './controllers/decrypt-message'; import TransactionController from './controllers/transactions'; import DetectTokensController from './controllers/detect-tokens'; @@ -418,10 +417,6 @@ export default class MetamaskController extends EventEmitter { provider: this.provider, }); - this.preferencesController.store.subscribe(async ({ currentLocale }) => { - await updateCurrentLocale(currentLocale); - }); - const tokensControllerMessenger = this.controllerMessenger.getRestricted({ name: 'TokensController', allowedActions: ['ApprovalController:addRequest'], @@ -744,19 +739,6 @@ export default class MetamaskController extends EventEmitter { initState: initState.OnboardingController, }); - this.incomingTransactionsController = new IncomingTransactionsController({ - blockTracker: this.blockTracker, - onNetworkDidChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:networkDidChange', - ), - getCurrentChainId: () => - this.networkController.state.providerConfig.chainId, - preferencesController: this.preferencesController, - onboardingController: this.onboardingController, - initState: initState.IncomingTransactionsController, - }); - // account tracker watches balances, nonces, and any code at their address this.accountTracker = new AccountTracker({ provider: this.provider, @@ -1192,6 +1174,9 @@ export default class MetamaskController extends EventEmitter { this.networkController.state.networksMetadata?.[ this.networkController.state.selectedNetworkClientId ]?.status, + getNetworkState: () => this.networkController.state, + hasCompletedOnboarding: () => + this.onboardingController.store.getState().completedOnboarding, onNetworkStateChange: (listener) => { networkControllerMessenger.subscribe( 'NetworkController:stateChange', @@ -1660,7 +1645,6 @@ export default class MetamaskController extends EventEmitter { CachedBalancesController: this.cachedBalancesController.store, AlertController: this.alertController.store, OnboardingController: this.onboardingController.store, - IncomingTransactionsController: this.incomingTransactionsController.store, PermissionController: this.permissionController, PermissionLogController: this.permissionLogController.store, SubjectMetadataController: this.subjectMetadataController, @@ -1706,8 +1690,6 @@ export default class MetamaskController extends EventEmitter { CurrencyController: this.currencyRateController, AlertController: this.alertController.store, OnboardingController: this.onboardingController.store, - IncomingTransactionsController: - this.incomingTransactionsController.store, PermissionController: this.permissionController, PermissionLogController: this.permissionLogController.store, SubjectMetadataController: this.subjectMetadataController, @@ -1803,7 +1785,7 @@ export default class MetamaskController extends EventEmitter { triggerNetworkrequests() { this.accountTracker.start(); - this.incomingTransactionsController.start(); + this.txController.startIncomingTransactionPolling(); if (this.preferencesController.store.getState().useCurrencyRateCheck) { this.currencyRateController.start(); } @@ -1814,7 +1796,7 @@ export default class MetamaskController extends EventEmitter { stopNetworkRequests() { this.accountTracker.stop(); - this.incomingTransactionsController.stop(); + this.txController.stopIncomingTransactionPolling(); if (this.preferencesController.store.getState().useCurrencyRateCheck) { this.currencyRateController.stop(); } @@ -1991,40 +1973,22 @@ export default class MetamaskController extends EventEmitter { * becomes unlocked are handled in MetaMaskController._onUnlock. */ setupControllerEventSubscriptions() { - const handleAccountsChange = async (origin, newAccounts) => { - if (this.isUnlocked()) { - this.notifyConnections(origin, { - method: NOTIFICATION_NAMES.accountsChanged, - // This should be the same as the return value of `eth_accounts`, - // namely an array of the current / most recently selected Ethereum - // account. - params: - newAccounts.length < 2 - ? // If the length is 1 or 0, the accounts are sorted by definition. - newAccounts - : // If the length is 2 or greater, we have to execute - // `eth_accounts` vi this method. - await this.getPermittedAccounts(origin), - }); + let lastSelectedAddress; + + this.preferencesController.store.subscribe(async (state) => { + const { selectedAddress, currentLocale } = state; + + await updateCurrentLocale(currentLocale); + + if (state?.featureFlags?.showIncomingTransactions) { + this.txController.startIncomingTransactionPolling(); + } else { + this.txController.stopIncomingTransactionPolling(); } - this.permissionLogController.updateAccountsHistory(origin, newAccounts); - }; - - // This handles account changes whenever the selected address changes. - let lastSelectedAddress; - this.preferencesController.store.subscribe(async ({ selectedAddress }) => { if (selectedAddress && selectedAddress !== lastSelectedAddress) { lastSelectedAddress = selectedAddress; - const permittedAccountsMap = getPermittedAccountsByOrigin( - this.permissionController.state, - ); - - for (const [origin, accounts] of permittedAccountsMap.entries()) { - if (accounts.includes(selectedAddress)) { - handleAccountsChange(origin, accounts); - } - } + await this._onAccountChange(selectedAddress); } }); @@ -2036,12 +2000,19 @@ export default class MetamaskController extends EventEmitter { const changedAccounts = getChangedAccounts(currentValue, previousValue); for (const [origin, accounts] of changedAccounts.entries()) { - handleAccountsChange(origin, accounts); + this._notifyAccountsChange(origin, accounts); } }, getPermittedAccountsByOrigin, ); + this.controllerMessenger.subscribe( + 'NetworkController:networkDidChange', + async () => { + await this.txController.updateIncomingTransactions(); + }, + ); + ///: BEGIN:ONLY_INCLUDE_IN(snaps) // Record Snap metadata whenever a Snap is added to state. this.controllerMessenger.subscribe( @@ -4819,4 +4790,38 @@ export default class MetamaskController extends EventEmitter { return null; } + + async _onAccountChange(newAddress) { + const permittedAccountsMap = getPermittedAccountsByOrigin( + this.permissionController.state, + ); + + for (const [origin, accounts] of permittedAccountsMap.entries()) { + if (accounts.includes(newAddress)) { + this._notifyAccountsChange(origin, accounts); + } + } + + await this.txController.updateIncomingTransactions(); + } + + async _notifyAccountsChange(origin, newAccounts) { + if (this.isUnlocked()) { + this.notifyConnections(origin, { + method: NOTIFICATION_NAMES.accountsChanged, + // This should be the same as the return value of `eth_accounts`, + // namely an array of the current / most recently selected Ethereum + // account. + params: + newAccounts.length < 2 + ? // If the length is 1 or 0, the accounts are sorted by definition. + newAccounts + : // If the length is 2 or greater, we have to execute + // `eth_accounts` vi this method. + await this.getPermittedAccounts(origin), + }); + } + + this.permissionLogController.updateAccountsHistory(origin, newAccounts); + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index ec7f40643..c9d0b66c6 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -16,6 +16,7 @@ import { METAMASK_HOTLIST_DIFF_FILE, } from '@metamask/phishing-controller'; import { NetworkType } from '@metamask/controller-utils'; +import { ControllerMessenger } from '@metamask/base-controller'; import { TransactionStatus } from '../../shared/constants/transaction'; import createTxMeta from '../../test/lib/createTxMeta'; import { NETWORK_TYPES } from '../../shared/constants/network'; @@ -23,6 +24,8 @@ import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; import { deferredPromise } from './lib/util'; +import TransactionController from './controllers/transactions'; +import PreferencesController from './controllers/preferences'; const Ganache = require('../../test/e2e/ganache'); @@ -83,6 +86,14 @@ function MockEthContract() { }; } +function MockPreferencesController(...args) { + const controller = new PreferencesController(...args); + + sinon.stub(controller.store, 'subscribe'); + + return controller; +} + // TODO, Feb 24, 2023: // ethjs-contract is being added to proxyquire, but we might want to discontinue proxyquire // this is for expediency as we resolve a bug for v10.26.0. The proper solution here would have @@ -91,6 +102,7 @@ function MockEthContract() { const MetaMaskController = proxyquire('./metamask-controller', { './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, 'ethjs-contract': MockEthContract, + './controllers/preferences': { default: MockPreferencesController }, }).default; const MetaMaskControllerMV3 = proxyquire('./metamask-controller', { @@ -279,6 +291,23 @@ describe('MetaMaskController', function () { beforeEach(function () { sandbox.spy(MetaMaskController.prototype, 'resetStates'); + sandbox.stub( + TransactionController.prototype, + 'updateIncomingTransactions', + ); + + sandbox.stub( + TransactionController.prototype, + 'startIncomingTransactionPolling', + ); + + sandbox.stub( + TransactionController.prototype, + 'stopIncomingTransactionPolling', + ); + + sandbox.spy(ControllerMessenger.prototype, 'subscribe'); + metamaskController = new MetaMaskController({ showUserConfirmation: noop, encryptor: { @@ -1647,6 +1676,60 @@ describe('MetaMaskController', function () { }); }); }); + + describe('incoming transactions', function () { + let txControllerStub, preferencesControllerSpy, controllerMessengerSpy; + + beforeEach(function () { + txControllerStub = TransactionController.prototype; + preferencesControllerSpy = metamaskController.preferencesController; + controllerMessengerSpy = ControllerMessenger.prototype; + }); + + it('starts incoming transaction polling if show incoming transactions enabled', async function () { + assert(txControllerStub.startIncomingTransactionPolling.notCalled); + + await preferencesControllerSpy.store.subscribe.lastCall.args[0]({ + featureFlags: { + showIncomingTransactions: true, + }, + }); + + assert(txControllerStub.startIncomingTransactionPolling.calledOnce); + }); + + it('stops incoming transaction polling if show incoming transactions disabled', async function () { + assert(txControllerStub.stopIncomingTransactionPolling.notCalled); + + await preferencesControllerSpy.store.subscribe.lastCall.args[0]({ + featureFlags: { + showIncomingTransactions: false, + }, + }); + + assert(txControllerStub.stopIncomingTransactionPolling.calledOnce); + }); + + it('updates incoming transactions when changing account', async function () { + assert(txControllerStub.updateIncomingTransactions.notCalled); + + await preferencesControllerSpy.store.subscribe.lastCall.args[0]({ + selectedAddress: 'foo', + }); + + assert(txControllerStub.updateIncomingTransactions.calledOnce); + }); + + it('updates incoming transactions when changing network', async function () { + assert(txControllerStub.updateIncomingTransactions.notCalled); + + await controllerMessengerSpy.subscribe.args + .filter((args) => args[0] === 'NetworkController:networkDidChange') + .slice(-1)[0][1](); + + assert(txControllerStub.updateIncomingTransactions.calledOnce); + }); + }); }); describe('MV3 Specific behaviour', function () { diff --git a/app/scripts/migrations/095.test.ts b/app/scripts/migrations/095.test.ts new file mode 100644 index 000000000..d4858de90 --- /dev/null +++ b/app/scripts/migrations/095.test.ts @@ -0,0 +1,364 @@ +import { migrate } from './095'; + +const INCOMING_TRANSACTION_MOCK = { + blockNumber: '1', + chainId: '0x539', + hash: '0xf1af8286e4fa47578c2aec5f08c108290643df978ebc766d72d88476eee90bab', + id: 1, + metamaskNetworkId: '1337', + status: 'confirmed', + time: 1671635520000, + txParams: { + from: '0xc87261ba337be737fa744f50e7aaf4a920bdfcd6', + gas: '0x5208', + gasPrice: '0x329af9707', + to: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + value: '0xDE0B6B3A7640000', + }, + type: 'incoming', +}; + +const INCOMING_TRANSACTION_2_MOCK = { + ...INCOMING_TRANSACTION_MOCK, + blockNumber: '2', + id: 2, + chainId: '0x540', + txParams: { + ...INCOMING_TRANSACTION_MOCK.txParams, + to: '0x2', + }, +}; + +const TRANSACTION_MOCK = { + ...INCOMING_TRANSACTION_MOCK, + blockNumber: '3', + id: 3, + type: 'contractInteraction', +}; + +describe('migration #95', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: 94 }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version: 95 }); + }); + + it('does nothing if no IncomingTransactionsController state', async () => { + const oldData = { + some: 'data', + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('removes IncomingTransactionsController state', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + }, + incomingTxLastFetchedBlockByChainId: { + '0x5': 1234, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: expect.any(Object), + }); + }); + + describe('moves incoming transactions', () => { + it('if no TransactionController state', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: INCOMING_TRANSACTION_2_MOCK, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: INCOMING_TRANSACTION_2_MOCK, + }, + lastFetchedBlockNumbers: expect.any(Object), + }, + }); + }); + + it('if existing TransactionController state', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: INCOMING_TRANSACTION_2_MOCK, + }, + }, + TransactionController: { + transactions: { + [TRANSACTION_MOCK.id]: TRANSACTION_MOCK, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: { + ...oldData.TransactionController.transactions, + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: INCOMING_TRANSACTION_2_MOCK, + }, + lastFetchedBlockNumbers: expect.any(Object), + }, + }); + }); + + it.each([ + ['undefined', undefined], + ['empty', {}], + ])( + 'does nothing if incoming transactions %s', + async (_title, incomingTransactions) => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions, + }, + TransactionController: { + transactions: { + [TRANSACTION_MOCK.id]: TRANSACTION_MOCK, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: oldData.TransactionController, + }); + }, + ); + }); + + describe('generates last fetched block numbers', () => { + it('if incoming transactions have chain ID, block number, and to address', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: INCOMING_TRANSACTION_2_MOCK, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: expect.any(Object), + lastFetchedBlockNumbers: { + [`${INCOMING_TRANSACTION_MOCK.chainId}#${INCOMING_TRANSACTION_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_MOCK.blockNumber, 10), + [`${INCOMING_TRANSACTION_2_MOCK.chainId}#${INCOMING_TRANSACTION_2_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_2_MOCK.blockNumber, 10), + }, + }, + }); + }); + + it('using highest block number for each chain ID and to address', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: { + ...INCOMING_TRANSACTION_2_MOCK, + chainId: INCOMING_TRANSACTION_MOCK.chainId, + txParams: { + ...INCOMING_TRANSACTION_2_MOCK.txParams, + to: INCOMING_TRANSACTION_MOCK.txParams.to, + }, + }, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: expect.any(Object), + lastFetchedBlockNumbers: { + [`${INCOMING_TRANSACTION_MOCK.chainId}#${INCOMING_TRANSACTION_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_2_MOCK.blockNumber, 10), + }, + }, + }); + }); + + it('ignoring incoming transactions with no chain ID', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: { + ...INCOMING_TRANSACTION_2_MOCK, + chainId: undefined, + }, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: expect.any(Object), + lastFetchedBlockNumbers: { + [`${INCOMING_TRANSACTION_MOCK.chainId}#${INCOMING_TRANSACTION_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_MOCK.blockNumber, 10), + }, + }, + }); + }); + + it('ignoring incoming transactions with no block number', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: { + ...INCOMING_TRANSACTION_2_MOCK, + blockNumber: undefined, + }, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: expect.any(Object), + lastFetchedBlockNumbers: { + [`${INCOMING_TRANSACTION_MOCK.chainId}#${INCOMING_TRANSACTION_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_MOCK.blockNumber, 10), + }, + }, + }); + }); + + it('ignoring incoming transactions with no to address', async () => { + const oldData = { + some: 'data', + IncomingTransactionsController: { + incomingTransactions: { + [INCOMING_TRANSACTION_MOCK.id]: INCOMING_TRANSACTION_MOCK, + [INCOMING_TRANSACTION_2_MOCK.id]: { + ...INCOMING_TRANSACTION_2_MOCK, + txParams: { + ...INCOMING_TRANSACTION_2_MOCK.txParams, + to: undefined, + }, + }, + }, + }, + }; + + const oldStorage = { + meta: { version: 94 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + TransactionController: { + transactions: expect.any(Object), + lastFetchedBlockNumbers: { + [`${INCOMING_TRANSACTION_MOCK.chainId}#${INCOMING_TRANSACTION_MOCK.txParams.to}`]: + parseInt(INCOMING_TRANSACTION_MOCK.blockNumber, 10), + }, + }, + }); + }); + }); +}); diff --git a/app/scripts/migrations/095.ts b/app/scripts/migrations/095.ts new file mode 100644 index 000000000..39128284e --- /dev/null +++ b/app/scripts/migrations/095.ts @@ -0,0 +1,94 @@ +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 95; + +/** + * This migration does the following: + * + * - Moves any incoming transactions from the IncomingTransactionsController to the TransactionController state. + * - Generates the new lastFetchedBlockNumbers object in the TransactionController using any existing incoming transactions. + * - Removes the IncomingTransactionsController state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + migrateData(versionedData.data); + return versionedData; +} + +function migrateData(state: Record): void { + moveIncomingTransactions(state); + generateLastFetchedBlockNumbers(state); + removeIncomingTransactionsControllerState(state); +} + +function moveIncomingTransactions(state: Record) { + const incomingTransactions: Record = + state.IncomingTransactionsController?.incomingTransactions || {}; + + if (Object.keys(incomingTransactions).length === 0) { + return; + } + + const transactions = state.TransactionController?.transactions || {}; + + const updatedTransactions = Object.values(incomingTransactions).reduce( + (result: Record, tx: any) => { + result[tx.id] = tx; + return result; + }, + transactions, + ); + + state.TransactionController = { + ...(state.TransactionController || {}), + transactions: updatedTransactions, + }; +} + +function generateLastFetchedBlockNumbers(state: Record) { + const incomingTransactions: Record = + state.IncomingTransactionsController?.incomingTransactions || {}; + + if (Object.keys(incomingTransactions).length === 0) { + return; + } + + const lastFetchedBlockNumbers: Record = {}; + + for (const tx of Object.values(incomingTransactions)) { + if (!tx.blockNumber || !tx.chainId || !tx.txParams.to) { + continue; + } + + const txBlockNumber = parseInt(tx.blockNumber, 10); + const key = `${tx.chainId}#${tx.txParams.to.toLowerCase()}`; + const highestBlockNumber = lastFetchedBlockNumbers[key] || -1; + + lastFetchedBlockNumbers[key] = Math.max(highestBlockNumber, txBlockNumber); + } + + state.TransactionController = { + ...state.TransactionController, + lastFetchedBlockNumbers, + }; +} + +function removeIncomingTransactionsControllerState( + state: Record, +) { + delete state.IncomingTransactionsController; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index aa7e7ddbc..c23ec9608 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -99,6 +99,7 @@ import * as m092 from './092'; import * as m092point1 from './092.1'; import * as m093 from './093'; import * as m094 from './094'; +import * as m095 from './095'; const migrations = [ m002, @@ -195,5 +196,6 @@ const migrations = [ m092point1, m093, m094, + m095, ]; export default migrations; diff --git a/jest.config.js b/jest.config.js index 9f4a2a858..c95ec4ce8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,9 @@ module.exports = { '/app/scripts/controllers/permissions/**/*.js', '/app/scripts/controllers/sign.ts', '/app/scripts/controllers/decrypt-message.ts', + '/app/scripts/controllers/transactions/etherscan.ts', + '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts', + '/app/scripts/controllers/transactions/IncomingTransactionHelper.ts', '/app/scripts/flask/**/*.js', '/app/scripts/lib/**/*.js', '/app/scripts/lib/createRPCMethodTrackingMiddleware.js', @@ -37,6 +40,9 @@ module.exports = { testMatch: [ '/app/scripts/constants/error-utils.test.js', '/app/scripts/controllers/app-state.test.js', + '/app/scripts/controllers/transactions/etherscan.test.ts', + '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts', + '/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts', '/app/scripts/controllers/mmi-controller.test.js', '/app/scripts/controllers/permissions/**/*.test.js', '/app/scripts/controllers/sign.test.ts', diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts index 1d7021402..d14e9400e 100644 --- a/shared/constants/transaction.ts +++ b/shared/constants/transaction.ts @@ -269,7 +269,7 @@ export interface TxParams { /** The amount of wei, in hexadecimal, to send */ value: string; /** The transaction count for the current account/network */ - nonce: number; + nonce: string; /** The amount of gwei, in hexadecimal, per unit of gas */ gasPrice?: string; /** The max amount of gwei, in hexadecimal, the user is willing to pay */ @@ -329,6 +329,7 @@ export interface TransactionMeta { * on incoming transactions! */ blockNumber?: string; + chainId: string; /** An internally unique tx identifier. */ id: number; /** Time the transaction was first suggested, in unix epoch time (ms). */ diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 12de298bd..79b943411 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -197,16 +197,6 @@ function defaultFixture() { gasEstimateType: 'none', gasFeeEstimates: {}, }, - IncomingTransactionsController: { - incomingTransactions: {}, - incomingTxLastFetchedBlockByChainId: { - [CHAIN_IDS.MAINNET]: null, - [CHAIN_IDS.LINEA_MAINNET]: null, - [CHAIN_IDS.GOERLI]: null, - [CHAIN_IDS.SEPOLIA]: null, - [CHAIN_IDS.LINEA_GOERLI]: null, - }, - }, KeyringController: { vault: '{"data":"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT","iv":"FbeHDAW5afeWNORfNJBR0Q==","salt":"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8="}', @@ -487,40 +477,6 @@ class FixtureBuilder { return this; } - withIncomingTransactionsController(data) { - merge( - this.fixture.data.IncomingTransactionsController - ? this.fixture.data.IncomingTransactionsController - : (this.fixture.data.IncomingTransactionsController = {}), - data, - ); - return this; - } - - withIncomingTransactionsControllerOneTransaction() { - return this.withIncomingTransactionsController({ - incomingTransactions: { - '0xf1af8286e4fa47578c2aec5f08c108290643df978ebc766d72d88476eee90bab': { - blockNumber: '1', - chainId: CHAIN_IDS.LOCALHOST, - hash: '0xf1af8286e4fa47578c2aec5f08c108290643df978ebc766d72d88476eee90bab', - id: 5748272735958807, - metamaskNetworkId: '1337', - status: 'confirmed', - time: 1671635520000, - txParams: { - from: '0xc87261ba337be737fa744f50e7aaf4a920bdfcd6', - gas: '0x5208', - gasPrice: '0x329af9707', - to: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - value: '0xDE0B6B3A7640000', - }, - type: 'incoming', - }, - }, - }); - } - withKeyringController(data) { merge(this.fixture.data.KeyringController, data); return this; @@ -1488,6 +1444,47 @@ class FixtureBuilder { }); } + withTransactionControllerIncomingTransaction() { + return this.withTransactionController({ + transactions: { + 5748272735958807: { + blockNumber: '1', + chainId: CHAIN_IDS.LOCALHOST, + hash: '0xf1af8286e4fa47578c2aec5f08c108290643df978ebc766d72d88476eee90bab', + id: 5748272735958807, + metamaskNetworkId: '1337', + status: 'confirmed', + time: 1671635520000, + txParams: { + from: '0xc87261ba337be737fa744f50e7aaf4a920bdfcd6', + gas: '0x5208', + gasPrice: '0x329af9707', + to: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + value: '0xDE0B6B3A7640000', + }, + type: 'incoming', + }, + }, + }); + } + + withTransactionControllerCompletedAndIncomingTransaction() { + const completedTransaction = + this.withTransactionControllerCompletedTransaction().fixture.data + .TransactionController.transactions; + + const incomingTransaction = + this.withTransactionControllerIncomingTransaction().fixture.data + .TransactionController.transactions; + + return this.withTransactionController({ + transactions: { + ...completedTransaction, + ...incomingTransaction, + }, + }); + } + build() { this.fixture.meta = { version: 74, diff --git a/test/e2e/tests/clear-activity.spec.js b/test/e2e/tests/clear-activity.spec.js index 96f6bc358..77d9fdb8b 100644 --- a/test/e2e/tests/clear-activity.spec.js +++ b/test/e2e/tests/clear-activity.spec.js @@ -21,8 +21,7 @@ describe('Clear account activity', function () { await withFixtures( { fixtures: new FixtureBuilder() - .withTransactionControllerCompletedTransaction() - .withIncomingTransactionsControllerOneTransaction() + .withTransactionControllerCompletedAndIncomingTransaction() .build(), ganacheOptions, title: this.test.title, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 5a4012071..839e0d76b 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -57,16 +57,6 @@ }, "EnsController": "object", "GasFeeController": "object", - "IncomingTransactionsController": { - "incomingTransactions": "object", - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - } - }, "KeyringController": { "isUnlocked": false, "keyringTypes": "object", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 7843dcd7f..98eaf99d3 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -100,6 +100,7 @@ "metaMetricsId": "fake-metrics-id", "eventsBeforeMetricsOptIn": "object", "traits": "object", + "transactions": "object", "fragments": "object", "segmentApiCalls": "object", "previousUserTraits": "object", @@ -113,14 +114,6 @@ "web3ShimUsageOrigins": "object", "seedPhraseBackedUp": true, "onboardingTabs": "object", - "incomingTransactions": "object", - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - }, "subjects": "object", "permissionHistory": "object", "permissionActivityLog": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 422bcb0e2..3dbc33e79 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -32,16 +32,6 @@ "usdConversionRate": "number" }, "GasFeeController": "object", - "IncomingTransactionsController": { - "incomingTransactions": "object", - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - } - }, "KeyringController": { "vault": "string" }, "MetaMetricsController": { "eventsBeforeMetricsOptIn": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 0a9fac1d0..ca6006c7a 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -32,16 +32,6 @@ "usdConversionRate": "number" }, "GasFeeController": "object", - "IncomingTransactionsController": { - "incomingTransactions": "object", - "incomingTxLastFetchedBlockByChainId": { - "0x1": null, - "0xe708": null, - "0x5": null, - "0xaa36a7": null, - "0xe704": null - } - }, "KeyringController": { "vault": "string" }, "MetaMetricsController": { "eventsBeforeMetricsOptIn": "object", diff --git a/ui/components/app/modals/account-details-modal/account-details-modal.test.js b/ui/components/app/modals/account-details-modal/account-details-modal.test.js index 6b365c5de..e238d8637 100644 --- a/ui/components/app/modals/account-details-modal/account-details-modal.test.js +++ b/ui/components/app/modals/account-details-modal/account-details-modal.test.js @@ -205,7 +205,6 @@ describe('Account Details Modal', () => { }, }, cachedBalances: {}, - incomingTransactions: {}, selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', accounts: { '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { diff --git a/ui/selectors/nonce-sorted-transactions-selector.test.js b/ui/selectors/nonce-sorted-transactions-selector.test.js index 89a2599af..6471cca92 100644 --- a/ui/selectors/nonce-sorted-transactions-selector.test.js +++ b/ui/selectors/nonce-sorted-transactions-selector.test.js @@ -83,7 +83,7 @@ const getStateTree = ({ featureFlags: { showIncomingTransactions: true, }, - incomingTransactions: [...incomingTxList], + transactions: [...incomingTxList], currentNetworkTxList: [...txList], }, }); diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index 61dc52883..9dbe31e84 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -27,6 +27,7 @@ const INVALID_INITIAL_TRANSACTION_TYPES = [ export const incomingTxListSelector = (state) => { const { showIncomingTransactions } = state.metamask.featureFlags; + if (!showIncomingTransactions) { return []; } @@ -34,8 +35,10 @@ export const incomingTxListSelector = (state) => { const { networkId } = state.metamask; const { chainId } = getProviderConfig(state); const selectedAddress = getSelectedAddress(state); - return Object.values(state.metamask.incomingTransactions).filter( + + return Object.values(state.metamask.transactions || {}).filter( (tx) => + tx.type === TransactionType.incoming && tx.txParams.to === selectedAddress && transactionMatchesNetwork(tx, chainId, networkId), ); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index fe2dc5b44..2040eae8e 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -85,6 +85,7 @@ import { import { decimalToHex } from '../../shared/modules/conversion.utils'; import { TxGasFees, PriorityLevels } from '../../shared/constants/gas'; import { + TransactionMeta, TransactionMetaMetricsEvent, TransactionType, } from '../../shared/constants/transaction'; @@ -94,10 +95,9 @@ import { isErrorWithMessage, logErrorWithMessage, } from '../../shared/modules/error'; -import { TransactionMeta } from '../../app/scripts/controllers/incoming-transactions'; import { TxParams } from '../../app/scripts/controllers/transactions/tx-state-manager'; -import { CustomGasSettings } from '../../app/scripts/controllers/transactions'; import { ThemeType } from '../../shared/constants/preferences'; +import { CustomGasSettings } from '../../app/scripts/controllers/transactions'; import * as actionConstants from './actionConstants'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) import { updateCustodyState } from './institutional/institution-actions'; From f1b00de693414891d96b4b1ba57e2aeb90a75529 Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Tue, 22 Aug 2023 07:49:36 -0300 Subject: [PATCH 078/102] fix(action): octokit not supported on MetaMask repos (#20544) * fix(action): octokit not supported on MetaMask repos The github action was constantly failing with the following error: octokit/request-action@v2.x is not allowed to be used in MetaMask/metamask-extension * fix(action): shellcheck was failing Shellcheck was failing in the CI. Had to craft the payload of the curl before doing the curl to fix this. --- .github/workflows/create-bug-report.yml | 40 +++++++++---------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/.github/workflows/create-bug-report.yml b/.github/workflows/create-bug-report.yml index f46296bea..33535e491 100644 --- a/.github/workflows/create-bug-report.yml +++ b/.github/workflows/create-bug-report.yml @@ -19,29 +19,17 @@ jobs: - name: Create bug report issue on planning repo if: env.version - uses: octokit/request-action@v2.x - with: - route: POST /repos/MetaMask/MetaMask-planning/issues - owner: MetaMask - title: v${{ env.version }} Bug Report - body: | - This bug report was automatically created by a GitHub action upon the creation of release branch `Version-v${{ env.version }}` (release cut). - - - **Expected actions for release engineers:** - - 1. Convert this issue into a Zenhub epic and link all bugs identified during the release regression testing phase to this epic. - - 2. After completing the first regression run, move this epic to "Regression Completed" on the [Extension Release Regression board](https://app.zenhub.com/workspaces/extension-release-regression-6478c62d937eaa15e95c33c5/board?filterLogic=any&labels=release-${{ env.version }},release-task). - - - Note that once the release is prepared for store submission, meaning the `Version-v${{ env.version }}` branch merges into `master`, another GitHub action will automatically close this epic. - - labels: | - [ - "type-bug", - "regression-RC", - "release-${{ env.version }}" - ] - env: - GITHUB_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} + run: | + payload=$(cat < Date: Tue, 22 Aug 2023 11:04:31 -0230 Subject: [PATCH 079/102] New Crowdin translations by Github Action (#18017) Co-authored-by: metamaskbot --- app/_locales/de/messages.json | 1138 +++++++++++++++++++++++++++- app/_locales/el/messages.json | 1171 ++++++++++++++++++++++++++++- app/_locales/es/messages.json | 1178 ++++++++++++++++++++++++++++- app/_locales/fr/messages.json | 1164 ++++++++++++++++++++++++++++- app/_locales/hi/messages.json | 1162 ++++++++++++++++++++++++++++- app/_locales/id/messages.json | 1164 ++++++++++++++++++++++++++++- app/_locales/ja/messages.json | 1162 ++++++++++++++++++++++++++++- app/_locales/ko/messages.json | 1202 ++++++++++++++++++++++++++++-- app/_locales/pt/messages.json | 1190 ++++++++++++++++++++++++++++- app/_locales/ru/messages.json | 1166 ++++++++++++++++++++++++++++- app/_locales/tl/messages.json | 1158 +++++++++++++++++++++++++++- app/_locales/tr/messages.json | 1164 ++++++++++++++++++++++++++++- app/_locales/vi/messages.json | 1166 ++++++++++++++++++++++++++++- app/_locales/zh_CN/messages.json | 1198 +++++++++++++++++++++++++++-- 14 files changed, 15949 insertions(+), 434 deletions(-) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 01948c981..5832c1c4c 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Über" }, + "accept": { + "message": "Akzeptieren" + }, "acceptTermsOfUse": { "message": "Ich habe die $1 gelesen und stimme ihnen zu", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Spitznamen hinzufügen" }, + "addAccount": { + "message": "Konto hinzufügen" + }, "addAcquiredTokens": { "message": "Fügen Sie die Token hinzu, die Sie mittels MetaMask erlangt haben" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Fügen Sie ein Netzwerk aus einer Liste beliebter Netzwerke hinzu oder fügen Sie es manuell hinzu. Interagieren Sie nur mit Organisationen, denen Sie vertrauen." }, + "addHardwareWallet": { + "message": "Hardware-Wallet hinzufügen" + }, + "addIPFSGateway": { + "message": "Fügen Sie Ihr bevorzugtes IPFS-Gateway hinzu" + }, "addMemo": { "message": "Notiz hinzufügen" }, @@ -244,6 +256,21 @@ "message": "Diese Netzwerkverbindung ist von Dritten abhängig. Diese Verbindung könnte unzuverlässiger sein oder Dritten erlauben, Ihre Aktivitäten zu verfolgen. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Neues Token hinzufügen" + }, + "addNft": { + "message": "NFT hinzufügen" + }, + "addNfts": { + "message": "NFTs hinzufügen" + }, + "addSnapAccountModalDescription": { + "message": "Entdecken Sie Möglichkeiten, um mit MetaMask Snaps die Sicherheit Ihres Kontos zu gewährleisten" + }, + "addSuggestedNFTs": { + "message": "Vorgeschlagene NFTs hinzufügen" + }, "addSuggestedTokens": { "message": "Vorgeschlagene Token hinzufügen" }, @@ -254,6 +281,9 @@ "message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Vertragsadressen finden Sie auf $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Netzwerk wird hinzugefügt" + }, "address": { "message": "Adresse" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Prioritätsgebühr (alias „Miner Tip“) geht direkt an Miner und veranlasst sie, Ihre Transaktion zu priorisieren." }, + "agreeTermsOfUse": { + "message": "Ich stimme MetaMasks $1 zu", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap-Tresor" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Warnungen" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Sie haben entweder bereits alle Ihre Verwaltungskonten verbunden oder haben kein Konto, dass Sie mit MetaMask Institutional verbinden könnten." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Keine verbindbaren Konten verfügbar" + }, "allOfYour": { "message": "Alle Ihre $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Alle Ihre NFTs von $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Erlauben dieser externen Erweiterung auf:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Erlauben Sie dieser Site zu:" }, + "allowThisSnapTo": { + "message": "Erlauben Sie diesem Snap Folgendes:" + }, "allowWithdrawAndSpend": { "message": "$1 erlauben, bis zu dem folgenden Betrag abzuheben und auszugeben:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Betrag" }, + "apiUrl": { + "message": "API-URL" + }, "appDescription": { "message": "Ethereum Browsererweiterung", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Erlauben Sie den Zugriff auf und die Übertragung von all Ihren $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Zugriff und Transfer aller Ihrer NFTs aus $1 erlauben?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Genehmigen" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Dies gestattet es Dritten, ohne weitere Benachrichtigung auf die folgenden NFTs zuzugreifen und diese zu übertragen, bis Sie dieses Zugriffsrecht widerrufen." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Dies gestattet es Dritten, ohne weitere Benachrichtigung auf alle Ihre NFTs von $1 zuzugreifen und diese zu übertragen, bis Sie dieses Zugriffsrecht widerrufen.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Zugriff auf und Transfer Ihrer $1 erlauben?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Wenn Sie versuchen, Assets direkt von einem Netzwerk in ein anderes zu senden, kann dies zu einem dauerhaften Asset-Verlust führen. Verwenden Sie unbedingt eine Bridge." }, + "attemptToCancelSwap": { + "message": "Versuchen, Swap für ~$1 abzubrechen", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Versuch mit der Blockchain zu verbinden." }, @@ -411,6 +473,9 @@ "average": { "message": "Durchschnitt" }, + "awaitingApproval": { + "message": "Warten auf Genehmigung ..." + }, "back": { "message": "Zurück" }, @@ -454,6 +519,9 @@ "message": "Dies ist eine BETA-Version. Bitte melden Sie Fehler $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Beta-Version von MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "MetaMask-Version" }, @@ -488,9 +556,45 @@ "message": "Konto bei $1 anzeigen", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Wenn Sie diese Anfrage bestätigen, nimmt Ihnen ein für Scams bekannter Dritter eventuell alle Ihre Vermögenswerte." + }, + "blockaidDescriptionBlurFarming": { + "message": "Wenn Sie diese Anfrage bestätigen, kann jemand Ihre bei Blur aufgelisteten Vermögenswerte stehlen." + }, + "blockaidDescriptionFailed": { + "message": "Aufgrund eines Fehlers wurde diese Anfrage vom Sicherheitsanbieter nicht überprüft. Gehen Sie mit Bedacht vor." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Sie interagieren mit einer schädlichen Domain. Wenn Sie diese Anfrage bestätigen, verlieren Sie eventuell Ihre Vermögenswerte." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Wenn Sie diese Anfrage bestätigen, verlieren Sie eventuell Ihre Vermögenswerte." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Wenn Sie diese Anfrage bestätigen, kann jemand Ihre bei Blur aufgelisteten Vermögenswerte stehlen." + }, + "blockaidDescriptionTransferFarming": { + "message": "Wenn Sie diese Anfrage bestätigen, nimmt Ihnen ein für Scams bekannter Dritter alle Ihre Vermögenswerte." + }, + "blockaidTitleDeceptive": { + "message": "Dies ist eine betrügerische Anfrage" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Die Anfrage ist möglicherweise nicht sicher" + }, + "blockaidTitleSuspicious": { + "message": "Dies ist eine verdächtige Anfrage" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Bridge" + }, "browserNotSupported": { "message": "Ihr Browser wird nicht unterstützt …" }, @@ -510,6 +614,10 @@ "message": "$1 kaufen", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Mehr $1 kaufen", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Jetzt kaufen" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Dies setzt die Nonce des Kontos zurück und löscht Daten aus dem Aktivitäten-Tab in Ihrem Wallet. Nur das aktuelle Konto und Netzwerk sind betroffen. Ihr Guthaben und eingehenden Transaktionen ändern sich nicht." }, + "click": { + "message": "Klicken," + }, "clickToConnectLedgerViaWebHID": { "message": "Klicken Sie hier, um Ihren Ledger über WebHID zu verbinden", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Sie verlassen jetzt MetaMask, um diesen Snap zu konfigurieren." + }, + "configureSnapPopupInstallDescription": { + "message": "Sie verlassen jetzt MetaMask, um diesen Snap zu installieren." + }, + "configureSnapPopupInstallTitle": { + "message": "Snap installieren" + }, + "configureSnapPopupLink": { + "message": "Zum Fortfahren auf diesen Link klicken:" + }, + "configureSnapPopupTitle": { + "message": "Snap konfigurieren" + }, "confirm": { "message": "Bestätigen" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Konto verbinden oder Neues erstellen" }, + "connectCustodialAccountMenu": { + "message": "Depotkonto verbinden" + }, + "connectCustodialAccountMsg": { + "message": "Bitte wählen Sie den Verwalter aus, den Sie verwenden möchten, um ein Token hinzuzufügen oder zu aktualisieren." + }, + "connectCustodialAccountTitle": { + "message": "Verwaltungskonten" + }, "connectManually": { "message": "Manuelle Verbindung zur aktuellen Site" }, + "connectSnap": { + "message": "Mit $1 verbinden", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Verbindung mit $1 wird hergestellt", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Verbindungsaufbau zum Linea-Testnetzwerk" }, + "connectingToLineaMainnet": { + "message": "Verbindung zum Linea Mainnet wird hergestellt" + }, "connectingToMainnet": { "message": "Verbinde zum Ethereum Mainnet" }, "connectingToSepolia": { "message": "Verbindungsaufbau zum Sepolia-Testnetzwerk" }, + "connectionFailed": { + "message": "Verbindung fehlgeschlagen" + }, + "connectionFailedDescription": { + "message": "Abrufen von $1 fehlgeschlagen. Netzwerkverbindung überprüfen und erneut versuchen.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Verbindungsanfrage" + }, "contactUs": { "message": "Kontaktaufnahme" }, @@ -708,7 +860,7 @@ "message": "Vertragsausführung" }, "contractDescription": { - "message": "Nehmen Sie sich einen Moment Zeit, um die Contract-Details zu überprüfen und sich so vor Betrügern zu schützen." + "message": "Nehmen Sie sich einen Moment Zeit, um die Drittanbieter-Details zu überprüfen und sich so vor Betrügern zu schützen." }, "contractInteraction": { "message": "Vertragsinteraktion" @@ -717,16 +869,16 @@ "message": "NFT-Contract" }, "contractRequestingAccess": { - "message": "Contract erbittet Zugriff" + "message": "Drittanbieter erbittet Zugriff" }, "contractRequestingSignature": { - "message": "Contract benötigt eine Unterschrift" + "message": "Drittanbieter erbittet eine Unterschrift" }, "contractRequestingSpendingCap": { - "message": "Contract fordert eine Ausgabenobergrenze" + "message": "Drittanbieter erbittet eine Ausgabenobergrenze" }, "contractTitle": { - "message": "Contract-Details" + "message": "Drittanbieter-Details" }, "contractToken": { "message": "Token-Contract" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Markt-Gasschätzungsdiagramm" }, + "custodian": { + "message": "Verwalter" + }, + "custodianAccount": { + "message": "Verwalterkonto" + }, + "custodianAccountAddedDesc": { + "message": "Sie können jetzt Verwaltungskonten in MetaMask Institutional verwenden." + }, + "custodianAccountAddedTitle": { + "message": "Ausgewählte Verwaltungskonten wurden hinzugefügt." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Bitte gehen Sie zu $1 und klicken Sie in der Benutzeroberfläche auf die Schaltfläche „Mit MMI verbinden“, um Ihre Konten erneut mit MMI zu verbinden." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Sie können jetzt Verwaltungskonten in MetaMask Institutional verwenden." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Ihr Verwaltungstoken wurde aktualisiert" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Dies ersetzt das Verwaltungstoken für die folgende Adresse:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Verwaltungstoken ersetzen" + }, + "custodyApiUrl": { + "message": "$1 API-URL" + }, + "custodyDeeplinkDescription": { + "message": "Genehmigen Sie die Transaktion in der $1-App. Wenn alle benötigten Verwaltungsgenehmigungen erteilt wurden, wir die Transaktion abgeschlossen. Überprüfen Sie Ihre $1-App für den Status." + }, + "custodyRefreshTokenModalDescription": { + "message": "Bitte gehen Sie zu $1 und klicken Sie in der Benutzeroberfläche auf die Schaltfläche „Mit MMI verbinden“, um Ihre Konten erneut mit MMI zu verbinden." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Ihr Verwalter stellt ein Token aus, dass die MetaMask-Institutional-Erweiterung automatisch beglaubigt, was die Verbindung zu Ihren Konten ermöglicht." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Dieses Token läuft aus Sicherheitsgründen nach einer gewissen Zeit ab. In diesem Fall müssen Sie erneut eine Verbindung mit MMI herstellen." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Warum sehe ich das?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Ihre Verwaltungssitzung ist abgelaufen" + }, + "custodySessionExpired": { + "message": "Verwaltungssitzung ist abgelaufen." + }, + "custodyWrongChain": { + "message": "Dieses Konto ist nicht für die Nutzung mit $1 konfiguriert" + }, "custom": { "message": "Erweitert" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "Kundensupport" }, + "dappRequestedSpendingCap": { + "message": "Von der Seite beantragte Ausgabenobergrenze" + }, "dappSuggested": { "message": "Seite vorgeschlagen" }, @@ -851,6 +1060,12 @@ "message": "$1 hat diesen Preis vorgeschlagen.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Seite vorgeschlagen" + }, + "dappSuggestedHighShortLabel": { + "message": "Seite (hoch)" + }, "dappSuggestedShortLabel": { "message": "Seite" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Löschen" }, + "deleteContact": { + "message": "Kontakt löschen" + }, "deleteNetwork": { "message": "Netzwerk löschen?" }, + "deleteNetworkIntro": { + "message": "Wenn Sie dieses Netzwerk löschen, müssen Sie es erneut hinzufügen, um Ihre Vermögenswerte in diesem Netzwerk anzuzeigen" + }, + "deleteNetworkTitle": { + "message": "$1-Netzwerk löschen?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Einzahlung" }, @@ -917,6 +1142,10 @@ "description": { "message": "Beschreibung" }, + "descriptionFromSnap": { + "message": "Beschreibung von $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Dieser Fehler könnte unregelmäßig auftreten, also versuchen Sie, Ihr die Erweiterung neu zu starten oder MetaMask Desktop zu deaktivieren." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Erinnerung Geheime Wiederherstellungsphrase abweisen" }, + "displayNftMedia": { + "message": "NFT-Medien anzeigen" + }, + "displayNftMediaDescription": { + "message": "Das Anzeigen von NFT-Medien und Daten gibt legt IP-Adresse OpenSea oder andere Drittanbieter gegenüber offen. Dies kann es Angreifern gestatten, Ihre IP-Adresse mit Ihrer Etherum-Adresse in Verbindung zu bringen. Die NFT-Autoerkennung benötigt diese Einstellung und wird nicht zur Verfügung stehen, wenn diese deaktiviert ist." + }, "domain": { "message": "Domäne" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Auto-Erkennung aktivieren" }, + "enableForAllNetworks": { + "message": "Für alle Netzwerke aktivieren" + }, "enableFromSettings": { "message": " In den Einstellungen aktivieren." }, + "enableSmartSwaps": { + "message": "Smart Swaps aktivieren" + }, + "enableSnap": { + "message": "Aktivieren" + }, "enableToken": { "message": "$1 aktivieren", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Aktiviert" + }, "encryptionPublicKeyNotice": { "message": "$1 wünscht Ihren öffentlichen Verschlüsselungsschlüssel. Durch Ihre Zustimmung kann diese Site verschlüsselte Nachrichten an Sie verfassen.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Erweiterte Token-Erkennung ist derzeit über $1 verfügbar. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask zeigt Ihnen ENS-Domains wie „https://metamask.eth“ direkt in der Adressleiste Ihres Browsers an. So funktioniert es:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Normale Browser können in der Regel nicht mit ENS- oder IPFS-Adressen umgehen, aber MetaMask hilft dabei. Bei Verwendung dieser Funktion wird Ihre IP-Adresse eventuell mit IPFS-Diensten von Drittanbietern geteilt." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask findet mithilfe von Ethereums ENS-Contract den mit dem ENS-Namen verbundenen Code." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Wenn der Code mit IPFS verknüpft ist, erhält MetaMask den Content vom IPFS-Netzwerk." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Daraufhin wird dieser, normalerweise eine Website oder etwas Ähnliches, angezeigt." + }, + "ensDomainsSettingTitle": { + "message": "ENS-Domains in der Adresszeile anzeigen" + }, "ensIllegalCharacter": { "message": "Unzulässiges Zeichen für ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Nummer eingeben" }, + "enterCustodianToken": { + "message": "$1-Token eingeben oder neues Token hinzufügen" + }, "enterMaxSpendLimit": { "message": "Max. Ausgabelimit eingeben" }, + "enterOptionalPassword": { + "message": "Optionales Passwort eingeben" + }, "enterPassword": { "message": "Passwort eingeben" }, "enterPasswordContinue": { "message": "Zum Fortfahren Passwort eingeben" }, + "enterTokenNameOrAddress": { + "message": "Tokennamen eingeben oder Adresse einfügen" + }, + "enterYourPassword": { + "message": "Passwort eingeben" + }, "errorCode": { "message": "Code: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Stapel:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Fehler bei der Verbindung zum benutzerdefinierten Netzwerk." + }, + "errorWithSnap": { + "message": "Fehler bei $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Der Gaspreis, der sich aus der Gaseinschätzung ergibt, ist derzeit nicht verfügbar." }, + "ethereumProviderAccess": { + "message": "Ethereum-Anbieter Zugriff auf $1 gewähren", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Öffentliche Ethereum-Adresse" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Experimentell" }, + "exploreMetaMaskSnaps": { + "message": "MetaMask Snaps erforschen" + }, "exportPrivateKey": { "message": "Private Key exportieren" }, + "extendWalletWithSnaps": { + "message": "Erweitern Sie das Wallet-Erlebnis." + }, "externalExtension": { "message": "Externe Erweiterung" }, @@ -1302,6 +1596,9 @@ "message": "Dateiimport fehlgeschlagen? Bitte hier klicken!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Die abgelegte Datei ist zu groß." + }, "flaskWelcomeUninstall": { "message": "Sie sollten diese Erweiterung deinstallieren", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Allgemein" }, + "getStarted": { + "message": "Los geht's" + }, + "globalTitle": { + "message": "Globales Menü" + }, + "globalTourDescription": { + "message": "Portfolio, verbundene Seite und mehr einsehen" + }, "goBack": { "message": "Zurück" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Ein Hardware-Wallet verknüpfen" }, + "hardwareWalletsInfo": { + "message": "Hardware-Wallet-Integrationen nutzen API-Aufrufe zur Kommunikation mit externen Servern, die Ihre IP-Adresse und die Adressen der Smart Contracts, mit denen Sie interagieren, sehen können." + }, "hardwareWalletsMsg": { "message": "Wählen Sie ein Hardware-Wallet aus, das Sie mit MetaMask verwenden möchten" }, @@ -1541,12 +1850,35 @@ "message": "Betrüger aber schon.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Ihr privater Key erlaubt $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "vollen Zugriff auf Ihr Wallet und Ihr Guthaben.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "zur-Anzeige-Halten-Kreis gesperrt" + }, + "holdToRevealPrivateKey": { + "message": "Halten, um privaten Key anzuzeigen" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Bewaren Sie Ihren privaten Key sicher auf" + }, "holdToRevealSRP": { "message": "Halten, um GWP anzuzeigen" }, "holdToRevealSRPTitle": { "message": "Bewahren Sie Ihre GWP sicher auf" }, + "holdToRevealUnlockedLabel": { + "message": "zur-Anzeige-Halten-Kreis entsperrt" + }, + "id": { + "message": "ID" + }, "ignoreAll": { "message": "Alle ignorieren" }, @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Fehler beim Importieren des Kontos." }, + "importAccountErrorIsSRP": { + "message": "Sie haben eine geheime Wiederherstellungsphrase (oder mnemonische Phrase) eingegeben. Um hier ein Konto zu importieren, müssen Sie einen privaten Key eingeben, wobei es sich um einen hexadezimalen String mit 64 Zeichen handelt." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Dies ist ein ungültiger Key. Sie haben einen hexadezimalen String eingegeben, er muss aber 64 Zeichen lang sein." + }, + "importAccountErrorNotHexadecimal": { + "message": "Dies ist ein ungültiger Key. Sie müssen einen hexadezimalen String mit 64 Zeichen eingeben." + }, + "importAccountJsonLoading1": { + "message": "Rechnen Sie damit, dass dieser JSON-Import ein paar Minuten dauert und MetaMask nicht reagiert." + }, + "importAccountJsonLoading2": { + "message": "Wir entschuldigen uns hierfür und werden dies in Zukunft beschleunigen." + }, "importAccountMsg": { - "message": " Importierte Accounts werden nicht mit der Seed-Wörterfolge deines ursprünglichen MetaMask Accounts verknüpft. Erfahre mehr über importierte Accounts." + "message": "Importierte Konten werden nicht mit der geheimen Wiederherstellungsphrase von MetaMask verknüpft. Mehr über importierte Konten erfahren" }, "importMyWallet": { "message": "Mein Wallet importieren" @@ -1615,18 +1962,29 @@ "message": "Ihre erste Transaktion wurde vom Netzwerk bestätigt. Klicken Sie auf Okay, um zurückzukehren." }, "inputLogicEmptyState": { - "message": "Geben Sie nur eine Nummer ein, die Sie den Contract jetzt oder in Zukunft ausgeben lassen möchten. Sie können die Ausgabenbegrenzung später jederzeit ändern." + "message": "Geben Sie nur eine Nummer ein, die Sie den Drittanbieter jetzt oder in Zukunft ausgeben lassen möchten. Sie können die Ausgabenbegrenzung später jederzeit ändern." }, "inputLogicEqualOrSmallerNumber": { - "message": "Dies erlaubt dem Contract, $1 von Ihrem aktuellen Guthaben auszugeben.", + "message": "Dies erlaubt dem Drittanbieter, $1 von Ihrem aktuellen Guthaben auszugeben.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Dies erlaubt dem Contract, Ihr gesamtes Token-Guthaben auszugeben, bis die Grenze erreicht wurde oder Sie die Einschränkung widerrufen. Sollte dies nicht Ihre Absicht sein, ziehen Sie eine niedrigere Ausgabegrenze in Betracht." + "message": "Dies erlaubt dem Drittanbieter, Ihr gesamtes Token-Guthaben auszugeben, bis die Grenze erreicht wurde oder Sie die Einschränkung widerrufen. Sollte dies nicht Ihre Absicht sein, ziehen Sie eine niedrigere Ausgabegrenze in Betracht." + }, + "insightsFromSnap": { + "message": "Einblicke von $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Installieren," }, + "installOrigin": { + "message": "Origin installieren" + }, + "installedOn": { + "message": "Installiert auf $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Guthaben reicht nicht aus." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Ungültige Eingabe! Die geheime Wiederherstellungsphrase berücksichtigt Groß- und Kleinschreibung." }, + "ipfsGateway": { + "message": "IPFS-Gateway" + }, + "ipfsGatewayDescription": { + "message": "MetaMask verwendet Dienste von Drittanbietern, um Bilder Ihrer auf IPFS gespeicherten NFTs wiederzugeben, Informationen zu ENS-Adressen anzuzeigen, die Sie in die Adressleiste Ihres Browsers eingegeben haben, und Symbole für verschiedene Token abzurufen. Ihre IP-Adresse kann diesen Diensten offengelegt werden, wenn Sie diese nutzen." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Wir verwenden Dienste von Drittanbietern, um Bilder Ihrer auf IPFS gespeicherten NFTs wiederzugeben, Informationen zu ENS-Adressen anzuzeigen, die Sie in die Adressleiste Ihres Browsers eingeben, und Symbole für verschiedene Token abzurufen. Ihre IP-Adresse kann diesen Diensten offengelegt werden, wenn Sie diese nutzen." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Durch die Auswahl von „Bestätigen“ wird die IPFS-Auflösung eingeschaltet. Sie lässt sich jederzeit in $1 wieder ausschalten.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Einstellungen > Sicherheit und Datenschutz" + }, "jazzAndBlockies": { "message": "Jazzicons und Blockies sind zwei verschiedene Arten von einzigartigen Symbolen, mit denen Sie ein Konto auf einen Blick erkennen können." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Zuletzt verkauft" }, + "layer1Fees": { + "message": "Ebene 1 Gebühren" + }, "learnCancelSpeeedup": { "message": "Erfahren Sie, wie Sie $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Wollen Sie $1 über Gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Mehr erfahren" + }, "learnMoreUpperCase": { "message": "Mehr erfahren" }, @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea-Testnetzwerk" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "Link" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Ausloggen" }, + "lockMetaMask": { + "message": "MetaMask sperren" + }, + "lockTimeInvalid": { + "message": "Sperrzeit muss eine Zahl zwischen 0 und 10080 sein" + }, "logo": { "message": "$1-Logo", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Die Schaltfläche Verbindungsstatus zeigt an, ob die Site, die Sie besuchen, mit Ihrem aktuell ausgewählten Konto verbunden ist." }, + "metamaskInstitutionalVersion": { + "message": "MetaMask-Institutional-Version" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps wird gewartet. Bitte versuchen Sie es später erneut." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Metriken" }, + "mismatchAccount": { + "message": "Ihr ausgewähltes Konto ($1) unterscheidet sich von dem Konto, das versucht, zu unterschreiben ($2)" + }, "mismatchedChainLinkText": { "message": "die Netzwerkdetails überprüfen", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Das angegebene Währungssymbol entspricht nicht dem Symbol, das wir für diese Chain-ID erwarten." }, + "mismatchedRpcChainId": { + "message": "Die vom benutzerdefinierten Netzwerk zurückgesendete Chain-ID stimmt nicht mit der angegebenen Chain-ID überein." + }, "mismatchedRpcUrl": { "message": "Laut unseren Aufzeichnungen stimmt der angegebene RPC-URL-Wert nicht mit einem bekannten Provider für diese Chain-ID überein." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Hier anfragen" }, + "mmiAddToken": { + "message": "Die Seite bei $1 möchte das folgende Verwaltungstoken in MetaMask Institutional autorisieren" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional ist weltweit konzipiert und aufgebaut." + }, + "more": { + "message": "mehr" + }, "moreComingSoon": { - "message": "Mehr in Kürze ..." + "message": "Mehr Anbieter in Kürze" + }, + "multipleSnapConnectionWarning": { + "message": "$1 möchte sich mit $2 Snaps verbinden. Fahren Sie nur fort, wenn Sie dieser Webseite vertrauen.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Du musst mindestens 1 Token auswählen." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Das Netzwerk ist ausgelastet. Die Gaspreise sind hoch und die Schätzungen sind weniger genau." }, + "networkMenu": { + "message": "Netzwerkmenü" + }, + "networkMenuHeading": { + "message": "Netzwerk wählen" + }, "networkName": { "message": "Netzwerkname" }, @@ -2033,6 +2450,10 @@ "message": "Die Gasgebühren betragen $1 bezogen auf die letzten 72 Stunden.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Wir können keine Verbindung zu $1 aufbauen", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Netzwerk-URL" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFTs" }, + "nftsPreviouslyOwned": { + "message": "Zuvor besessen" + }, "nickname": { "message": "Nickname" }, @@ -2138,6 +2562,12 @@ "noConversionRateAvailable": { "message": "Kein Umrechnungskurs verfügbar" }, + "noNFTs": { + "message": "Noch keine NFTs" + }, + "noNetworksFound": { + "message": "Für die vorliegende Suchanfrage wurde kein Netzwerk gefunden" + }, "noSnaps": { "message": "Keine Snaps installiert" }, @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Ist dies das richtige Konto? Es unterscheidet sich von dem aktuell ausgewählten Konto in Ihrer Wallet" }, + "notEnoughBalance": { + "message": "Guthaben reicht nicht aus" + }, "notEnoughGas": { "message": "Nicht genügend Gas" }, + "note": { + "message": "Notiz" + }, + "notePlaceholder": { + "message": "Der Genehmiger sieht diese Notiz, wenn die Transaktion beim Verwalter genehmigt wird." + }, + "notificationTransactionFailedMessage": { + "message": "Transaktion $1 ist fehlgeschlagen! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Transaktion ist fehlgeschlagen $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Fehlgeschlagene Transaktion", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Transaktion $1 wurde bestätigt!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Bestätigte Transaktion", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Auf $1 ansehen", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Benachrichtigungen" + }, "notifications10ActionText": { "message": "Einstellungen ansehen", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Ethereum Merge ist da!" }, + "notifications18ActionText": { + "message": "Sicherheitsalarme aktivieren" + }, + "notifications18DescriptionOne": { + "message": "Erhalten Sie Alarme von Dritten, falls Sie eine bösartige Anfrage erhalten.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Gehen Sie immer mit Sorgfalt vor, bevor Sie irgendwelche Anfragen genehmigen.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea ist der erste Anbieter für diese Funktion. Mehr Anbieter folgen in Kürze!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Bleiben Sie dank Sicherheitswarnungen geschützt" + }, + "notifications19ActionText": { + "message": "NFT-Autoerkennung" + }, + "notifications19DescriptionOne": { + "message": "Zwei Möglichkeiten, wie Sie beginnen können:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Wir unterstützen im Moment ausschließlich ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Fügen Sie Ihre NFTs manuell hinzu oder aktivieren Sie die NFT-Autoerkennung in Einstellungen > Experimentell.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Sehen Sie Ihre NFTs wie nie zuvor" + }, "notifications1Description": { "message": "Mobile MetaMask-Anwender können jetzt Token in ihren mobilen Wallets swappen. Scannen Sie den QR-Code, um die mobile App zu erhalten und mit dem Swapping zu beginnen.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Swappen auf dem Handy ist da!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Mehr erfahren", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Falls Sie die neueste Version von Firefox nutzen, könnte ein Problem auftreten, das mit der Einstellung der U2F-Unterstützung seitens Firefox in Verbindung steht.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Benutzer von Ledger und Firefox haben Verbindungsprobleme", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Ausprobieren" + }, + "notifications21Description": { + "message": "Wir haben Swaps in der MetaMask-Erweiterung aktualisiert, um die Nutzung einfacher und schneller zu gestalten.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Neue und aktualisierte Swaps!" + }, + "notifications22ActionText": { + "message": "Alles klar" + }, + "notifications22Description": { + "message": "💡 Klicken Sie einfach auf das globale Menü oder Kontomenü, um sie zu finden!" + }, + "notifications22Title": { + "message": "Suchen Sie Ihre Kontodetails oder die Block-Explorer-URL?" + }, + "notifications23ActionText": { + "message": "Sicherheitswarnungen aktivieren" + }, + "notifications23DescriptionOne": { + "message": "Fallen Sie nicht bekannten Betrügereien zum Opfer und schützen Sie gleichzeitig Ihre Privatsphäre mit Sicherheitswarnungen von Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Wir haben für Sie diese Funktion aktiviert, wenn Sie die Sicherheitswarnungen von OpenSea erhalten haben." + }, + "notifications23DescriptionTwo": { + "message": "Vor der Bestätigung von Anfragen lassen Sie stets die nötige Sorgfalt walten." + }, + "notifications23Title": { + "message": "Gehen Sie mit Sicherheitswarnungen auf Nummer sicher" + }, "notifications3ActionText": { "message": "Mehr erfahren", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2498,6 +3046,9 @@ "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Vorgang fehlgeschlagen" + }, "optional": { "message": "Optional" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Passwörter stimmen nicht überein" }, + "pasteJWTToken": { + "message": "Token hier einfügen oder ablegen:" + }, "pastePrivateKey": { "message": "Füge deine Private Key Zeichenfolge hier ein:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Zugriff auf das Internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Gestatten Sie dem Snap Zugriff auf das Internet. Dies kann zum Senden und Empfangen von Daten mit Servern von Drittanbietern verwendet werden.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Verbinden Sie sich mit dem $1-Snap.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Webseite oder Snap erlauben, mit $1 zu interagieren.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Regelmäßige Transaktionen planen und ausführen.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Gestatten Sie dem Snap das Ausführen von Aktionen, die regelmäßig zu festgelegten Zeiten, Daten oder Intervallen stattfinden. Dies kann verwendet werden, um zeitkritische Interaktionen oder Benachrichtigungen auszulösen.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Dialogfenster in MetaMask anzeigen.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Gestatten Sie dem Snap das Anzeigen von MetaMask-Popups mit benutzerdefiniertem Text sowie Schaltflächen, um eine Aktion zuzulassen oder abzulehnen.\nDies kann zum Beispiel zur Erstellung von Alarmen, Bestätigungen und Opt-In-Flows für ein Snap verwendet werden.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Siehe Adresse, Kontostand, Aktivität und Einleitung von Transaktionen", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Auf den Ethereum-Anbieter zugreifen.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Gestatten Sie dem Snap die direkte Kommunikation mit MetaMask, damit es Daten aus der Blockchain lesen und Nachrichten sowie Transaktionen vorschlagen kann.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Leiten Sie beliebige Schlüssel ab, die für diesen Snap eindeutig sind.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Gestatten Sie dem Snap das Ableiten von beliebigen Keys, die für diese App einzigartig sind, ohne sie offenzulegen. Diese Keys sind von Ihrem/n MetaMask-Konto/en getrennt und stehen nicht in Verbindung mit Ihren privaten Keys oder Ihrer geheimen Wiederherstellungsphrase. Andere Snaps können nicht auf diese Informationen zugreifen.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Verwenden Sie Lebenszyklus-Hooks.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Erlauben Sie dem Snap Lebenszyklus-Hooks zu verwenden, um den Code während seines Lebenszyklus zu bestimmten Zeiten auszuführen.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Für unbestimmte Zeit ausführen.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Gestatten Sie dem Snap, unbegrenzt ausgeführt zu werden, während es zum Beispiel große Datenmengen verarbeitet.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Ethereum-Konten hinzufügen und kontrollieren", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Verwalten Sie Ihre Konten und Vermögenswerte unter $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Gestatten Sie dem Snap, BIP-32-Keys basierend auf Ihrer geheimen Wiederherstellungsphrase abzuleiten, ohne diese offenzulegen. Dies gewährt vollständigen Zugriff auf alle Konten und Vermögenswerte auf $1.\nMit der Berechtigung zur Verwaltung von Keys kann das Snap eine Vielzahl von Blockchain-Protokollen über Ethereum (EVMs) hinaus unterstützen.", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Verwalten Sie Ihre „$1“-Konten und Vermögenswerte.", + "message": "Verwalten Sie Ihre $1-Konten und Vermögenswerte.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Gestatten Sie dem Snap, BIP-44-Keys basierend auf Ihrer geheimen Wiederherstellungsphrase abzuleiten, ohne diese offenzulegen. Dies gewährt vollständigen Zugriff auf alle Konten und Vermögenswerte auf $1.\nMit der Berechtigung zur Verwaltung von Keys kann das Snap eine Vielzahl von Blockchain-Protokollen über Ethereum (EVMs) hinaus unterstützen.", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Verwalten Sie Ihre $1-Konten und Vermögenswerte.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Ihre Saten speichern und auf Ihrem Gerät verwalten.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Gestatten Sie dem Snap das Speichern, Aktualisieren und sichere Abrufen von Daten mit Verschlüsselung. Andere Snaps können auf diese Informationen nicht zugreifen.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Benachrichtigungen anzeigen.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Gestatten Sie dem Snap, Benachrichtigungen in MetaMask anzuzeigen. Ein kurzer Benachrichtigungstext kann von einem Snap für umsetzbare oder zeitkritische Informationen ausgelöst werden.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "$1 erlauben, direkt mit diesem Snap zu kommunizieren.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Gestatten Sie $1 das Senden von Nachrichten an das Snap sowie den Empfang von Antworten vom Snap.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Transaktions-Einsichten abrufen und anzeigen.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Gestatten Sie dem Snap das Dekodieren von Transaktionen und das Anzeigen von Einblicken innerhalb der MetaMask UI. Dies kann für Anti-Phishing und Sicherheitslösungen verwendet werden.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Ursprung der Webseite anzeigen, die Transaktionen vorschlägt", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Gestattem Sie dem Snap, die Herkunft (URI) von Webseiten anzuzeigen, die Transaktionen vorschlagen. Dies kann für Anti-Phishing und Sicherheitslösungen verwendet werden.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Unbekannte Berechtigung: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Öffentlichen Key für $1 ($2) anzeigen.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Erlauben Sie dem Snap, Ihre öffentlichen Schlüssel (und Adressen) für $1 einzusehen. Dies gewährt keine Kontrolle über Konten oder Vermögenswerte.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Öffentlichen Key für $1 anzeigen.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Support für WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Gestattem Sie dem Snap den Zugriff auf Low-Level-Ausführungsumgebungen via WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Berechtigungen" }, + "permissionsTitle": { + "message": "Berechtigungen" + }, + "permissionsTourDescription": { + "message": "Hier können Sie Ihre verbundenen Konten finden und Berechtigungen verwalten" + }, "personalAddressDetected": { "message": "Personalisierte Adresse identifiziert. Bitte füge die Token Contract Adresse ein." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portfolio" }, + "portfolioDashboard": { + "message": "Portfolio-Dashboard" + }, "preferredLedgerConnectionType": { "message": "Bevorzugter Ledger-Verbindungstyp", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Privater Key", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Privater Key für $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Warnung: Niemals jemandem deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "In Warteschlange" }, + "quoteRate": { + "message": "Angebotskurs" + }, "reAddAccounts": { "message": "alle anderen Konten erneut hinzuzufügen" }, @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Dieses Konto wird aus Ihrer Wallet entfernt. Bitte stellen Sie sicher, dass Sie den ursprünglichen Seedschlüssel oder den privaten Schlüssel für dieses importierte Konto haben, bevor Sie fortfahren. Über die Dropdown-Liste des Kontos können Sie Konten importieren oder neu anlegen." }, + "removeJWT": { + "message": "Verwaltungstoken entfernen" + }, + "removeJWTDescription": { + "message": "Sind Sie sicher, dass Sie dieses Token entfernen möchten? Alle diesem Token zugewiesenen Konten werden ebenfalls von der Erweiterung entfernt: " + }, "removeNFT": { "message": "NFT entfernen" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Sie können die Benutzereinstellungen, die bevorzugte Einstellungen und Kontoadressen umfassen, aus einer vormals gesicherten JSON-Datei wiederherstellen." }, + "resultPageError": { + "message": "Fehler" + }, + "resultPageErrorDefaultMessage": { + "message": "Der Vorgang ist fehlgeschlagen." + }, + "resultPageSuccess": { + "message": "Erolg" + }, + "resultPageSuccessDefaultMessage": { + "message": "Der Vorgang wurde erfolgreich abgeschlossen." + }, "retryTransaction": { "message": "Transaktion wiederholen" }, @@ -2939,16 +3607,27 @@ "message": "Erlaubnis zum Zugriff auf alle Ihre $1 sowie deren Übertragung entziehen?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Erlaubnis zum Zugriff auf alle Ihre NFTs von $1 sowie zu deren Übertragung entziehen?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Wenn Sie diese Erlaubnis entziehen, werden Dritte ohne weiteren Hinweis keinen Zugriff mehr auf alle Ihre $1 haben und diese nicht mehr übertragen können.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Dies widerruft die Erlaubnis für Dritte, auf Ihre gesamten NFTs von $1 zuzugreifen und sie zu übertragen, ohne dass eine weitere Mitteilung erfolgt.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Erlaubniss zu widerrufen" + }, "revokeSpendingCap": { "message": "Ausgabengrenze für Ihr $1 aufgeben", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Mit diesem Contract können Sie keine weiteren Ihrer aktuellen oder zukünftigen Token ausgeben." + "message": "Dieser Drittanbieter kann keine weiteren Ihrer aktuellen oder zukünftigen Token ausgeben." }, "rpcUrl": { "message": "Neue RPC-URL" @@ -2986,9 +3665,28 @@ "security": { "message": "Sicherheit" }, + "securityAlert": { + "message": "Sicherheitsalarm von $1 und $2" + }, + "securityAlerts": { + "message": "Sicherheitswarnung" + }, + "securityAlertsDescription1": { + "message": "Diese Funktion warnt Sie vor schädlichen Aktivitäten, indem sie Ihre Transaktionen und Signaturanfragen lokal überprüft. Ihre Daten werden dazu nicht an Dritte weitergegeben, die diesen Dienst anbieten. Denken Sie immer an Ihre eigene Sorgfaltspflicht, bevor Sie Anfragen genehmigen, denn es gibt keine Garantie dafür, dass diese Funktion alle schädlichen Aktivitäten auch tatsächlich erkennt." + }, + "securityAlertsDescription2": { + "message": "Achten Sie darauf, Ihrer eigenen Sorgfaltspflicht nachzukommen, bevor Sie eine Anfrage bestätigen. Es gibt keine Garantie dafür, dass alle E-Mail-Aktivitäten von dieser Funktion erkannt werden." + }, "securityAndPrivacy": { "message": "Sicherheit & Datenschutz" }, + "securityProviderAdviceBy": { + "message": "Sicherheitstipp von $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Details anzeigen" + }, "seedPhraseConfirm": { "message": "Bestätigen Sie die geheime Wiederherstellungsphrase" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Schreiben Sie Ihre geheime Sicherungsphrase auf" }, + "select": { + "message": "Auswählen" + }, "selectAccounts": { "message": "Wählen Sie das Konto/die Konten aus, um sie auf dieser Seite zu verwenden" }, + "selectAccountsForSnap": { + "message": "Das Konto/Die Konten zur Verwendung mit diesem Snap auszusuchen" + }, "selectAll": { "message": "\nAlle auswählen" }, + "selectAllAccounts": { + "message": "Alle Konten auswählen" + }, "selectAnAccount": { "message": "Ein Konto auswählen" }, "selectAnAccountAlreadyConnected": { "message": "Dieses Konto wurde bereits mit MetaMask verbunden" }, + "selectAnAccountHelp": { + "message": "Wählen Sie die Verwaltungskonten zur Nutzung in MetaMask Institutional aus." + }, "selectHdPath": { "message": "HD-Pfad auswählen" }, + "selectJWT": { + "message": "Token auswählen" + }, "selectNFTPrivacyPreference": { "message": "NFT-Erkennung in den Einstellungen aktivieren" }, @@ -3113,6 +3826,9 @@ "message": "$1 ohne Ausgabenlimit genehmigen", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Snap-Konto hinzufügen" + }, "settings": { "message": "Einstellungen" }, @@ -3141,9 +3857,21 @@ "message": "Aktivieren Sie dies, um Etherscan zu aktivieren und eingehende Transaktionen in der Transaktionsliste anzuzeigen", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Dies hängt von jedem Netzwerk ab, das Zugriff auf Ihre Ethereum-Adresse und IP-Adresse hat." + }, + "showMore": { + "message": "Mehr anzeigen" + }, + "showNft": { + "message": "NFT anzeigen" + }, "showPermissions": { "message": "Berechtigungen anzeigen" }, + "showPrivateKey": { + "message": "Privaten Key anzeigen" + }, "showPrivateKeys": { "message": "Private Keys anzeigen" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Mir ist klar, dass ich meine Konten und alle dazugehörigen Vermögenswerte verlieren kann, solange ich keine Sicherungskopie meiner Geheimen Wiederherstellungsphrase erstelle." }, + "smartContracts": { + "message": "Smart Contracts" + }, + "smartSwap": { + "message": "Smart Swap" + }, + "smartSwapsAreHere": { + "message": "Die Smart Swaps sind da!" + }, + "smartSwapsDescription": { + "message": "MetaMask Swaps ist jetzt wesentlich intelligenter! Die Aktivierung von Smart Swaps wird es MetaMask erlauben, Ihre Swaps programmatisch zu optimieren, um zu helfen:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Nicht genug Guthaben für einen Smart Swap." + }, + "smartSwapsErrorUnavailable": { + "message": "Smart Swaps sind vorrübergehend nicht verfügbar." + }, + "smartSwapsSubDescription": { + "message": "* Smart Swaps werden mehrmals versuchen, Ihren Swap privat einzureichen. Sollten alle Versuche fehlschlagen, wird die Transaktion öffentlich ausgestrahlt, um sicherzustellen, dass Ihre Transaktion erfolgreich durchgeführt wird." + }, + "snapConfigure": { + "message": "Konfigurieren" + }, + "snapConnectionWarning": { + "message": "$1 möchte sich mit $2 verbinden. Fahren Sie nur fort, wenn Sie dieser Webseite vertrauen.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Diese Inhalte stammen von $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Entscheiden Sie sich, wie sie mit MetaMask Snaps Ihr neues Konto schützen möchten." + }, + "snapCreateAccountTitle": { + "message": "Ein $1-Konto erstellen", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "Snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Von MetaMask" + }, + "snapDetailAudits": { + "message": "Prüfung" + }, + "snapDetailDeveloper": { + "message": "Entwickler" + }, + "snapDetailLastUpdated": { + "message": "Aktualisiert" + }, + "snapDetailManageSnap": { + "message": "Snap verwalten" + }, + "snapDetailTags": { + "message": "Tags" + }, + "snapDetailVersion": { + "message": "Version" + }, + "snapDetailWebsite": { + "message": "Webseite" + }, + "snapDetailsCreateASnapAccount": { + "message": "Ein Snap-Konto erstellen" + }, + "snapDetailsInstalled": { + "message": "Installiert" + }, "snapError": { "message": "Snap-Fehler: '$1'. Fehler-Code: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Snap installieren" }, + "snapInstallRequest": { + "message": "Durch die Installation von $1 werden folgende Berechtigungen erteilt. Nur fortfahren, wenn Sie $1 vertrauen.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Installation ist abgeschlossen" + }, "snapInstallWarningCheck": { "message": "Um zu bestätigen, dass Sie das verstanden haben, markieren Sie das Kästchen.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Um zu bestätigen, dass Sie alles verstanden haben, markieren Sie alle Kästchen.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Seien Sie vorsichtig" + }, "snapInstallWarningKeyAccess": { "message": "Sie gewähren dem Snap „$1“ wichtige $2-Zugriffsrechte. Dies kann nicht rückgängig gemacht werden und gibt „$1“ Kontrolle über Ihre $2-Konten und Vermögenswerte. Stellen Sie sicher, dass Sie „$1“ vertrauen, bevor Sie fortfahren.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "$2 öffentlichen Key-Zugriff auf $1 erteilen", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 konnte nicht installiert werden.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Installation fehlgeschlagen", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Geprüft" + }, + "snapResultError": { + "message": "Fehler" + }, + "snapResultSuccess": { + "message": "Erfolg" + }, + "snapResultSuccessDescription": { + "message": "$1 ist einsatzbereit" + }, "snapUpdate": { "message": "Snap aktualisieren" }, + "snapUpdateAvailable": { + "message": "Ein Update ist verfügbar" + }, + "snapUpdateErrorDescription": { + "message": "$1 konnte nicht aktualisiert werden.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Update fehlgeschlagen", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 möchte $2 auf $3 hochstufen, was folgende Berechtigungen erteilen würde. Nur fortfahren, wenn Sie $2 vertrauen.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Update ist abgeschlossen" + }, "snaps": { "message": "Snaps" }, "snapsInsightLoading": { "message": "Transaktions-Einsicht wird geladen ..." }, + "snapsInvalidUIError": { + "message": "Die vom Snap spezifizierte UI ist ungültig." + }, "snapsNoInsight": { "message": "Der Snap brachte keine Einsicht" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Sie sind sich darüber im Klaren, dass es sich bei dem Snap, den Sie im Begriff sind zu installieren, um einen wie in den Consensys $1 definierten Dienst eines Drittanbieters handelt. Ihre Verwendung von Drittanbieterdiensten unterliegt den Nutzungsbedingungen des Drittanbieters. Wenn Sie auf diesen Drittanbieter zugreifen, ihm vertrauen oder ihn verwenden geschieht das auf Ihr eigenes Risiko. Consensys lehnt jegliche Verantwortung sowie Haftung für etwaige durch die Nutzung von Drittanbieterdiensten entstandene Verluste auf Ihrem Konto ab.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Alle Daten, die Sie mit Drittanbieterdiensten teilen, werden direkt von diesen Drittanbieterdiensten im Einklang mit deren Datenschutzerklärung erfasst. Für weitere Informationen lesen Sie bitte die jeweiligen Datenschutzerklärungen.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys hat keinen Zugriff auf die Daten, die Sie mit diesen Drittanbietern teilen.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Verwalten Sie Ihre Snaps" }, + "snapsTermsOfUse": { + "message": "Nutzungsbedingungen" + }, "snapsToggle": { "message": "Ein Snap wird nur ausgeführt, wenn er aktiviert ist" }, "snapsUIError": { - "message": "Die vom Snap spezifizierte UI ist ungültig.", + "message": "Kontaktieren Sie die Ersteller von $1 für weitere Unterstützung.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Geben Sie nur eine Nummer ein, auf die $1 jetzt oder in Zukunft zugreifen kann. Sie können das die Token-Begrenzung später jederzeit ändern.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Antrag aus Ausgabenobergrenze für $1" + }, "srpInputNumberOfWords": { "message": "Ich habe eine $1-Wort-Phrase", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Anfangen" }, + "srpSecurityQuizImgAlt": { + "message": "Ein Auge mit einem Schlüsselloch in der Mitte und drei schwebenden Passwortfeldern" + }, "srpSecurityQuizIntroduction": { "message": "Zur Enthüllung Ihrer geheimen Wiederherstellungsphrase, müssen Sie zwei Fragen beantworten" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "stabil" }, + "stake": { + "message": "Anteil" + }, "stateLogError": { "message": "Fehler beim Abfragen der Statelogs." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Nicht verbunden" }, + "statusNotConnectedAccount": { + "message": "Keine verbundenen Konten" + }, "step1LatticeWallet": { "message": "Verbinden Sie Ihr Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Dies ist der Mindestbetrag, den Sie erhalten werden. Je nach Slippage können Sie auch mehr erhalten." }, + "swapAnyway": { + "message": "Swap trotzdem ausführen" + }, "swapApproval": { "message": "$1 für Swaps genehmigen", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Sie benötigen $1 mehr $2, um diesen Swap abzuschließen", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Sind Sie noch da?" + }, + "swapAreYouStillThereDescription": { + "message": "Wir sind bereit, Ihnen die neuesten Angebote zu zeigen, wenn Sie fortfahren möchten" + }, "swapBuildQuotePlaceHolderText": { "message": "Keine Token verfügbar mit $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Mit Ihrer Hardware-Wallet bestätigen" }, + "swapContinueSwapping": { + "message": "Mit Swap fortfahren" + }, "swapContractDataDisabledErrorDescription": { "message": "In der Ethereum-App auf Ihrem Ledger gehen Sie zu \"Einstellungen\" und erlauben Vertragsdaten. Versuchen Sie dann Ihren Swap erneut." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Limit bearbeiten" }, + "swapEditTransactionSettings": { + "message": "Transaktionseinstellungen anpassen" + }, "swapEnableDescription": { "message": "Dies ist erforderlich und gibt MetaMask die Erlaubnis, Ihren $1 zu swappen.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Das macht $1 für Swapping", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Einen Betrag eingeben" + }, "swapEstimatedNetworkFees": { "message": "Geschätzte Netzwerkgebühren" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Swap fehlgeschlagen" }, + "swapFetchingQuote": { + "message": "Angebot wird eingeholt" + }, "swapFetchingQuoteNofN": { "message": "Angebot $1 von $2 ", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Enthält eine MetaMask-Gebühr von $1%.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Enthält eine MetaMask-Gebühr von $1% – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Mehr über Swaps erfahren" + }, "swapLowSlippageError": { "message": "Transaktion kann fehlschlagen, maximale Slippage zu niedrig." }, @@ -3626,6 +4533,10 @@ "message": "Neue Kurse in $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Keine Token verfügbar mit $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Ihre $1 werden Ihrem Konto gutgeschrieben, sobald diese Transaktion abgeschlossen ist.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Kursdetails" }, + "swapQuoteNofM": { + "message": "$1 von $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Kursquelle" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Kurs-Timeout" }, + "swapQuotesNotAvailableDescription": { + "message": "Reduzieren Sie den Umfang Ihres Handels oder probieren Sie es mit einem anderen Token." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Versuchen Sie die Menge oder Slippage Einstellungen anzupassen und versuchen Sie es erneut." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Unten sind alle Kurse aus verschiedenen Liquiditätsquellen zusammengefasst." }, + "swapSelectToken": { + "message": "Token auswählen" + }, + "swapShowLatestQuotes": { + "message": "Neueste Angebote anzeigen" + }, "swapSlippageNegative": { "message": "Slippage muss größer oder gleich Null sein" }, + "swapSlippageNegativeDescription": { + "message": "Slippage muss größer oder gleich Null sein" + }, + "swapSlippageNegativeTitle": { + "message": "Zum Fortfahren Slippage erhöhen" + }, + "swapSlippageOverLimitDescription": { + "message": "Slippage-Tolleranz muss 15 % oder weniger betragen. Alles darüber resultiert in einem schlechten Kurs." + }, + "swapSlippageOverLimitTitle": { + "message": "Zum Fortfahren Slippage senken" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Maximale Slippage ist zu niedrig, weswegen Ihre Transaktion fehlschlagen könnte." + }, + "swapSlippageTooLowTitle": { + "message": "Slippage erhöhen, um Fehlschlag der Transaktion zu verhindern" + }, "swapSlippageTooltip": { "message": "Wenn sich der Kurs zwischen der Aufgabe Ihrer Bestellung und der Bestätigung ändert, nennt man das „Slippage”. Ihr Swap wird automatisch storniert, wenn die Abweichung die von Ihnen eingestellte „Abweichungstoleranz” überschreitet." }, + "swapSlippageVeryHighDescription": { + "message": "Die eingegebene Slippage wird als sehr hoch angesehen und könnte einen schlechten Kurs bewirken" + }, + "swapSlippageVeryHighTitle": { + "message": "Sehr hohe Slippage" + }, + "swapSlippageZeroDescription": { + "message": "Es gibt weniger Anbieter mit Null-Slippage, was in einem weniger konkurrenzfähigen Angebot resultieren könne." + }, + "swapSlippageZeroTitle": { + "message": "Suche nach Null-Slippage-Anbietern" + }, "swapSource": { "message": "Liquiditätsquelle" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "zur Bestätigung mit deiner Hardware-Wallet" }, + "swapTokenAddedManuallyDescription": { + "message": "Überprüfen Sie dieses Token auf $1 und stellen Sie sicher, dass es sich um das Token handelt, das Sie handeln möchten.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token manuell hinzugefügt" + }, "swapTokenAvailable": { "message": "Ihr $1 wurde Ihrem Konto hinzugefügt.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Auf $1 Quellen überprüft.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 wurde nur auf 1 Quelle bestätigt. Ziehen Sie in Betracht, es vor dem Fortfahren auf $2 zu bestätigen.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Möglicherweise unglaubwürdiges Token" + }, "swapTooManyDecimalsError": { "message": "$1 erlaubt bis zu $2 Dezimalstellen", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Nicht genug $1, um diese Transaktion abzuschließen", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Nicht genügend $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "In Aktivität anzeigen" }, + "switch": { + "message": "Wechseln" + }, "switchEthereumChainConfirmationDescription": { "message": "Dadurch wird das ausgewählte Netzwerk innerhalb von MetaMask auf ein zuvor hinzugefügtes Netzwerk umgeschaltet:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Sie haben gewechselt zu" }, + "switcherTitle": { + "message": "Netzwerkwechsler" + }, + "switcherTourDescription": { + "message": "Klicken Sie auf das Symbol, um das Netzwerk zu wechseln oder ein neues Netzwerk hinzuzufügen" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Das Wechseln der Netzwerke wird alle ausstehenden Bestätigungen abbrechen" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Nutzungsbedingungen" }, + "termsOfUse": { + "message": "Nutzungsbedingungen" + }, + "termsOfUseAgreeText": { + "message": " Ich akzeptiere die Nutzungsbedingungen, die meine Verwendung von MetaMask einschließlich aller seiner Funktionen betreffen" + }, + "termsOfUseFooterText": { + "message": "Bitte scrollen, um alle Sektionen zu lesen" + }, + "termsOfUseTitle": { + "message": "Unsere Nutzungsbedingungen wurden aktualisiert" + }, "testNetworks": { "message": "Test-Netzwerke" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Was Sie beachten sollten:" }, + "thirdPartySoftware": { + "message": "Mitteilung bzgl. Drittanbieter-Software", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "diese Sammlung" + }, + "thisServiceIsExperimental": { + "message": "Dieser Dienst ist experimentell. Durch die Aktivierung dieser Funktion stimmen Sie den $1 von OpenSea zu.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Zeit" }, @@ -3863,12 +4867,45 @@ "message": "An: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Sie sind einem Risiko auf Phishing-Angriffe ausgesetzt. Schützen Sie sich selbst, indem Sie eth_sign deaktivieren." + }, "toggleEthSignDescriptionField": { "message": "Aktivieren Sie dies, um Dapps zu erlauben, Ihre Unterschrift über eth_sign-Anfragen anzufordern. eth_sign ist eine offene Unterzeichnungsmethode, mit der Sie einen beliebigen Hash unterzeichnen können, wodurch ein gefährliches Phishing-Risiko entsteht. Unterzeichnen Sie eth_sign-Anfragen nur, wenn Sie lesen können, was Sie unterzeichnen und der Quelle der Anfrage vertrauen." }, "toggleEthSignField": { "message": "eth_sign-Anfragen ein- oder ausschalten" }, + "toggleEthSignModalBannerBoldText": { + "message": " werden Sie eventuell betrogen" + }, + "toggleEthSignModalBannerText": { + "message": "Wenn Sie gebeten werden, diese Einstellung zu aktivieren," + }, + "toggleEthSignModalCheckBox": { + "message": "Ich bin mir darüber im Klaren, dass ich mein Guthaben und meine NFTs verlieren könnte, wenn ich eth_sign-Anfragen aktivere. " + }, + "toggleEthSignModalDescription": { + "message": "Das Erlauben von eth_sign-Anfragen kann Sie für Phishing-Angriffe verwundbar machen. Prüfen Sie die URL immer und sein Sie vorsichtig, wenn Sie Nachrichten unterschreiben, die Code enthalten." + }, + "toggleEthSignModalFormError": { + "message": "Der Text ist falsch" + }, + "toggleEthSignModalFormLabel": { + "message": "Geben Sie „Ich unterschreibe nur, was ich verstehe“ ein, um fortzufahren" + }, + "toggleEthSignModalFormValidation": { + "message": "Ich unterschreibe nur, was ich verstehe" + }, + "toggleEthSignModalTitle": { + "message": "Nutzung auf eigenes Risiko" + }, + "toggleEthSignOff": { + "message": "AUS (empfohlen)" + }, + "toggleEthSignOn": { + "message": "EIN (nicht empfohlen)" + }, "token": { "message": "Token" }, @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Tokensymbol" }, + "tokens": { + "message": "Token" + }, "tokensFoundTitle": { "message": "$1 neue Token gefunden", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Ich verstehe" }, + "tooltipSatusConnected": { + "message": "verbunden" + }, + "tooltipSatusNotConnected": { + "message": "nicht verbunden" + }, "total": { "message": "Gesamt" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "Bei der Transaktion ist ein Fehler aufgetreten." }, + "transactionFailed": { + "message": "Transaktion fehlgeschlagen" + }, "transactionFee": { "message": "Transaktionsgebühr" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Gesamte Gasgebühr" }, + "transactionNote": { + "message": "Transaktionsnotiz" + }, "transactionResubmitted": { "message": "Transaktion an $1 bei $2 mit erhöhter Gasgebühr erneut übermittelt" }, "transactionSecurityCheck": { - "message": "Sicherheitsprüfung der Transaktion" + "message": "Sicherheitsalarme aktivieren" }, "transactionSecurityCheckDescription": { "message": "Wir verwenden APIs von Drittanbietern, um Risiken bei unsignierten Transaktionen und Signaturanfragen zu erkennen und anzuzeigen, bevor Sie diese signieren. Diese Dienste haben Zugriff auf Ihre unsignierten Transaktions- und Signaturanfragen, Ihre Kontoadresse und Ihre bevorzugte Sprache." }, + "transactionSettings": { + "message": "Transaktionseinstellungen" + }, "transactionSubmitted": { "message": "Transaktion mit einer Gasgebühr von $1 bei $2 übermittelt." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transferieren von" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Wir haben Probleme mit der Verbindung zu Ihrem Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Überprüfen Sie die Verbindungsanleitung Ihres Hardware-Wallets und versuchen Sie es erneut.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Sollten Sie die neueste Version von Firefox verwenden, könnten Sie einem Problem begegnen, dass mit der Einstelllung der U2F-Unterstützung seitens Firefox zusammenhängt. Erfahren Sie $1, wie Sie dieses Problem beheben.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "hier", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Wir hatten Probleme mit der Verbindung zu Ihrem $1, versuchen Sie, $2 zu überprüfen und versuchen es erneut.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "Aufwärtspfeil" }, + "update": { + "message": "Update" + }, "updatedWithDate": { "message": "$1 aktualisiert" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Diese URL wird derzeit vom $1-Netzwerk verwendet." }, + "use4ByteResolution": { + "message": "Smart Contracts dekodieren" + }, + "use4ByteResolutionDescription": { + "message": "Um das Benutzererlebnis zu verbessern, passen wir die Aktivitätsregisterkarte mit Nachrichten an, die auf den Smart Contracts basieren, mit denen Sie interagieren. MetaMask verwendet einen Dienst namens 4byte.directory, um Daten zu entschlüsseln und Ihnen eine Version eines Smart Contracts anzuzeigen, die leichter zu lesen ist. Dies trägt dazu bei, die Wahrscheinlichkeit zu verringern, dass Sie bösartige Smart-Contract-Aktionen genehmigen, kann aber dazu führen, dass Ihre IP-Adresse weitergegeben wird." + }, "useMultiAccountBalanceChecker": { "message": "Kontoguthaben-Anfragen sammeln" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Erhalten Sie Aktualisierungen von Kontoständen schneller, indem Sie Anfragen zum Kontostand bündeln. Auf diese Weise können wir Ihre Kontostände auf einmal abrufen, wodurch Sie schnellere Aktualisierungen erhalten und eine bessere Erfahrung machen. Wenn diese Funktion deaktiviert ist, ist es für Dritte weniger wahrscheinlich, dass sie Ihre Konten miteinander in Verbindung bringen können." + }, "useNftDetection": { "message": "NFTs automatisch erkennen" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Zeigt eine Warnung für Phishing-Domänen, die Ethereum Benutzer ansprechen" }, + "useSiteSuggestion": { + "message": "Seitenvorschlag verwenden" + }, "useTokenDetectionPrivacyDesc": { "message": "Die automatische Anzeige der an Ihr Konto gesendeten Token erfordert die Kommunikation mit Servern von Drittanbietern, um die Bilder der Token abzurufen. Diese Server haben Zugriff auf Ihre IP-Adresse." }, @@ -4170,7 +5256,7 @@ "message": "Nutzername" }, "verifyContractDetails": { - "message": "Contract-Details verifizieren" + "message": "Drittanbieter-Details überprüfen" }, "verifyThisTokenDecimalOn": { "message": "Token Dezimalstellen finden Sie auf $1", @@ -4184,12 +5270,18 @@ "message": "Überprüfen Sie diesen Token auf $1 und stellen Sie sicher, dass dies der Token ist, den Sie handeln möchten.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Version" + }, "view": { "message": "Anzeigen" }, "viewAllDetails": { "message": "Alle Details anzeigen" }, + "viewAllQuotes": { + "message": "alle Angebote anzeigen" + }, "viewContact": { "message": "Kontakt anzeigen" }, @@ -4213,9 +5305,18 @@ "message": "$1 auf Etherscan anzeigen", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Im Explorer anzeigen" + }, "viewOnOpensea": { "message": "Auf Opensea ansehen" }, + "viewPortfolioDashboard": { + "message": "Portfolio-Dashboard anzeigen" + }, + "viewinCustodianApp": { + "message": "In Verwaltungs-App anzeigen" + }, "viewinExplorer": { "message": "$1 im Explorer anzeigen", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,6 +5350,10 @@ "wantToAddThisNetwork": { "message": "Möchten Sie dieses Netzwerk hinzufügen?" }, + "wantsToAddThisAsset": { + "message": "$1 möchte dieses Asset zu Ihrem Wallet hinzufügen", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Warnung" }, @@ -4320,6 +5425,9 @@ "youSign": { "message": "Du unterschreibst" }, + "yourAccounts": { + "message": "Ihre Konten" + }, "yourFundsMayBeAtRisk": { "message": "Ihr Guthaben könnte gefährdet sein" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 691927f66..5586e6c95 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Σχετικά με" }, + "accept": { + "message": "Αποδοχή" + }, "acceptTermsOfUse": { "message": "Έχω διαβάσει και συμφωνώ με το $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Προσθήκη ενός ψευδωνύμου" }, + "addAccount": { + "message": "Προσθήκη λογαριασμού" + }, "addAcquiredTokens": { "message": "Προσθέστε τα token που αποκτήσατε χρησιμοποιώντας το MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Προσθέστε από μια λίστα δημοφιλών δικτύων ή προσθέστε ένα δίκτυο με μη αυτόματο τρόπο. Να αλληλεπιδράτε μόνο με άτομα/οργανισμούς που εμπιστεύεστε." }, + "addHardwareWallet": { + "message": "Προσθήκη πορτοφολιού υλικού" + }, + "addIPFSGateway": { + "message": "Προσθέστε την πύλη IPFS που προτιμάτε" + }, "addMemo": { "message": "Προσθήκη σημειώματος" }, @@ -244,6 +256,21 @@ "message": "Αυτή η σύνδεση δικτύου βασίζεται σε τρίτους. H σύνδεση ενδέχεται να είναι λιγότερο αξιόπιστη ή να επιτρέπει σε τρίτους να παρακολουθούν τη δραστηριότητα. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Προσθήκη νέου token" + }, + "addNft": { + "message": "Προσθήκη του NFT" + }, + "addNfts": { + "message": "Προσθήκη των NFT" + }, + "addSnapAccountModalDescription": { + "message": "Ανακαλύψτε επιλογές για να διατηρήσετε τον λογαριασμό σας ασφαλή με τον MetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Προσθήκη προτεινόμενων NFT" + }, "addSuggestedTokens": { "message": "Προσθέστε τα Προτεινόμενα Tokens" }, @@ -254,6 +281,9 @@ "message": "Αδυναμία εύρεσης token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε διακριτικό επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων Token μπορούν να βρεθούν στο $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Προσθήκη Δικτύου" + }, "address": { "message": "Διεύθυνση" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Το τέλος προτεραιότητας (γνωστό και ως “miner tip”) πηγαίνει άμεσα στους miner και τους ενθαρρύνει να δώσουν προτεραιότητα στη συναλλαγή σας." }, + "agreeTermsOfUse": { + "message": "Συμφωνώ με το $1 του MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "Θησαυροφυλάκιο AirGap" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Ειδοποιήσεις" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Είτε έχετε ήδη συνδέσει όλους τους λογαριασμούς θεματοφύλακα είτε δεν έχετε κανέναν λογαριασμό να συνδέσετε με το MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Δεν υπάρχουν διαθέσιμοι λογαριασμοί για σύνδεση" + }, "allOfYour": { "message": "Όλα σας τα $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Όλα τα NFT σας από το $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Επιτρέψτε σε αυτή την εξωτερική επέκταση να:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Επιτρέψτε σε αυτόν τον ιστότοπο να:" }, + "allowThisSnapTo": { + "message": "Επιτρέψτε αυτό το snap να:" + }, "allowWithdrawAndSpend": { "message": "Επιτρέψτε στο $1 να κάνει ανάληψη και να ξοδέψει μέχρι το ακόλουθο ποσό:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Ποσό" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "Ένα Πορτοφόλι Ethereum στο Πρόγραμμα Περιήγησής σας", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Δίνετε άδεια για να αποκτήσετε πρόσβαση σε όλα σας τα $1;", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Επιτρέπετε την πρόσβαση και τη μεταφορά όλων των NFT σας από το $1;", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Έγκριση" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Αυτό επιτρέπει σε τρίτα μέρη να έχουν πρόσβαση και να μεταφέρουν τα ακόλουθα NFT χωρίς περαιτέρω ειδοποίηση μέχρι να ανακαλέσετε την πρόσβασή τους." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Αυτό επιτρέπει σε ένα τρίτο μέρος να έχει πρόσβαση και να μεταφέρει όλα τα NFT σας από το $1 χωρίς περαιτέρω ειδοποίηση μέχρι να ανακαλέσετε την πρόσβασή του.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Επιτρέπετε την πρόσβαση και τη μεταφορά του $1;", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Εάν επιχειρήσετε να στείλετε περιουσιακά στοιχεία απευθείας από ένα δίκτυο σε ένα άλλο, αυτό ενδέχεται να οδηγήσει σε μόνιμη απώλεια περιουσιακών στοιχείων. Βεβαιωθείτε ότι χρησιμοποιείτε μια διασύνδεση." }, + "attemptToCancelSwap": { + "message": "Προσπάθεια ακύρωσης της ανταλλαγής για ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Προσπάθεια σύνδεσης στην αλυσίδα μπλοκ." }, @@ -411,6 +473,9 @@ "average": { "message": "Μέσος Όρος" }, + "awaitingApproval": { + "message": "Σε αναμονή έγκρισης..." + }, "back": { "message": "Πίσω" }, @@ -454,6 +519,9 @@ "message": "Αυτή είναι μια δοκιμαστική έκδοση. Παρακαλώ αναφέρετε σφάλματα $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Δοκιμαστική Έκδοση του MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Δοκιμαστική έκδοση MetaMask" }, @@ -488,9 +556,45 @@ "message": "Προβολή λογαριασμού με $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Εάν εγκρίνετε αυτό το αίτημα, ένα τρίτο μέρος που είναι γνωστό για απάτες μπορεί να πάρει όλα τα περιουσιακά σας στοιχεία." + }, + "blockaidDescriptionBlurFarming": { + "message": "Εάν εγκρίνετε αυτό το αίτημα, κάποιος μπορεί να κλέψει τα περιουσιακά σας στοιχεία που είναι καταχωρημένα στο Blur." + }, + "blockaidDescriptionFailed": { + "message": "Λόγω κάποιου σφάλματος, αυτό το αίτημα δεν επαληθεύτηκε από τον πάροχο ασφαλείας. Προχωρήστε με προσοχή." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Αλληλεπιδράτε με έναν κακόβουλο τομέα. Εάν εγκρίνετε αυτό το αίτημα, ενδέχεται να χάσετε τα περιουσιακά σας στοιχεία." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Εάν εγκρίνετε αυτό το αίτημα, ενδέχεται να χάσετε τα περιουσιακά σας στοιχεία." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Εάν εγκρίνετε αυτό το αίτημα, κάποιος μπορεί να κλέψει τα περιουσιακά σας στοιχεία που είναι καταχωρημένα στο OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Εάν εγκρίνετε αυτό το αίτημα, ένας τρίτος που είναι γνωστός για απάτες θα πάρει όλα τα περιουσιακά σας στοιχεία." + }, + "blockaidTitleDeceptive": { + "message": "Αυτό είναι ένα παραπλανητικό αίτημα" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Το αίτημα μπορεί να μην είναι ασφαλές" + }, + "blockaidTitleSuspicious": { + "message": "Αυτό είναι ένα ύποπτο αίτημα" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Διασύνδεση" + }, "browserNotSupported": { "message": "Το Πρόγραμμα Περιήγησής σας δεν υποστηρίζεται..." }, @@ -510,6 +614,10 @@ "message": "Αγορά $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Αγοράστε περισσότερα $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Αγοράστε Τώρα" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Αυτό επαναφέρει το «nonce» του λογαριασμού και διαγράφει τα δεδομένα από την καρτέλα δραστηριότητας στο πορτοφόλι σας. Θα επηρεαστεί μόνο ο τρέχων λογαριασμός και το δίκτυο. Τα υπόλοιπα και οι εισερχόμενες συναλλαγές σας δεν θα αλλάξουν." }, + "click": { + "message": "Κάντε κλικ" + }, "clickToConnectLedgerViaWebHID": { "message": "Κάντε κλικ εδώ για να συνδέσετε το Ledger σας μέσω WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Τώρα αποχωρείτε από το MetaMask για να ρυθμίσετε αυτό το snap." + }, + "configureSnapPopupInstallDescription": { + "message": "Τώρα αποχωρείτε από το MetaMask για να εγκαταστήσετε αυτό το snap." + }, + "configureSnapPopupInstallTitle": { + "message": "Εγκατάσταση του snap" + }, + "configureSnapPopupLink": { + "message": "Κάντε κλικ σε αυτόν τον σύνδεσμο για να συνεχίσετε:" + }, + "configureSnapPopupTitle": { + "message": "Διαμόρφωση του snap" + }, "confirm": { "message": "Επιβεβαίωση" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Σύνδεση λογαριασμού ή δημιουργία νέου" }, + "connectCustodialAccountMenu": { + "message": "Συνδέστε τον λογαριασμό ως θεματοφύλακας" + }, + "connectCustodialAccountMsg": { + "message": "Επιλέξτε τον θεματοφύλακα που θέλετε να συνδέσετε για να προσθέσετε ή να ανανεώσετε ένα token." + }, + "connectCustodialAccountTitle": { + "message": "Λογαριασμοί θεματοφύλακα" + }, "connectManually": { "message": "Χειροκίνητη σύνδεση στον τρέχοντα ιστότοπο" }, + "connectSnap": { + "message": "Σύνδεση $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Σύνδεση με $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Σύνδεση στο δίκτυο δοκιμών Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Σύνδεση στο Linea Mainnet" + }, "connectingToMainnet": { "message": "Σύνδεση στο Κύριο Δίκτυο Ethereum" }, "connectingToSepolia": { "message": "Σύνδεση στο δίκτυο δοκιμών Sepolia" }, + "connectionFailed": { + "message": "Η σύνδεση απέτυχε" + }, + "connectionFailedDescription": { + "message": "Η λήψη του $1 απέτυχε, ελέγξτε το δίκτυό σας και προσπαθήστε ξανά.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Αίτημα σύνδεσης" + }, "contactUs": { "message": "Επικοινωνήστε μαζί μας" }, @@ -708,7 +860,7 @@ "message": "Ανάπτυξη Συμβολαίου" }, "contractDescription": { - "message": "Για να προστατευτείτε από τους απατεώνες, αφιερώστε λίγο χρόνο για να επαληθεύσετε τα στοιχεία του συμβαλλόμενου." + "message": "Για να προστατευτείτε από τους απατεώνες, αφιερώστε λίγο χρόνο για να επαληθεύσετε τα στοιχεία τρίτων." }, "contractInteraction": { "message": "Αλληλεπίδραση Συμβολαίου" @@ -717,16 +869,16 @@ "message": "Συμβόλαιο NFT" }, "contractRequestingAccess": { - "message": "Συμβόλαιο που ζητά πρόσβαση" + "message": "Τρίτος που ζητά πρόσβαση" }, "contractRequestingSignature": { - "message": "Συμβόλαιο με αίτημα υπογραφής" + "message": "Τρίτος που ζητά υπογραφή" }, "contractRequestingSpendingCap": { - "message": "Ο συμβαλλόμενος απαιτεί ανώτατο όριο δαπανών" + "message": "Τρίτος που ζητά ανώτατο όριο δαπανών" }, "contractTitle": { - "message": "Λεπτομέρειες του συμβαλλόμενου" + "message": "Στοιχεία τρίτου μέρους" }, "contractToken": { "message": "Token του συμβαλλόμενου" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Γράφημα εκτιμώμενων τελών συναλλαγής αγοράς" }, + "custodian": { + "message": "Custodian" + }, + "custodianAccount": { + "message": "Λογαριασμός Custodian" + }, + "custodianAccountAddedDesc": { + "message": "Μπορείτε τώρα να χρησιμοποιήσετε τους λογαριασμούς σας ως θεματοφύλακας στο MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Έχουν προστεθεί επιλεγμένοι custodian λογαριασμοί." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Παρακαλούμε μεταβείτε στο $1 και κάντε κλικ στο κουμπί «Σύνδεση με το MMI» στο περιβάλλον εργασίας χρήστη για να συνδέσετε ξανά τους λογαριασμούς σας με το MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Τώρα μπορείτε να χρησιμοποιήσετε τους λογαριασμούς θεματοφύλακα στο MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Το token θεματοφύλακα ανανεώθηκε" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Αυτό θα αντικαταστήσει το token θεματοφύλακα για την ακόλουθη διεύθυνση:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Αντικατάσταση token θεματοφύλακα" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "Εγκρίνετε τη συναλλαγή στην εφαρμογή $1. Μόλις πραγματοποιηθούν όλες οι απαιτούμενες εγκρίσεις σχετικά με τον θεματοφύλακα, η συναλλαγή θα ολοκληρωθεί. Ελέγξτε την κατάσταση στην εφαρμογή $1." + }, + "custodyRefreshTokenModalDescription": { + "message": "Παρακαλούμε μεταβείτε στο $1 και κάντε κλικ στο κουμπί \"Σύνδεση με το MMI\" στο περιβάλλον εργασίας χρήστη για να συνδέσετε ξανά τους λογαριασμούς σας με το MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Ο θεματοφύλακάς σας εκδίδει ένα token που πιστοποιεί την επέκταση του MetaMask Institutional, επιτρέποντάς σας να συνδέσετε τους λογαριασμούς σας." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Αυτό το token λήγει μετά από ένα ορισμένο χρονικό διάστημα για λόγους ασφαλείας. Αυτό απαιτεί την επανασύνδεσή σας στο MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Γιατί το βλέπω αυτό;" + }, + "custodyRefreshTokenModalTitle": { + "message": "Η συνεδρία σας ως θεματοφύλακας έληξε" + }, + "custodySessionExpired": { + "message": "Η συνεδρία θεματοφύλακα έληξε." + }, + "custodyWrongChain": { + "message": "Αυτός ο λογαριασμός δεν έχει ρυθμιστεί για χρήση με $1" + }, "custom": { "message": "Σύνθετες" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "υποστήριξη πελατών" }, + "dappRequestedSpendingCap": { + "message": "Αιτούμενο ανώτατο όριο δαπανών του ιστότοπου" + }, "dappSuggested": { "message": "Προτεινόμενο από την ιστοσελίδα" }, @@ -851,6 +1060,12 @@ "message": "Το $1 έχει προτείνει αυτή την τιμή.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Προτεινόμενος ιστότοπος" + }, + "dappSuggestedHighShortLabel": { + "message": "Ιστότοπος (υψηλό)" + }, "dappSuggestedShortLabel": { "message": "Ιστοσελίδα" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Διαγραφή" }, + "deleteContact": { + "message": "Διαγραφή επαφής" + }, "deleteNetwork": { "message": "Διαγραφή Δικτύου;" }, + "deleteNetworkIntro": { + "message": "Εάν διαγράψετε αυτό το δίκτυο, θα πρέπει να το προσθέσετε ξανά για να δείτε τα περιουσιακά σας στοιχεία σε αυτό το δίκτυο" + }, + "deleteNetworkTitle": { + "message": "Διαγραφή του δικτύου $1;", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Κατάθεση" }, @@ -917,6 +1142,10 @@ "description": { "message": "Περιγραφή" }, + "descriptionFromSnap": { + "message": "Περιγραφή από $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Αυτό το σφάλμα μπορεί να είναι περιστασιακό, οπότε δοκιμάστε να επανεκκινήσετε την επέκταση ή απενεργοποιήστε το MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Παράβλεψη υπενθύμισης αντιγράφου ασφαλείας Ανάκτησης Μυστικής Φράσης" }, + "displayNftMedia": { + "message": "Εμφάνιση των μέσων NFT" + }, + "displayNftMediaDescription": { + "message": "Η εμφάνιση των μέσων και δεδομένων NFT εκθέτει τη διεύθυνση IP σας στο OpenSea ή σε άλλους τρίτους. Αυτό μπορεί να επιτρέψει σε εισβολείς να συσχετίσουν τη διεύθυνση IP σας με τη διεύθυνση σας στο Ethereum. Η αυτόματη ανίχνευση των NFT βασίζεται σε αυτή τη ρύθμιση και δεν θα είναι διαθέσιμη όταν αυτή είναι απενεργοποιημένη." + }, "domain": { "message": "Τομέας" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Ενεργοποίηση Αυτόματου Εντοπισμού" }, + "enableForAllNetworks": { + "message": "Ενεργοποίηση σε όλα τα δίκτυα" + }, "enableFromSettings": { "message": " Ενεργοποίηση του από τις Ρυθμίσεις." }, + "enableSmartSwaps": { + "message": "Ενεργοποίηση των έξυπνων ανταλλαγών" + }, + "enableSnap": { + "message": "Ενεργοποίηση" + }, "enableToken": { "message": "ενεργοποίηση $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Ενεργοποιημένο" + }, "encryptionPublicKeyNotice": { "message": "Το $1 θα ήθελε το δημόσιο σας κλειδί κρυπτογράφησης. Με τη συγκατάθεσή σας, αυτός ο ιστότοπος θα είναι σε θέση να σας συνθέσει κρυπτογραφημένα μηνύματα.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Ο βελτιωμένος εντοπισμός για token είναι επί του παρόντος διαθέσιμος στο $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "Το MetaMask σας επιτρέπει να βλέπετε τομείς ENS όπως \"https://metamask.eth\" ακριβώς στη γραμμή διευθύνσεων του προγράμματος περιήγησής σας. Να πώς λειτουργεί:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Τα συνηθισμένα προγράμματα περιήγησης συνήθως δεν χειρίζονται διευθύνσεις ENS ή IPFS, αλλά το MetaMask βοηθάει σε αυτό. Χρησιμοποιώντας αυτή τη λειτουργία ενδέχεται να μοιραστείτε τη διεύθυνση IP σας με υπηρεσίες IPFS τρίτων." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "Το MetaMask ελέγχει το συμβόλαιο ENS του Ethereum για να βρει τον κώδικα που συνδέεται με το όνομα ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Εάν ο κώδικας συνδέεται με το IPFS, λαμβάνει το περιεχόμενο από το δίκτυο IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Στη συνέχεια, μπορείτε να δείτε το περιεχόμενο, συνήθως μια ιστοσελίδα ή κάτι παρόμοιο." + }, + "ensDomainsSettingTitle": { + "message": "Εμφάνιση τομέων ENS στη γραμμή διευθύνσεων" + }, "ensIllegalCharacter": { "message": "Μη έγκυρος χαρακτήρας για το ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Εισάγετε έναν αριθμό" }, + "enterCustodianToken": { + "message": "Πληκτρολογήστε το token $1 ή προσθέστε ένα νέο token" + }, "enterMaxSpendLimit": { "message": "Εισάγετε Το Μέγιστο Όριο Δαπάνης" }, + "enterOptionalPassword": { + "message": "Πληκτρολογήστε προαιρετικό κωδικό πρόσβασης" + }, "enterPassword": { "message": "Εισάγετε τον κωδικό πρόσβασης" }, "enterPasswordContinue": { "message": "Πληκτρολογήστε τον κωδικό πρόσβασης για να συνεχίσετε" }, + "enterTokenNameOrAddress": { + "message": "Πληκτρολογήστε το όνομα του token ή επικολλήστε τη διεύθυνση" + }, + "enterYourPassword": { + "message": "Πληκτρολογήστε τον κωδικό πρόσβασής σας" + }, "errorCode": { "message": "Κωδικός: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Στοίβα:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Σφάλμα κατά τη σύνδεση στο προσαρμοσμένο δίκτυο." + }, + "errorWithSnap": { + "message": "Σφάλμα με $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Το εφεδρικό τέλος συναλλαγής που παρέχεται ως η κύρια υπηρεσία εκτίμησης τελών συναλλαγής, δεν είναι διαθέσιμο αυτή τη στιγμή." }, + "ethereumProviderAccess": { + "message": "Χορήγηση πρόσβασης στον πάροχο Ethereum σε $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Δημόσια Διεύθυνση Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Πειραματικό" }, + "exploreMetaMaskSnaps": { + "message": "Εξερευνήστε το MetaMask Snaps" + }, "exportPrivateKey": { "message": "Εξαγωγή Ιδιωτικού Κλειδιού" }, + "extendWalletWithSnaps": { + "message": "Επεκτείνετε την εμπειρία του πορτοφολιού." + }, "externalExtension": { "message": "Εξωτερική Επέκταση" }, @@ -1302,6 +1596,9 @@ "message": "Η εισαγωγή αρχείων δεν λειτουργεί; Κάντε κλικ εδώ!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Το υποβαλλόμενο αρχείο είναι πολύ μεγάλο." + }, "flaskWelcomeUninstall": { "message": "θα πρέπει να καταργήσετε την εγκατάσταση αυτής της επέκτασης", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Γενικά" }, + "getStarted": { + "message": "Ξεκινήστε" + }, + "globalTitle": { + "message": "Γενικό μενού" + }, + "globalTourDescription": { + "message": "Δείτε το χαρτοφυλάκιό σας, τις συνδεδεμένες ιστοσελίδες, τις ρυθμίσεις και πολλά άλλα" + }, "goBack": { "message": "Μετάβαση Πίσω" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Συνδέστε ένα πορτοφόλι εξοπλισμού" }, + "hardwareWalletsInfo": { + "message": "Οι ενσωματώσεις πορτοφολιών υλικού χρησιμοποιούν κλήσεις API σε εξωτερικούς διακομιστές, οι οποίοι μπορούν να δουν τη διεύθυνση IP σας και τις διευθύνσεις έξυπνων συμβολαίων με τις οποίες αλληλεπιδράτε." + }, "hardwareWalletsMsg": { "message": "Επιλέξτε ένα πορτοφόλι εξοπλισμού το οποίο θέλετε να χρησιμοποιήσετε με το MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "αλλά οι απατεώνες μπορεί να το κάνουν.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Το ιδιωτικό σας κλειδί παρέχει $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "πλήρη πρόσβαση στο πορτοφόλι και τα κεφάλαιά σας.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "κρατήστε πατημένο για να αποκαλύψετε το κυκλάκι με κλειδί" + }, + "holdToRevealPrivateKey": { + "message": "Κρατήστε πατήμενο για να αποκαλύψετε το ιδιωτικό κλειδί" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Κρατήστε το ιδιωτικό σας κλειδί ασφαλές" + }, "holdToRevealSRP": { - "message": "Κρατήστε το πατημένο για να αποκαλυφθεί το ΜΦΑ" + "message": "Κρατήστε πατημένο για αποκάλυψη της ΜΦΑ" }, "holdToRevealSRPTitle": { - "message": "Κρατήστε το ΜΦΑ σας ασφαλές" + "message": "Κρατήστε τη ΜΦΑ σας ασφαλή" + }, + "holdToRevealUnlockedLabel": { + "message": "κρατήστε πατημένο για να μην αποκαλύψετε το κυκλάκι με κλειδί" + }, + "id": { + "message": "Αναγνωριστικό" }, "ignoreAll": { "message": "Αγνόηση όλων" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Σφάλμα εισαγωγής λογαριασμού." }, + "importAccountErrorIsSRP": { + "message": "Έχετε πληκτρολογήσει μια Μυστική Φράση Ανάκτησης (ή μνημονική). Για να εισαγάγετε έναν λογαριασμό εδώ, πρέπει να πληκτρολογήσετε ένα ιδιωτικό κλειδί, το οποίο είναι μια δεκαεξαδική συμβολοσειρά μήκους 64 χαρακτήρων." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Αυτό δεν είναι ένα έγκυρο ιδιωτικό κλειδί. Πληκτρολογήσατε μια δεκαεξαδική συμβολοσειρά, αλλά πρέπει να έχει μήκος 64 χαρακτήρων." + }, + "importAccountErrorNotHexadecimal": { + "message": "Αυτό δεν είναι ένα έγκυρο ιδιωτικό κλειδί. Πρέπει να εισαγάγετε μια δεκαεξαδική συμβολοσειρά μήκους 64 χαρακτήρων." + }, + "importAccountJsonLoading1": { + "message": "Υπολογίστε ότι αυτή η εισαγωγή JSON θα διαρκέσει μερικά λεπτά και το MetaMask θα «παγώσει»." + }, + "importAccountJsonLoading2": { + "message": "Ζητάμε συγγνώμη και θα το κάνουμε πιο γρήγορα στο μέλλον." + }, "importAccountMsg": { - "message": "Οι λογαριασμοί που εισάγονται δεν θα συσχετιστούν με τη Μυστική Φράση Ανάκτησης του λογαριασμού σας MetaTask που δημιουργήθηκε αρχικά. Μάθετε περισσότερα για τους εισηγμένους λογαριασμούς" + "message": "Οι εισαχθέντες λογαριασμοί δεν θα συσχετίζονται με τη Μυστική Φράση Ανάκτησης στο MetaTask Μάθετε περισσότερα για τους εισαχθέντες λογαριασμούς" }, "importMyWallet": { "message": "Εισαγωγή Πορτοφολιού μου" @@ -1615,18 +1962,29 @@ "message": "Η αρχική σας συναλλαγή επιβεβαιώθηκε από το δίκτυο. Πατήστε ΕΝΤΑΞΕΙ για επιστροφή." }, "inputLogicEmptyState": { - "message": "Εισαγάγετε μόνο έναν αριθμό για τον συμβαλλόμενο που αισθάνεστε άνετα να δαπανήσει χρήματα τώρα ή στο μέλλον. Μπορείτε πάντα να αυξήσετε το ανώτατο όριο δαπανών αργότερα." + "message": "Πληκτρολογήστε μόνο έναν αριθμό που σας βολεύει να ξοδέψει ο τρίτος τώρα ή στο μέλλον. Μπορείτε πάντα να αυξήσετε το ανώτατο όριο δαπανών αργότερα." }, "inputLogicEqualOrSmallerNumber": { - "message": "Αυτό επιτρέπει στον συμβαλλόμενο να δαπανήσει $1 από το τρέχον υπόλοιπό σας.", + "message": "Αυτό επιτρέπει στον τρίτο να ξοδέψει $1 από το τρέχον υπόλοιπό σας.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Αυτό επιτρέπει στον συμβαλλόμενο να δαπανήσει όλο το υπόλοιπό σας μέχρι να φτάσει το ανώτατο όριο ή να ανακαλέσει το ανώτατο όριο δαπανών. Εάν δεν το θέλετε αυτό, εξετάστε το ενδεχόμενο να ορίσετε χαμηλότερο όριο δαπανών." + "message": "Αυτό επιτρέπει στον τρίτο να ξοδέψει όλο το υπόλοιπο των tokens σας μέχρι να φτάσει το ανώτατο όριο ή να ανακαλέσετε το ανώτατο όριο δαπανών. Εάν δεν το θέλετε αυτό, εξετάστε το ενδεχόμενο να ορίσετε χαμηλότερο όριο δαπανών." + }, + "insightsFromSnap": { + "message": "Απόψεις για το $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Εγκατάσταση" }, + "installOrigin": { + "message": "Προέλευση εγκατάστασης" + }, + "installedOn": { + "message": "Εγκαταστάθηκε στο $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Ανεπαρκές υπόλοιπο." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Μη έγκυρη εισαγωγή! Η Μυστική σας Φράση Ανάκτησης κάνει διάκριση πεζών-κεφαλαίων." }, + "ipfsGateway": { + "message": "Πύλη IPFS" + }, + "ipfsGatewayDescription": { + "message": "Το MetaMask χρησιμοποιεί υπηρεσίες τρίτων για να προβάλει εικόνες των NFT που είναι αποθηκευμένα στο IPFS, να εμφανίζει πληροφορίες σχετικά με τις διευθύνσεις ENS που έχουν εισαχθεί στη γραμμή διευθύνσεων του προγράμματος περιήγησης και να αντλεί εικονίδια για διαφορετικά tokens. Η διεύθυνση IP σας ενδέχεται να εκτεθεί σε αυτές τις υπηρεσίες όταν τις χρησιμοποιείτε." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Χρησιμοποιούμε υπηρεσίες τρίτων για την προβολή εικόνων των NFT σας που είναι αποθηκευμένα στο IPFS, την εμφάνιση πληροφοριών σχετικά με τις διευθύνσεις ENS που έχουν εισαχθεί στη γραμμή διευθύνσεων του προγράμματος περιήγησής σας και την ανάκτηση εικονιδίων για διαφορετικά tokens. Η διεύθυνση IP σας ενδέχεται να εκτεθεί σε αυτές τις υπηρεσίες όταν τις χρησιμοποιείτε." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Η επιλογή «Επιβεβαίωση» ενεργοποιεί την ανάλυση IPFS. Μπορείτε να την απενεργοποιήσετε στις $1 ανά πάσα στιγμή.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Ρυθμίσεις > Ασφάλεια και απόρρητο" + }, "jazzAndBlockies": { "message": "Τα Jazzicons και τα Blockies είναι δύο διαφορετικά στυλ μοναδικών εικονιδίων που σας βοηθούν να αναγνωρίζετε έναν λογαριασμό με μια ματιά." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Τελευταία πώληση" }, + "layer1Fees": { + "message": "Τέλη επιπέδου 1" + }, "learnCancelSpeeedup": { "message": "Μάθετε πώς να $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Θέλετε να $1 για το τέλος συναλλαγής;", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Μάθετε περισσότερα" + }, "learnMoreUpperCase": { "message": "Μάθε περισσότερα" }, @@ -1765,16 +2145,16 @@ "message": "Πριν κάνετε κλικ στην επιβεβαίωση:" }, "ledgerConnectionInstructionStepFour": { - "message": "Ενεργοποιήστε τα \"έξυπνα δεδομένα συμβολαίου\" ή \"τυφλή υπογραφή\" στη συσκευή σας Ledger" + "message": "Ενεργοποιήστε τα \"δεδομένα έξυπνων συμβολαίων\" ή την \"τυφλή υπογραφή\" στη συσκευή σας Ledger." }, "ledgerConnectionInstructionStepOne": { - "message": "Ενεργοποίηση χρήσης Ledger Live κάτω από τις Ρυθμίσεις > Για προχωρημένους" + "message": "Ενεργοποιήστε την χρήση του Ledger Live στην ενότητα Ρυθμίσεις > Για προχωρημένους." }, "ledgerConnectionInstructionStepThree": { - "message": "Συνδέστε τη συσκευή Ledger και επιλέξτε την εφαρμογή Ethereum" + "message": "Βεβαιωθείτε ότι το Ledger είναι συνδεδεμένο και επιλέξτε την εφαρμογή Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Άνοιγμα και ξεκλείδωμα εφαρμογής Ledger Live" + "message": "Ανοίξτε και ξεκλειδώστε την εφαρμογή Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Προσαρμόστε το πώς μπορείτε να συνδέσετε Ledger σας με το MetaMask. Το $1 συνιστάται, αλλά και άλλες επιλογές είναι διαθέσιμες. Διαβάστε περισσότερα εδώ: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Δίκτυο δοκιμών Linea Goerli" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "Σύνδεσμος" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Αποσύνδεση" }, + "lockMetaMask": { + "message": "Κλείδωμα του MetaMask" + }, + "lockTimeInvalid": { + "message": "Ο χρόνος κλειδώματος πρέπει να είναι ένας αριθμός μεταξύ 0 και 10080" + }, "logo": { "message": "Λογότυπο $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Το κουμπί κατάστασης σύνδεσης εμφανίζει αν η ιστοσελίδα που επισκέπτεστε είναι συνδεδεμένη με τον τρέχοντα επιλεγμένο λογαριασμό σας." }, + "metamaskInstitutionalVersion": { + "message": "Έκδοση του MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "Συντήρηση των ανταλλαγών MetaMask. Παρακαλούμε ελέγξτε αργότερα." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Μετρήσεις" }, + "mismatchAccount": { + "message": "Ο λογαριασμός ($1) που επιλέξατε είναι διαφορετικός από τον λογαριασμό που προσπαθείτε να υπογράψετε ($2)" + }, "mismatchedChainLinkText": { "message": "επαληθεύστε τα στοιχεία δικτύου", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Το σύμβολο νομίσματος που υποβλήθηκε δεν ταιριάζει με αυτό που αναμενόταν για αυτό το αναγνωριστικό αλυσίδας." }, + "mismatchedRpcChainId": { + "message": "Το αναγνωριστικό αλυσίδας που επιστρέφεται από το προσαρμοσμένο δίκτυο δεν ταιριάζει με το υποβληθέν αναγνωριστικό αλυσίδας." + }, "mismatchedRpcUrl": { "message": "Σύμφωνα με τις καταχωρήσεις μας, η τιμή RPC URL που υποβλήθηκε δεν ταιριάζει με κάποιον γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Υποβάλετε αίτημα εδώ" }, + "mmiAddToken": { + "message": "Η σελίδα στο $1 θα ήθελε να εξουσιοδοτήσει το ακόλουθο token Custodian στο MetaMask Institutional" + }, + "mmiBuiltAroundTheWorld": { + "message": "Το MetaMask Institutional σχεδιάζεται και λειτουργεί σε όλο τον κόσμο." + }, + "more": { + "message": "περισσότερα" + }, "moreComingSoon": { - "message": "Περισσότερα έρχονται σύντομα..." + "message": "Προσεχώς περισσότεροι πάροχοι" + }, + "multipleSnapConnectionWarning": { + "message": "Το $1 θέλει να συνδεθεί με το snap $2. Προχωρήστε μόνο αν εμπιστεύεστε αυτόν τον ιστότοπο.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Πρέπει να επιλέξετε 1 τουλάχιστον διακριτικό." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Το δίκτυο είναι απασχολημένο. Τα τέλη συναλλαγής είναι υψηλά και οι εκτιμήσεις λιγότερο ακριβείς." }, + "networkMenu": { + "message": "Μενού δικτύου" + }, + "networkMenuHeading": { + "message": "Επιλέξτε ένα δίκτυο" + }, "networkName": { "message": "Ονομασία Δικτύου" }, @@ -2033,6 +2450,10 @@ "message": "Τα τέλη συναλλαγών είναι $1 σε σχέση με τις τελευταίες 72 ώρες.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Δεν μπορούμε να συνδεθούμε στο $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL Δικτύου" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Προηγούμενη ιδιοκτησία" + }, "nickname": { "message": "Ψευδώνυμο" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Δεν Υπάρχει Διαθέσιμη Ισοτιμία Μετατροπής" }, + "noNFTs": { + "message": "Δεν υπάρχουν NFT ακόμα" + }, + "noNetworksFound": { + "message": "Δεν βρέθηκαν δίκτυα για το συγκεκριμένο ερώτημα αναζήτησης" + }, "noSnaps": { - "message": "Δεν εγκαταστάθηκαν Snaps" + "message": "Δεν έχετε εγκαταστήσει κανένα snap." }, "noThanksVariant2": { "message": "Όχι, ευχαριστώ." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Είναι ο σωστός λογαριασμός; Είναι διαφορετικό από τον τρέχοντα επιλεγμένο λογαριασμό στο πορτοφόλι σας" }, + "notEnoughBalance": { + "message": "Ανεπαρκές υπόλοιπο" + }, "notEnoughGas": { "message": "Δεν Υπάρχει Αρκετό τέλος συναλλαγής" }, + "note": { + "message": "Σημείωση" + }, + "notePlaceholder": { + "message": "Ο υπεύθυνος έγκρισης θα δει αυτήν τη σημείωση όταν εγκρίνει τη συναλλαγή ως θεματοφύλακας." + }, + "notificationTransactionFailedMessage": { + "message": "Η συναλλαγή $1 απέτυχε! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Η συναλλαγή απέτυχε! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Αποτυχημένη συναλλαγή", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Η συναλλαγή $1 επιβεβαιώθηκε!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Επιβεβαιωμένη συναλλαγή", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Προβολή σε $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Ειδοποιήσεις" + }, "notifications10ActionText": { "message": "Μετάβαση στις Ρυθμίσεις", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Η συγχώνευση στο Ethereum είναι εδώ!" }, + "notifications18ActionText": { + "message": "Ενεργοποίηση ειδοποιήσεων ασφαλείας" + }, + "notifications18DescriptionOne": { + "message": "Λάβετε ειδοποιήσεις από τρίτους όταν μπορεί να έχετε λάβει ένα κακόβουλο αίτημα.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Φροντίστε πάντα να κάνετε τη δική σας επιμελή έρευνα προτού εγκρίνετε οποιαδήποτε αιτήματα.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "Το OpenSea είναι ο πρώτος πάροχος με αυτή τη λειτουργία. Σύντομα θα προστεθούν και άλλοι πάροχοι!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Μείνετε ασφαλείς με ειδοποιήσεις ασφαλείας" + }, + "notifications19ActionText": { + "message": "Ενεργοποίηση αυτόματης ανίχνευσης NFT" + }, + "notifications19DescriptionOne": { + "message": "Δύο τρόποι για να ξεκινήσετε:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Προς το παρόν υποστηρίζουμε μόνο το ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Προσθέστε χειροκίνητα τα NFT σας ή ενεργοποιήστε την αυτόματη ανίχνευση NFT στις Ρυθμίσεις > Πειραματικά.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Δείτε τα NFT σας όπως ποτέ άλλοτε" + }, "notifications1Description": { "message": "Οι χρήστες του MetaMask Mobile μπορούν τώρα να ανταλλάξουν tokens μέσα στο κινητό τους πορτοφόλι. Σαρώστε τον κωδικό QR για να πάρετε την εφαρμογή για κινητά και να αρχίσετε να ανταλλάζετε.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Η ανταλλαγή στο κινητό είναι εδώ!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Μάθετε περισσότερα", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Αν χρησιμοποιείτε την τελευταία έκδοση του Firefox, ίσως αντιμετωπίζετε ένα πρόβλημα που σχετίζεται με την κατάργηση της υποστήριξης U2F από τον Firefox.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Οι χρήστες του Ledger και του Firefox αντιμετωπίζουν προβλήματα σύνδεσης", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Δοκιμάστε το" + }, + "notifications21Description": { + "message": "Ενημερώσαμε τις Ανταλλαγές στην επέκταση του MetaMask ώστε να είναι ευκολότερη και ταχύτερη η χρήση τους.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Παρουσιάζουμε τις νέες και ανανεωμένες Ανταλλαγές!" + }, + "notifications22ActionText": { + "message": "Εντάξει" + }, + "notifications22Description": { + "message": "💡 Απλά κάντε κλικ στο γενικό μενού ή στο μενού λογαριασμού για να τα βρείτε!" + }, + "notifications22Title": { + "message": "Ψάχνετε τα στοιχεία του λογαριασμού σας ή τη διεύθυνση URL στο Block Explorer;" + }, + "notifications23ActionText": { + "message": "Ενεργοποίηση ειδοποιήσεων ασφαλείας" + }, + "notifications23DescriptionOne": { + "message": "Αποφύγετε τις γνωστές απάτες, διατηρώντας παράλληλα το απόρρητό σας με τις ειδοποιήσεις ασφαλείας της Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Εάν ενεργοποιήσατε τις ειδοποιήσεις ασφαλείας από το OpenSea, σας μεταφέραμε σε αυτή τη λειτουργία." + }, + "notifications23DescriptionTwo": { + "message": "Κάνετε πάντα τον δικό σας επιμελή έλεγχο προτού εγκρίνετε τα αιτήματα." + }, + "notifications23Title": { + "message": "Μείνετε ασφαλείς με ειδοποιήσεις ασφαλείας" + }, "notifications3ActionText": { "message": "Μάθε περισσότερα", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Συνδεθείτε μόνο με ιστότοπους που εμπιστεύεστε." }, "openFullScreenForLedgerWebHid": { - "message": "Ανοίξτε το MetaMask σε πλήρη οθόνη για να συνδέσετε το ledger σας μέσω WebHID.", + "message": "Μεταβείτε σε πλήρη οθόνη για να συνδέσετε το Ledger σας.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Προβολή στον εξερευνητή μπλοκ" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Δοκιμαστικά)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Η λειτουργία απέτυχε" + }, "optional": { "message": "Προαιρετικό" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Οι κωδικοί πρόσβασης δεν ταιριάζουν" }, + "pasteJWTToken": { + "message": "Επικολλήστε ή αφήστε το token σας εδώ:" + }, "pastePrivateKey": { "message": "Επικολλήστε τη συμβολοσειρά ιδιωτικού κλειδιού εδώ:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Πρόσβαση στο Διαδίκτυο.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να έχει πρόσβαση στο διαδίκτυο. Αυτό μπορεί να χρησιμοποιηθεί τόσο για την αποστολή όσο και για τη λήψη δεδομένων με διακομιστές τρίτων.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Συνδεθείτε στο Snap $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Επιτρέψτε στον ιστότοπο ή στο συμπληρωματικό πρόγραμμα να αλληλεπιδράσει με το $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Προγραμματισμός και εκτέλεση περιοδικών ενεργειών.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εκτελεί ενέργειες που εκτελούνται περιοδικά σε καθορισμένες ώρες, ημερομηνίες ή διαστήματα. Αυτό μπορεί να χρησιμοποιηθεί για την ενεργοποίηση αλληλεπιδράσεων ή ειδοποιήσεων που σχετίζονται με το χρόνο.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Εμφάνιση παραθύρων διαλόγου στο MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εμφανίζει αναδυόμενα παράθυρα του MetaMask με προσαρμοσμένο κείμενο, πεδίο εισαγωγής και κουμπιά για την έγκριση ή την απόρριψη μιας ενέργειας.\nΜπορεί να χρησιμοποιηθεί για τη δημιουργία π.χ. ειδοποιήσεων, επιβεβαιώσεων και ροών επιλογής για το συμπληρωματικό πρόγραμμα.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Βλέπε διεύθυνση, υπόλοιπο λογαριασμού, δραστηριότητα και έναρξη συναλλαγών", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Πρόσβαση στον πάροχο του Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να επικοινωνεί απευθείας με το MetaMask, προκειμένου να διαβάζει δεδομένα από την αλυσίδα συστοιχιών και να προτείνει μηνύματα και συναλλαγές.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Δημιουργία τυχαίων κλειδιών μοναδικών σε αυτό το στιγμιότυπο.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να παράγει τυχαία κλειδιά μοναδικά για αυτό το συμπληρωματικό πρόγραμμα, χωρίς να τα εκθέτει. Αυτά τα κλειδιά είναι ξεχωριστά από τον λογαριασμό (-ους) σας στο MetaMask και δεν σχετίζονται με τα ιδιωτικά σας κλειδιά ή τη Μυστική Φράση Ανάκτησης. Άλλα συμπληρωματικά προγράμματα δεν μπορούν να έχουν πρόσβαση σε αυτές τις πληροφορίες.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Χρησιμοποιήστε επιχειρηματικά μοντέλα.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Επιτρέψτε στο snap να χρησιμοποιεί lifecycle hook για την εκτέλεση κώδικα σε συγκεκριμένες χρονικές στιγμές κατά τη διάρκεια του κύκλου ζωής του.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Εκτέλεση επ' αόριστον.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εκτελείται επ' αόριστον, ενώ, για παράδειγμα, επεξεργάζεται μεγάλες ποσότητες δεδομένων.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Προσθήκη και έλεγχος λογαριασμών στο Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Ελέγξτε τους λογαριασμούς και τα περιουσιακά σας στοιχεία στο $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εξάγει ζεύγη κλειδιών BIP-32 με βάση τη Μυστική Φράση Ανάκτησης, χωρίς να την αποκαλύψετε. Αυτό παρέχει πλήρη πρόσβαση σε όλους τους λογαριασμούς και τα περιουσιακά στοιχεία στο $1.\nΜε τη δυνατότητα διαχείρισης των κλειδιών, το συμπληρωματικό πρόγραμμα μπορεί να υποστηρίξει μια ποικιλία πρωτοκόλλων blockchain πέραν του Ethereum (EVMs).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Ελέγξτε τους λογαριασμούς και τα περιουσιακά σας στοιχεία σας στο \"$1\".", + "message": "Ελέγξτε τους $1 λογαριασμούς και τα περιουσιακά σας στοιχεία.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εξάγει ζεύγη κλειδιών BIP-44 με βάση τη Μυστική Φράση Ανάκτησης, χωρίς να την αποκαλύψετε. Αυτό παρέχει πλήρη πρόσβαση σε όλους τους λογαριασμούς και τα περιουσιακά στοιχεία στο $1.\nΜε τη δυνατότητα διαχείρισης των κλειδιών, το συμπληρωματικό πρόγραμμα μπορεί να υποστηρίξει μια ποικιλία πρωτοκόλλων blockchain πέραν του Ethereum (EVMs).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Ελέγξτε τους $1 λογαριασμούς σας και τα περιουσιακά σας στοιχεία.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Αποθηκεύστε και διαχειριστείτε τα δεδομένα του στη συσκευή σας.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να αποθηκεύει, να ενημερώνει και να ανακτά δεδομένα με ασφάλεια και με κρυπτογράφηση. Άλλα συμπληρωματικά προγράμματα δεν μπορούν να έχουν πρόσβαση σε αυτές τις πληροφορίες.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Εμφάνιση ειδοποιήσεων.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να εμφανίζει ειδοποιήσεις εντός του MetaMask. Ένα σύντομο κείμενο ειδοποίησης μπορεί να ενεργοποιηθεί από το συμπληρωματικό πρόγραμμα για πληροφορίες που μπορούν να ληφθούν υπόψη ή σχετίζονται με το χρόνο.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Επιτρέψτε στο $1 να επικοινωνήσει απευθείας με αυτό το στιγμιότυπο.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Επιτρέψτε στο $1 να στέλνει μηνύματα στο συμπληρωματικό πρόγραμμα και να λαμβάνει απάντηση από το συμπληρωματικό πρόγραμμα.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Λήψη και εμφάνιση πληροφοριών σχετικά με τις συναλλαγές.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να αποκωδικοποιεί τις συναλλαγές και να εμφανίζει πληροφορίες εντός του MetaMask UI. Αυτό μπορεί να χρησιμοποιηθεί για λύσεις προστασίας κατά της εξαπάτησης και της ασφάλειας.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Δείτε την προέλευση των ιστότοπων που προτείνουν συναλλαγές", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να βλέπει την προέλευση (URI) των ιστότοπων που προτείνουν συναλλαγές. Αυτό μπορεί να χρησιμοποιηθεί για λύσεις προστασίας κατά της εξαπάτησης και της ασφάλειας.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Άγνωστη άδεια: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Δείτε το δημόσιο κλειδί σας για το $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να δει τα δημόσια κλειδιά (και τις διευθύνσεις) σας για το $1. Αυτό δεν παραχωρεί κανέναν έλεγχο λογαριασμών ή περιουσιακών στοιχείων.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Δείτε το δημόσιο κλειδί σας για το $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Υποστήριξη για το WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Επιτρέψτε στο συμπληρωματικό πρόγραμμα να έχει πρόσβαση σε περιβάλλοντα εκτέλεσης χαμηλού επιπέδου μέσω του WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Άδειες" }, + "permissionsTitle": { + "message": "Άδειες" + }, + "permissionsTourDescription": { + "message": "Βρείτε τους συνδεδεμένους λογαριασμούς σας και διαχειριστείτε τις άδειες εδώ" + }, "personalAddressDetected": { "message": "Η προσωπική διεύθυνση εντοπίστηκε. Καταχωρίστε τη διεύθυνση συμβολαίου διακριτικού." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Χαρτοφυλάκιο" }, + "portfolioDashboard": { + "message": "Πίνακας ελέγχου χαρτοφυλακίου" + }, "preferredLedgerConnectionType": { "message": "Προτιμώμενος Τύπος Σύνδεσης Ledger", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Ιδιωτικό Κλειδί", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Ιδιωτικό κλειδί για $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Προειδοποίηση: Μην αποκαλύπτετε ποτέ αυτό το κλειδί. Οποιοσδήποτε έχει τα ιδιωτικά κλειδιά σας μπορεί να κλέψει όλα τα περιουσιακά στοιχεία του λογαριασμού σας." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Σε Αναμονή" }, + "quoteRate": { + "message": "Τιμή προσφοράς" + }, "reAddAccounts": { "message": "προσθέστε εκ νέου τυχόν άλλους λογαριασμούς" }, @@ -2751,7 +3401,7 @@ "message": "Λήψη" }, "recipientAddressPlaceholder": { - "message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS" + "message": "Εισάγετε τη δημόσια διεύθυνση (0x) ή το όνομα ENS" }, "recommendedGasLabel": { "message": "Προτεινόμενο" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Αυτός ο λογαριασμός θα καταργηθεί από το πορτοφόλι σας. Παρακαλούμε βεβαιωθείτε ότι έχετε την αρχική φράση επαναφοράς ή ιδιωτικό κλειδί για αυτόν τον εισαγόμενο λογαριασμό πριν συνεχίσετε. Μπορείτε να εισαγάγετε ή να δημιουργήσετε ξανά λογαριασμούς από το αναπτυσσόμενο μενού του λογαριασμού. " }, + "removeJWT": { + "message": "Αφαίρεση του token θεματοφύλακα" + }, + "removeJWTDescription": { + "message": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε αυτό το token; Όλοι οι λογαριασμοί που αντιστοιχούν σε αυτό το token θα αφαιρεθούν και από την επέκταση: " + }, "removeNFT": { "message": "Αφαίρεση NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Μπορείτε να επαναφέρετε τις ρυθμίσεις χρήστη που περιλαμβάνουν τις προτιμήσεις και τις διευθύνσεις λογαριασμών από ένα αρχείο JSON για το οποίο έχει δημιουργηθεί στο παρελθόν αντίγραφο ασφαλείας." }, + "resultPageError": { + "message": "Σφάλμα" + }, + "resultPageErrorDefaultMessage": { + "message": "Η λειτουργία απέτυχε." + }, + "resultPageSuccess": { + "message": "Επιτυχία" + }, + "resultPageSuccessDefaultMessage": { + "message": "Η λειτουργία ολοκληρώθηκε με επιτυχία." + }, "retryTransaction": { "message": "Επανάληψη Συναλλαγής" }, @@ -2939,16 +3607,27 @@ "message": "Ανάκληση άδειας πρόσβασης σε όλα σας τα $1;", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Ανάκληση της άδειας πρόσβασης και μεταφοράς όλων των NFT σας από το $1;", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Με την ανάκληση της άδειας, το ακόλουθο $1 δεν θα έχει πλέον πρόσβαση στο $2 σας", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Αυτό ανακαλεί την άδεια για ένα τρίτο μέρος να έχει πρόσβαση και να μεταφέρει όλα τα NFT σας από το $1 χωρίς περαιτέρω ειδοποίηση.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Ανάκληση άδειας" + }, "revokeSpendingCap": { "message": "Ανάκληση του ανώτατου ορίου δαπανών για το $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Αυτός ο συμβαλλόμενος δεν θα μπορεί να ξοδέψει άλλα από τα τρέχοντα ή μελλοντικά σας tokens." + "message": "Αυτός ο τρίτος δεν θα μπορεί να ξοδέψει άλλα από τα τρέχοντα ή μελλοντικά σας tokens." }, "rpcUrl": { "message": "Νέο RPC URL" @@ -2974,6 +3653,9 @@ "searchAccounts": { "message": "Αναζήτηση Λογαριασμών" }, + "searchResults": { + "message": "Αποτελέσματα Αναζήτησης" + }, "secretRecoveryPhrase": { "message": "Μυστική Φράση Ανάκτησης" }, @@ -2983,9 +3665,28 @@ "security": { "message": "Ασφάλεια" }, + "securityAlert": { + "message": "Ειδοποίηση ασφαλείας από $1 και $2" + }, + "securityAlerts": { + "message": "Ειδοποιήσεις ασφαλείας" + }, + "securityAlertsDescription1": { + "message": "Αυτή η λειτουργία σας προειδοποιεί για κακόβουλη δραστηριότητα, καθώς εξετάζει τοπικά τις συναλλαγές και τα αιτήματα υπογραφής σας. Τα δεδομένα σας δεν κοινοποιούνται στους τρίτους που παρέχουν αυτή την υπηρεσία. Πάντα να προβαίνετε σε δικό σας επιμελή έλεγχο προτού εγκρίνετε οποιαδήποτε αιτήματα. Δεν υπάρχει καμία εγγύηση ότι αυτή η λειτουργία θα εντοπίσει όλες τις κακόβουλες δραστηριότητες." + }, + "securityAlertsDescription2": { + "message": "Φροντίστε πάντα να κάνετε τον δικό σας επιμελή έλεγχο προτού εγκρίνετε οποιαδήποτε αιτήματα. Δεν υπάρχει καμία εγγύηση ότι όλες οι κακόβουλες δραστηριότητες θα ανιχνεύονται από αυτή τη λειτουργία." + }, "securityAndPrivacy": { "message": "Ασφάλεια και Απόρρητο" }, + "securityProviderAdviceBy": { + "message": "Συμβουλές ασφαλείας από $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Δείτε λεπτομέρειες" + }, "seedPhraseConfirm": { "message": "Επιβεβαίωση Μυστικής Φράσης Ανάκτησης" }, @@ -3040,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Γράψτε τη Μυστική Φράση Ανάκτησης σας" }, + "select": { + "message": "Επιλέξτε" + }, "selectAccounts": { "message": "Επιλέξτε τον/τους λογαριασμό(ούς) που θα χρησιμοποιήσετε σε αυτόν τον ιστότοπο" }, + "selectAccountsForSnap": { + "message": "Επιλέξτε τον λογαριασμό (-ους) που θέλετε να χρησιμοποιήσετε με αυτό το snap" + }, "selectAll": { "message": "Επιλογή όλων" }, + "selectAllAccounts": { + "message": "Επιλέξτε όλους τους λογαριασμούς" + }, "selectAnAccount": { "message": "Επιλέξτε Λογαριασμό" }, "selectAnAccountAlreadyConnected": { "message": "Αυτός ο λογαριασμός έχει ήδη συνδεθεί με το MetaMask" }, + "selectAnAccountHelp": { + "message": "Επιλέξτε τους λογαριασμούς θεματοφύλακα που θα χρησιμοποιηθούν στο MetaMask Institutional." + }, "selectHdPath": { "message": "Επιλέξτε Διαδρομή HD" }, + "selectJWT": { + "message": "Επιλέξτε token" + }, "selectNFTPrivacyPreference": { "message": "Ενεργοποιήστε την ανίχνευση NFT στις Ρυθμίσεις" }, @@ -3110,6 +3826,9 @@ "message": "Έγκριση $1 χωρίς όριο δαπανών", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Προσθήκη λογαριασμού στο snap" + }, "settings": { "message": "Ρυθμίσεις" }, @@ -3138,9 +3857,21 @@ "message": "Επιλέξτε αυτό για να χρησιμοποιήσετε Etherscan για να εμφανίσετε τις εισερχόμενες συναλλαγές στη λίστα συναλλαγών", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Αυτό βασίζεται σε κάθε δίκτυο που θα έχει πρόσβαση στη διεύθυνση Ethereum και στη διεύθυνση IP σας." + }, + "showMore": { + "message": "Εμφάνιση περισσότερων" + }, + "showNft": { + "message": "Εμφάνιση των NFT" + }, "showPermissions": { "message": "Εμφάνιση δικαιωμάτων" }, + "showPrivateKey": { + "message": "Εμφάνιση ιδιωτικού κλειδιού" + }, "showPrivateKeys": { "message": "Εμφάνιση Ιδιωτικών Κλειδιών" }, @@ -3183,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Καταλαβαίνω ότι μέχρι να δημιουργήσω αντίγραφα ασφαλείας για τη Μυστική Φράση Ανάκτησής μου, μπορεί να χάσω τους λογαριασμούς μου και όλα τα περιουσιακά στοιχεία τους." }, + "smartContracts": { + "message": "Έξυπνα συμβόλαια" + }, + "smartSwap": { + "message": "Έξυπνες ανταλλαγές" + }, + "smartSwapsAreHere": { + "message": "Οι Έξυπνες Ανταλλαγές είναι εδώ!" + }, + "smartSwapsDescription": { + "message": "Οι Ανταλλαγές στο MetaMask μόλις έγιναν πολύ πιο έξυπνες! Η ενεργοποίηση των Έξυπνων Ανταλλαγών θα επιτρέψει στο MetaMask να βελτιστοποιήσει προγραμματιστικά τις Ανταλλαγές σας, ώστε να σας βοηθήσει:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Δεν υπάρχουν αρκετά κεφάλαια για έξυπνες ανταλλαγές." + }, + "smartSwapsErrorUnavailable": { + "message": "Οι Έξυπνες Ανταλλαγές είναι προσωρινά μη διαθέσιμες." + }, + "smartSwapsSubDescription": { + "message": "* Οι Έξυπνες Ανταλλαγές θα προσπαθήσουν να υποβάλουν τη συναλλαγή σας ιδιωτικά, πολλές φορές. Εάν αποτύχουν όλες οι προσπάθειες, η συναλλαγή θα μεταδοθεί δημόσια για να διασφαλιστεί ότι η Ανταλλαγή σας θα πραγματοποιηθεί με επιτυχία." + }, + "snapConfigure": { + "message": "Διαμόρφωση" + }, + "snapConnectionWarning": { + "message": "Το $1 θέλει να συνδεθεί με το $2. Συνεχίστε μόνο αν εμπιστεύεστε αυτόν τον ιστότοπο.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Αυτό το περιεχόμενο προέρχεται από το $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Επιλέξτε πώς θα προστατεύσετε τον νέο σας λογαριασμό χρησιμοποιώντας το MetaMask Snaps." + }, + "snapCreateAccountTitle": { + "message": "Δημιουργήστε έναν $1 λογαριασμό", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Από το MetaMask" + }, + "snapDetailAudits": { + "message": "Έλεγχος" + }, + "snapDetailDeveloper": { + "message": "Προγραμματιστής" + }, + "snapDetailLastUpdated": { + "message": "Ενημερώθηκε" + }, + "snapDetailManageSnap": { + "message": "Διαχείριση του snap" + }, + "snapDetailTags": { + "message": "Ετικέτες" + }, + "snapDetailVersion": { + "message": "Έκδοση" + }, + "snapDetailWebsite": { + "message": "Ιστότοπος" + }, + "snapDetailsCreateASnapAccount": { + "message": "Δημιουργήστε έναν λογαριασμό στο Snap" + }, + "snapDetailsInstalled": { + "message": "Εγκαταστάθηκε" + }, "snapError": { "message": "Σφάλμα Snap: '$1'. Κωδικός Σφάλματος: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3194,6 +3994,13 @@ "snapInstall": { "message": "Εγκατάσταση Snap" }, + "snapInstallRequest": { + "message": "Με την εγκατάσταση, εκχωρούνται στο $1 οι ακόλουθες άδειες. Συνεχίστε μόνο αν εμπιστεύεστε το $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Η εγκατάσταση ολοκληρώθηκε" + }, "snapInstallWarningCheck": { "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα πλαίσια ελέγχου.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3202,30 +4009,93 @@ "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα κουτάκια.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Προχωρήστε με προσοχή" + }, "snapInstallWarningKeyAccess": { "message": "Εκχωρείτε στο $2 βασική πρόσβαση στο snap \"$1\". Αυτό είναι αμετάκλητο και παρέχει στο \"$1\" τον έλεγχο των λογαριασμών και των περιουσιακών σας στοιχείων $2. Βεβαιωθείτε ότι εμπιστεύεστε το \"$1\" προτού συνεχίσετε.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Δώστε πρόσβαση με το δημόσιο κλειδί $2 στο $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "Το $1 δεν μπόρεσε να εγκατασταθεί.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Η εγκατάσταση απέτυχε", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Ελέγχθηκε" + }, + "snapResultError": { + "message": "Σφάλμα" + }, + "snapResultSuccess": { + "message": "Επιτυχία" + }, + "snapResultSuccessDescription": { + "message": "Το $1 είναι έτοιμο για χρήση" + }, "snapUpdate": { "message": "Ενημέρωση Snap" }, + "snapUpdateAvailable": { + "message": "Διαθέσιμη ενημέρωση" + }, + "snapUpdateErrorDescription": { + "message": "Το $1 δεν μπόρεσε να ενημερωθεί.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Η ενημέρωση απέτυχε", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "Το $1 θέλει να ενημερώσει το $2 στο $3, το οποίο του δίνει τις ακόλουθες άδειες. Συνεχίστε μόνο αν εμπιστεύεστε το $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Η ενημέρωση ολοκληρώθηκε" + }, "snaps": { "message": "Snaps" }, "snapsInsightLoading": { "message": "Φόρτωση πληροφοριών συναλλαγών..." }, + "snapsInvalidUIError": { + "message": "Το UI που καθορίζεται από το snap δεν είναι έγκυρο." + }, "snapsNoInsight": { "message": "Η ενέργεια δεν επέστρεψε καμία πληροφορία" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Αναγνωρίζετε ότι το συμπληρωματικό πρόγραμμα που πρόκειται να εγκαταστήσετε είναι μια Υπηρεσία Τρίτων όπως ορίζεται στο Consensys $1. Η χρήση των Υπηρεσιών Τρίτων διέπεται από ξεχωριστούς όρους και προϋποθέσεις που καθορίζονται από τον πάροχο των Υπηρεσιών Τρίτων. Η πρόσβαση, η στήριξη ή η χρήση της Υπηρεσίας Τρίτων γίνεται με δική σας ευθύνη. Η Consensys αποποιείται κάθε ευθύνη και υποχρέωση για οποιεσδήποτε απώλειες λόγω της χρήσης από εσάς των Υπηρεσιών Τρίτων.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Οποιεσδήποτε πληροφορίες μοιράζεστε με τις Υπηρεσίες Τρίτων θα συλλέγονται απευθείας από τις εν λόγω Υπηρεσίες Τρίτων σύμφωνα με τις πολιτικές απορρήτου τους. Ανατρέξτε στις πολιτικές απορρήτου τους για περισσότερες πληροφορίες.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Η Consensys δεν έχει πρόσβαση στις πληροφορίες που μοιράζεστε με αυτά τα τρίτα μέρη.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Διαχειριστείτε τα Snaps σας" }, + "snapsTermsOfUse": { + "message": "Όροι Χρήσης" + }, "snapsToggle": { "message": "Ένα snap θα εκτελεστεί μόνο εάν είναι ενεργοποιημένο" }, "snapsUIError": { - "message": "Η Διεπαφή Χρήστη (UI) που καθορίζεται από το στιγμιότυπο δεν είναι έγκυρη.", + "message": "Επικοινωνήστε με τους διαχειριστές του $1 για περαιτέρω υποστήριξη.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3281,6 +4151,9 @@ "message": "Εισαγάγετε μόνο έναν αριθμό στον οποίο αισθάνεστε άνετα με το $1 να έχει πρόσβαση τώρα ή στο μέλλον. Μπορείτε πάντα να αυξήσετε το όριο token αργότερα.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Αίτημα ανώτατου ορίου δαπανών για το $1" + }, "srpInputNumberOfWords": { "message": "Έχω μια φράση $1 λέξεων", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3296,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Ξεκινήστε" }, + "srpSecurityQuizImgAlt": { + "message": "Ένα μάτι με κλειδαρότρυπα στο κέντρο και τρία αιωρούμενα πεδία κωδικών πρόσβασης" + }, "srpSecurityQuizIntroduction": { "message": "Για να σας αποκαλύψουμε τη Μυστική Φράση Ανάκτησης, πρέπει να απαντήσετε σωστά σε δύο ερωτήσεις" }, @@ -3362,6 +4238,9 @@ "stableLowercase": { "message": "σταθερό" }, + "stake": { + "message": "Stake" + }, "stateLogError": { "message": "Σφάλμα κατά την ανάκτηση αρχείων καταγραφής κατάστασης." }, @@ -3380,6 +4259,9 @@ "statusNotConnected": { "message": "Δεν έχει συνδεθεί" }, + "statusNotConnectedAccount": { + "message": "Δεν υπάρχουν συνδεδεμένοι λογαριασμοί" + }, "step1LatticeWallet": { "message": "Συνδέστε το Lattice1 σας" }, @@ -3508,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Αυτό είναι το ελάχιστο ποσό που θα λάβετε. Μπορεί να λάβετε περισσότερα ανάλογα με την ολίσθηση." }, + "swapAnyway": { + "message": "Ανταλλαγή ούτως ή άλλως" + }, "swapApproval": { "message": "Έγκριση $1 για swaps", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3516,6 +4401,12 @@ "message": "Χρειάζεστε $1 περισσότερα $2 για να ολοκληρώσετε αυτήν την ανταλλαγή", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Είστε ακόμα εδώ;" + }, + "swapAreYouStillThereDescription": { + "message": "Είμαστε έτοιμοι να σας δείξουμε τις τελευταίες προσφορές, όποτε θέλετε να συνεχίσετε" + }, "swapBuildQuotePlaceHolderText": { "message": "Δεν υπάρχουν διαθέσιμα tokens που να ταιριάζουν σε $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3523,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Επιβεβαιώστε με το υλικό πορτοφόλι σας" }, + "swapContinueSwapping": { + "message": "Συνεχίστε με τις ανταλλαγές" + }, "swapContractDataDisabledErrorDescription": { "message": "Στην εφαρμογή Ethereum στο Ledger, μεταβείτε στις \"Ρυθμίσεις\" και επιτρέψτε τα δεδομένα συμβολαίου. Στη συνέχεια, δοκιμάστε ξανά την ανταλλαγή σας." }, @@ -3541,6 +4435,9 @@ "swapEditLimit": { "message": "Επεξεργασία ορίου" }, + "swapEditTransactionSettings": { + "message": "Επεξεργασία ρυθμίσεων συναλλαγών" + }, "swapEnableDescription": { "message": "Αυτό απαιτείται και δίνει άδεια στο MetaMask για να ανταλλάξετε το $1 σας.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3549,6 +4446,9 @@ "message": "Αυτό θα $1 για ανταλλαγή", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Πληκτρολογήστε ένα ποσό" + }, "swapEstimatedNetworkFees": { "message": "Εκτιμώμενα τέλη δικτύου" }, @@ -3562,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Η αλλαγή απέτυχε" }, + "swapFetchingQuote": { + "message": "Λήψη προσφοράς" + }, "swapFetchingQuoteNofN": { "message": "Λήψη προσφοράς $1 από $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3602,6 +4505,13 @@ "message": "Περιλαμβάνει $1% τέλος MetaMask.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Περιλαμβάνει μια χρέωση $1% MetaMask - $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Μάθετε περισσότερα για τις Ανταλλαγές" + }, "swapLowSlippageError": { "message": "Η συναλλαγή ενδέχεται να αποτύχει, η μέγιστη ολίσθηση είναι πολύ χαμηλή." }, @@ -3623,6 +4533,10 @@ "message": "Νέες προσφορές σε $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Δεν υπάρχουν διαθέσιμα tokens που να ταιριάζουν σε $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Το $1 σας θα προστεθεί στον λογαριασμό σας μόλις ολοκληρωθεί αυτή η συναλλαγή.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3650,6 +4564,10 @@ "swapQuoteDetails": { "message": "Λεπτομέρειες προσφοράς" }, + "swapQuoteNofM": { + "message": "$1 από $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Πηγή προσφοράς" }, @@ -3659,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Έληξε το χρονικό όριο προσφοράς" }, + "swapQuotesNotAvailableDescription": { + "message": "Μειώστε το μέγεθος της συναλλαγής σας ή χρησιμοποιήστε ένα διαφορετικό token." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Δοκιμάστε να προσαρμόσετε το ποσό ή τις ρυθμίσεις ολίσθησης και δοκιμάστε ξανά." }, @@ -3690,21 +4611,57 @@ "message": "Επιλέξτε μια προσφορά" }, "swapSelectAToken": { - "message": "Επιλέξτε ένα token" + "message": "Επιλέξτε token" }, "swapSelectQuotePopoverDescription": { "message": "Παρακάτω είναι όλες οι προσφορές που συγκεντρώθηκαν από πολλαπλές πηγές ρευστότητας." }, + "swapSelectToken": { + "message": "Επιλέξτε token" + }, + "swapShowLatestQuotes": { + "message": "Εμφάνιση των τελευταίων προσφορών" + }, "swapSlippageNegative": { "message": "Η ολίσθηση πρέπει να είναι μεγαλύτερη ή ίση με το μηδέν" }, + "swapSlippageNegativeDescription": { + "message": "Η απόκλιση πρέπει να είναι μεγαλύτερη ή ίση με μηδέν" + }, + "swapSlippageNegativeTitle": { + "message": "Αυξήστε την απόκλιση για να συνεχίσετε" + }, + "swapSlippageOverLimitDescription": { + "message": "Το περθώριο απόκλισης πρέπει να είναι 15% ή λιγότερο. Οτιδήποτε υψηλότερο θα οδηγήσει σε δυσμενή τιμή." + }, + "swapSlippageOverLimitTitle": { + "message": "Μειώστε την απόκλιση για να συνεχίσετε" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Η μέγιστη απόκλιση είναι πολύ χαμηλή, με αποτέλεσμα την αποτυχία της συναλλαγής σας." + }, + "swapSlippageTooLowTitle": { + "message": "Αυξήστε την απόκλιση για να αποφύγετε την αποτυχία της συναλλαγής" + }, "swapSlippageTooltip": { "message": "Εάν η τιμή αλλάξει μεταξύ της ώρας που η εντολή αγοράς σας υποβάλλεται και επιβεβαιώνεται, αυτό ονομάζεται «ολίσθηση». Η συναλλαγή θα ακυρωθεί αυτόματα εάν η ολίσθηση υπερβαίνει τη ρύθμιση «ανοχή ολίσθησης»." }, + "swapSlippageVeryHighDescription": { + "message": "Η απόκλιση που καταχωρήθηκε θεωρείται πολύ υψηλή και μπορεί να οδηγήσει σε δυσμενή τιμή" + }, + "swapSlippageVeryHighTitle": { + "message": "Πολύ υψηλή απόκλιση" + }, + "swapSlippageZeroDescription": { + "message": "Υπάρχουν λίγοι πάροχοι προσφορών με μηδενική απόκλιση, γεγονός που θα οδηγήσει σε λιγότερο ανταγωνιστική προσφορά." + }, + "swapSlippageZeroTitle": { + "message": "Εξεύρεση παρόχων με μηδενική απόκλιση" + }, "swapSource": { "message": "Πηγή ρευστότητας" }, @@ -3721,7 +4678,7 @@ "message": "Ανταλλαγή από" }, "swapSwapSwitch": { - "message": "Αλλαγή από και προς tokens" + "message": "Αλλαγή της σειράς των tokens" }, "swapSwapTo": { "message": "Εναλλαγή σε" @@ -3729,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "για επιβεβαίωση με το υλικό πορτοφόλι σας" }, + "swapTokenAddedManuallyDescription": { + "message": "Επαληθεύστε αυτό το token στο $1 και βεβαιωθείτε ότι είναι το token που θέλετε να κάνετε συναλλαγές.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Το token προστέθηκε χειροκίνητα" + }, "swapTokenAvailable": { "message": "Το $1 σας έχει προστεθεί στον λογαριασμό σας.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3755,6 +4719,13 @@ "message": "Επαληθευμένο σε $1 πηγές.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "Το $1 επαληθεύεται μόνο από 1 πηγή. Εξετάστε το ενδεχόμενο επαλήθευσης σε $2 πριν προχωρήσετε.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Ενδεχομένως μη αυθεντικό token" + }, "swapTooManyDecimalsError": { "message": "Το $1 επιτρέπει έως και $2 δεκαδικά ψηφία", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3792,9 +4763,16 @@ "message": "Δεν υπάρχουν αρκετά $1 για να ολοκληρωθεί αυτή η συναλλαγή", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Δεν υπάρχουν αρκετά $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Προβολή σε δραστηριότητα" }, + "switch": { + "message": "Αλλαγή" + }, "switchEthereumChainConfirmationDescription": { "message": "Αυτό θα αλλάξει το επιλεγμένο δίκτυο στο MetaMask σε ένα δίκτυο που έχει προστεθεί προηγουμένως:" }, @@ -3817,6 +4795,12 @@ "switchedTo": { "message": "Έχετε αλλάξει σε" }, + "switcherTitle": { + "message": "Επιλογέας δικτύου" + }, + "switcherTourDescription": { + "message": "Κάντε κλικ στο εικονίδιο για να αλλάξετε δίκτυο ή να προσθέσετε ένα νέο δίκτυο" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Η εναλλαγή δικτύων θα ακυρώσει όλες τις εκκρεμείς επιβεβαιώσεις" }, @@ -3835,6 +4819,18 @@ "termsOfService": { "message": "Όροι παροχής υπηρεσιών" }, + "termsOfUse": { + "message": "όροι χρήσης" + }, + "termsOfUseAgreeText": { + "message": " Συμφωνώ με τους Όρους Χρήσης, οι οποίοι ισχύουν για τη χρήση του MetaMask και όλων των λειτουργιών του" + }, + "termsOfUseFooterText": { + "message": "Μετακινηθείτε προς τα κάτω για να διαβάσετε όλα τα τμήματα" + }, + "termsOfUseTitle": { + "message": "Οι Όροι Χρήσης μας έχουν ενημερωθεί" + }, "testNetworks": { "message": "Δοκιμαστικά δίκτυα" }, @@ -3847,6 +4843,17 @@ "thingsToKeep": { "message": "Πράγματα που πρέπει να έχετε υπόψη σας:" }, + "thirdPartySoftware": { + "message": "Ειδοποίηση για λογισμικό τρίτων", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "αυτή η συλλογή" + }, + "thisServiceIsExperimental": { + "message": "Η υπηρεσία αυτή είναι πειραματική. Ενεργοποιώντας αυτή τη λειτουργία, συμφωνείτε με τους $1 του OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Ώρα" }, @@ -3860,11 +4867,44 @@ "message": "Προς: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Κινδυνεύετε από επιθέσεις phishing. Προστατευτείτε απενεργοποιώντας το eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Ενεργοποιήστε το για να επιτρέπετε στις εφαρμογές να ζητούν την υπογραφή σας χρησιμοποιώντας αιτήματα eth_sign. Το eth_sign είναι μια ανοιχτή μέθοδος υπογραφής που σας επιτρέπει να υπογράψετε έναν τυχαίο κατακερματισμό, γεγονός που το καθιστά επικίνδυνο για phishing. Υπογράψτε αιτήματα eth_sign μόνο αν μπορείτε να διαβάσετε αυτό που υπογράφετε και εμπιστεύεστε την προέλευση του αιτήματος." + "message": "Αν ενεργοποιήσετε αυτή τη ρύθμιση, ενδέχεται να λάβετε αιτήματα υπογραφής που δεν είναι αναγνώσιμα. Υπογράφοντας ένα μήνυμα που δεν καταλαβαίνετε, μπορεί να συμφωνείτε να παραχωρήσετε τα κεφάλαια και τα NFT σας." }, "toggleEthSignField": { - "message": "Εναλλαγή αιτημάτων eth_sign" + "message": "Αιτήματα eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " μπορεί να σας εξαπατήσουν" + }, + "toggleEthSignModalBannerText": { + "message": "Αν σας ζητήθηκε να ενεργοποιήσετε αυτή τη ρύθμιση," + }, + "toggleEthSignModalCheckBox": { + "message": "Κατανοώ ότι μπορεί να χάσω όλα μου τα κεφάλαια και τα NFT αν ενεργοποιήσω τα αιτήματα eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Η αποδοχή αιτημάτων eth_sign μπορεί να σας καταστήσει ευάλωτους σε επιθέσεις phishing. Να ελέγχετε πάντα τη διεύθυνση URL και να είστε προσεκτικοί όταν υπογράφετε μηνύματα που περιέχουν κώδικα." + }, + "toggleEthSignModalFormError": { + "message": "Το κείμενο είναι λανθασμένο" + }, + "toggleEthSignModalFormLabel": { + "message": "Πληκτρολογήστε «Υπογράφω μόνο ό,τι κατανοώ» για να συνεχίσετε" + }, + "toggleEthSignModalFormValidation": { + "message": "Υπογράφω μόνο ό,τι κατανοώ" + }, + "toggleEthSignModalTitle": { + "message": "Χρησιμοποιήστε το με δική σας ευθύνη" + }, + "toggleEthSignOff": { + "message": "ΑΝΕΝΕΡΓΟ (συνιστάται)" + }, + "toggleEthSignOn": { + "message": "ΕΝΕΡΓΟ (δεν συνιστάται)" }, "token": { "message": "Διακριτικό" @@ -3908,6 +4948,9 @@ "tokenSymbol": { "message": "Σύμβολο Token" }, + "tokens": { + "message": "Tokens" + }, "tokensFoundTitle": { "message": "Βρέθηκαν $1 νέα tokens", "description": "$1 is the number of new tokens detected" @@ -3915,6 +4958,12 @@ "tooltipApproveButton": { "message": "Καταλαβαίνω" }, + "tooltipSatusConnected": { + "message": "συνδεδεμένο" + }, + "tooltipSatusNotConnected": { + "message": "μη συνδεδεμένο" + }, "total": { "message": "Σύνολο" }, @@ -3987,6 +5036,9 @@ "transactionErrored": { "message": "Η συναλλαγή αντιμετώπισε ένα σφάλμα." }, + "transactionFailed": { + "message": "Η συναλλαγή απέτυχε" + }, "transactionFee": { "message": "Χρέωση Συναλλαγής" }, @@ -4011,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Σύνολο Τέλους Συναλλαγής" }, + "transactionNote": { + "message": "Σημείωση συναλλαγής" + }, "transactionResubmitted": { "message": "Η συναλλαγή υποβλήθηκε ξανά με το τέλος gas να έχει αυξηθεί για $1 σε $2" }, "transactionSecurityCheck": { - "message": "Ενεργοποίηση παρόχων ασφάλειας συναλλαγών" + "message": "Ενεργοποίηση ειδοποιήσεων ασφαλείας" }, "transactionSecurityCheckDescription": { "message": "Χρησιμοποιούμε API τρίτων για τον εντοπισμό και την εμφάνιση των κινδύνων που ενέχουν τα ανυπόγραφα αιτήματα συναλλαγών και υπογραφών πριν τα υπογράψετε. Αυτές οι υπηρεσίες θα έχουν πρόσβαση στα ανυπόγραφα αιτήματα συναλλαγών και υπογραφών σας, στη διεύθυνση του λογαριασμού σας και στην προτιμώμενη γλώσσα σας." }, + "transactionSettings": { + "message": "Ρυθμίσεις συναλλαγών" + }, "transactionSubmitted": { "message": "Η συναλλαγή στάλθηκε με τέλος gas του $1 σε $2." }, @@ -4035,6 +5093,22 @@ "transferFrom": { "message": "Μεταφορά Από" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Έχουμε πρόβλημα με τη σύνδεσή σας στο Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Ανατρέξτε στον οδηγό σύνδεσης πορτοφολιού υλικού και δοκιμάστε ξανά.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Αν χρησιμοποιείτε την τελευταία έκδοση του Firefox, ίσως αντιμετωπίζετε ένα πρόβλημα που σχετίζεται με την κατάργηση της υποστήριξης U2F από τον Firefox. Μάθετε πώς να διορθώσετε αυτό το πρόβλημα $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "εδώ", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Είχαμε πρόβλημα να συνδεθούμε με το $1 σας, δοκιμάστε να ξαναδείτε το $2 και προσπαθήστε ξανά.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4121,6 +5195,9 @@ "upArrow": { "message": "πάνω βέλος" }, + "update": { + "message": "Ενημέρωση" + }, "updatedWithDate": { "message": "Ενημερώθηκε το $1" }, @@ -4130,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Αυτό το URL χρησιμοποιείται επί του παρόντος από το δίκτυο $1." }, + "use4ByteResolution": { + "message": "Αποκωδικοποίηση έξυπνων συμβολαίων" + }, + "use4ByteResolutionDescription": { + "message": "Για να βελτιώσουμε την εμπειρία του χρήστη, προσαρμόζουμε την καρτέλα δραστηριότητας με μηνύματα που βασίζονται στα έξυπνα συμβόλαια με τα οποία αλληλεπιδράτε. Το MetaMask χρησιμοποιεί μια υπηρεσία που ονομάζεται 4byte.directory για την αποκωδικοποίηση δεδομένων και την εμφάνιση μιας έκδοσης ενός έξυπνου συμβολαίου που είναι πιο ευανάγνωστο. Αυτό συμβάλλει στη μείωση των πιθανοτήτων σας να εγκρίνετε κακόβουλες ενέργειες έξυπνων συμβολαίων, αλλά μπορεί να έχει ως αποτέλεσμα την κοινοποίηση της διεύθυνσης IP σας." + }, "useMultiAccountBalanceChecker": { "message": "Μαζικά αιτήματα υπολοίπου λογαριασμού" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Γρηγορότερες ενημερώσεις υπολοίπου με ομαδοποίηση αιτημάτων υπολοίπου λογαριασμού. Αυτό μας επιτρέπει να συγκεντρώνουμε τα υπόλοιπα των λογαριασμών σας μαζί, ώστε να λαμβάνετε ταχύτερες ενημερώσεις για βελτιωμένη εμπειρία. Όταν αυτή η λειτουργία είναι απενεργοποιημένη, τα τρίτα μέρη ενδέχεται να είναι λιγότερο πιθανό να συσχετίσουν τους λογαριασμούς σας μεταξύ τους." + }, "useNftDetection": { "message": "Αυτόματη Ανίχνευση NFT" }, @@ -4157,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Εμφάνιση μιας προειδοποίησης για τομείς Απάτης Ηλεκτρονικού Ψαρέματος που στοχεύουν χρήστες του Ethereum" }, + "useSiteSuggestion": { + "message": "Χρήση πρότασης ιστότοπου" + }, "useTokenDetectionPrivacyDesc": { "message": "Η αυτόματη εμφάνιση των token που αποστέλλονται στον λογαριασμό σας συνεπάγεται επικοινωνία με διακομιστές τρίτων για τη λήψη εικόνων των token. Αυτοί οι διακομιστές θα έχουν πρόσβαση στη διεύθυνση IP σας." }, @@ -4167,7 +5256,7 @@ "message": "Όνομα χρήστη" }, "verifyContractDetails": { - "message": "Επιβεβαίωση στοιχείων συμβαλλομένου" + "message": "Επαλήθευση στοιχείων τρίτων" }, "verifyThisTokenDecimalOn": { "message": "Το δεκαδικό token μπορεί να βρεθεί σε $1", @@ -4181,12 +5270,18 @@ "message": "Επαληθεύστε αυτό το token για $1 και βεβαιωθείτε ότι αυτό είναι το token που θέλετε να κάνετε συναλλαγές.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Έκδοση" + }, "view": { "message": "Προβολή" }, "viewAllDetails": { "message": "Προβολή όλων των λεπτομερειών" }, + "viewAllQuotes": { + "message": "προβολή όλων των προσφορών" + }, "viewContact": { "message": "Εμφάνιση Επαφής" }, @@ -4210,9 +5305,18 @@ "message": "Προβολή $1 στην Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Προβολή στον Eξερευνητή" + }, "viewOnOpensea": { "message": "Προβολή στο Opensea" }, + "viewPortfolioDashboard": { + "message": "Προβολή πίνακα ελέγχου χαρτοφυλακίου" + }, + "viewinCustodianApp": { + "message": "Προβολή στην custodian εφαρμογή" + }, "viewinExplorer": { "message": "Προβολή $1 στον Εξερευνητή", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4246,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Θέλετε να προσθέσετε αυτό το δίκτυο;" }, + "wantsToAddThisAsset": { + "message": "Το $1 θέλει να προσθέσει αυτό το περιουσιακό στοιχείο στο πορτοφόλι σας", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Προειδοποίηση" }, "warningTooltipText": { - "message": "$1 Ο συμβαλλόμενος μπορεί να ξοδέψει ολόκληρο το υπόλοιπο των tokens σας χωρίς περαιτέρω ειδοποίηση ή συγκατάθεση. Προστατέψτε τον εαυτό σας προσαρμόζοντας ένα χαμηλότερο όριο δαπανών.", + "message": "$1 Ο τρίτος θα μπορούσε να ξοδέψει ολόκληρο το υπόλοιπο των tokens σας χωρίς περαιτέρω ειδοποίηση ή συγκατάθεση. Προστατέψτε τον εαυτό σας προσαρμόζοντας ένα χαμηλότερο όριο δαπανών.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4317,6 +5425,9 @@ "youSign": { "message": "Υπογράφετε" }, + "yourAccounts": { + "message": "Οι λογαριασμοί σας" + }, "yourFundsMayBeAtRisk": { "message": "Τα κεφάλαιά σας μπορεί να κινδυνεύουν" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 2f094077c..0c81f3cfb 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Acerca de" }, + "accept": { + "message": "Aceptar" + }, "acceptTermsOfUse": { "message": "Leí y estoy de acuerdo con $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Añadir un apodo" }, + "addAccount": { + "message": "Añadir cuenta" + }, "addAcquiredTokens": { "message": "Agregar los tokens que adquirió con MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Agregue desde una lista de redes populares o agregue una red manualmente. Interactúe solo con las entidades en las que confía." }, + "addHardwareWallet": { + "message": "Agregar monedero físico" + }, + "addIPFSGateway": { + "message": "Agregue su puerta de enlace de IPFS preferida" + }, "addMemo": { "message": "Añadir memo" }, @@ -244,6 +256,21 @@ "message": "Esta conexión de red depende de terceros. Y puede ser menos confiable o permite que terceros rastreen la actividad. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Agregar nuevo token" + }, + "addNft": { + "message": "Añadir NFT" + }, + "addNfts": { + "message": "Añadir NFT" + }, + "addSnapAccountModalDescription": { + "message": "Descubra opciones para mantener su cuenta segura con MetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Añadir NFT sugeridos" + }, "addSuggestedTokens": { "message": "Agregar tokens sugeridos" }, @@ -254,6 +281,9 @@ "message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Agregando red" + }, "address": { "message": "Dirección" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "La tarifa de prioridad (también llamada “propina del minero”) va directamente a los mineros para incentivarlos a priorizar su transacción." }, + "agreeTermsOfUse": { + "message": "Acepto los $1 de MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "Bóveda AirGap" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Alertas" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Ya ha conectado todas sus cuentas custodiadas o no tiene ninguna cuenta para conectarse a MetaMask Institucional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "No hay cuentas disponibles para conectarse" + }, "allOfYour": { "message": "Todo su $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Todos sus NFT de $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Permitir que esta extensión externa haga lo siguiente:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Permitir que este sitio haga lo siguiente:" }, + "allowThisSnapTo": { + "message": "Permitir que este snap haga:" + }, "allowWithdrawAndSpend": { "message": "Permitir que se retire $1 y gastar hasta el siguiente importe:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Importe" }, + "apiUrl": { + "message": "URL de la API" + }, "appDescription": { "message": "Una cartera de Ethereum en el explorador", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "¿Dar permiso para acceder a todo su $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "¿Permitir acceder a y transferir todos sus NFT desde $1?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Aprobar" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Esto permite que un tercero acceda y transfiera los siguientes NFT sin previo aviso hasta que usted revoque su acceso." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Esto permite que un tercero acceda y transfiera todos sus NFT de $1 sin previo aviso hasta que usted revoque su acceso.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "¿Permitir el acceso y la transferencia de su $1?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Si intenta enviar activos directamente de una red a otra, esto puede provocar la pérdida permanente de activos. Asegúrese de utilizar un puente." }, + "attemptToCancelSwap": { + "message": "Intentar cancelar el intercambio por ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Intentando una conexión a la cadena de bloques." }, @@ -411,6 +473,9 @@ "average": { "message": "Promedio" }, + "awaitingApproval": { + "message": "Esperando aprobación..." + }, "back": { "message": "Volver" }, @@ -454,6 +519,9 @@ "message": "Esta es una versión beta. Por favor, comunique los errores $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Versión beta de MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Versión Beta de MetaMask" }, @@ -488,9 +556,45 @@ "message": "Ver cuenta en $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Si aprueba esta solicitud, un tercero conocido por realizar estafas podría tomar todos sus activos." + }, + "blockaidDescriptionBlurFarming": { + "message": "Si aprueba esta solicitud, alguien puede robar sus activos enlistados en Blur." + }, + "blockaidDescriptionFailed": { + "message": "Debido a un error, el proveedor de seguridad no verificó esta solicitud. Proceda con precaución." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Está interactuando con un dominio malicioso. Si aprueba esta solicitud, podría perder sus activos." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Si aprueba esta solicitud, podría perder sus activos." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Si aprueba esta solicitud, alguien puede robar sus activos enlistados en OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Si aprueba esta solicitud, un tercero conocido por estafas tomará todos sus activos." + }, + "blockaidTitleDeceptive": { + "message": "Esta es una solicitud engañosa" + }, + "blockaidTitleMayNotBeSafe": { + "message": "La solicitud puede no ser segura" + }, + "blockaidTitleSuspicious": { + "message": "Esta es una solicitud sospechosa" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Puente" + }, "browserNotSupported": { "message": "Su explorador no es compatible..." }, @@ -510,6 +614,10 @@ "message": "Comprar $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Comprar más $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Comprar ahora" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Esto restablece el nonce de la cuenta y borra los datos de la pestaña de actividad en su billetera. Solo la cuenta actual y la red se verán afectadas. Sus saldos y transacciones entrantes no cambiarán." }, + "click": { + "message": "Clic" + }, "clickToConnectLedgerViaWebHID": { "message": "Haga clic aquí para conectar su Ledger a través de WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Ahora está saliendo de MetaMask para configurar este snap." + }, + "configureSnapPopupInstallDescription": { + "message": "Ahora está saliendo de MetaMask para instalar este snap." + }, + "configureSnapPopupInstallTitle": { + "message": "Instalar snap" + }, + "configureSnapPopupLink": { + "message": "Haga clic para continuar:" + }, + "configureSnapPopupTitle": { + "message": "Configurar snap" + }, "confirm": { "message": "Confirmar" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Conectar cuenta o crear nueva" }, + "connectCustodialAccountMenu": { + "message": "Conectar cuenta custodiada" + }, + "connectCustodialAccountMsg": { + "message": "Elija la cuenta custodiada que desea conectar para agregar o actualizar un token." + }, + "connectCustodialAccountTitle": { + "message": "Cuentas custodiadas" + }, "connectManually": { "message": "Conectarse manualmente al sitio actual" }, + "connectSnap": { + "message": "Conectar $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Conectarse a $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Conectando a la red de prueba Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Estableciendo conexión a la red principal de Linea" + }, "connectingToMainnet": { "message": "Estableciendo conexión a la red principal de Ethereum" }, "connectingToSepolia": { "message": "Conectando a la red de prueba Sepolia" }, + "connectionFailed": { + "message": "Conexión fallida" + }, + "connectionFailedDescription": { + "message": "Error al obtener $1, verifique su red y vuelva a intentarlo.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Solicitud de conexión" + }, "contactUs": { "message": "Contáctenos" }, @@ -708,7 +860,7 @@ "message": "Implementación de contrato" }, "contractDescription": { - "message": "Para protegerse contra estafadores, tómese un momento para verificar los detalles del contrato." + "message": "Para protegerse contra estafadores, tómese un momento para verificar los detalles del tercero." }, "contractInteraction": { "message": "Interacción con el contrato" @@ -717,16 +869,16 @@ "message": "Contrato de NFT" }, "contractRequestingAccess": { - "message": "Contrato que solicita el acceso" + "message": "Tercero que solicita acceso" }, "contractRequestingSignature": { - "message": "Contrato con solicitud de firma" + "message": "Tercero que solicita firma" }, "contractRequestingSpendingCap": { - "message": "Contrato que solicita límite de gasto" + "message": "Tercero que solicita límite de gasto" }, "contractTitle": { - "message": "Detalles del contrato" + "message": "Detalles del tercero" }, "contractToken": { "message": "Contrato del token" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Gráfico de estimación de gas de mercado" }, + "custodian": { + "message": "Custodio" + }, + "custodianAccount": { + "message": "Cuenta custodiada" + }, + "custodianAccountAddedDesc": { + "message": "Ahora puede usar sus cuentas de custodia en MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Se agregaron cuentas de custodia seleccionadas." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Por favor, vaya a $1 y haga clic en el botón 'Conectar a MMI' dentro de su interfaz de usuario para volver a conectar sus cuentas a MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Ahora ya puede usar sus cuentas custodiadas en MetaMask Institucional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Su token custodiado se ha actualizado" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Esto reemplazará el token custodiado de la siguiente dirección:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Reemplazar token custodiado" + }, + "custodyApiUrl": { + "message": "$1 URL de la API" + }, + "custodyDeeplinkDescription": { + "message": "Apruebe la transacción en la aplicación $1. Una vez que se hayan realizado todas las aprobaciones custodiadas requeridas, la transacción se completará. Verifique el estado en su aplicación $1." + }, + "custodyRefreshTokenModalDescription": { + "message": "Vaya a $1 y haga clic en el botón 'Conectar a MMI' dentro de su interfaz de usuario para volver a conectar sus cuentas a MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Su custodio emite un token que autentica la extensión MetaMask Institucional, lo que le permite conectar sus cuentas." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Este token caduca después de un cierto período por razones de seguridad. Esto requiere que vuelva a conectarse a MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "¿Por qué estoy viendo esto?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Su sesión de custodio ha expirado" + }, + "custodySessionExpired": { + "message": "Su sesión custodiada ha expirado." + }, + "custodyWrongChain": { + "message": "Esta cuenta no está configurada para utilizarla con $1" + }, "custom": { "message": "Avanzado" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "atención al cliente" }, + "dappRequestedSpendingCap": { + "message": "Límite de gasto solicitado por el sitio" + }, "dappSuggested": { "message": "Sitio sugerido" }, @@ -851,6 +1060,12 @@ "message": "$1 ha sugerido este precio.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Sitio sugerido" + }, + "dappSuggestedHighShortLabel": { + "message": "Sitio (alto)" + }, "dappSuggestedShortLabel": { "message": "Sitio" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Eliminar" }, + "deleteContact": { + "message": "Eliminar contacto" + }, "deleteNetwork": { "message": "¿Eliminar red?" }, + "deleteNetworkIntro": { + "message": "Si elimina esta red, deberá volver a agregarla para ver sus activos en esta red" + }, + "deleteNetworkTitle": { + "message": "¿Eliminar la red de $1?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Depositar" }, @@ -917,6 +1142,10 @@ "description": { "message": "Descripción" }, + "descriptionFromSnap": { + "message": "Descripción de $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Este error podría ser intermitente, así que intente reiniciar la extensión o desactive MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Ignorar el recordatorio de respaldo de la frase de recuperación" }, + "displayNftMedia": { + "message": "Mostrar medios NFT" + }, + "displayNftMediaDescription": { + "message": "Mostrar los medios y datos NFT expone su dirección IP a OpenSea u otros terceros. Esto puede permitir que los atacantes asocien su dirección IP con su dirección Ethereum. La detección automática de NFT se basa en esta configuración y no estará disponible cuando esté desactivada." + }, "domain": { "message": "Dominio" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Activar autodetección" }, + "enableForAllNetworks": { + "message": "Habilitar para todas las redes" + }, "enableFromSettings": { "message": " Actívela en Configuración." }, + "enableSmartSwaps": { + "message": "Habilitar intercambios inteligentes" + }, + "enableSnap": { + "message": "Activar" + }, "enableToken": { "message": "activar $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Activado" + }, "encryptionPublicKeyNotice": { "message": "$1 quisiera su clave pública de cifrado. Al aceptar, este sitio podrá redactar mensajes cifrados para usted.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Actualmente, la detección mejorada de token se encuentra disponible en $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask le permite ver dominios ENS como \"https://metamask.eth\" directamente en la barra de direcciones de su navegador. Así es como funciona:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Los navegadores normales no suelen manejar direcciones ENS o IPFS, pero MetaMask ayuda con esto. El uso de esta función podría compartir su dirección IP con servicios de terceros de IPFS." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask verifica con el contrato ENS de Ethereum para encontrar el código conectado al nombre ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Si el código está vinculado a IPFS, obtiene el contenido de la red IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Después, puede ver el contenido, generalmente un sitio web o algo similar." + }, + "ensDomainsSettingTitle": { + "message": "Mostrar dominios ENS en la barra de direcciones" + }, "ensIllegalCharacter": { "message": "Caracter ilegal para ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Ingrese un número" }, + "enterCustodianToken": { + "message": "Ingrese su token $1 o agregue un token nuevo" + }, "enterMaxSpendLimit": { "message": "Escribir límite máximo de gastos" }, + "enterOptionalPassword": { + "message": "Ingrese la contraseña opcional" + }, "enterPassword": { "message": "Escribir contraseña" }, "enterPasswordContinue": { "message": "Escribir contraseña para continuar" }, + "enterTokenNameOrAddress": { + "message": "Ingrese el nombre del token o pegue la dirección" + }, + "enterYourPassword": { + "message": "Ingrese su contraseña" + }, "errorCode": { "message": "Código: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Pila:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Error al conectarse a la red personalizada." + }, + "errorWithSnap": { + "message": "Error con $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Se muestra el precio del gas de respaldo, ya que el servicio para calcular el precio del gas principal no se encuentra disponible en este momento." }, + "ethereumProviderAccess": { + "message": "Otorgar acceso al proveedor de Ethereum a $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Dirección pública de Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Experimental" }, + "exploreMetaMaskSnaps": { + "message": "Explore complementos de MetaMask" + }, "exportPrivateKey": { "message": "Exportar clave privada" }, + "extendWalletWithSnaps": { + "message": "Amplíe la experiencia de uso de la cartera." + }, "externalExtension": { "message": "Extensión externa" }, @@ -1302,6 +1596,9 @@ "message": "¿No funciona la importación del archivo? Haga clic aquí.", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "El archivo arrastrado es demasiado grande." + }, "flaskWelcomeUninstall": { "message": "le recomendamos que desinstale esta extensión", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "General" }, + "getStarted": { + "message": "Comenzar" + }, + "globalTitle": { + "message": "Menú global" + }, + "globalTourDescription": { + "message": "Vea su portafolio, sitios conectados, configuraciones y más" + }, "goBack": { "message": "Volver" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Conectar una cartera de hardware" }, + "hardwareWalletsInfo": { + "message": "Las integraciones de billetera de hardware utilizan llamadas API a servidores externos, que pueden ver su dirección IP y las direcciones de contrato inteligente con las que interactúa." + }, "hardwareWalletsMsg": { "message": "Seleccione una cartera de hardware que desee usar con MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "pero los defraudadores sí.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Su clave privada proporciona $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "acceso completo a su monedero y a sus fondos.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "mantenga presionado para revelar el círculo bloqueado" + }, + "holdToRevealPrivateKey": { + "message": "Mantenga presionado para revelar su clave privada" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Mantenga segura su clave privada" + }, "holdToRevealSRP": { - "message": "Mantén presionado para revelar su SRP" + "message": "Mantenga presionado para revelar su SRP" }, "holdToRevealSRPTitle": { - "message": "Proteja su SRP" + "message": "Mantenga segura su SRP" + }, + "holdToRevealUnlockedLabel": { + "message": "mantenga presionado para revelar el círculo desbloqueado" + }, + "id": { + "message": "Id." }, "ignoreAll": { "message": "Ignorar todo" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Error al importar la cuenta." }, + "importAccountErrorIsSRP": { + "message": "Ha ingresado una frase de recuperación secreta (o mnemotécnica). Para importar una cuenta aquí, debe ingresar una clave privada, que es una cadena hexadecimal de 64 caracteres." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Esta no es una clave privada válida. Ha ingresado una cadena hexadecimal, pero debería tener 64 caracteres." + }, + "importAccountErrorNotHexadecimal": { + "message": "Esta no es una clave privada válida. Debe ingresar una cadena hexadecimal de 64 caracteres." + }, + "importAccountJsonLoading1": { + "message": "Considere que esta importación de JSON tomará unos minutos y congelerá su MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Nos disculpamos, y lo haremos más rápido en un futuro." + }, "importAccountMsg": { - "message": " Las cuentas importadas no se asociarán con la frase secreta de recuperación de la cuenta original de MetaMask. Más información sobre las cuentas importadas " + "message": "Las cuentas importadas no se asociarán con su frase secreta de recuperación de MetaMask. Obtenga más información sobre las cuentas importadas" }, "importMyWallet": { "message": "Importar mi cartera" @@ -1615,18 +1962,29 @@ "message": "La red confirmó la transacción inicial. Haga clic en Aceptar para volver." }, "inputLogicEmptyState": { - "message": "Ingrese solo una cantidad que esté dispuesto a gastar en el contrato ahora o en el futuro. Siempre puede aumentar el límite de gastos más adelante." + "message": "Ingrese solo una cantidad que esté dispuesto a gastar en el tercero ahora o en el futuro. Siempre puede aumentar el límite de gastos más adelante." }, "inputLogicEqualOrSmallerNumber": { - "message": "Esto permite que el contrato gaste $1 de su saldo actual.", + "message": "Esto permite que el tercero gaste $1 de su saldo actual.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Esto permite que el contrato gaste todo su saldo de tokens hasta que alcance el límite o usted revoque el límite de gasto. Si esta no es su intención, considere establecer un límite de gasto más bajo." + "message": "Esto permite que el tercero gaste todo su saldo de tokens hasta que alcance el límite o usted revoque el límite de gasto. Si esta no es su intención, considere establecer un límite de gasto más bajo." + }, + "insightsFromSnap": { + "message": "Perspectivas de $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Instalar" }, + "installOrigin": { + "message": "Instalar origen" + }, + "installedOn": { + "message": "Instalado en $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Saldo insuficiente." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "¡Entrada inválida! La frase secreta de recuperación distingue entre mayúsculas y minúsculas." }, + "ipfsGateway": { + "message": "Puerta de enlace de IPFS" + }, + "ipfsGatewayDescription": { + "message": "MetaMask utiliza servicios de terceros para mostrar imágenes de sus NFT almacenados en IPFS, mostrar información relacionada con las direcciones ENS ingresadas en la barra de direcciones de su navegador y obtener íconos para diferentes tokens. Su dirección IP puede estar expuesta a estos servicios cuando los está utilizando." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Utilizamos servicios de terceros para mostrar imágenes de sus NFT almacenados en IPFS, mostrar información relacionada con las direcciones ENS ingresadas en la barra de direcciones de su navegador y obtener íconos para diferentes tokens. Su dirección IP puede estar expuesta a estos servicios cuando los está utilizando." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Al seleccionar Confirmar, se activa la resolución de IPFS. Puede desactivarlo en $1 en cualquier momento.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Configuración > Seguridad y privacidad" + }, "jazzAndBlockies": { "message": "Jazzicons y Blockies son dos estilos distintos de íconos únicos que pueden ayudarlo a identificar rápidamente una cuenta." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Última venta" }, + "layer1Fees": { + "message": "Tarifas de la capa 1" + }, "learnCancelSpeeedup": { "message": "Aprenda cómo $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "¿Quiere $1 sobre gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Más información" + }, "learnMoreUpperCase": { "message": "Más información" }, @@ -1765,16 +2145,16 @@ "message": "Antes de hacer clic en Confirmar:" }, "ledgerConnectionInstructionStepFour": { - "message": "Habilite \"datos de contrato inteligente\" o \"firma ciega\" en su dispositivo Ledger" + "message": "Habilite \"datos de contrato inteligente\" o \"firma ciega\" en su dispositivo Ledger." }, "ledgerConnectionInstructionStepOne": { - "message": "Habilite el uso de Ledger Live en Configuración > Avanzada" + "message": "Habilite el uso de Ledger Live en Configuración > Avanzada." }, "ledgerConnectionInstructionStepThree": { - "message": "Conecte su dispositivo Ledger y seleccione la aplicación Ethereum" + "message": "Asegúrese de que su Ledger esté conectado y seleccione la aplicación Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Abra y desbloquee la aplicación Ledger Live" + "message": "Abra y desbloquee la aplicación Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Personalice la forma de conectar su Ledger a MetaMask. Se recomienda $1, pero hay otras opciones disponibles. Lea más aquí: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Red de prueba Linea Goerli" }, + "lineaMainnet": { + "message": "Red principal de Linea" + }, "link": { "message": "Vínculo" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Bloquear" }, + "lockMetaMask": { + "message": "Cerrar MetaMask" + }, + "lockTimeInvalid": { + "message": "El tiempo de bloqueo debe ser un número entre 0 y 10080" + }, "logo": { "message": "Logo de $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "El botón de estado de la conexión muestra si el sitio web que visita está conectado a la cuenta seleccionada actualmente." }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Institucional" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps está en mantenimiento. Vuelva a comprobarlo más tarde." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Indicadores" }, + "mismatchAccount": { + "message": "Su cuenta seleccionada ($1) es diferente a la cuenta que intenta firmar ($2)" + }, "mismatchedChainLinkText": { "message": "verifique los detalles de la red", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "El símbolo de moneda enviado no coincide con lo que esperamos para este ID de cadena." }, + "mismatchedRpcChainId": { + "message": "El ID de cadena devuelto por la red personalizada no coincide con el ID de cadena enviado." + }, "mismatchedRpcUrl": { "message": "Según nuestros registros, el valor de la URL de RPC enviado no coincide con un proveedor conocido para este ID de cadena." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Solicítelo aquí" }, + "mmiAddToken": { + "message": "A la página de $1 le gustaría autorizar el siguiente token custodiado en MetaMask Institucional" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional está diseñado y construido en todo el mundo." + }, + "more": { + "message": "más" + }, "moreComingSoon": { - "message": "Más próximamente..." + "message": "Muy pronto más proveedores" + }, + "multipleSnapConnectionWarning": { + "message": "$1 quiere conectarse con $2 complementos. Continúe solo si confía en este sitio web.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Debe seleccionar al menos 1 token." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "La red está ocupada. Los precios del gas son altos y las estimaciones son menos precisas." }, + "networkMenu": { + "message": "Menú de red" + }, + "networkMenuHeading": { + "message": "Seleccionar una red" + }, "networkName": { "message": "Nombre de la red" }, @@ -2033,6 +2450,10 @@ "message": "Las tarifas del gas son de $1 en relación con las últimas 72 horas.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "No podemos conectar a $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Dirección URL de la red" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Poseído anteriormente" + }, "nickname": { "message": "Apodo" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "No hay tasa de conversión disponible" }, + "noNFTs": { + "message": "No hay ningún NFT aún" + }, + "noNetworksFound": { + "message": "No se encontraron redes para la consulta de búsqueda dada" + }, "noSnaps": { - "message": "No hay complementos instalados" + "message": "No tiene ningún complemento instalado." }, "noThanksVariant2": { "message": "No, gracias." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "¿Esta es la cuenta correcta? Es distinta de la cuenta seleccionada actualmente en la cartera" }, + "notEnoughBalance": { + "message": "Saldo insuficiente" + }, "notEnoughGas": { "message": "No hay gas suficiente" }, + "note": { + "message": "Nota" + }, + "notePlaceholder": { + "message": "El verificador verá esta nota cuando apruebe la transacción en la cuenta custodiada." + }, + "notificationTransactionFailedMessage": { + "message": "¡La transacción $1 fallida! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "¡Transacción fallida! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Transacción fallida", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "¡Transacción $1 confirmada!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Transacción confirmada", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Ver en $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Notificaciones" + }, "notifications10ActionText": { "message": "Ir a configuración", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "¡La Fusión de Ethereum está aquí!" }, + "notifications18ActionText": { + "message": "Habilitar alertas de seguridad" + }, + "notifications18DescriptionOne": { + "message": "Reciba alertas de terceros cuando haya recibido una solicitud maliciosa.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Siempre asegúrese de hacer su propia diligencia debida antes de aprobar cualquier solicitud.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea es el primer proveedor de seguridad para esta función. ¡Pronto habrá más proveedores!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Manténgase seguro con alertas de seguridad" + }, + "notifications19ActionText": { + "message": "Activar la detección automática de NFT" + }, + "notifications19DescriptionOne": { + "message": "Hay dos maneras de comenzar:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Por el momento solo admitimos ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Añada manualmente sus NFT, o active la detección automática de NFT en Configuración > Experimental.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Vea sus NFT como nunca antes" + }, "notifications1Description": { "message": "Los usuarios de la aplicación móvil de MetaMask ahora pueden canjear tokens en su cartera móvil. Escanee el código QR para obtener la aplicación móvil y comience a canjear.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "¡El canje en dispositivos móviles ya está aquí!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Más información", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Si tiene la última versión de Firefox, es posible que experimente un problema relacionado con la eliminación de la compatibilidad con U2F de Firefox.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Usuarios de Ledger y Firefox están experimentando problemas de conexión", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Inténtelo" + }, + "notifications21Description": { + "message": "Actualizamos la función Intercambios en la extensión MetaMask para que sea más fácil y rápida de usar.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "¡Presentamos la función Intercambios nueva y renovada!" + }, + "notifications22ActionText": { + "message": "Entendido" + }, + "notifications22Description": { + "message": "💡 ¡Simplemente haga clic en el menú global o en el menú de la cuenta para encontrarlos!" + }, + "notifications22Title": { + "message": "¿Busca los detalles de su cuenta o la URL del explorador de bloques?" + }, + "notifications23ActionText": { + "message": "Habilitar alertas de seguridad" + }, + "notifications23DescriptionOne": { + "message": "Aléjese de las estafas conocidas mientras conserva su privacidad con las alertas de seguridad impulsadas por Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Si habilitó las alertas de seguridad de OpenSea, lo trasladamos a esta función." + }, + "notifications23DescriptionTwo": { + "message": "Siempre asegúrese de hacer su propia diligencia debida antes de aprobar cualquier solicitud." + }, + "notifications23Title": { + "message": "Manténgase seguro con alertas de seguridad" + }, "notifications3ActionText": { "message": "Leer más", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Conéctese solo con sitios de confianza." }, "openFullScreenForLedgerWebHid": { - "message": "Abra MetaMask en pantalla completa para conectar su Ledger a través de WebHID.", + "message": "Pase al modo de pantalla completa para conectar su Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Abrir en el explorador de bloques" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Operación fallida" + }, "optional": { "message": "Opcional" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Las contraseñas no coinciden" }, + "pasteJWTToken": { + "message": "Pegue o suelte su token aquí:" + }, "pastePrivateKey": { "message": "Pegue aquí la cadena de clave privada:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Acceso a internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Permita que el complemento tenga acceso a Internet. Esto se puede usar tanto para enviar como para recibir datos con servidores de terceros.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Conéctese al complemento de $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Permita que el sitio web o el complemento interactúen con $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Programar y ejecutar acciones periódicas.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Permita que el complemento realice acciones que se ejecutan periódicamente en horas, fechas o intervalos fijos. Esto se puede usar para activar interacciones o notificaciones sensibles al tiempo.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Mostrar ventanas de diálogo en MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Permita que el complemento muestre ventanas emergentes de MetaMask con texto personalizado, campo de entrada y botones para aprobar o rechazar una acción.\nSe puede usar para crear, p. ej. alertas, confirmaciones y flujos de suscripción para un complemento.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Ver las direcciones de las cuentas permitidas (requerido)", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Acceda al proveedor de Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Permita que el complemento se comunique directamente con MetaMask para que lea datos de la cadena de bloques y sugiera mensajes y transacciones.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Obtenga claves arbitrarias únicas para este complemento.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Permita que el complemento obtenga claves arbitrarias únicas para este complemento, sin exponerlas. Estas claves son independientes de su(s) cuenta(s) de MetaMask y no están relacionadas con sus claves privadas ni su frase de recuperación secreta. Otros complementos no pueden acceder a esta información.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Utilice ganchos de ciclo de vida.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Permita que el snap use ganchos de ciclo de vida para ejecutar código en momentos específicos durante su ciclo de vida.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Ejecutar indefinidamente.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Permita que el complemento se ejecute indefinidamente mientras, por ejemplo, procesa grandes cantidades de datos.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Agregar y controlar cuentas de Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Controle sus cuentas y activos por menos de $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Permita que el complemento obtenga pares de claves BIP-32 en función de su frase de recuperación secreta sin exponerla. Esto otorga acceso completo a todas las cuentas y activos en $1.\nGracias a la capacidad de administrar claves, el complemento puede admitir una amplia variedad de protocolos de cadena de bloques aparte de Ethereum (EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Controle sus cuentas y activos \"$1\".", + "message": "Controle sus cuentas y activos $1.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Permita que el complemento obtenga pares de claves BIP-44 en función de su frase de recuperación secreta sin exponerla. Esto otorga acceso completo a todas las cuentas y activos en $1.\nGracias a la capacidad de administrar claves, el complemento puede admitir una amplia variedad de protocolos de cadena de bloques aparte de Ethereum (EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Controle sus cuentas y activos $1.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Almacene y administre sus datos en su dispositivo.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Permita que el complemento almacene, actualice y recupere datos de forma segura con cifrado. Otros complementos no pueden acceder a esta información.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Mostrar notificaciones.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Permita que el complemento muestre notificaciones dentro de MetaMask. Se puede activar un breve texto de notificación con un complemento para obtener información procesable o sensible al tiempo.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Permitir que $1 se comunique directamente con este complemento.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Permita que $1 envíe mensajes al complemento y reciba una respuesta del mismo.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Obtenga y muestre información de transacciones.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Permita que el complemento decodifique transacciones y muestre información dentro de la interfaz de usuario de MetaMask. Esto se puede utilizar para soluciones antiphishing y de seguridad.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Ver los orígenes de los sitios web que sugieren transacciones", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Permita que el complemento vea el origen (URI) de los sitios web que sugieren transacciones. Esto se puede utilizar para soluciones antiphishing y de seguridad.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Permiso desconocido: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Ver su clave pública para $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Permita que el complemento vea sus claves (y direcciones) públicas para $1. Esto no otorga ningún control de cuentas o activos.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Vea su clave pública para $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Soporte para WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Permita que el complemento acceda a entornos de ejecución de bajo nivel a través de WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Permisos" }, + "permissionsTitle": { + "message": "Permisos" + }, + "permissionsTourDescription": { + "message": "Encuentre sus cuentas conectadas y administre los permisos aquí" + }, "personalAddressDetected": { "message": "Se detectó una dirección personal. Ingrese la dirección de contrato del token." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portafolio" }, + "portfolioDashboard": { + "message": "Panel de cartera" + }, "preferredLedgerConnectionType": { "message": "Tipo de conexión a Ledger preferida", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Clave privada", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Clave privada para $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Advertencia: No revele esta clave. Cualquier persona que tenga sus claves privadas podría robar los activos de su cuenta." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "En cola" }, + "quoteRate": { + "message": "Tarifa de cotización" + }, "reAddAccounts": { "message": "volver a agregar cualquier otra cuenta" }, @@ -2751,7 +3401,7 @@ "message": "Recibir" }, "recipientAddressPlaceholder": { - "message": "Búsqueda, dirección pública (0x) o ENS" + "message": "Ingrese la dirección pública (0x) o el nombre de ENS" }, "recommendedGasLabel": { "message": "Recomendado" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Esta cuenta se quitará de la cartera. Antes de continuar, asegúrese de tener la frase secreta de recuperación original o la clave privada de esta cuenta importada. Puede importar o crear cuentas nuevamente en la lista desplegable de la cuenta. " }, + "removeJWT": { + "message": "Eliminar token custodiado" + }, + "removeJWTDescription": { + "message": "¿Está seguro de que desea eliminar este token? Todas las cuentas asignadas a este token también se eliminarán de la extensión: " + }, "removeNFT": { "message": "Eliminar NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Puede restaurar la configuración del usuario que contiene preferencias y direcciones de cuenta desde en un archivo JSON previamente respaldado." }, + "resultPageError": { + "message": "Error" + }, + "resultPageErrorDefaultMessage": { + "message": "La operación falló." + }, + "resultPageSuccess": { + "message": "Éxito" + }, + "resultPageSuccessDefaultMessage": { + "message": "La operación se completó con éxito." + }, "retryTransaction": { "message": "Reintentar transacción" }, @@ -2939,16 +3607,27 @@ "message": "¿Revocar el permiso para acceder y transferir todos sus $1?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "¿Revocar el permiso para acceder y transferir todos sus NFT desde $1?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Esto revoca el permiso para que un tercero acceda y transfiera la totalidad de su $1 sin previo aviso.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Esto revoca el permiso para que un tercero acceda y transfiera todos sus NFT desde $1 sin previo aviso.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Revocar permiso" + }, "revokeSpendingCap": { "message": "Revocar un límite de gasto para su $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Este contrato no podrá gastar más de sus tokens actuales o futuros." + "message": "Este tercero no podrá gastar más de sus tokens actuales o futuros." }, "rpcUrl": { "message": "Nueva dirección URL de RPC" @@ -2986,9 +3665,28 @@ "security": { "message": "Seguridad" }, + "securityAlert": { + "message": "Alerta de seguridad de $1 y $2" + }, + "securityAlerts": { + "message": "Alertas de seguridad" + }, + "securityAlertsDescription1": { + "message": "Esta función lo alerta sobre actividades maliciosas al revisar localmente sus transacciones y solicitudes de firma. Sus datos no se comparten con los terceros que brindan este servicio. Siempre haga su propia diligencia debida antes de aprobar cualquier solicitud. No hay garantía de que esta característica detecte todas la actividades maliciosas." + }, + "securityAlertsDescription2": { + "message": "Siempre asegúrese de hacer su propia diligencia debida antes de aprobar cualquier solicitud. No hay garantía de que esta característica detecte toda la actividad maliciosa." + }, "securityAndPrivacy": { "message": "Seguridad y privacidad" }, + "securityProviderAdviceBy": { + "message": "Asesoramiento de seguridad por $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Ver detalles" + }, "seedPhraseConfirm": { "message": "Confirmar frase secreta de recuperación" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Anote la frase secreta de recuperación" }, + "select": { + "message": "Seleccionar" + }, "selectAccounts": { "message": "Seleccionar cuentas" }, + "selectAccountsForSnap": { + "message": "Seleccione la(s) cuenta(s) para usar con este snap" + }, "selectAll": { "message": "Seleccionar todo" }, + "selectAllAccounts": { + "message": "Seleccionar todas las cuentas" + }, "selectAnAccount": { "message": "Seleccionar una cuenta" }, "selectAnAccountAlreadyConnected": { "message": "Esta cuenta ya se conectó a MetaMask." }, + "selectAnAccountHelp": { + "message": "Seleccione las cuentas custodiadas para usar en MetaMask Institucional." + }, "selectHdPath": { "message": "Seleccione la ruta de acceso al disco duro" }, + "selectJWT": { + "message": "Seleccionar token" + }, "selectNFTPrivacyPreference": { "message": "Active la detección de NFT en Configuraciones" }, @@ -3113,6 +3826,9 @@ "message": "Aprobar $1 sin límite preestablecido", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Añadir una cuenta snap" + }, "settings": { "message": "Configuración" }, @@ -3141,9 +3857,21 @@ "message": "Seleccione esta opción para usar Etherscan para mostrar las transacciones entrantes en la lista de transacciones", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Esto depende de cada red que tendrá acceso a su dirección Ethereum y su dirección IP." + }, + "showMore": { + "message": "Mostrar más" + }, + "showNft": { + "message": "Mostrar NFT" + }, "showPermissions": { "message": "Mostrar permisos" }, + "showPrivateKey": { + "message": "Mostrar clave privada" + }, "showPrivateKeys": { "message": "Mostrar claves privadas" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Entiendo que hasta que no haga una copia de seguridad de mi frase secreta de recuperación, puedo perder mis cuentas y todos los activos asociados." }, + "smartContracts": { + "message": "Contratos inteligentes" + }, + "smartSwap": { + "message": "Intercambio inteligente" + }, + "smartSwapsAreHere": { + "message": "¡La función Intercambios inteligentes ya está aquí!" + }, + "smartSwapsDescription": { + "message": "¡La función Intercambios de MetaMask ahora es mucho más inteligente! Habilitar Intercambios inteligentes permitirá que MetaMask optimice mediante programación su intercambio para ayudar a:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "No hay suficientes fondos para un intercambio inteligente." + }, + "smartSwapsErrorUnavailable": { + "message": "La función Intercambios inteligentes no está disponible temporalmente." + }, + "smartSwapsSubDescription": { + "message": "* La función Intercambios inteligentes intentará enviar su transacción de forma privada varias veces. Si todos los intentos fallan, la transacción se transmitirá públicamente para garantizar que su intercambio se realice con éxito." + }, + "snapConfigure": { + "message": "Configurar" + }, + "snapConnectionWarning": { + "message": "$1 quiere conectarse a $2. Continúe solo si confía en este sitio web.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Este contenido proviene de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Elija cómo proteger su nueva cuenta con MetaMask Snaps." + }, + "snapCreateAccountTitle": { + "message": "Cree una cuenta $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Por MetaMask" + }, + "snapDetailAudits": { + "message": "Auditar" + }, + "snapDetailDeveloper": { + "message": "Desarrollador" + }, + "snapDetailLastUpdated": { + "message": "Actualizado" + }, + "snapDetailManageSnap": { + "message": "Administrar snap" + }, + "snapDetailTags": { + "message": "Etiquetas" + }, + "snapDetailVersion": { + "message": "Versión" + }, + "snapDetailWebsite": { + "message": "Sitio web" + }, + "snapDetailsCreateASnapAccount": { + "message": "Crear una cuenta Snap" + }, + "snapDetailsInstalled": { + "message": "Instalado" + }, "snapError": { "message": "Error de complemento: '$1'. Código de error: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Instalar complemento" }, + "snapInstallRequest": { + "message": "La instalación de $1 otorga los siguientes permisos. Solo continúe si confía en $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Instalación completa" + }, "snapInstallWarningCheck": { "message": "Para confirmar que comprende, verifique la casilla.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Para confirmar que comprende, marque todas las casillas.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Proceda con precaución" + }, "snapInstallWarningKeyAccess": { "message": "Está otorgando acceso clave de $2 al complemento \"$1\". Esto es irrevocable y le otorga a \"$1\" el control de sus cuentas y activos de $2. Asegúrese de que confía en \"$1\" antes de continuar.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Dar a $2 el acceso de clave pública a $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 no se pudo instalar.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Instalación fallida", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Auditado" + }, + "snapResultError": { + "message": "Error" + }, + "snapResultSuccess": { + "message": "Éxito" + }, + "snapResultSuccessDescription": { + "message": "$1 está listo para usar" + }, "snapUpdate": { "message": "Actualizar complemento" }, + "snapUpdateAvailable": { + "message": "Actualización disponible" + }, + "snapUpdateErrorDescription": { + "message": "$1 no se pudo actualizar.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Actualización fallida", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 quiere actualizar $2 a $3, lo que le otorga los siguientes permisos. Continúe solo si confía en $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Actualización completa" + }, "snaps": { "message": "Complementos" }, "snapsInsightLoading": { "message": "Cargando información de transacción..." }, + "snapsInvalidUIError": { + "message": "La IU especificada por el complemento no es válida." + }, "snapsNoInsight": { "message": "El complemento no devolvió ninguna información" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Usted reconoce que el complemento que está a punto de instalar es un Servicio de terceros según se define en Consensys $1. Su uso de los Servicios de terceros se rige por términos y condiciones separados establecidos por el proveedor de Servicios de terceros. Usted accede, confía o utiliza el Servicio de terceros bajo su propio riesgo. Consensys se exime de toda responsabilidad por cualquier pérdida a causa de su uso de los Servicios de terceros.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Cualquier información que comparta con Servicios de terceros será recopilada directamente por dichos Servicios de terceros de acuerdo con sus políticas de privacidad. Consulte sus políticas de privacidad para obtener más información.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys no tiene acceso a la información que comparta con estos terceros.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Administre sus complementos" }, + "snapsTermsOfUse": { + "message": "Términos de uso" + }, "snapsToggle": { "message": "Un complemento solo se ejecutará si está habilitado" }, "snapsUIError": { - "message": "La IU especificada por el complemento no es válida.", + "message": "Póngase en contacto con los creadores de $1 para obtener más ayuda.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3250,7 +4117,7 @@ "message": "Hemos actualizado la tarifa de gas en función de las condiciones actuales de la red y la hemos aumentado al menos un 10% (exigido por la red)." }, "speedUpPopoverTitle": { - "message": "Agilizar transacción" + "message": "Acelerar la transacción" }, "speedUpTooltipText": { "message": "Nueva tarifa de gas" @@ -3284,6 +4151,9 @@ "message": "Ingrese solo una cantidad con la que se sienta cómodo para que $1 acceda ahora o en el futuro. Siempre puede aumentar el límite de tokens más tarde.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Solicitud de límite de gastos para su $1" + }, "srpInputNumberOfWords": { "message": "Tengo una frase de $1 palabras", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3297,7 +4167,10 @@ "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, "srpSecurityQuizGetStarted": { - "message": "Iniciar" + "message": "Comenzar" + }, + "srpSecurityQuizImgAlt": { + "message": "Un ojo con un ojo de cerradura en el centro y tres campos de contraseña flotantes" }, "srpSecurityQuizIntroduction": { "message": "Para revelar su frase secreta de recuperación, debe responder correctamente dos preguntas" @@ -3309,16 +4182,16 @@ "message": "No puede ayudarlo" }, "srpSecurityQuizQuestionOneRightAnswerDescription": { - "message": "Anótela, grábela en metal o guárdela en múltiples lugares secretos para que nunca la pierda. Si la extravía, se ha ido para siempre." + "message": "Escríbalo, grábelo en metal o guárdelo en múltiples lugares secretos para que nunca lo pierda. Si lo pierde, lo ha perdido para siempre." }, "srpSecurityQuizQuestionOneRightAnswerTitle": { - "message": "¡Correcto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación" + "message": "¡Cierto! Nadie puede ayudarlo a recuperar su Frase secreta de recuperación" }, "srpSecurityQuizQuestionOneWrongAnswer": { "message": "Puede recuperarla para usted" }, "srpSecurityQuizQuestionOneWrongAnswerDescription": { - "message": "Si pierde su frase secreta de recuperación, ésta desaparecerá para siempre. Nadie puede ayudarle a recuperarla, sin importar lo que digan." + "message": "Si pierde su frase secreta de recuperación, ésta se perderá para siempre. Nadie puede ayudarle a recuperarla, sin importar lo que digan." }, "srpSecurityQuizQuestionOneWrongAnswerTitle": { "message": "¡Incorrecto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación" @@ -3336,7 +4209,7 @@ "message": "¡Correcto! Compartir su frase secreta de recuperación nunca es una buena idea" }, "srpSecurityQuizQuestionTwoWrongAnswer": { - "message": "Debiera brindársela" + "message": "Debería dársela" }, "srpSecurityQuizQuestionTwoWrongAnswerDescription": { "message": "Cualquiera que afirme necesitar su frase secreta de recuperación le está mintiendo. Si la comparte, le robarán sus activos." @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "estable" }, + "stake": { + "message": "Staking" + }, "stateLogError": { "message": "Error al recuperar los registros de estado." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "No conectado" }, + "statusNotConnectedAccount": { + "message": "No hay cuentas conectadas" + }, "step1LatticeWallet": { "message": "Conecte su Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Se refiere al monto mínimo que recibirá. Puede recibir más en función del desfase." }, + "swapAnyway": { + "message": "Intercambiar de todos modos" + }, "swapApproval": { "message": "Aprobar $1 para canjes", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Necesita $1 más $2 para completar este canje", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "¿Sigue ahí?" + }, + "swapAreYouStillThereDescription": { + "message": "Estamos listos para mostrarle las últimas cotizaciones cuando desee continuar" + }, "swapBuildQuotePlaceHolderText": { "message": "No hay tokens disponibles que coincidan con $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Confirmar con la cartera de hardware" }, + "swapContinueSwapping": { + "message": "Continuar intercambiando" + }, "swapContractDataDisabledErrorDescription": { "message": "En la aplicación de Ethereum en su Ledger, diríjase a \"Configuración\" y habilite los datos de contrato. A continuación, intente canjear de nuevo." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Editar límite" }, + "swapEditTransactionSettings": { + "message": "Editar la configuración de la transacción" + }, "swapEnableDescription": { "message": "Esta acción es obligatoria y le da permiso a MetaMask para canjear su $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Esto será $1 por intercambiar", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Introduzca un importe" + }, "swapEstimatedNetworkFees": { "message": "Cuotas de red estimadas" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Error al canjear" }, + "swapFetchingQuote": { + "message": "Obteniendo cotización" + }, "swapFetchingQuoteNofN": { "message": "Obtener cotización $1 de $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Incluye una tasa de MetaMask del $1%.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Incluye una tarifa MetaMask de $1% - $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Más información sobre Intercambios" + }, "swapLowSlippageError": { "message": "Es posible que la transacción tenga errores, el desfase máximo es demasiado bajo." }, @@ -3626,6 +4533,10 @@ "message": "Cotizaciones nuevas en $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "No hay tokens disponibles que coincidan con $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Su $1 se agregará a la cuenta una vez que se procese esta transacción.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Detalles de cotización" }, + "swapQuoteNofM": { + "message": "$1 de $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Fuente de la cotización" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Tiempo de espera de cotizaciones" }, + "swapQuotesNotAvailableDescription": { + "message": "Reduzca el tamaño de su operación o utilice un token diferente." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Intente ajustar la configuración de monto o desfase y vuelva a intentarlo." }, @@ -3693,21 +4611,57 @@ "message": "Seleccionar una cotización" }, "swapSelectAToken": { - "message": "Seleccionar un token" + "message": "Seleccionar token" }, "swapSelectQuotePopoverDescription": { "message": "A continuación, se muestran todas las cotizaciones recopiladas de diversas fuentes de liquidez." }, + "swapSelectToken": { + "message": "Seleccionar token" + }, + "swapShowLatestQuotes": { + "message": "Mostrar cotizaciones más recientes" + }, "swapSlippageNegative": { "message": "El desfase debe ser mayor o igual que cero" }, + "swapSlippageNegativeDescription": { + "message": "El desfase debe ser mayor o igual que cero" + }, + "swapSlippageNegativeTitle": { + "message": "Aumentar el desfase para continuar" + }, + "swapSlippageOverLimitDescription": { + "message": "La tolerancia al desfase debe ser del 15% o menos. Cualquier cosa más alta resultará en una mala tasa." + }, + "swapSlippageOverLimitTitle": { + "message": "Reducir el desfase para continuar" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "El desfase máximo es demasiado bajo, lo que puede hacer que su transacción fracase." + }, + "swapSlippageTooLowTitle": { + "message": "Aumente el desfase para evitar fallas en las transacciones" + }, "swapSlippageTooltip": { "message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina \"desfase\". El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración de la \"tolerancia de desfase\"." }, + "swapSlippageVeryHighDescription": { + "message": "El desfase ingresado se considera muy alto y puede resultar en una mala tasa" + }, + "swapSlippageVeryHighTitle": { + "message": "Desfase muy alto" + }, + "swapSlippageZeroDescription": { + "message": "Hay menos proveedores de cotizaciones de deslizamiento cero, lo que resultará en una cotización menos competitiva." + }, + "swapSlippageZeroTitle": { + "message": "Abastecimiento de proveedores de desfase cero" + }, "swapSource": { "message": "Fuente de liquidez" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "confirmar con la cartera de hardware" }, + "swapTokenAddedManuallyDescription": { + "message": "Verifique este token en $1 y asegúrese de que sea el token que desea operar.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token añadido manualmente" + }, "swapTokenAvailable": { "message": "Su $1 se agregó a la cuenta.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Verificar en $1 fuentes.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 solo se verifica en 1 fuente. Considere verificarlo en $2 antes de continuar.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Token potencialmente falso" + }, "swapTooManyDecimalsError": { "message": "$1 permite hasta $2 decimales", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "No hay $1 suficientes para completar esta transacción", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "No hay suficiente $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Ver en actividad" }, + "switch": { + "message": "Cambiar" + }, "switchEthereumChainConfirmationDescription": { "message": "Esto cambiará la red seleccionada en MetaMask por una red agregada con anterioridad:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Ha cambiado a" }, + "switcherTitle": { + "message": "Selector de red" + }, + "switcherTourDescription": { + "message": "Haga clic en el icono para cambiar de red o agregar una nueva red" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Cambiar de red cancelará todas las confirmaciones pendientes" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Términos de servicio" }, + "termsOfUse": { + "message": "términos de uso" + }, + "termsOfUseAgreeText": { + "message": " Acepto los Términos de uso, que se aplican al uso que hago de MetaMask y de todas sus funcionalidades" + }, + "termsOfUseFooterText": { + "message": "Por favor, desplácese para leer todas las secciones" + }, + "termsOfUseTitle": { + "message": "Nuestros Términos de uso han sido actualizados" + }, "testNetworks": { "message": "Redes de prueba" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Cosas a tener en cuenta:" }, + "thirdPartySoftware": { + "message": "Aviso de software de terceros", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "esta colección" + }, + "thisServiceIsExperimental": { + "message": "Este servicio es experimental. Al habilitar esta función, usted acepta los $1 de OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Tiempo" }, @@ -3863,11 +4867,44 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Está en riesgo de sufrir ataques de phishing. Protéjase desactivando eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Actívelo para permitir que las dapps soliciten su firma mediante solicitudes eth_sign. eth_sign es un método de firma abierto que le permite firmar un hash arbitrario, lo que lo convierte en un peligroso riesgo de phishing. Solo firme solicitudes de eth_sign si puede leer lo que está firmando y confiar en el origen de la solicitud." + "message": "Si activa esta opción, es posible que reciba solicitudes de firma que no sean legibles. Al firmar un mensaje que no entiende, podría estar dando su consentimiento para ceder sus fondos y NFT." }, "toggleEthSignField": { - "message": "Alternar solicitudes de eth_sign" + "message": "Solicitudes de eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " usted podría estar siendo estafado" + }, + "toggleEthSignModalBannerText": { + "message": "Si se le ha pedido que active esta configuración," + }, + "toggleEthSignModalCheckBox": { + "message": "Entiendo que puedo perder todos mis fondos y mis NFT si activo las solicitudes de eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Permitir solicitudes eth_sign puede hacerlo vulnerable a ataques de phishing. Siempre revise la URL y tenga cuidado al firmar mensajes que contengan código." + }, + "toggleEthSignModalFormError": { + "message": "El texto es incorrecto" + }, + "toggleEthSignModalFormLabel": { + "message": "Ingrese “Firmo solo lo que entiendo” para continuar" + }, + "toggleEthSignModalFormValidation": { + "message": "Firmo solo lo que entiendo" + }, + "toggleEthSignModalTitle": { + "message": "Úselo bajo su propio riesgo" + }, + "toggleEthSignOff": { + "message": "DESACTIVADO (Recomendado)" + }, + "toggleEthSignOn": { + "message": "ACTIVADO (No recomendado)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Símbolo del token" }, + "tokens": { + "message": "Tokens" + }, "tokensFoundTitle": { "message": "$1 nuevos tokens encontrados", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Comprendo" }, + "tooltipSatusConnected": { + "message": "conectado" + }, + "tooltipSatusNotConnected": { + "message": "no conectado" + }, "total": { "message": "Total" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "La transacción encontró un error." }, + "transactionFailed": { + "message": "Transacción fallida" + }, "transactionFee": { "message": "Cuota de transacción" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Tarifa total de gas" }, + "transactionNote": { + "message": "Nota de transacción" + }, "transactionResubmitted": { "message": "Transacción reenviada con la cuota de gas aumentada a $1 en $2" }, "transactionSecurityCheck": { - "message": "Habilitar proveedores de seguridad de transacciones" + "message": "Habilitar alertas de seguridad" }, "transactionSecurityCheckDescription": { "message": "Usamos API de terceros para detectar y mostrar los riesgos involucrados en transacciones sin firmar y solicitudes de firma antes de que las firme. Estos servicios tendrán acceso a su transacción no firmada y solicitudes de firma, la dirección de su cuenta y su idioma preferido." }, + "transactionSettings": { + "message": "Ajustes de la transacción" + }, "transactionSubmitted": { "message": "Transacción enviada con una cuota de gas de $1 en $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transferir desde" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Tenemos problemas para conectarnos con su Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Revise nuestra guía de conexión de monederos físicos y vuelva a intentarlo.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Si tiene la última versión de Firefox, es posible que experimente un problema relacionado con la eliminación de la compatibilidad con U2F de Firefox. Aprenda a solucionar este problema $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "aquí", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Tuvimos problemas al conectar su $1. Pruebe revisar $2 e inténtelo de nuevo.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "flecha ascendente" }, + "update": { + "message": "Actualizar" + }, "updatedWithDate": { "message": "$1 actualizado" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "En este momento, la red $1 está utilizando esta dirección URL." }, + "use4ByteResolution": { + "message": "Decodificar contratos inteligentes" + }, + "use4ByteResolutionDescription": { + "message": "Para mejorar la experiencia del usuario, personalizamos la pestaña de actividad con mensajes basados en los contratos inteligentes con los que interactúa. MetaMask usa un servicio llamado 4byte.directory para decodificar datos y mostrarle una versión de un contrato inteligente que es más fácil de leer. Esto ayuda a reducir sus posibilidades de aprobar acciones de contratos inteligentes maliciosos, pero puede resultar en que se comparta su dirección IP." + }, "useMultiAccountBalanceChecker": { "message": "Solicitudes de saldo de cuenta por lotes" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Obtenga actualizaciones de saldo más rápidas mediante el procesamiento por lotes de solicitudes de saldo de cuenta. Esto nos permite obtener los saldos de su cuenta juntos, para que obtenga actualizaciones más rápidas para una experiencia mejorada. Cuando esta función está desactivada, es menos probable que terceros logran asociar a sus cuentas entre sí." + }, "useNftDetection": { "message": "Detección automática de NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Mostrar una advertencia respecto de los dominios de phishing dirigidos a los usuarios de Ethereum" }, + "useSiteSuggestion": { + "message": "Usar sugerencia del sitio" + }, "useTokenDetectionPrivacyDesc": { "message": "La visualización automática de tokens enviados a su cuenta implica la comunicación con servidores de terceros para obtener imágenes de tokens. Esos servicios tendrán acceso a su dirección IP." }, @@ -4170,7 +5256,7 @@ "message": "Nombre de usuario" }, "verifyContractDetails": { - "message": "Verificar detalles del contrato" + "message": "Verificar detalles de terceros" }, "verifyThisTokenDecimalOn": { "message": "Los decimales del token se pueden encontrar en $1", @@ -4184,12 +5270,18 @@ "message": "Verifique este token en $1 y asegúrese de que sea el token con el que quiere realizar la transacción.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Versión" + }, "view": { "message": "Ver" }, "viewAllDetails": { "message": "Ver todos los detalles" }, + "viewAllQuotes": { + "message": "ver todas las cotizaciones" + }, "viewContact": { "message": "Ver contacto" }, @@ -4213,9 +5305,18 @@ "message": "Ver $1 en Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Ver en el explorador" + }, "viewOnOpensea": { "message": "Ver en Opensea" }, + "viewPortfolioDashboard": { + "message": "Ver panel de cartera" + }, + "viewinCustodianApp": { + "message": "Ver en la aplicación de custodia" + }, "viewinExplorer": { "message": "Ver $1 en el explorador", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "¿Desea añadir esta red?" }, + "wantsToAddThisAsset": { + "message": "$1 quiere agregar este activo a su monedero", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Advertencia" }, "warningTooltipText": { - "message": "$1 El contrato podría gastar todo su saldo de tokens sin previo aviso o consentimiento. Protéjase personalizando un límite de gasto más bajo.", + "message": "$1 El tercero podría gastar todo su saldo de tokens sin previo aviso o consentimiento. Protéjase personalizando un límite de gasto más bajo.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Está firmando" }, + "yourAccounts": { + "message": "Sus cuentas" + }, "yourFundsMayBeAtRisk": { "message": "Sus fondos podrían estar en riesgo" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 1e990f8d7..a3e801697 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -106,6 +106,9 @@ "about": { "message": "À propos" }, + "accept": { + "message": "Accepter" + }, "acceptTermsOfUse": { "message": "J’ai lu et j’accepte les $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Ajouter un pseudo" }, + "addAccount": { + "message": "Ajouter un compte" + }, "addAcquiredTokens": { "message": "Ajouter les jetons que vous avez acquis par l’intermédiaire de MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Ajoutez à partir d’une liste de réseaux populaires ou ajoutez un réseau manuellement. Interagissez uniquement avec les entités en lesquelles vous avez confiance." }, + "addHardwareWallet": { + "message": "Ajouter un portefeuille matériel" + }, + "addIPFSGateway": { + "message": "Ajoutez votre passerelle IPFS préférée" + }, "addMemo": { "message": "Ajouter un mémo" }, @@ -244,6 +256,21 @@ "message": "Cette connexion réseau repose sur des tiers. Elle peut être moins fiable ou permettre à des tiers de suivre l’activité des utilisateurs. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Ajouter un nouveau jeton" + }, + "addNft": { + "message": "Ajouter un NFT" + }, + "addNfts": { + "message": "Ajouter des NFT" + }, + "addSnapAccountModalDescription": { + "message": "Découvrez les différentes options disponibles pour sécuriser votre compte avec lMetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Ajouter les NFT suggérés" + }, "addSuggestedTokens": { "message": "Ajouter les jetons suggérés" }, @@ -251,9 +278,12 @@ "message": "Ajouter le jeton" }, "addTokenByContractAddress": { - "message": "Vous ne trouvez pas de jeton ? Vous pouvez ajouter manuellement n’importe quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1", + "message": "Vous n’arrivez pas à trouver un jeton ? Vous pouvez ajouter manuellement n’importe quel jeton en copiant et collant son adresse. Les adresses des contrats de jetons sont disponibles sur $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Ajout de réseau" + }, "address": { "message": "Adresse" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Les frais de priorité (aussi appelés « pourboire du mineur ») vont directement aux mineurs et les incitent à accorder la priorité à votre transaction." }, + "agreeTermsOfUse": { + "message": "J’accepte les $1 de MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "Coffre-fort AirGap" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Alertes" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Soit vous avez déjà connecté tous vos comptes dépositaires, soit vous n’avez aucun compte à connecter à MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Aucun compte à connecter" + }, "allOfYour": { "message": "Tous vos $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Tous vos NFT via $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Autoriser cette extension externe à :" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Autoriser ce site à :" }, + "allowThisSnapTo": { + "message": "Autoriser ce snap à :" + }, "allowWithdrawAndSpend": { "message": "Permettre à $1 de retirer et de dépenser jusqu’au montant suivant :", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Montant" }, + "apiUrl": { + "message": "URL de l’API" + }, "appDescription": { "message": "Extension Ethereum pour navigateur", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Voulez-vous lui accorder l’autorisation d’accéder et de transférer tous vos $1 ?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Autoriser l’accès et le transfert de vos NFT via $1 ?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Approuver" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Tant que vous n’aurez pas révoqué cette autorisation, l’autre partie pourra accéder à votre portefeuille et transférer sans préavis les NFT suivants." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Tant que vous n’aurez pas révoqué cette autorisation, l’autre partie pourra accéder à votre portefeuille et transférer sans préavis les NFT via $1$.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Autoriser l’accès et le transfert de vos $1 ?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Si vous essayez d’envoyer des actifs directement d’un réseau à un autre, une perte permanente des actifs pourrait en résulter. Assurez-vous d’utiliser une passerelle." }, + "attemptToCancelSwap": { + "message": "Tentative d’annulation du swap pour ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Tentative de connexion au réseau" }, @@ -411,6 +473,9 @@ "average": { "message": "Moyen" }, + "awaitingApproval": { + "message": "En attente d’approbation..." + }, "back": { "message": "Retour" }, @@ -454,6 +519,9 @@ "message": "Il s’agit d’une version bêta. Veuillez signaler les bogues $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Version Beta de MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Version MetaMask Beta" }, @@ -488,9 +556,45 @@ "message": "Afficher le compte à $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "BlockAid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Si vous approuvez cette demande, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs." + }, + "blockaidDescriptionBlurFarming": { + "message": "Si vous approuvez cette demande, quelqu’un pourrait s'emparer de vos actifs répertoriés sur Blur." + }, + "blockaidDescriptionFailed": { + "message": "À la suite d’une erreur, cette demande n’a pas été vérifiée par le fournisseur de services de sécurité. Veuillez agir avec prudence." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Vous interagissez avec un domaine malveillant. Si vous approuvez cette demande, vous risquez de perdre vos actifs." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Si vous approuvez cette demande, vous risquez de perdre vos actifs." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Si vous approuvez cette demande, quelqu’un pourrait s'emparer de vos actifs répertoriés sur OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Si vous approuvez cette demande, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs." + }, + "blockaidTitleDeceptive": { + "message": "Cette demande trompeuse" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Cette demande peut présenter des risques" + }, + "blockaidTitleSuspicious": { + "message": "Cette demande suspecte" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Pont" + }, "browserNotSupported": { "message": "Votre navigateur internet n’est pas compatible..." }, @@ -510,6 +614,10 @@ "message": "Acheter $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Acheter plus de $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Achetez maintenant" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Cela réinitialise le nonce du compte et efface les données de l’onglet « Activité » dans votre portefeuille. Seuls le compte et le réseau actuels seront affectés. Aucun changement ne sera apporté aux soldes ou transactions entrantes." }, + "click": { + "message": "Cliquez ici" + }, "clickToConnectLedgerViaWebHID": { "message": "Cliquez ici pour connecter votre Ledger via WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Vous devez maintenant quitter MetaMask pour configurer ce snap." + }, + "configureSnapPopupInstallDescription": { + "message": "Vous devez maintenant quitter MetaMask pour installer ce snap." + }, + "configureSnapPopupInstallTitle": { + "message": "Installer le snap" + }, + "configureSnapPopupLink": { + "message": "Cliquez sur ce lien pour continuer :" + }, + "configureSnapPopupTitle": { + "message": "Configurer le snap" + }, "confirm": { "message": "Confirmer" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Connecter un compte ou en créer un nouveau" }, + "connectCustodialAccountMenu": { + "message": "Connexion au compte de dépôt" + }, + "connectCustodialAccountMsg": { + "message": "Veuillez choisir le dépositaire auquel vous souhaitez vous connecter afin d’ajouter ou d’actualiser un jeton." + }, + "connectCustodialAccountTitle": { + "message": "Comptes de dépôt" + }, "connectManually": { "message": "Se connecter manuellement au site actuel" }, + "connectSnap": { + "message": "Connecter $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Connectez-vous à $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Connexion au réseau de test Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Connexion au réseau principal de Linea" + }, "connectingToMainnet": { "message": "Connexion au réseau principal Ethereum" }, "connectingToSepolia": { "message": "Connexion au réseau de test Sepolia" }, + "connectionFailed": { + "message": "Échec de la connexion" + }, + "connectionFailedDescription": { + "message": "L’extraction de $1 a échoué, vérifiez votre connexion réseau et réessayez.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Demande de connexion" + }, "contactUs": { "message": "Nous contacter" }, @@ -708,7 +860,7 @@ "message": "Déploiement de contrat" }, "contractDescription": { - "message": "Pour vous protéger des escrocs, prenez un moment pour vérifier les détails du contrat." + "message": "Pour vous protéger contre les escrocs, prenez le temps de vérifier les informations relatives aux tiers." }, "contractInteraction": { "message": "Interaction avec un contrat" @@ -717,16 +869,16 @@ "message": "Contrat NFT" }, "contractRequestingAccess": { - "message": "Contrat demandant l’accès" + "message": "Tiers demandant l'accès" }, "contractRequestingSignature": { - "message": "Contrat nécessitant une signature" + "message": "Tiers nécessitant une signature" }, "contractRequestingSpendingCap": { - "message": "Contrat prévoyant un plafond de dépenses" + "message": "Tiers prévoyant un plafond de dépenses" }, "contractTitle": { - "message": "Détails du contrat" + "message": "Détails du tiers" }, "contractToken": { "message": "Contrat de jetons" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Graphique d’estimation de gas du marché" }, + "custodian": { + "message": "Dépôt" + }, + "custodianAccount": { + "message": "Compte de dépôt" + }, + "custodianAccountAddedDesc": { + "message": "Vous pouvez maintenant utiliser vos comptes de dépôt dans MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Les comptes de dépôt sélectionnés ont été ajoutés." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Accédez à $1 et cliquez sur le bouton « Se connecter à MMI » dans l’interface utilisateur pour reconnecter vos comptes à MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Vous pouvez désormais utiliser vos comptes de dépôt dans MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Votre jeton de dépôt a été actualisé" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Ceci remplacera le jeton de dépôt pour l’adresse suivante :" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Remplacer le jeton de dépôt" + }, + "custodyApiUrl": { + "message": "URL DE L’API $1" + }, + "custodyDeeplinkDescription": { + "message": "Approuvez la transaction dans l’application $1. La transaction sera effectuée une fois que toutes les approbations de dépôt auront été obtenues. Vérifiez l’état de votre application $1." + }, + "custodyRefreshTokenModalDescription": { + "message": "Accédez à $1 et cliquez sur le bouton « Se connecter à MMI » dans l’interface utilisateur pour reconnecter vos comptes à MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Votre dépositaire émet un jeton qui authentifie l’extension « MetaMask Institutional » et vous permet ainsi de connecter vos comptes." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Ce jeton expire après un certain temps pour des raisons de sécurité. Si le jeton expire, vous devrez vous reconnecter à MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Pourquoi ce message s’affiche-t-il ?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Votre session en mode dépositaire a expiré" + }, + "custodySessionExpired": { + "message": "La session du dépositaire a expiré." + }, + "custodyWrongChain": { + "message": "Ce compte n’est pas configuré pour être utilisé avec $1" + }, "custom": { "message": "Paramètres avancés" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "service client" }, + "dappRequestedSpendingCap": { + "message": "Plafond de dépenses demandé par le site" + }, "dappSuggested": { "message": "Site suggéré" }, @@ -851,6 +1060,12 @@ "message": "$1 a suggéré ce prix.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Site suggéré" + }, + "dappSuggestedHighShortLabel": { + "message": "Site (élevé)" + }, "dappSuggestedShortLabel": { "message": "Site" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Supprimer" }, + "deleteContact": { + "message": "Supprimer le contact" + }, "deleteNetwork": { "message": "Supprimer le réseau ?" }, + "deleteNetworkIntro": { + "message": "Si vous supprimez ce réseau, vous devrez l’ajouter à nouveau pour voir vos actifs sur ce réseau" + }, + "deleteNetworkTitle": { + "message": "Supprimer le réseau $1 ?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Effectuez un dépôt" }, @@ -917,6 +1142,10 @@ "description": { "message": "Description" }, + "descriptionFromSnap": { + "message": "Description provenant de $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Cette erreur peut être passagère – essayez de redémarrer l’extension ou de désactiver MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Annuler le rappel de sauvegarde de la phrase secrète de récupération" }, + "displayNftMedia": { + "message": "Afficher les médias NFT" + }, + "displayNftMediaDescription": { + "message": "L’affichage des médias et des données NFT expose votre adresse IP à OpenSea ou à d’autres fournisseurs de services tiers. Cela peut permettre à des hackers d’associer votre adresse IP à votre adresse Ethereum. L’autodétection des NFT dépend de ce paramètre et ne sera pas disponible lorsque celui-ci est désactivé." + }, "domain": { "message": "Domaine" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Activer la détection automatique" }, + "enableForAllNetworks": { + "message": "Activer pour tous les réseaux" + }, "enableFromSettings": { "message": " Activez-la depuis les Paramètres." }, + "enableSmartSwaps": { + "message": "Activer les contrats de swap intelligents" + }, + "enableSnap": { + "message": "Activer" + }, "enableToken": { "message": "activer $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Activé" + }, "encryptionPublicKeyNotice": { "message": "$1 aimerait avoir votre clé publique de cryptage. En y consentant, ce site sera en mesure de vous composer des messages cryptés.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "La détection améliorée des jetons est actuellement disponible sur $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask vous permet de voir les domaines ENS comme « https://metamask.eth » dans la barre d’adresse de votre navigateur. Voici comment cela fonctionne :" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Les navigateurs ordinaires ne prennent généralement pas en charge les adresses ENS ou IPFS, mais MetaMask peut y remédier. L’utilisation de cette fonctionnalité peut entraîner le partage de votre adresse IP avec des services tiers IPFS." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask vérifie le contrat ENS d'Ethereum pour trouver le code lié au nom de domaine ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Si le code est lié au protocole IPFS, il obtient le contenu du réseau IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Vous pourrez alors voir le contenu, qui est généralement un site web ou quelque chose de similaire." + }, + "ensDomainsSettingTitle": { + "message": "Afficher les domaines ENS dans la barre d’adresse" + }, "ensIllegalCharacter": { "message": "Caractère invalide pour l’ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Saisissez un nombre" }, + "enterCustodianToken": { + "message": "Saisissez votre jeton de $1 ou ajoutez un nouveau jeton" + }, "enterMaxSpendLimit": { "message": "Saisissez la limite de dépenses maximale" }, + "enterOptionalPassword": { + "message": "Entrez le mot de passe facultatif" + }, "enterPassword": { "message": "Entrez votre mot de passe" }, "enterPasswordContinue": { "message": "Entrez votre mot de passe pour continuer" }, + "enterTokenNameOrAddress": { + "message": "Saisissez le nom du jeton ou copiez-collez l’adresse" + }, + "enterYourPassword": { + "message": "Saisissez votre mot de passe" + }, "errorCode": { "message": "Code : $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Stack :", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Une erreur s’est produite lors de la connexion au réseau personnalisé." + }, + "errorWithSnap": { + "message": "Erreur avec $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Le prix de carburant de sauvegarde est fourni, car le service principal d’estimation du carburant est momentanément indisponible." }, + "ethereumProviderAccess": { + "message": "Accorder au fournisseur d’Ethereum l’autorisation d’accéder à $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Adresse publique d’Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Expérimental" }, + "exploreMetaMaskSnaps": { + "message": "Explorer les Snaps MetaMask" + }, "exportPrivateKey": { "message": "Exporter la clé privée" }, + "extendWalletWithSnaps": { + "message": "Prolongez l’expérience avec ce portefeuille." + }, "externalExtension": { "message": "Extension externe" }, @@ -1302,6 +1596,9 @@ "message": "L’importation de fichier ne fonctionne pas ? Cliquez ici !", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Le fichier déconnecté est trop volumineux." + }, "flaskWelcomeUninstall": { "message": "vous devriez désinstaller cette extension", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Général" }, + "getStarted": { + "message": "Commencer" + }, + "globalTitle": { + "message": "Menu global" + }, + "globalTourDescription": { + "message": "Voyez votre portfolio, vos sites connectés, vos paramètres, et bien plus encore" + }, "goBack": { "message": "Retour" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Connecter un portefeuille matériel" }, + "hardwareWalletsInfo": { + "message": "Les intégrations de portefeuilles matériels utilisent des appels API vers des serveurs externes qui peuvent voir votre adresse IP et les adresses des contrats intelligents avec lesquels vous interagissez." + }, "hardwareWalletsMsg": { "message": "Selectionnez le portefeuille matériel que vous voulez utiliser avec MetaMask" }, @@ -1541,11 +1850,34 @@ "message": "mais les hameçonneurs pourraient le faire.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Votre clé privée vous donne $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "un accès illimité à votre portefeuille et à vos fonds.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "appuyez longuement pour révéler le cercle verrouillé" + }, + "holdToRevealPrivateKey": { + "message": "Appuyez longuement pour révéler la clé privée" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Conservez votre clé privée en lieu sûr" + }, "holdToRevealSRP": { - "message": "Appuyez longuement pour révéler PSR" + "message": "Appuyez longuement pour révéler la PSR" }, "holdToRevealSRPTitle": { - "message": "Protégez votre PSR" + "message": "Conservez votre PSR en lieu sûr" + }, + "holdToRevealUnlockedLabel": { + "message": "appuyez longuement pour révéler le cercle déverrouillé" + }, + "id": { + "message": "Identifiant" }, "ignoreAll": { "message": "Ignorer tout" @@ -1563,6 +1895,21 @@ "importAccountError": { "message": "Erreur d’importation de compte." }, + "importAccountErrorIsSRP": { + "message": "Vous avez saisi une phrase secrète de récupération (ou mnémonique). Pour importer un compte ici, vous devez saisir une clé privée, qui est une chaîne hexadécimale de 64 caractères." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Il ne s’agit pas d’une clé privée valide. Vous avez saisi une chaîne hexadécimale, mais elle doit comporter 64 caractères." + }, + "importAccountErrorNotHexadecimal": { + "message": "Il ne s’agit pas d’une clé privée valide. Vous devez saisir une chaîne hexadécimale de 64 caractères." + }, + "importAccountJsonLoading1": { + "message": "Cette importation JSON prendra quelques minutes et pourra faire planter MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Désolé, nous essaierons d’accélérer ce processus à l’avenir." + }, "importAccountMsg": { "message": "Les comptes importés ne seront pas associés à la phrase secrète de récupération que vous avez créée au départ dans MetaMask. En savoir plus sur les comptes importés" }, @@ -1615,18 +1962,29 @@ "message": "Votre transaction initiale a été confirmée par le réseau. Cliquez sur OK pour retourner à l’écran précédent." }, "inputLogicEmptyState": { - "message": "Saisissez uniquement le montant que vous êtes prêt à dépenser maintenant ou à l’avenir, prévu au contrat. Vous pourrez toujours augmenter le plafond des dépenses plus tard." + "message": "N'entrez qu'une somme que vous pouvez accepter que le tiers dépense aujourd'hui ou à l'avenir. Vous pourrez toujours augmenter le plafond de dépenses ultérieurement." }, "inputLogicEqualOrSmallerNumber": { - "message": "Cela permet de dépenser $1 de votre solde actuel, conformément au contrat.", + "message": "Cela permet au tiers de dépenser $1 de votre solde actuel.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Cela permet de dépenser tout votre solde de jetons jusqu’au plafond prévu dans le contrat ou à annuler ce même plafond. Sinon, prévoyez de fixer un plafond de dépenses inférieur." + "message": "Cela permet au tiers de dépenser tout votre solde de jetons jusqu'à ce qu'il atteigne le plafond ou que vous révoquiez le plafond de dépenses. Si ce n'est pas votre intention, envisagez de fixer un plafond de dépenses plus bas." + }, + "insightsFromSnap": { + "message": "Aperçus $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Installez" }, + "installOrigin": { + "message": "Installer Origin" + }, + "installedOn": { + "message": "Installé le $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Solde insuffisant." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Entrée invalide ! La phrase secrète de récupération est sensible à la casse." }, + "ipfsGateway": { + "message": "Passerelle IPFS" + }, + "ipfsGatewayDescription": { + "message": "MetaMask utilise des services tiers pour afficher des images de vos NFT stockés sur IPFS, des informations relatives aux adresses ENS saisies dans la barre d’adresse de votre navigateur et l’icône des différents jetons. Votre adresse IP peut être exposée si vous avez recours à ces services." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Nous utilisons des services tiers pour afficher des images de vos NFT stockés sur IPFS, affichons des informations relatives aux adresses ENS saisies dans la barre d’adresse de votre navigateur et récupérons les icônes des différents jetons. Votre adresse IP peut être exposée si vous avez recours à ces services." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "En sélectionnant « Confirmer », vous activez la résolution IPFS. Vous pouvez désactiver cette option à tout moment dans $1.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Paramètres > Sécurité et confidentialité" + }, "jazzAndBlockies": { "message": "Les Jazzicons et les Blockies sont deux styles différents d’icônes uniques qui vous aident à identifier un compte en un coup d’œil." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Dernière vente" }, + "layer1Fees": { + "message": "Frais de couche 1 (L1)" + }, "learnCancelSpeeedup": { "message": "Découvrir comment $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Vous voulez $1 sur les frais de gaz ?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "En savoir plus" + }, "learnMoreUpperCase": { "message": "En savoir plus" }, @@ -1765,16 +2145,16 @@ "message": "Avant de cliquer sur confirmer :" }, "ledgerConnectionInstructionStepFour": { - "message": "Activez les « données de contrat intelligent » ou la « signature aveugle » sur votre dispositif Ledger" + "message": "Activez les « données de contrat intelligent » ou la « signature aveugle » sur votre dispositif Ledger." }, "ledgerConnectionInstructionStepOne": { - "message": "Activez l’option « Utiliser Ledger Live » sous Paramètres > Avancés" + "message": "Activez l’option « Utiliser Ledger Live » sous Paramètres > Avancé." }, "ledgerConnectionInstructionStepThree": { - "message": "Branchez votre dispositif Ledger et sélectionnez l’application Ethereum" + "message": "Assurez-vous que votre dispositif Ledger est branché et sélectionnez l'application Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Ouvrez et déverrouillez l’application Ledger Live" + "message": "Ouvrez et déverrouillez l’application Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Personnalisez la façon dont vous souhaitez connecter votre Ledger à MetaMask. $1 est recommandé, mais d’autres options sont disponibles. En savoir plus ici : $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Réseau de test Linea Goerli" }, + "lineaMainnet": { + "message": "Le réseau principal de Linea" + }, "link": { "message": "Associer" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Déconnexion" }, + "lockMetaMask": { + "message": "Verrouiller MetaMask" + }, + "lockTimeInvalid": { + "message": "Le temps de verrouillage doit être un nombre compris entre 0 et 10 080" + }, "logo": { "message": "Logo $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Le bouton d’état de connexion indique si le site Web que vous visitez est connecté à votre compte actuellement sélectionné." }, + "metamaskInstitutionalVersion": { + "message": "Version MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps est en cours de maintenance. Nous vous invitons à revenir plus tard." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Indicateurs" }, + "mismatchAccount": { + "message": "Le compte sélectionné ($1) est différent du compte que vous essayez de signer ($2)" + }, "mismatchedChainLinkText": { "message": "vérifier les détails du réseau", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Le symbole monétaire soumis ne correspond à cet ID de chaîne." }, + "mismatchedRpcChainId": { + "message": "L’ID de chaîne renvoyé par le réseau personnalisé ne correspond pas à l’ID de chaîne fourni." + }, "mismatchedRpcUrl": { "message": "Selon nos informations, la valeur de l’URL RPC soumise ne correspond pas à un fournisseur connu pour cet ID de chaîne." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Demandez ici" }, + "mmiAddToken": { + "message": "La page à $1 souhaite autoriser le jeton de dépôt suivant dans MetaMask institutionnel" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional est conçu et établi dans le monde entier." + }, + "more": { + "message": "plus" + }, "moreComingSoon": { - "message": "D’autres à venir..." + "message": "D’autres fournisseurs seront bientôt disponibles" + }, + "multipleSnapConnectionWarning": { + "message": "$1 veut se connecter avec $2 snaps. Ne procédez que si vous avez confiance dans ce site web.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Vous devez sélectionner au moins 1 jeton." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Le réseau est occupé. Les gas fees sont élevés et les estimations sont moins précises." }, + "networkMenu": { + "message": "Menu du réseau" + }, + "networkMenuHeading": { + "message": "Sélectionner un réseau" + }, "networkName": { "message": "Nom du réseau" }, @@ -2033,6 +2450,10 @@ "message": "Le prix du carburant est de $1 au regard des 72 dernières heures.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Nous ne pouvons pas nous connecter à $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL du réseau" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Précédemment possédés" + }, "nickname": { "message": "Pseudonyme" }, @@ -2138,6 +2562,12 @@ "noConversionRateAvailable": { "message": "Aucun taux de conversion disponible" }, + "noNFTs": { + "message": "Aucun NFT pour le moment" + }, + "noNetworksFound": { + "message": "Aucun réseau trouvé pour la requête donnée" + }, "noSnaps": { "message": "Aucun Snap installé" }, @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "S’agit-il du bon compte ? Il est différent de celui actuellement sélectionné dans votre portefeuille" }, + "notEnoughBalance": { + "message": "Solde insuffisant" + }, "notEnoughGas": { "message": "Pas assez de gaz" }, + "note": { + "message": "Note" + }, + "notePlaceholder": { + "message": "L’approbateur verra cette note lorsqu’il approuvera la transaction auprès du dépositaire." + }, + "notificationTransactionFailedMessage": { + "message": "La transaction $1 a échoué ! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "La transaction a échoué ! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Transaction non conclue", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "La transaction $1 a été confirmée !", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Transaction confirmée", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Consulter sur $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Notifications" + }, "notifications10ActionText": { "message": "Ouvrir les paramètres", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "La fusion Ethereum est en marche !" }, + "notifications18ActionText": { + "message": "Activer les alertes de sécurité" + }, + "notifications18DescriptionOne": { + "message": "Recevez des alertes de tiers en cas de demande potentiellement malveillante.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Vous devez faire preuve de diligence raisonnable avant d’approuver toute demande.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea est le premier fournisseur à proposer cette fonctionnalité. D’autres le feront bientôt !", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Restez en sécurité grâce aux alertes de sécurité" + }, + "notifications19ActionText": { + "message": "Activer la détection automatique des NFT" + }, + "notifications19DescriptionOne": { + "message": "Vous pouvez commencer de deux façons :", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Pour l’instant, nous ne prenons en charge que l’ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Ajoutez manuellement vos NFT ou activez la détection automatique des NFT dans Paramètres > Expérimental.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Une nouvelle façon de voir vos NFT" + }, "notifications1Description": { "message": "Les utilisateurs de MetaMask Mobile peuvent désormais échanger des jetons dans leur portefeuille mobile. Scannez le code QR pour obtenir l’application mobile et commencez à échanger.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Les swaps sur mobile sont enfin possibles !", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "En savoir plus", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Si vous utilisez la dernière version de Firefox, il se peut que vous rencontriez un problème, car Firefox ne prend plus en charge la norme d’authentification U2F.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Les utilisateurs de Ledger et de Firefox rencontrent des problèmes de connexion", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Essayez-les" + }, + "notifications21Description": { + "message": "Nous avons mis à jour les swaps dans l’extension MetaMask afin qu’ils soient plus faciles et plus rapides à utiliser.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Voici la nouvelle version améliorée des swaps !" + }, + "notifications22ActionText": { + "message": "J’ai compris" + }, + "notifications22Description": { + "message": "💡 Il suffit de cliquer sur le menu global ou le menu du compte pour les trouver !" + }, + "notifications22Title": { + "message": "Vous cherchez les détails de votre compte ou l’URL de l’explorateur de blocs ?" + }, + "notifications23ActionText": { + "message": "Activer les alertes de sécurité" + }, + "notifications23DescriptionOne": { + "message": "Évitez les escroqueries et protégez vos donnés personnelles grâce aux alertes de sécurité fournies par Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Les utilisateurs qui ont déjà activé les alertes de sécurité OpenSea recevront désormais des alertes de sécurité de ce nouveau fournisseur." + }, + "notifications23DescriptionTwo": { + "message": "Vous devez faire preuve de diligence raisonnable avant d’approuver toute demande." + }, + "notifications23Title": { + "message": "Restez en sécurité grâce aux alertes de sécurité" + }, "notifications3ActionText": { "message": "En savoir plus", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Ne vous connectez qu’aux sites auxquels vous faites confiance." }, "openFullScreenForLedgerWebHid": { - "message": "Ouvrez MetaMask en mode plein écran pour connecter votre Ledger via WebHID.", + "message": "Passez en plein écran pour connecter votre Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Ouvrir dans l’explorateur de blocs" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "L’opération a échoué" + }, "optional": { "message": "Facultatif" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Les mots de passe ne correspondent pas" }, + "pasteJWTToken": { + "message": "Collez ou déposez votre jeton ici :" + }, "pastePrivateKey": { "message": "Collez votre clé privée ici:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Accéder à internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Autoriser le snap à accéder à l’internet. Cela permet d’envoyer et de recevoir des données avec des serveurs tiers.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Connexion au Snap $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Autoriser le site Web ou le snap à interagir avec $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Planifiez et exécutez des actions périodiques.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Autoriser le snap à effectuer des actions exécutables à des heures, des dates ou des intervalles fixes. Cela permet de déclencher des interactions ou des notifications sensibles au facteur temps.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Afficher les boîtes de dialogue dans MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Autoriser le snap à afficher des fenêtres contextuelles MetaMask avec un texte personnalisé, un champ de saisie et des boutons pour approuver ou rejeter une action.\nCela permet de créer par exemple des alertes, des confirmations et des flux d’acceptation pour un snap.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Consultez l’adresse, le solde du compte et l’activité, et lancez des transactions", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Accéder au fournisseur d’Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Autoriser le snap à communiquer directement avec MetaMask, afin qu’il puisse lire les données de la blockchain et suggérer des messages et des transactions.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Dériver des clés arbitraires uniques à ce snap.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Autoriser le snap à dériver des clés arbitraires uniques à ce snap, sans les exposer. Ces clés sont distinctes de votre (vos) compte(s) MetaMask et ne sont pas associées à vos clés privées, ni à votre phrase de récupération secrète. Les autres snaps ne peuvent pas accéder à ces informations.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Utilisez les hooks de cycle de vie.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Autorisez le Snap à utiliser des hooks de cycle de vie pour exécuter du code à des moments spécifiques de son cycle de vie.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Fonctionner indéfiniment.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Autoriser l’exécution illimitée du snap pendant, par exemple, le traitement de volumes importants de données.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Ajouter et gérer des comptes Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Contrôlez vos comptes et vos actifs sous $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Autoriser le snap à dériver des paires de clés BIP-32 basées sur votre phrase secrète de récupération sans l’exposer. Cela donne un accès complet à tous les comptes et actifs sur $1.\nGrâce au pouvoir de gestion des clés, le snap peut prendre en charge différents protocoles de la blockchain au-delà d’Ethereum (EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Contrôlez vos comptes et actifs « $1 ».", + "message": "Contrôlez vos comptes et actifs de $1.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Autoriser le snap à dériver des paires de clés BIP-44 basées sur votre phrase secrète de récupération sans l’exposer. Cela donne un accès complet à tous les comptes et actifs sur $1.\nGrâce au pouvoir de gestion des clés, le snap peut prendre en charge différents protocoles de la blockchain au-delà d’Ethereum (EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Contrôlez vos actifs et vos comptes « $1 ».", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Stockez et gérez ses données sur votre appareil.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Autoriser le snap à stocker, à mettre à jour et à récupérer des données en toute sécurité grâce au chiffrement. Les autres snaps ne peuvent pas accéder à ces informations.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Afficher les notifications.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Autoriser le snap à afficher des notifications dans MetaMask. Un petit texte de notification peut être déclenché par un snap pour fournir des informations exploitables ou sensibles au facteur temps.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Autorisez $1 à communiquer directement avec ce Snap.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Autoriser $1 à envoyer des messages au snap et à recevoir une réponse de celui-ci.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Récupérez et affichez les aperçus de transaction.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Autoriser le snap à décoder les transactions et à afficher des informations dans l’interface utilisateur de MetaMask. Cela permet développer des solutions de sécurité et de lutte contre l’hameçonnage.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Voir les sites web à l’origine des demandes de transaction", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Autoriser le snap à voir l'origine (URI) des sites Web qui suggèrent des transactions. Cela permet de développer des solutions de sécurité et de lutte contre l'hameçonnage.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Autorisation inconnue : $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Consultez votre clé publique pour $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Autoriser le snap à consulter vos clés publiques (et vos adresses) pour $1. Le contrôle des comptes ou des actifs est impossible.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Afficher votre clé publique pour $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Prise en charge de WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Autoriser le snap à accéder à des environnements d’exécution de faible niveau via WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Autorisations" }, + "permissionsTitle": { + "message": "Autorisations" + }, + "permissionsTourDescription": { + "message": "Trouvez vos comptes connectés et gérez les autorisations ici" + }, "personalAddressDetected": { "message": "Votre adresse personnelle a été détectée. Veuillez saisir à la place l’adresse du contrat du jeton." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portefeuille" }, + "portfolioDashboard": { + "message": "Tableau de bord du portefeuille" + }, "preferredLedgerConnectionType": { "message": "Type de connexion préférée au Ledger", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Clé privée", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Clé privée pour $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Avertissement : ne divulguez jamais cette clé, quiconque possède vos clés privées peut voler tous les actifs de votre compte." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "En attente" }, + "quoteRate": { + "message": "Cotation" + }, "reAddAccounts": { "message": "ajouter de nouveau tous les autres comptes" }, @@ -2751,7 +3401,7 @@ "message": "Recevoir" }, "recipientAddressPlaceholder": { - "message": "Recherche, adresse publique (0x) ou ENS" + "message": "Saisissez l’adresse publique (0x) ou le nom de domaine ENS" }, "recommendedGasLabel": { "message": "Recommandé" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Ce compte va être supprimé de votre portefeuille. Veuillez vérifier que vous avez la phrase secrète de récupération originale de ce compte ou la clé privée pour ce compte importé avant de continuer. Vous pouvez importer ou créer à nouveau des comptes à partir du menu des comptes. " }, + "removeJWT": { + "message": "Supprimer le jeton de dépôt" + }, + "removeJWTDescription": { + "message": "Êtes-vous sûr de vouloir supprimer ce jeton ? Tous les comptes attribués à ce jeton seront également supprimés de l’extension : " + }, "removeNFT": { "message": "Supprimer le NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Vous pouvez restaurer les paramètres de l’utilisateur qui contiennent les préférences et les adresses de compte à partir d’un fichier JSON précédemment sauvegardé." }, + "resultPageError": { + "message": "Erreur" + }, + "resultPageErrorDefaultMessage": { + "message": "L’opération a échoué." + }, + "resultPageSuccess": { + "message": "Réussite" + }, + "resultPageSuccessDefaultMessage": { + "message": "L’opération a été effectuée avec succès." + }, "retryTransaction": { "message": "Retenter la transaction" }, @@ -2939,16 +3607,27 @@ "message": "Révoquer l’autorisation d’accès et de transfert de tous vos $1 ?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Révoquer l’autorisation d’accès et de transfert de tous vos NFT via $1 ?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Cela révoque l’autorisation pour un tiers d’accéder et de transférer la totalité de vos $1 sans préavis.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Tant que vous n’aurez pas révoqué cette autorisation, l’autre partie pourra accéder à votre portefeuille et transférer sans préavis les NFT via $1.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Retirer l'autorisation" + }, "revokeSpendingCap": { "message": "Supprimez le plafond des dépenses pour vos $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "L’autre partie à ce contrat ne pourra plus dépenser vos jetons actuels ou futurs." + "message": "Ce tiers ne pourra plus dépenser vos jetons actuels ou futurs." }, "rpcUrl": { "message": "Nouvelle URL de RPC" @@ -2986,9 +3665,28 @@ "security": { "message": "Sécurité" }, + "securityAlert": { + "message": "Alerte de sécurité provenant de $1 et de $2" + }, + "securityAlerts": { + "message": "Alertes de sécurité" + }, + "securityAlertsDescription1": { + "message": "Cette fonctionnalité vous avertit de toute activité malveillante en examinant localement vos transactions et vos demandes de signature. Vos données ne sont pas partagées avec les tiers qui fournissent ce service. Vous devez faire preuve de diligence raisonnable avant d’approuver toute demande. Rien ne garantit que toutes les activités malveillantes seront détectées par cette fonctionnalité." + }, + "securityAlertsDescription2": { + "message": "Vous devez faire preuve de diligence raisonnable avant d’approuver toute demande. Rien ne garantit que toutes les activités malveillantes seront détectées par cette fonctionnalité." + }, "securityAndPrivacy": { "message": "Sécurité et confidentialité" }, + "securityProviderAdviceBy": { + "message": "Conseils de sécurité fournis par $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Voir les détails" + }, "seedPhraseConfirm": { "message": "Confirmer la phrase secrète de récupération" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Notez votre phrase secrète de récupération" }, + "select": { + "message": "Sélectionner" + }, "selectAccounts": { "message": "Sélectionnez le(s) compte(s) à utiliser sur ce site" }, + "selectAccountsForSnap": { + "message": "Sélectionnez le(s) compte(s) à utiliser avec ce snap" + }, "selectAll": { "message": "Tout sélectionner" }, + "selectAllAccounts": { + "message": "Sélectionner tous les comptes" + }, "selectAnAccount": { "message": "Sélectionner un compte" }, "selectAnAccountAlreadyConnected": { "message": "Ce compte a déjà été connecté à MetaMask" }, + "selectAnAccountHelp": { + "message": "Sélectionnez les comptes de dépôt que vous voulez utiliser dans MetaMask Institutional." + }, "selectHdPath": { "message": "Sélectionner le chemin HD" }, + "selectJWT": { + "message": "Sélectionnez un jeton" + }, "selectNFTPrivacyPreference": { "message": "Activez la détection de NFT dans les Paramètres" }, @@ -3113,6 +3826,9 @@ "message": "Approuver $1 sans limite de dépenses", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Ajouter un compte snap" + }, "settings": { "message": "Paramètres" }, @@ -3141,9 +3857,21 @@ "message": "Sélectionnez ceci pour utiliser Etherscan afin d’afficher les transactions entrantes dans la liste des transactions", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Cela dépend de chaque réseau qui aura accès à votre adresse Ethereum et à votre adresse IP." + }, + "showMore": { + "message": "Afficher plus" + }, + "showNft": { + "message": "Afficher le NFT" + }, "showPermissions": { "message": "Afficher les autorisations" }, + "showPrivateKey": { + "message": "Afficher la clé privée" + }, "showPrivateKeys": { "message": "Afficher les clés privées" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Je suis conscient(e) que tant que je n’aurai pas sauvegardé ma phrase secrète de récupération, je risque de perdre mes comptes et tous leurs actifs." }, + "smartContracts": { + "message": "Contrats intelligents" + }, + "smartSwap": { + "message": "Contrat de swap intelligent" + }, + "smartSwapsAreHere": { + "message": "Les contrats de swap intelligents sont enfin arrivés !" + }, + "smartSwapsDescription": { + "message": "Les swaps sont devenus beaucoup plus intelligents sur MetaMask ! L’activation des contrats de swap intelligents permettra à MetaMask d’optimiser programmatiquement le processus contractuel pour vous aider à :" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Fonds insuffisants pour souscrire un contrat de swap intelligent." + }, + "smartSwapsErrorUnavailable": { + "message": "Les contrats de swap intelligents sont temporairement indisponibles." + }, + "smartSwapsSubDescription": { + "message": "* Lorsque vous souscrivez un contrat de swap intelligent, ce contrat sera proposé en privé à différents investisseurs. Si toutes les tentatives échouent, le contrat sera diffusé publiquement afin de trouver preneur." + }, + "snapConfigure": { + "message": "Configurer" + }, + "snapConnectionWarning": { + "message": "$1 veut se connecter à $2. Ne continuez que si vous avez confiance en ce site web.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Ce contenu provient de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Choisissez comment vous voulez sécuriser votre nouveau compte à l’aide de MetaMask Snaps." + }, + "snapCreateAccountTitle": { + "message": "Créer un compte $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Conçu par MetaMask" + }, + "snapDetailAudits": { + "message": "Audit" + }, + "snapDetailDeveloper": { + "message": "Développeur" + }, + "snapDetailLastUpdated": { + "message": "Mis à jour" + }, + "snapDetailManageSnap": { + "message": "Gérer ce snap" + }, + "snapDetailTags": { + "message": "Tags" + }, + "snapDetailVersion": { + "message": "Version" + }, + "snapDetailWebsite": { + "message": "Site web" + }, + "snapDetailsCreateASnapAccount": { + "message": "Créer un compte snap" + }, + "snapDetailsInstalled": { + "message": "Installé" + }, "snapError": { "message": "Erreur de snap : « $1 ». Code d’erreur : « $2 »", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Installer Snap" }, + "snapInstallRequest": { + "message": "L’installation de $1 lui donne les autorisations suivantes. Ne continuez que si vous faites confiance à $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Installation terminée" + }, "snapInstallWarningCheck": { "message": "Cochez la case pour confirmer que vous avez compris.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,25 +4009,88 @@ "message": "Veuillez confirmer que vous avez bien compris en cochant toutes les cases.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Agissez avec prudence" + }, "snapInstallWarningKeyAccess": { "message": "Vous autorisez $2 à accéder à la clé du snap « $1 ». Cette action est irréversible et accorde à « $1 » le contrôle de vos comptes et actifs $2. Assurez-vous que vous faites confiance à « $1 » avant de continuer.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Autoriser $2 à accéder à la clé publique de $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 n’a pas pu être installé.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "L’installation a échoué", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Audité" + }, + "snapResultError": { + "message": "Erreur" + }, + "snapResultSuccess": { + "message": "Réussi" + }, + "snapResultSuccessDescription": { + "message": "$1 est prêt à être utilisé" + }, "snapUpdate": { "message": "Mettre à jour Snap" }, + "snapUpdateAvailable": { + "message": "Une mise à jour est disponible" + }, + "snapUpdateErrorDescription": { + "message": "La mise à jour de $1 a échoué.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "La mise à jour a échoué", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 veut mettre à jour $2 vers $3, ce qui lui donne les autorisations suivantes. Ne continuez que si vous faites confiance à $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Mise à jour terminée" + }, "snaps": { "message": "Snaps" }, "snapsInsightLoading": { "message": "Chargement de l’aperçu de transaction…" }, + "snapsInvalidUIError": { + "message": "L’interface utilisateur (IU) spécifiée par le snap n’est pas valide." + }, "snapsNoInsight": { "message": "Le snap n’a renvoyé aucun aperçu" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Vous reconnaissez que le snap que vous êtes sur le point d’installer est un « Service tiers » tel que défini dans les $1 de Consensys. L’utilisation des services tiers est régie par des conditions distinctes définies par le fournisseur du service tiers. Si vous décidez d’utiliser ce service tiers, vous le faites à vos propres risques. Consensys décline toute responsabilité pour toute perte liée à l’utilisation des services tiers.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Toute information que vous partagez avec des services tiers sera collectée directement par ces services tiers conformément à leur politique de confidentialité. Pour plus d’informations, veuillez consulter leur politique de confidentialité.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys n’a pas accès aux informations que vous partagez avec ces tiers.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Gérez vos Snaps" }, + "snapsTermsOfUse": { + "message": "Conditions d’utilisation" + }, "snapsToggle": { "message": "Un snap ne s’exécute que s’il est activé" }, @@ -3284,6 +4151,9 @@ "message": "Saisissez uniquement le nombre de jetons $1 que vous êtes prêt à utiliser maintenant ou à l’avenir. Vous pourrez toujours augmenter la limite de jetons plus tard.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Demande de fixer un plafond de dépenses pour votre $1" + }, "srpInputNumberOfWords": { "message": "J’ai une phrase de $1 mots", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Commencer" }, + "srpSecurityQuizImgAlt": { + "message": "Un œil avec un trou de serrure au centre et trois champs de mots de passe flottants" + }, "srpSecurityQuizIntroduction": { "message": "Pour révéler votre Phrase secrète de récupération, vous devez répondre correctement à deux questions" }, @@ -3318,7 +4191,7 @@ "message": "Pourra la récupérer pour vous" }, "srpSecurityQuizQuestionOneWrongAnswerDescription": { - "message": "Personne ne peut vous aider à récupérer votre phrase secrète de récupération si jamais vous la perdez." + "message": "Personne ne peut vous aider à récupérer votre Phrase secrète de récupération si jamais vous la perdez." }, "srpSecurityQuizQuestionOneWrongAnswerTitle": { "message": "C'est faux ! Personne ne peut vous aider à récupérer votre Phrase secrète de récupération." @@ -3330,7 +4203,7 @@ "message": "Ne la lui fournissez pas, car cette personne essaie de vous arnaquer." }, "srpSecurityQuizQuestionTwoRightAnswerDescription": { - "message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." + "message": "Toute personne qui vous demande votre Phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." }, "srpSecurityQuizQuestionTwoRightAnswerTitle": { "message": "C'est exact ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit." @@ -3339,7 +4212,7 @@ "message": "Vous devez la lui fournir" }, "srpSecurityQuizQuestionTwoWrongAnswerDescription": { - "message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." + "message": "Toute personne qui vous demande votre Phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." }, "srpSecurityQuizQuestionTwoWrongAnswerTitle": { "message": "C'est faux ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit." @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "stable" }, + "stake": { + "message": "Staker" + }, "stateLogError": { "message": "Erreur lors du chargement des journaux d’état." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Non connecté" }, + "statusNotConnectedAccount": { + "message": "Aucun compte connecté" + }, "step1LatticeWallet": { "message": "Connectez votre Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Il s’agit du montant minimal que vous recevrez. Vous pouvez recevoir plus en fonction du glissement." }, + "swapAnyway": { + "message": "Procéder de toute façon au swap" + }, "swapApproval": { "message": "Approuver $1 pour les swaps", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Vous avez besoin de $1 $2 de plus pour effectuer ce swap", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Êtes-vous toujours là ?" + }, + "swapAreYouStillThereDescription": { + "message": "Si vous le souhaitez, nous sommes prêts à vous présenter les dernières cotations" + }, "swapBuildQuotePlaceHolderText": { "message": "Aucun jeton disponible correspondant à $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Confirmez avec votre portefeuille matériel" }, + "swapContinueSwapping": { + "message": "Continuer à faire des swaps" + }, "swapContractDataDisabledErrorDescription": { "message": "Dans l’application Ethereum de votre Ledger, allez dans « Paramètres » et autorisez les données de contrat. Ensuite, retentez votre swap." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Modifier la limite" }, + "swapEditTransactionSettings": { + "message": "Modifier les paramètres de la transaction" + }, "swapEnableDescription": { "message": "Cette information est nécessaire et autorise MetaMask à effectuer le swap de vos $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Ce sera $1 pour le swap", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Saisissez le montant" + }, "swapEstimatedNetworkFees": { "message": "Frais de réseau estimés" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Échec du swap" }, + "swapFetchingQuote": { + "message": "Récupération de la cotation" + }, "swapFetchingQuoteNofN": { "message": "Récupération de cotation $1 sur $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Comprend des frais MetaMask à hauteur de $1 %.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Comprend des frais MetaMask à hauteur de $1 % – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "En savoir plus sur les swaps" + }, "swapLowSlippageError": { "message": "La transaction peut échouer, car le glissement maximal est trop faible." }, @@ -3626,6 +4533,10 @@ "message": "Nouvelles cotations dans $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Aucun jeton disponible correspondant à $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Vos $1 seront ajoutés à votre compte une fois que cette transaction sera traitée.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Détails de la cotation" }, + "swapQuoteNofM": { + "message": "$1 sur $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Origine de la cotation" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Les cotations ont expiré" }, + "swapQuotesNotAvailableDescription": { + "message": "Réduisez le montant de la transaction ou utilisez un autre jeton." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Essayez d’ajuster le montant ou les paramètres de glissement, puis réessayez." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Vous trouverez ci-dessous toutes les cotations obtenues auprès de multiples sources de liquidité." }, + "swapSelectToken": { + "message": "Sélectionner le jeton" + }, + "swapShowLatestQuotes": { + "message": "Afficher les dernières cotations" + }, "swapSlippageNegative": { "message": "Le glissement doit être supérieur ou égal à zéro" }, + "swapSlippageNegativeDescription": { + "message": "Le slippage doit être supérieur ou égal à zéro" + }, + "swapSlippageNegativeTitle": { + "message": "Augmentez le slippage pour continuer" + }, + "swapSlippageOverLimitDescription": { + "message": "La tolérance au slippage doit être inférieure ou égale à 15 %. Une tolérance plus élevée peut se traduire par un taux de change désavantageux." + }, + "swapSlippageOverLimitTitle": { + "message": "Réduisez le slippage pour continuer" + }, "swapSlippagePercent": { "message": "$1 %", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "La transaction peut échouer, car le slippage maximal est trop faible." + }, + "swapSlippageTooLowTitle": { + "message": "Augmentez le slippage pour éviter l’échec de la transaction" + }, "swapSlippageTooltip": { "message": "Si le prix fluctue entre le moment où vous placez un ordre et le moment où il est exécuté, on parle alors d’un « effet de glissement » ou « slippage ». Votre swap sera automatiquement annulé si ce phénomène dépasse le « seuil de glissement toléré » que vous avez fixé." }, + "swapSlippageVeryHighDescription": { + "message": "Le slippage saisi est considéré comme très élevé et peut donner lieu à un taux de change désavantageux" + }, + "swapSlippageVeryHighTitle": { + "message": "Slippage très élevé" + }, + "swapSlippageZeroDescription": { + "message": "Il y a moins de prestataires de services d’investissement sans slippage, ce qui se traduit par une cotation moins compétitive." + }, + "swapSlippageZeroTitle": { + "message": "Recherche de prestataires de services d’investissement sans slippage" + }, "swapSource": { "message": "Source de liquidité" }, @@ -3724,7 +4678,7 @@ "message": "Swap de" }, "swapSwapSwitch": { - "message": "Inverser les jetons de/vers" + "message": "Inverser l’échange de jetons" }, "swapSwapTo": { "message": "Swap vers" @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "pour confirmer avec votre portefeuille matériel" }, + "swapTokenAddedManuallyDescription": { + "message": "Vérifiez ce jeton sur $1 et assurez-vous qu’il s’agit bien du jeton que vous souhaitez échanger.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Jeton ajouté manuellement" + }, "swapTokenAvailable": { "message": "Votre $1 a été ajouté à votre compte.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Vérification effectuée sur $1 sources.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 n’a été vérifié que par 1 source. Envisagez de le vérifier auprès de $2 sources avant de continuer.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Jeton potentiellement inauthentique" + }, "swapTooManyDecimalsError": { "message": "$1 accepte jusqu’à $2 décimales", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Pas assez de $1 pour effectuer cette transaction", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Pas assez de $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Afficher dans l’activité" }, + "switch": { + "message": "Changer" + }, "switchEthereumChainConfirmationDescription": { "message": "Ceci permet de remplacer le réseau sélectionné dans MetaMask par un réseau précédemment ajouté :" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Vous êtes passé à" }, + "switcherTitle": { + "message": "Commutateur réseau" + }, + "switcherTourDescription": { + "message": "Cliquez sur l’icône pour changer de réseau ou ajouter un nouveau réseau" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Le changement de réseau annulera toutes les confirmations en attente" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Conditions de service" }, + "termsOfUse": { + "message": "conditions d’utilisation" + }, + "termsOfUseAgreeText": { + "message": " J’accepte les conditions d’utilisation de MetaMask et de ses fonctionnalités" + }, + "termsOfUseFooterText": { + "message": "Faites défiler pour lire toutes les sections" + }, + "termsOfUseTitle": { + "message": "Nos conditions d’utilisation ont été mises à jour" + }, "testNetworks": { "message": "Réseaux de test" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Les choses que vous devez garder à l’esprit :" }, + "thirdPartySoftware": { + "message": "Avis sur les logiciels développés par des tiers", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "cette collection" + }, + "thisServiceIsExperimental": { + "message": "Ce service est expérimental. En activant cette fonctionnalité, vous acceptez les $1 d’OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Temps" }, @@ -3863,11 +4867,44 @@ "message": "Vers : $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Vous êtes vulnérable aux attaques par hameçonnage. Protégez-vous en désactivant eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Activez cette option pour permettre aux dapps de demander votre signature à l’aide de requêtes eth_sign. eth_sign est un mode de signature non restrictif qui vous permet de signer une fonction de hachage arbitraire, cela signifie que ce mode de signature peut vous rendre plus vulnérable aux attaques par hameçonnage. Vous devez donc toujours lire attentivement les requêtes eth_sign et vous assurer que l’expéditeur est digne de confiance avant de les signer." + "message": "Si vous activez ce paramètre, vous risquez de recevoir des demandes de signature illisibles. En signant un message que vous ne comprenez pas, vous pourriez accepter de céder vos fonds et vos NFTs." }, "toggleEthSignField": { - "message": "Activer/Désactiver les requêtes eth-sign" + "message": "Requêtes Eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " il se peut qu’il essaie de vous arnaquer" + }, + "toggleEthSignModalBannerText": { + "message": "Si quelqu’un vous a demandé d’activer ce paramètre," + }, + "toggleEthSignModalCheckBox": { + "message": "Je sais que je peux perdre tous mes fonds et mes NFT si j’active les demandes eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "L’autorisation des demandes eth_sign peut vous rendre vulnérable aux attaques par hameçonnage. Assurez-vous de l’adresse URL et faites preuve de vigilance avant de signer des messages qui contiennent du code." + }, + "toggleEthSignModalFormError": { + "message": "Le texte est incorrect" + }, + "toggleEthSignModalFormLabel": { + "message": "Tapez « Je ne signe que ce que je comprends » pour continuer" + }, + "toggleEthSignModalFormValidation": { + "message": "Je ne signe que ce que je comprends" + }, + "toggleEthSignModalTitle": { + "message": "Utilisez cette fonctionnalité à vos risques et périls" + }, + "toggleEthSignOff": { + "message": "Désactiver (recommandé)" + }, + "toggleEthSignOn": { + "message": "Activer (recommandé)" }, "token": { "message": "Jeton" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Symbole du jeton" }, + "tokens": { + "message": "Jetons" + }, "tokensFoundTitle": { "message": "$1 nouveaux jetons trouvés", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Je comprends" }, + "tooltipSatusConnected": { + "message": "connecté" + }, + "tooltipSatusNotConnected": { + "message": "non connecté" + }, "total": { "message": "Total" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "La transaction a rencontré une erreur." }, + "transactionFailed": { + "message": "La transaction a échoué" + }, "transactionFee": { "message": "Frais de transaction" }, @@ -4014,6 +5063,9 @@ "transactionHistoryTotalGasFee": { "message": "Total des frais de transaction" }, + "transactionNote": { + "message": "Note de transaction" + }, "transactionResubmitted": { "message": "La transaction a été soumise à nouveau avec une augmentation du prix du gaz, désormais de $1 à $2" }, @@ -4023,6 +5075,9 @@ "transactionSecurityCheckDescription": { "message": "Nous utilisons des API tierces pour détecter et afficher les risques liés aux transactions non signées et aux demandes de signature avant que vous ne les signiez. Ces services auront accès à vos transactions non signées et à vos demandes de signature, à l’adresse de votre compte et à la langue que vous préférez." }, + "transactionSettings": { + "message": "Paramètres de la transaction" + }, "transactionSubmitted": { "message": "Transaction envoyée sur $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transfert depuis" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Nous avons des difficultés à connecter votre Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Consultez notre guide de connexion au portefeuille matériel et réessayez.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Si vous utilisez la dernière version de Firefox, il se peut que vous rencontriez un problème, car Firefox ne prend plus en charge la norme d’authentification U2F. Découvrez $1 comment vous pouvez résoudre ce problème.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "ici", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Nous avons eu des difficultés à nous connecter à votre $1. Essayez de vérifier votre $2 et réessayez.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "flèche vers le haut" }, + "update": { + "message": "Mise à jour" + }, "updatedWithDate": { "message": "Mis à jour $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Cette URL est actuellement utilisée par le réseau $1." }, + "use4ByteResolution": { + "message": "Décoder les contrats intelligents" + }, + "use4ByteResolutionDescription": { + "message": "Pour améliorer l’expérience utilisateur, nous personnalisons les messages qui s’affichent dans l’onglet d’activité en fonction des contrats intelligents avec lesquels vous interagissez. MetaMask utilise un service appelé 4byte.directory pour décoder les données et vous montrer une version plus facile à lire des contrats intelligents. Ainsi vous aurez moins de chances d’approuver l’exécution de contrats intelligents malveillants, mais cela peut nécessiter le partage de votre adresse IP." + }, "useMultiAccountBalanceChecker": { "message": "Demandes d’informations concernant le solde de plusieurs comptes" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Bénéficiez d’une mise à jour plus rapide des soldes en regroupant les demandes d’informations concernant le solde des comptes. Cela nous permet de récupérer plus rapidement les informations dont nous avons besoin pour mettre à jour le solde de vos comptes. En désactivant cette fonctionnalité, vous limitez la capacité des tiers à établir un lien entre vos différents comptes." + }, "useNftDetection": { "message": "Détection automatique des NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Cela permet d’afficher un avertissement pour les domaines d’hameçonnage ciblant les utilisateurs d’Ethereum" }, + "useSiteSuggestion": { + "message": "Utiliser la suggestion du site" + }, "useTokenDetectionPrivacyDesc": { "message": "L’affichage automatique des tokens envoyés sur votre compte implique une communication avec des serveurs externes afin de récupérer les images des tokens. Ces serveurs auront accès à votre adresse IP." }, @@ -4170,7 +5256,7 @@ "message": "Nom d’utilisateur" }, "verifyContractDetails": { - "message": "Vérifiez les détails du contrat" + "message": "Vérifier les informations relatives aux tiers" }, "verifyThisTokenDecimalOn": { "message": "Décimale de jeton disponible sur $1", @@ -4184,12 +5270,18 @@ "message": "Vérifiez ce jeton sur $1 et qu’il s’agit bien de celui que vous souhaitez échanger.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Version" + }, "view": { "message": "Affichez" }, "viewAllDetails": { "message": "Afficher tous les détails" }, + "viewAllQuotes": { + "message": "afficher toutes les cotations" + }, "viewContact": { "message": "Voir le contact" }, @@ -4213,9 +5305,18 @@ "message": "Afficher $1 sur Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Afficher sur l’explorateur" + }, "viewOnOpensea": { "message": "Afficher sur Opensea" }, + "viewPortfolioDashboard": { + "message": "Afficher le tableau de bord du portefeuille" + }, + "viewinCustodianApp": { + "message": "Afficher dans l’application dépositaire" + }, "viewinExplorer": { "message": "Voir $1 dans l’explorateur", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,6 +5350,10 @@ "wantToAddThisNetwork": { "message": "Voulez-vous ajouter ce réseau ?" }, + "wantsToAddThisAsset": { + "message": "$1 veut ajouter cet actif à votre portefeuille", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Avertissement" }, @@ -4320,6 +5425,9 @@ "youSign": { "message": "Vous signez" }, + "yourAccounts": { + "message": "Vos comptes" + }, "yourFundsMayBeAtRisk": { "message": "Vous risquez de perdre une partie ou la totalité du capital investi" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index d80b47528..aa2bf3c08 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -106,6 +106,9 @@ "about": { "message": "इसके बारे में" }, + "accept": { + "message": "स्वीकार करें" + }, "acceptTermsOfUse": { "message": "मैंने $1 पढ़ लिया है और मैं सहमत हूं", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "एक उपनाम जोड़ें" }, + "addAccount": { + "message": "खाता जोड़ें" + }, "addAcquiredTokens": { "message": "आपके द्वारा MetaMask का उपयोग करके प्राप्त किए गए टोकन जोड़ें" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "लोकप्रिय नेटवर्क की सूची से जोड़ें या मैन्युअल रूप से नेटवर्क जोड़ें। केवल उन संस्थाओं के साथ संवाद करें जिन पर आप भरोसा करते हैं।" }, + "addHardwareWallet": { + "message": "हार्डवेयर वॉलेट जोड़ें" + }, + "addIPFSGateway": { + "message": "अपना पसंदीदा IPFS गेटवे जोड़ें।" + }, "addMemo": { "message": "मेमो जोड़ें" }, @@ -244,6 +256,21 @@ "message": "यह नेटवर्क कनेक्शन तृतीय पक्षों पर निर्भर करता है। यह कनेक्शन संभवतः कम विश्वसनीय है या तृतीय-पक्षों को एक्टिविटी को ट्रैक करने में सक्षम कर सकता है। $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "नया टोकन जोड़ें" + }, + "addNft": { + "message": "NFT जोड़ें" + }, + "addNfts": { + "message": "NFTs जोड़ें" + }, + "addSnapAccountModalDescription": { + "message": "MetaMask स्नैप्स के साथ अपने खाते को सुरक्षित रखने के विकल्प खोजें" + }, + "addSuggestedNFTs": { + "message": "सुझाए गए NFTs जोड़ें" + }, "addSuggestedTokens": { "message": "सुझाए गए टोकन जोड़ें" }, @@ -254,6 +281,9 @@ "message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का पता पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन अनुबंध पते $1 पर मिल सकते हैं।", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "नेटवर्क जोड़ रहे हैं" + }, "address": { "message": "पता" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "प्राथमिकता शुल्क (उर्फ \"माइनर टिप\") सीधे खनिकों के पास जाता है और उन्हें आपके लेन-देन को प्राथमिकता देने के लिए प्रोत्साहित करता है।" }, + "agreeTermsOfUse": { + "message": "मैं MetaMask के $1 से सहमत हूं", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap का वॉल्ट" }, @@ -302,10 +336,20 @@ "alerts": { "message": "चेतावनियां" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "आपने या तो अपने सभी संरक्षक खातों को पहले ही जोड़ लिया है या MetaMask इंस्टीट्यूशनल से जुड़ने के लिए कोई खाता नहीं है।" + }, + "allCustodianAccountsConnectedTitle": { + "message": "कनेक्ट करने के लिए कोई खाता उपलब्ध नहीं है" + }, "allOfYour": { "message": "आपके सभी $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "आपके सभी NFTs $1 से शुरू", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "इस बाहरी एक्सटेंशन को इसकी अनुमति दें:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "इस साइट को इसकी अनुमति दें:" }, + "allowThisSnapTo": { + "message": "इस स्नैप को इसकी अनुमति दें:" + }, "allowWithdrawAndSpend": { "message": "$1 को निम्नलिखित तक राशि निकालने और खर्च करने की अनुमति दें:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "राशि" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "आपके ब्राउजर में एक Ethereum वॉलेट", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "आपके सभी $1 को एक्सेस और ट्रांसफर करने के लिए अनुमति दें", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "अपने सभी NFT's को $1 से एक्सेस करने और स्थानांतरित करने की अनुमति दें?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "अनुमोदित करें" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "यह किसी तीसरे पक्ष को बिना किसी नोटिस के निम्नलिखित NFTs को ऐक्सेस करने और स्थानांतरित करने की अनुमति देता है जब तक कि आप इसकी ऐक्सेस को रद्द नहीं कर देते।" }, + "approveTokenDescriptionWithoutSymbol": { + "message": "यह किसी तीसरे पक्ष को आपके सभी NFTs को $1 से बिना किसी और सूचना के तब तक एक्सेस और स्थानांतरित करने की अनुमति देता है जब तक कि आप इसकी एक्सेस को रद्द नहीं कर देते।", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "अपने $1 को ऐक्सेस करने और स्थानांतरण की अनुमति दें?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "यदि आप ऐसेट्स को सीधे एक नेटवर्क से दूसरे नेटवर्क पर भेजने का प्रयास करते हैं, तो इसके परिणामस्वरूप स्थायी ऐसेट्स का नुकसान हो सकता है। ब्रिज का उपयोग करना सुनिश्चित करें।" }, + "attemptToCancelSwap": { + "message": "~$1 के लिए स्वैप रद्द करने का प्रयास करें", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "ब्लॉकचेन से कनेक्ट करने का प्रयास कर रहे हैं।" }, @@ -411,6 +473,9 @@ "average": { "message": "औसत" }, + "awaitingApproval": { + "message": "मंजूरी का इंतजार है..." + }, "back": { "message": "वापस" }, @@ -454,6 +519,9 @@ "message": "यह बीटा संस्करण है। कृपया बग रिपोर्ट करें $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "MetaMask इंस्टीट्यूशनल बीटा संस्करण" + }, "betaMetamaskVersion": { "message": "MetaMask बीटा संस्करण" }, @@ -488,9 +556,45 @@ "message": "$1 पर खाता देखें", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "ब्लॉकएड" + }, + "blockaidDescriptionApproveFarming": { + "message": "यदि आप इस अनुरोध को स्वीकार करते हैं, तो घोटालों के लिए मशहूर कोई तृतीय पक्ष आपके सारे एसेट चुरा सकती है।" + }, + "blockaidDescriptionBlurFarming": { + "message": "यदि आप इस अनुरोध को स्वीकार करते हैं, तो कोई Blur पर लिस्टेड आपके सारे एसेट चुरा सकता है।" + }, + "blockaidDescriptionFailed": { + "message": "कोई समस्या होने के कारण, इस अनुरोध को सिक्यूरिटी प्रोवाइडर द्वारा सत्यापित नहीं किया गया। सावधानी से आगे बढ़ें।" + }, + "blockaidDescriptionMaliciousDomain": { + "message": "आप एक बुरी नीयत वाले डोमेन से इंटरैक्ट कर रहे हैं। यदि आप इस अनुरोध को स्वीकार करते हैं, तो आप अपने सारे एसेट गंवा सकते हैं।x" + }, + "blockaidDescriptionMightLoseAssets": { + "message": "यदि आप इस अनुरोध को स्वीकार करते हैं, तो आप अपने सारे एसेट गंवा सकते हैं।" + }, + "blockaidDescriptionSeaportFarming": { + "message": "यदि आप इस अनुरोध को स्वीकार करते हैं, तो कोई OpenSea पर लिस्टेड आपके सारे एसेट चुरा सकता है।" + }, + "blockaidDescriptionTransferFarming": { + "message": "यदि आप इस अनुरोध को स्वीकार करते हैं, तो घोटालों के लिए मशहूर कोई तृतीय पक्ष आपके सारे एसेट चुरा सकती है।" + }, + "blockaidTitleDeceptive": { + "message": "इस अनुरोध को धोखेबाज़ी के उद्देश्य से भेजी गई है" + }, + "blockaidTitleMayNotBeSafe": { + "message": "हो सकता है कि अनुरोध सुरक्षित न हो" + }, + "blockaidTitleSuspicious": { + "message": "इस अनुरोध पर एकदम से भरोसा नहीं किया जा सकता" + }, "blockies": { "message": "ब्लॉकीज़" }, + "bridge": { + "message": "ब्रिज" + }, "browserNotSupported": { "message": "आपका ब्राउजर सपोर्टेड नहीं है..." }, @@ -510,6 +614,10 @@ "message": "$1 खरीदें", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "ज्यादा $1 खरीदें", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "अभी खरीदें" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "यह खाते की अस्थायीता को रीसेट करता है और आपके वॉलेट में गतिविधि टैब से डेटा मिटा देता है। केवल मौजूदा खाता और नेटवर्क प्रभावित होंगे। आपकी शेष राशि और आने वाले लेन-देन नहीं बदलेंगे।" }, + "click": { + "message": "क्लिक करें" + }, "clickToConnectLedgerViaWebHID": { "message": "अपने लेजर को WebHID के जरिये कनेक्ट करने के लिए यहां क्लिक करें", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "कॉइनगेको" }, + "configureSnapPopupDescription": { + "message": "अब आप इस स्नैप को कॉन्फिगर करने के लिए MetaMask से बाहर जा रहे हैं।" + }, + "configureSnapPopupInstallDescription": { + "message": "अब आप इस स्नैप को इंस्टॉल करने के लिए MetaMask से बाहर जा रहे हैं।" + }, + "configureSnapPopupInstallTitle": { + "message": "स्नैप इंस्टॉल करें" + }, + "configureSnapPopupLink": { + "message": "जारी रखने के लिए इस लिंक पर क्लिक करें:" + }, + "configureSnapPopupTitle": { + "message": "स्नैप को कॉन्फिगर करें" + }, "confirm": { "message": "पुष्टि करें" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "खाता कनेक्ट करें या नया बनाएं" }, + "connectCustodialAccountMenu": { + "message": "कस्टोडियल खाता कनेक्ट करें" + }, + "connectCustodialAccountMsg": { + "message": "टोकन जोड़ने या रीफ्रेश करने के लिए कृपया उस संरक्षक को चुनें जिससे आप जुड़ना चाहते हैं।" + }, + "connectCustodialAccountTitle": { + "message": "संरक्षक खाते" + }, "connectManually": { "message": "वर्तमान साइट से मैन्युअल रूप से कनेक्ट करें" }, + "connectSnap": { + "message": "$1 को कनेक्ट करें", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "$1 से कनेक्ट करें", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Linea Goerli टेस्ट नेटवर्क से कनेक्ट हो रहा है" }, + "connectingToLineaMainnet": { + "message": "Linea Mainnet से कनेक्ट हो रहा है" + }, "connectingToMainnet": { "message": "Ethereum Mainnet से कनेक्ट हो रहा है" }, "connectingToSepolia": { "message": "सेपोलिया टेस्ट नेटवर्क से कनेक्ट कर रहा है" }, + "connectionFailed": { + "message": "कनेक्शन विफल रहा" + }, + "connectionFailedDescription": { + "message": "$1 को लाना विफल रहा, अपना नेटवर्क जांचें और पुन: प्रयास करें।", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "कनेक्शन अनुरोध" + }, "contactUs": { "message": "हमसे संपर्क करें" }, @@ -708,7 +860,7 @@ "message": "अनुबंध परिनियोजन" }, "contractDescription": { - "message": "अपने आप को धोखेबाजों से बचाने के लिए, अनुबंध विवरण की समीक्षा करने के लिए कुछ समय निकालें।" + "message": "स्कैमर्स से खुद को बचाने के लिए, कुछ समय निकालकर थर्ड-पार्टी के विवरण को सत्यापित कर लें।" }, "contractInteraction": { "message": "अनुबंध इंटरैक्शन" @@ -717,16 +869,16 @@ "message": "एनएफटी (NFT) अनुबंध" }, "contractRequestingAccess": { - "message": "ऐक्सेस के लिए अनुरोध करने वाला अनुबंध" + "message": "थर्ड पार्टी एक्सेस का अनुरोध कर रहा है" }, "contractRequestingSignature": { - "message": "हस्ताक्षर का अनुरोध करते हुए अनुबंध" + "message": "थर्ड पार्टी हस्ताक्षर का अनुरोध कर रहा है" }, "contractRequestingSpendingCap": { - "message": "खर्च की सीमा का अनुरोध करने वाला अनुबंध" + "message": "थर्ड पार्टी खर्च की सीमा का अनुरोध कर रहा है" }, "contractTitle": { - "message": "अनुबंध विवरण" + "message": "थर्ड-पार्टी विवरण" }, "contractToken": { "message": "टोकन अनुबंध" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "मार्केट गैस एस्टीमेट ग्राफ" }, + "custodian": { + "message": "कस्टोडियन" + }, + "custodianAccount": { + "message": "कस्टोडियन खाता" + }, + "custodianAccountAddedDesc": { + "message": "अब आप MetaMask इंस्टीट्यूशनल में अपने संरक्षक खातों का उपयोग कर सकते हैं।" + }, + "custodianAccountAddedTitle": { + "message": "चयनित संरक्षक खाते जोड़ दिए गए हैं।" + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "कृपया $1 पर जाएं और अपने खातों को फिर से MMI से जोड़ने के लिए उनके यूजर इंटरफेस के भीतर 'MMI से कनेक्ट करें' बटन पर क्लिक करें।" + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "अब आप MetaMask इंस्टीट्यूशनल में अपने संरक्षक खातों का उपयोग कर सकते हैं।" + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "आपका संरक्षक टोकन रीफ्रेश कर दिया गया है" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "यह निम्नलिखित पते के लिए संरक्षक टोकन को प्रतिस्थापित करेगा:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "संरक्षक टोकन बदलें" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "$1 ऐप में लेन-देन को स्वीकृति दें। एक बार सभी आवश्यक कस्टडी अनुमोदन किए जाने के बाद लेनदेन पूरा हो जाएगा। स्थिति के लिए अपना $1 ऐप देखें।" + }, + "custodyRefreshTokenModalDescription": { + "message": "कृपया $1 पर जाएं और अपने खातों को फिर से MMI से जोड़ने के लिए उनके यूजर इंटरफेस के भीतर 'MMI से कनेक्ट करें' बटन पर क्लिक करें।" + }, + "custodyRefreshTokenModalDescription1": { + "message": "आपका कस्टोडियन एक टोकन जारी करता है जो MetaMask इंस्टीट्यूशनल एक्सटेंशन को प्रमाणित करता है, जिससे आप अपने खातों को कनेक्ट कर सकते हैं।" + }, + "custodyRefreshTokenModalDescription2": { + "message": "यह टोकन सुरक्षा कारणों से एक निश्चित अवधि के बाद समाप्त हो जाता है। इसके लिए आपको MMI से फिर से कनेक्ट करना होगा।" + }, + "custodyRefreshTokenModalSubtitle": { + "message": "मैं यह क्यों देख रहा हूँ?" + }, + "custodyRefreshTokenModalTitle": { + "message": "आपका कस्टोडियन सेशन समाप्त हो गया" + }, + "custodySessionExpired": { + "message": "संरक्षक सत्र समाप्त हो गया।" + }, + "custodyWrongChain": { + "message": "यह खाता $1 के साथ उपयोग करने के लिए सेटअप नहीं किया गया है" + }, "custom": { "message": "उन्नत" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "ग्राहक सहायता सेवा" }, + "dappRequestedSpendingCap": { + "message": "साइट ने खर्च की सीमा का अनुरोध किया" + }, "dappSuggested": { "message": "साइट का सुझाव दिया गया" }, @@ -851,6 +1060,12 @@ "message": "$1 ने इस कीमत का सुझाव दिया है।", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "साइट का सुझाव दिया गया" + }, + "dappSuggestedHighShortLabel": { + "message": "साइट (उच्च)" + }, "dappSuggestedShortLabel": { "message": "साइट" }, @@ -902,9 +1117,19 @@ "delete": { "message": "हटाएँ" }, + "deleteContact": { + "message": "संपर्क डिलीट करें" + }, "deleteNetwork": { "message": "नेटवर्क हटाएं?" }, + "deleteNetworkIntro": { + "message": "अगर आप इस नेटवर्क को हटाते हैं, तो आपको इस नेटवर्क में अपने एसेट देखने के लिए इसे फिर से जोड़ना होगा" + }, + "deleteNetworkTitle": { + "message": "$1 नेटवर्क को हटाएं?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "जमा करें" }, @@ -917,6 +1142,10 @@ "description": { "message": "विवरण" }, + "descriptionFromSnap": { + "message": "$1 से विवरण", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "यह त्रुटि रुक-रुक कर हो सकती है, इसलिए एक्सटेंशन को फिर से शुरू करने का प्रयास करें या MetaMask डेस्कटॉप को अक्षम करें।" }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "रिकवरी फ्रेज़ बैकअप अनुस्मारक खारिज करें" }, + "displayNftMedia": { + "message": "NFT मीडिया को दिखाएं" + }, + "displayNftMediaDescription": { + "message": "NFT मीडिया और डेटा दिखाने से आपका IP एड्रेस OpenSea या अन्य तृतीय पक्षों के सामने आ जाता है। ऐसा होने पर, हमला करने वाले आपके IP एड्रेस को आपके Ethereum एड्रेस के साथ जोड़ पाते हैं। NFT ऑटोडिटेक्शन की सुविधा इस सेटिंग पर निर्भर करती है, और इस सेटिंग को बंद किए जाने पर उपलब्ध नहीं रहेगी।" + }, "domain": { "message": "डोमेन" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " ऑटो डिटेक्ट सक्षम करें" }, + "enableForAllNetworks": { + "message": "सभी नेटवर्क पर इनेबल करें" + }, "enableFromSettings": { "message": " इसे सेटिंग्स से इनेबल करें।" }, + "enableSmartSwaps": { + "message": "स्मार्ट स्वैप सक्षम करें" + }, + "enableSnap": { + "message": "सक्षम करें" + }, "enableToken": { "message": "$1 इनेबल करें", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "सक्षम किया गया" + }, "encryptionPublicKeyNotice": { "message": "$1 आपकी सार्वजनिक एन्क्रिप्शन कुंजी चाहता है। सहमति देने पर, यह साइट आपके लिए एन्क्रिप्ट किए गए संदेशों को लिखने में सक्षम होगी।", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "उन्नत टोकन डिटेक्शन वर्तमान में $1 पर उपलब्ध है। $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask आपको सीधे आपके ब्राउज़र के एड्रेस बार में \"https://metamask.eth\" जैसे ENS डोमेन देखने की सुविधा देता है। यह ऐसे काम करता है:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "आमतौर पर इस्तेमाल किए जाने वाले ब्राउज़र यूं तो ENS या IPFS एड्रेसों को हैंडल नहीं करते हैं, लेकिन MetaMask इसमें मदद करता है। इस फीचर का उपयोग करके आपके IP एड्रेस को IPFS तृतीय-पक्ष सेवाओं के साथ शेयर किया जा सकता है।" + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "ENS नाम से जुड़े कोड को खोजने के लिए MetaMask, Ethereum के ENS कॉन्ट्रैक्ट की जांच करता है।" + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "अगर कोड IPFS से लिंक्ड है, तो उसे IPFS नेटवर्क से कॉन्टेंट प्राप्त होता है।" + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "इसके बाद, आप कॉन्टेंट देख सकते हैं, आमतौर पर एक वेबसाइट या कुछ इसी तरह का।" + }, + "ensDomainsSettingTitle": { + "message": "एड्रेस बार में ENS डोमेन दिखाएँ" + }, "ensIllegalCharacter": { "message": "ENS के लिए गैर-कानूनी कैरेक्टर।" }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "कोई संख्या दर्ज करें" }, + "enterCustodianToken": { + "message": "अपना $1 टोकन दर्ज करें या एक नया टोकन जोड़ें" + }, "enterMaxSpendLimit": { "message": "अधिकतम खर्च सीमा दर्ज करें" }, + "enterOptionalPassword": { + "message": "वैकल्पिक पासवर्ड डालें" + }, "enterPassword": { "message": "पासवर्ड दर्ज करें" }, "enterPasswordContinue": { "message": "जारी रखने के लिए पासवर्ड दर्ज करें" }, + "enterTokenNameOrAddress": { + "message": "टोकन नाम दर्ज करें या पता पेस्ट करें" + }, + "enterYourPassword": { + "message": "अपना पासवर्ड दर्ज करें" + }, "errorCode": { "message": "कोड: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "स्टैक:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "कस्टम नेटवर्क से कनेक्ट करने में त्रुटि" + }, + "errorWithSnap": { + "message": "$1 के साथ त्रुटि", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "बैकअप गैस की कीमत प्रदान की जाती है क्योंकि मुख्य गैस अनुमान सर्विस अभी उपलब्ध नहीं है।" }, + "ethereumProviderAccess": { + "message": "इथेरियम प्रदाता को $1 तक पहुंच प्रदान करें", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Ethereum सार्वजनिक ऐड्रेस" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "प्रयोगात्मक" }, + "exploreMetaMaskSnaps": { + "message": "MetaMask स्नैप्स का अन्वेषण करें" + }, "exportPrivateKey": { "message": "निजी कुंजी निर्यात करें" }, + "extendWalletWithSnaps": { + "message": "वॉलेट अनुभव का विस्तार करें।" + }, "externalExtension": { "message": "बाहरी एक्स्टेन्शन" }, @@ -1302,6 +1596,9 @@ "message": "फाइल आयात काम नहीं कर रहा है? यहां क्लिक करें!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "ड्रॉप की गई फाइल बहुत बड़ी है." + }, "flaskWelcomeUninstall": { "message": "आपको इस एक्सटेन्शन को अनइंस्टाल करना चाहिए", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "सामान्य" }, + "getStarted": { + "message": "शुरू करें" + }, + "globalTitle": { + "message": "वैश्विक मेन्यू" + }, + "globalTourDescription": { + "message": "अपना पोर्टफ़ोलियो, जुड़ी हुई साइटें, सेटिंग्स आदि देखें" + }, "goBack": { "message": "वापस जाएं" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "हार्डवेयर वॉलेट कनेक्ट करें" }, + "hardwareWalletsInfo": { + "message": "हार्डवेयर वॉलेट इंटीग्रेशन्स, एक्सटर्नल सर्वरों पर एपीआई कॉल्स की मदद लेते हैं, जो आपके आईपी एड्रेस और आपके द्वारा इंटरैक्ट किए गए स्मार्ट कॉन्ट्रैक्ट एड्रेस को देख सकता है।" + }, "hardwareWalletsMsg": { "message": "किसी हार्डवेयर वॉलेट का चयन करें, जिसे आप MetaMask के साथ उपयोग करना चाहते हैं।" }, @@ -1541,11 +1850,34 @@ "message": "लेकिन फिशर कर सकते हैं।", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "आपकी निजी कुंजी $1 प्रदान करती है", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "आपके वॉलेट और फंड की पूरी एक्सेस।", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "लॉक किए गए सर्कल को दिखाने के लिए होल्ड करें" + }, + "holdToRevealPrivateKey": { + "message": "निजी कुंजी को दिखाने के लिए होल्ड करें" + }, + "holdToRevealPrivateKeyTitle": { + "message": "अपनी निजी कुंजी सुरक्षित रखें" + }, "holdToRevealSRP": { - "message": "SRP दिखाने के लिए होल्ड करें" + "message": "SRP को दिखाने के लिए होल्ड करें" }, "holdToRevealSRPTitle": { - "message": "अपना SRP सुरक्षित रखें" + "message": "अपनी SRP सुरक्षित रखें" + }, + "holdToRevealUnlockedLabel": { + "message": "लॉक किए गए सर्कल को दिखाने के लिए होल्ड करें" + }, + "id": { + "message": "आईडी" }, "ignoreAll": { "message": "सभी को अनदेखा करें" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "खाता आयात करने में त्रुटि।" }, + "importAccountErrorIsSRP": { + "message": "आपने सीक्रेट रिकवरी फ्रेज (या स्मरक) दर्ज किया है। अकाउंट को यहां इंपोर्ट करने के लिए, आपको एक प्राइवेट की दर्ज करनी होगी, जो कि 64 \nकैरेक्टर की लंबाई की एक हेक्साडेसिमल स्ट्रिंग है।" + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "यह मान्य प्राइवेट की नहीं है। आपने हेक्साडेसिमल स्ट्रिंग दर्ज की है, लेकिन यह 64 कैरक्टर लंबी होनी चाहिए।" + }, + "importAccountErrorNotHexadecimal": { + "message": "यह मान्य प्राइवेट की नहीं है। आपको 64 कैरेक्टर की लंबाई की हेक्साडेसिमल स्ट्रिंग दर्ज करनी होगी।" + }, + "importAccountJsonLoading1": { + "message": "इस JSON इंपोर्ट में कुछ मिनट लगने की अपेक्षा करें और MetaMask को फ्रीज करें।" + }, + "importAccountJsonLoading2": { + "message": "हम क्षमा चाहते हैं, और भविष्य में हम इसे और तेज करेंगे।" + }, "importAccountMsg": { - "message": "आयातित खाते आपके मूल रूप से बनाए गए MetaMask खाते के गुप्त रिकवरी फ्रेज से संबद्ध नहीं होंगे। आयातित खातों के बारे में अधिक जानें" + "message": "आयातित खाते आपके MetaMask गुप्त पुनर्प्राप्ति वाक्यांश से संबद्ध नहीं होंगे। आयातित खातों के बारे में अधिक जानें" }, "importMyWallet": { "message": "मेरा वॉलेट आयात करें" @@ -1615,18 +1962,29 @@ "message": "नेटवर्क द्वारा आपके प्रारंभिक लेनदेन की पुष्टि की गई थी। वापस जाने के लिए ठीक पर क्लिक करें।" }, "inputLogicEmptyState": { - "message": "केवल वही संख्या दर्ज करें जो आप अभी या भविष्य में अनुबंध खर्च के साथ सहज महसूस करते हैं। आप बाद में कभी भी खर्च की सीमा बढ़ा सकते हैं।" + "message": "केवल वही संख्या दर्ज करें जो आप अभी या भविष्य में थर्ड पार्टी खर्च के साथ सहज महसूस करते हैं। आप बाद में कभी भी खर्च की सीमा बढ़ा सकते हैं।" }, "inputLogicEqualOrSmallerNumber": { - "message": "यह अनुबंध को आपकी वर्तमान शेष राशि से $1 खर्च करने की अनुमति देता है।", + "message": "यह आपको अपनी वर्तमान शेष राशि से $1 खर्च करने की अनुमति देता है।", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "यह अनुबंध को आपके सभी टोकन शेष को तब तक खर्च करने की अनुमति देता है जब तक कि यह सीमा तक नहीं पहुंच जाता या आप खर्च की सीमा को रद्द नहीं कर देते। यदि इसका इरादा नहीं है, तो कम खर्च करने की सीमा निर्धारित करने पर विचार करें।" + "message": "यह आपको अपने सभी टोकन शेष को तब तक खर्च करने की अनुमति देता है जब तक कि यह सीमा तक नहीं पहुंच जाता या आप खर्च की सीमा को रद्द नहीं कर देते। यदि आपका वह इरादा नहीं है, तो कम खर्च करने की सीमा निर्धारित करने पर विचार करें।" + }, + "insightsFromSnap": { + "message": "$1 से इनसाइट्स", + "description": "$1 represents the name of the snap" }, "install": { "message": "इंस्टॉल करें" }, + "installOrigin": { + "message": "उत्पत्ति इंस्टॉल करें" + }, + "installedOn": { + "message": "$1 पर इंस्टॉल किया गया", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "अपर्याप्त शेषराशि।" }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "अमान्य निवेश! गुप्त पुनर्प्राप्ति वाक्यांश केस संवेदी है।" }, + "ipfsGateway": { + "message": "IPFS गेटवे" + }, + "ipfsGatewayDescription": { + "message": "IPFS पर स्टोर किए गए आपके NFT की इमेज दिखाने, आपके ब्राउज़र के एड्रेस बार में एंटर किए गए ENS एड्रेस से संबंधित जानकारी दिखाने और अलग-अलग टोकनों के लिए आइकन लाने के लिए MetaMask तृतीय-पक्ष की सेवाओं का इस्तेमाल करता है। जब आप इन सेवाओं का इस्तेमाल कर रहे हों तो आपका IP एड्रेस इन सेवाओं को भी दिख सकता है।" + }, + "ipfsToggleModalDescriptionOne": { + "message": "हम IPFS पर स्टोर किए गए आपके NFT की इमेज दिखाने, आपके ब्राउज़र के एड्रेस बार में एंटर किए गए ENS एड्रेस से संबंधित जानकारी दिखाने और अलग-अलग टोकनों के लिए आइकन लाने के लिए तृतीय-पक्ष के सेवाओं का इस्तेमाल करते हैं। जब आप इन सेवाओं का इस्तेमाल कर रहे हों तो आपका IP एड्रेस इन सेवाओं को भी दिख सकता है।" + }, + "ipfsToggleModalDescriptionTwo": { + "message": "पुष्टि का चयन करने से IPFS रिजॉल्यूशन चालू हो जाता है। आप इसे किसी भी समय $1 में बंद कर सकते हैं।", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "सेटिंग्स > सुरक्षा और गोपनीयता" + }, "jazzAndBlockies": { "message": "जाज़िकॉन्स और ब्लॉकीज़ विशिष्ट आइकनों की दो अलग-अलग शैलियां हैं जो आपको एक नज़र में किसी अकाउंट की पहचान करने में मदद करती हैं।" }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "पिछली बार बेचा गया" }, + "layer1Fees": { + "message": "परत 1 शुल्क" + }, "learnCancelSpeeedup": { "message": "$1 करने का तरीका जानें", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "गैस के बारे में $1 चाहते हैं?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "और अधिक जानें" + }, "learnMoreUpperCase": { "message": "अधिक जानें" }, @@ -1765,10 +2145,10 @@ "message": "पुष्टि पर क्लिक करने से पहले:" }, "ledgerConnectionInstructionStepFour": { - "message": "अपने लेजर डिवाइस पर \"स्मार्ट कॉन्ट्रैक्ट डेटा\" या \"ब्लाइंड साइनिंग\" इनेबल करें" + "message": "अपने लेजर डिवाइस पर \"स्मार्ट कॉन्ट्रैक्ट डेटा\" या \"ब्लाइंड साइनिंग\" एनेबल करें" }, "ledgerConnectionInstructionStepOne": { - "message": "सेटिंग्स> एडवांस के तहत उपयोग लेजर लाइव इनेबल करें" + "message": "सेटिंग्स> एडवांस्ड के तहत यूज़ लेज़र लाइव एनेबल करें" }, "ledgerConnectionInstructionStepThree": { "message": "अपने लेजर डिवाइस में प्लग इन करें और Ethereum ऐप चुनें" @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea Goerli टेस्ट नेटवर्क" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "लिंक" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "लॉक" }, + "lockMetaMask": { + "message": "MetaMask लॉक करें" + }, + "lockTimeInvalid": { + "message": "लॉक का समय 0 और 10080 के बीच की कोई संख्या होनी चाहिए" + }, "logo": { "message": "$1 लोगो", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "कनेक्शन स्थिति बटन दिखाता है कि आप जिस वेबसाइट पर जा रहे हैं, वह आपके वर्तमान में चयनित खाते से कनेक्ट है।" }, + "metamaskInstitutionalVersion": { + "message": "MetaMask इंस्टीट्यूशनल संस्करण" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask स्वैप का रखरखाव किया जा रहा है। कृपया बाद में वापस देखें।" }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "मेट्रिक्स" }, + "mismatchAccount": { + "message": "आपका चयनित खाता ($1) साइन-इन करने का प्रयास करने वाले खाते से भिन्न है ($2)" + }, "mismatchedChainLinkText": { "message": "नेटवर्क विवरण सत्यापित करें", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "सबमिट किया गया मुद्रा संकेत इस चेन आईडी के लिए हमारी अपेक्षा से मेल नहीं खाता।" }, + "mismatchedRpcChainId": { + "message": "कस्टम नेटवर्क द्वारा वापस की गई चेन आईडी सबमिट की गई चेन आईडी से मेल नहीं खाती।" + }, "mismatchedRpcUrl": { "message": "हमारे रिकॉर्ड के अनुसार, सबमिट किया गया RPC-URL मान इस चेन आईडी के लिए किसी ज्ञात प्रदाता से मेल नहीं खाता।" }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "यहां अनुरोध करें" }, + "mmiAddToken": { + "message": "$1 पर मौजूद पेज MetaMask इंस्टीट्यूशनल में निम्नलिखित कस्टोडियन टोकन को अधिकृत करना चाहता है" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask इंस्टीट्यूशनल को दुनिया भर में डिजाइन किया और बनाया गया है।" + }, + "more": { + "message": "अधिक" + }, "moreComingSoon": { - "message": "और अधिक जल्द ही आ रहा..." + "message": "जल्द ही और प्रोवाइडर उपलब्ध होंगे" + }, + "multipleSnapConnectionWarning": { + "message": "$1 चाहता है कि $2 स्नैप्स के साथ जुड़े। तभी आगे बढ़ें अगर आप इस वेबसाइट पर भरोसा करते हैं।", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "कम से कम 1 टोकन का चयन करना होगा।" @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "नेटवर्क व्यस्त है। गैस की कीमतें अधिक हैं और अनुमान कम सटीक हैं।" }, + "networkMenu": { + "message": "नेटवर्क मेन्यू" + }, + "networkMenuHeading": { + "message": "एक नेटवर्क का चयन करें" + }, "networkName": { "message": "नेटवर्क का नाम" }, @@ -2033,6 +2450,10 @@ "message": "पिछले 72 घंटों के सापेक्ष गैस शुल्क $1 है।", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "हम $1 से कनेक्ट नहीं कर सकते", + "description": "$1 represents the network name" + }, "networkURL": { "message": "नेटवर्क URL" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "पहले के स्वामित्व में" + }, "nickname": { "message": "उपनाम" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "कोई भी रूपांतरण दर उपलब्ध नहीं है" }, + "noNFTs": { + "message": "अभी तक कोई NFT नहीं" + }, + "noNetworksFound": { + "message": "दी गई सर्च क्वेरी के लिए कोई नेटवर्क नहीं मिले" + }, "noSnaps": { - "message": "कोई स्नैप इंस्टाल नहीं किया गया" + "message": "आपके पास इंस्टाल किए गए स्नैप नहीं हैं।" }, "noThanksVariant2": { "message": "नहीं, धन्यवाद।" @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "क्या यह सही खाता है? यह आपके वॉलेट में वर्तमान में चयनित खाते से अलग है" }, + "notEnoughBalance": { + "message": "अपर्याप्त शेषराशि" + }, "notEnoughGas": { "message": "पर्याप्त गैस नहीं" }, + "note": { + "message": "टिप्पणी" + }, + "notePlaceholder": { + "message": "अनुमोदक इस टिप्पणी को कस्टोडियन के पास लेन-देन का अनुमोदन करते समय देखेगा।" + }, + "notificationTransactionFailedMessage": { + "message": "लेन-देन $1 विफल रहा! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "लेन-देन विफल रहा! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "विफल लेन-देन", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "लेनदेन $1 की पुष्टि हो गई!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "पुष्टि की हुई लेन-देन", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "$1 पर देखें", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "सूचनाएं" + }, "notifications10ActionText": { "message": "सेटिंग्स में जाएं", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "इथेरियम मर्ज यहाँ है!" }, + "notifications18ActionText": { + "message": "सुरक्षा अलर्ट सक्षम करें" + }, + "notifications18DescriptionOne": { + "message": "जब आपको कोई दुर्भावनापूर्ण अनुरोध प्राप्त हुआ हो, तो तृतीय पक्षों से अलर्ट प्राप्त करें।", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "किसी भी अनुरोध को स्वीकार करने से पहले हमेशा अपनी खुद की सावधानी बरतना सुनिश्चित करें।", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea इस सुविधा का पहला प्रदाता है। अधिक प्रदाता जल्द ही आ रहे हैं!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "सुरक्षा अलर्ट के साथ सुरक्षित रहें" + }, + "notifications19ActionText": { + "message": "NFT ऑटो-डिटेक्शन सक्षम करें" + }, + "notifications19DescriptionOne": { + "message": "आप दो तरीकों से शुरुआत कर सकते हैं:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "हम इस समय केवल ERC-721 का समर्थन करते हैं।", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "अपने NFT को मैन्युअल रूप से जोड़ें, या सेटिंग> एक्सपेरिमेंटल में जाकर NFT ऑटोडिटेक्शन चालू करें।", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "अपने NFTs को ऐसे देखें जैसे पहले कभी नहीं देखा" + }, "notifications1Description": { "message": "MetaMask Mobile उपयोगकर्ता अब अपने मोबाइल वॉलेट के अंदर टोकन स्वैप कर सकते हैं। मोबाइल ऐप प्राप्त करने के लिए QR कोड को स्कैन करें और स्वैप करना शुरू करें।", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "मोबाइल पर स्वैपिंग यहां है!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "और अधिक जानें", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "यदि आप फायरफॉक्स के नवीनतम संस्करण का उपयोग कर रहे हैं, तो आप फायरफॉक्स से U2F समर्थन छोड़ने से संबंधित समस्या का अनुभव कर सकते हैं।", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "लेजर और फायरफॉक्स उपयोगकर्ता कनेक्शन संबंधी समस्याओं का सामना कर रहे हैं", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "इसे आजमाएं" + }, + "notifications21Description": { + "message": "हमने MetaMask एक्सटेंशन में स्वैप को उपयोग में आसान और तेज बनाने के लिए अपडेट किया है।", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "ला रहे हैं नया और फ्रेश स्वैप!" + }, + "notifications22ActionText": { + "message": "समझ गए" + }, + "notifications22Description": { + "message": "💡 उन्हें ढूंढने के लिए बस ग्लोबल मेनू या अकाउंट मेनू पर क्लिक करें!" + }, + "notifications22Title": { + "message": "क्या आप अपने अकाउंट की जानकारी या ब्लॉक एक्सप्लोरर URL खोज रहे हैं?" + }, + "notifications23ActionText": { + "message": "सुरक्षा अलर्ट सक्षम करें" + }, + "notifications23DescriptionOne": { + "message": "Blockaid द्वारा संचालित सुरक्षा अलर्ट के साथ अपनी गोपनीयता बनाए रखते हुए ज्ञात घोटालों से दूर रहें।" + }, + "notifications23DescriptionThree": { + "message": "यदि आपने OpenSea से सुरक्षा अलर्ट सक्षम किया है, तो हमने आपको इस फीचर पर ला दिया है।" + }, + "notifications23DescriptionTwo": { + "message": "अनुरोध को स्वीकार करने से पहले हमेशा अपनी खुद की सावधानी बरतें।" + }, + "notifications23Title": { + "message": "सुरक्षा अलर्ट के साथ सुरक्षित रहें" + }, "notifications3ActionText": { "message": "और पढ़ें", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "केवल उन साइटों से कनेक्ट करें, जिन पर आप भरोसा करते हैं।" }, "openFullScreenForLedgerWebHid": { - "message": "अपने लेजर को WebHID के माध्यम से कनेक्ट करने के लिए MetaMask को पूर्ण स्क्रीन में खोलें।", + "message": "अपने लेजर को कनेक्ट करने के लिए फुल स्क्रीन पर जाएं।", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "ब्लॉक एक्सप्लोरर में खोलें" }, "openSea": { - "message": "OpenSea (बीटा)" + "message": "OpenSea + Blockaid (बीटा)" }, "openSeaNew": { "message": "ओपनसी" }, + "operationFailed": { + "message": "प्रचालन विफल रहा" + }, "optional": { "message": "वैकल्पिक" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "पासवर्ड मेल नहीं खाते" }, + "pasteJWTToken": { + "message": "अपना टोकन यहां पेस्ट या ड्रॉप करें:" + }, "pastePrivateKey": { "message": "अपनी निजी कुंजी स्ट्रिंग यहाँ पेस्ट करें:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "इंटरनेट एक्सेस करें।", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "स्नैप को इंटरनेट एक्सेस करने दें। इसका उपयोग थर्ड-पार्टी सर्वर के साथ डेटा भेजने और प्राप्त करने दोनों के लिए किया जा सकता है।", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "$1 स्नैप से कनेक्ट करें।", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "$1 के साथ इंटरैक्ट करने के लिए वेबसाइट या स्नैप को अनुमति दें।", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "समय-समय पर आने वाले क्रियाओं को शेड्यूल और निष्पादित करें।", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "स्नैप को अनुमति दें ताकि वह नियत समय, दिनांक या अंतराल पर चलने वाली कार्रवाइयां कर सके। इसका उपयोग समय-संवेदी इंटरैक्शन या सूचनाओं को ट्रिगर करने के लिए किया जा सकता है।", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "MetaMask में डायलॉग विंडो प्रदर्शित करें।", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "स्नैप को MetaMask पॉपअप को कस्टम टेक्स्ट, इनपुट फील्ड और किसी कार्वराई को स्वीकृत या अस्वीकृत करने के बटन प्रदर्शित करने की अनुमति दें।\nउदाहरण के लिए स्नैप के लिए अलर्ट, पुष्टिकरण और ऑप्ट-इन प्रवाह सृजित करने के लिए इसका उपयोग किया जा सकता है।", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "पता, खाते की शेषराशि, गतिविधि देखें और लेन-देन शुरू करें", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "एथेरियम प्रदाता को ऐक्सेस करें।", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "स्नैप को MetaMask के साथ सीधे संवाद करने की अनुमति दें, ताकि वह ब्लॉकचेन से डेटा पढ़ सके और संदेशों और लेनदेन का सुझाव दे सके।", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "इस स्नैप के लिए अनूठी स्वेच्छित कुंजियां प्राप्त करें।", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "स्नैप को इस स्नैप के लिए विशिष्ट विवेकाधीन चाबियों को बिना उन्हें उजागर किए, प्राप्त करने की अनुमति दें। ये चाबियां आपके MetaMask खाते (खातों) से अलग हैं और आपकी निजी चाबियों या सीक्रेट रिकवरी फ्रेज से संबंधित नहीं हैं। इस जानकारी को अन्य स्नैप्स एक्सेस नहीं कर सकते हैं।", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "लाइफसाइकल हुक्स का इस्तेमाल करें।", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "स्नैप को उसके लाइफसाइकल के दौरान खास समयों पर कोड चलाने के लिए लाइफसाइकल हुक का इस्तेमाल करने की अनुमति दें।", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "अनिश्चित काल तक चलाएं।", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "उदाहरण के लिए, बड़ी मात्रा में डेटा प्रॉसेस करते समय स्नैप को अनिश्चित काल तक चलने दें।", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "एथेरियम खाते जोड़ें और नियंत्रित करें", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "$1($2) के तहत अपने खातों और संपत्तियों को नियंत्रित करें।", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "स्नैप को अपने सीक्रेट रिकवरी फ्रेज के आधार पर, उसे उजागर किए बिना BIP-32 चाबी के जोड़े प्राप्त करने की अनुमति दें। यह $1 पर सभी खातों और संपत्तियों तक पूरा एक्सेस प्रदान करता है।\nचाबियों को प्रबंधित करने की शक्ति के साथ, एथेरियम (ईवीएम) से परे विभिन्न प्रकार के ब्लॉकचेन प्रोटोकॉल का समर्थन स्नैप कर सकता है।", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "आपके \"$1\" अकाउंट्स और एसेट्स नियंत्रित करें।", + "message": "अपने $1 अकाउंट्स और एसेट्स नियंत्रित करें।", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "स्नैप को अपने सीक्रेट रिकवरी फ्रेज के आधार पर, उसे उजागर किए बिना, BIP-44 चाबी के जोड़े को प्राप्त करने की अनुमति दें। यह $1 पर सभी खातों और संपत्तियों तक पूरा एक्सेस प्रदान करता है।\nचाबियों को प्रबंधित करने की शक्ति के साथ, एथेरियम (ईवीएम) से परे विभिन्न प्रकार के ब्लॉकचेन प्रोटोकॉल का समर्थन स्नैप कर सकता है।", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "आपके $1 खातों और संपत्तियों को नियंत्रित करें।", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "उसके डेटा को अपने डिवाइस पर स्टोर करें और प्रबंधित करें।", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "स्नैप को एन्क्रिप्शन के साथ सुरक्षित रूप से डेटा को स्टोर करने, अपडेट करने और पुनः प्राप्त करने की अनुमति दें। अन्य स्नैप्स इस जानकारी को एक्सेस नहीं कर सकते हैं।", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "नोटीफिकेशंस दिखाएं।", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "स्नैप को MetaMask के भीतर सूचनाएं प्रदर्शित करने की अनुमति दें। कार्रवाई योग्य या समय-संवेदी जानकारी के लिए एक शॉर्ट नोटिफिकेशन टेक्स्ट को स्नैप द्वारा ट्रिगर किया जा सकता है।", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "$1 को इस स्नैप से सीधे संवाद करने की अनुमति दें।", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "स्नैप को संदेश भेजने और स्नैप से प्रतिक्रिया प्राप्त करने के लिए $1 को अनुमति दें।", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "ट्रांजैक्शन इनसाइट्स प्राप्त करें और प्रदर्शित करें।", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "स्नैप को लेन-देन को डिकोड करने और MetaMask UI के भीतर इनसाइट्स दिखाने की अनुमति दें। इसका उपयोग एंटी-फिशिंग और सुरक्षा समाधान के लिए किया जा सकता है।", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "लेन-देन का सुझाव देने वाली वेबसाइटों की स्रोत देखें", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "स्नैप को लेनदेन का सुझाव देने वाली वेबसाइटों की जड़ों (URI) को देखने की अनुमति दें। इसका उपयोग एंटी-फिशिंग और सुरक्षा समाधान के लिए किया जा सकता है।", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "अज्ञात अनुमति: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "$1 ($2) के लिए अपनी पब्लिक की को देखें।", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "स्नैप को $1 के लिए आपकी सार्वजनिक चाबियों (और पतों) को देखने की अनुमति दें। यह खातों या संपत्तियों पर कोई नियंत्रण प्रदान नहीं करता है।", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "$1 के लिए अपनी पब्लिक की को देखें", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "वेब असेंबली के लिए समर्थन।", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "स्नैप को WebAssembly के माध्यम से निम्न-स्तरीय निष्पादन वातावरण को एक्सेस करने की अनुमति दें।", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "अनुमतियाँ" }, + "permissionsTitle": { + "message": "अनुमतियां" + }, + "permissionsTourDescription": { + "message": "अपने जुड़े हुए खाते ढूंढें और अनुमतियां यहां प्रबंधित करें" + }, "personalAddressDetected": { "message": "व्यक्तिगत पते का पता चला। टोकन अनुबंध पता दर्ज करें।" }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "पोर्टफोलियो" }, + "portfolioDashboard": { + "message": "पोर्टफोलियो डैशबोर्ड" + }, "preferredLedgerConnectionType": { "message": "वरीयता वाले लेजर कनेक्शन के प्रकार", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "निजी कुंजी", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "$1 के लिए निजी कुंजी", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "चेतावनी: इस कुंजी का खुलासा कभी न करें। आपकी निजी कुंजियों के साथ कोई भी व्यक्ति आपके खाते में रखी कोई भी परिसंपत्ति चुरा सकता है।" }, @@ -2738,6 +3385,9 @@ "queued": { "message": "कतारबद्ध" }, + "quoteRate": { + "message": "उद्धरण का दर" + }, "reAddAccounts": { "message": "किसी अन्य अकाउंट को फिर से जोड़ें" }, @@ -2751,7 +3401,7 @@ "message": "प्राप्त करें" }, "recipientAddressPlaceholder": { - "message": "खोज, सार्वजनिक पता (0x) या ENS" + "message": "सार्वजनिक पता (0x) या ENS नाम डालें" }, "recommendedGasLabel": { "message": "अनुशंसित" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "यह खाता आपके वॉलेट से निकाल दिया जाएगा। कृपया सुनिश्चित करें कि जारी रखने से पहले आपके पास इस आयातित खाते के लिए मूल गुप्त रिकवरी फ्रेज या निजी कुंजी है। आप खाता ड्रॉप-डाउन से फिर से खाते आयात कर सकते हैं या बना सकते हैं। " }, + "removeJWT": { + "message": "संरक्षक टोकन हटाएं" + }, + "removeJWTDescription": { + "message": "क्या आप सुनिश्चित हैं कि आप इस टोकन को हटाना चाहते हैं? इस टोकन को सौंपे गए सभी खातों को एक्सटेंशन से भी हटा दिया जाएगा: " + }, "removeNFT": { "message": "NFT हटाएं" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "वरीयताएं और अकाउंट एड्रेस से युक्त यूजर सेटिंग्स को आप पहले से बैकअप की गई JSON फाइल से रीस्टोर सकते हैं।" }, + "resultPageError": { + "message": "त्रुटि" + }, + "resultPageErrorDefaultMessage": { + "message": "परिचालन विफल रहा।" + }, + "resultPageSuccess": { + "message": "सफल" + }, + "resultPageSuccessDefaultMessage": { + "message": "परिचालन सफलतापूर्वक पूरा हुआ।" + }, "retryTransaction": { "message": "लेनदेन का पुनः प्रयास करें" }, @@ -2939,16 +3607,27 @@ "message": "आपके सभी $1 को एक्सेस और स्थानांतरित करने की अनुमति निरस्त करें?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "आपके सभी NFT's को $1 से एक्सेस और स्थानांतरित करने की अनुमति रद्द करें?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "यह बिना किसी नोटिस के आपके सभी $1 तक पहुंचने और स्थानांतरित करने के लिए किसी तीसरे पक्ष की अनुमति को निरस्त करता है।", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "यह बिना किसी सूचना के आपके सभी एनएफटी को $1 से एक्सेस और स्थानांतरित करने के लिए किसी तीसरे पक्ष की अनुमति को रद्द कर देता है।", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "अनुमति रद्द करने की" + }, "revokeSpendingCap": { "message": "अपने $1 के लिए खर्च की सीमा को हटा दें", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "यह अनुबंध आपके वर्तमान या भविष्य के और टोकन पर खर्च करने में असमर्थ है।" + "message": "यह थर्ड पार्टी आपके वर्तमान या भविष्य के और ज्यादा टोकन खर्च करने में असमर्थ होगा।" }, "rpcUrl": { "message": "नया RPC URL" @@ -2986,9 +3665,28 @@ "security": { "message": "सुरक्षा" }, + "securityAlert": { + "message": "$1 और $2 से सुरक्षा चेतावनी" + }, + "securityAlerts": { + "message": "सुरक्षा चेतावनी" + }, + "securityAlertsDescription1": { + "message": "यह फीचर स्थानीय रूप से आपके लेनदेन और हस्ताक्षर अनुरोधों की समीक्षा करके आपको दुर्भावनापूर्ण गतिविधि के प्रति सचेत करती है। आपका डेटा यह सेवा प्रदान करने वाले तृतीय पक्षों के साथ साझा नहीं किया जाता है। किसी भी अनुरोध को मंजूरी देने से पहले हमेशा जांच करें। इस बात की कोई गारंटी नहीं है कि यह फीचर सभी दुर्भावनापूर्ण गतिविधियों का पता लगा लेगी।" + }, + "securityAlertsDescription2": { + "message": "किसी भी अनुरोध को मंज़ूरी देने से पहले हमेशा पूरी जांच-पड़ताल ज़रूर करें। इस बात की कोई गारंटी नहीं है कि इस सुविधा के माध्यम से बुरी नीयत से की गई सभी गतिविधि का पता लगाया जा सकेगा।" + }, "securityAndPrivacy": { "message": "सुरक्षा और गोपनीयता" }, + "securityProviderAdviceBy": { + "message": "$1 की ओर से सुरक्षा सलाह", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "ब्यौरा देखें" + }, "seedPhraseConfirm": { "message": "सीक्रेट रिकवरी फ्रेज की पुष्टि करें" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "अपना सीक्रेट रिकवरी फ्रेज लिखें" }, + "select": { + "message": "चयन करें" + }, "selectAccounts": { "message": "इस साइट पर उपयोग करने के लिए खाते (खातों) का चयन करें" }, + "selectAccountsForSnap": { + "message": "इस स्नैप के साथ उपयोग करने के लिए खाता(खातों) का चयन करने की" + }, "selectAll": { "message": "सभी का चयन करें" }, + "selectAllAccounts": { + "message": "सभी खातों का चयन करें" + }, "selectAnAccount": { "message": "किसी खाते का चयन करें" }, "selectAnAccountAlreadyConnected": { "message": "यह खाता पहले ही MetaMask से जुड़ा हुआ है" }, + "selectAnAccountHelp": { + "message": "MetaMask इंस्टीट्यूशनल में उपयोग करने के लिए संरक्षक खातों का चयन करें।" + }, "selectHdPath": { "message": "HD पथ का चयन करें" }, + "selectJWT": { + "message": "टोकन चुनें" + }, "selectNFTPrivacyPreference": { "message": "सेटिंग्स में NFT डिटेक्शन चालू करें" }, @@ -3113,6 +3826,9 @@ "message": "बिना किसी खर्च सीमा के $1 स्वीकृत करें", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "स्नैप खाता जोड़ें" + }, "settings": { "message": "सेटिंग" }, @@ -3141,9 +3857,21 @@ "message": "लेनदेन सूची में आने वाले लेनदेन को दिखाने के लिए Etherscan का उपयोग करने के लिए इसका चयन करें", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "यह हर उस नेटवर्क पर निर्भर करता है जिसे आपके Ethereum एड्रेस और आपके IP एड्रेस का एक्सेस होगा।" + }, + "showMore": { + "message": "अधिक दिखाएं" + }, + "showNft": { + "message": "NFT दिखाएं" + }, "showPermissions": { "message": "अनुमतियां दिखाएं" }, + "showPrivateKey": { + "message": "निजी कुंजी दिखाएं" + }, "showPrivateKeys": { "message": "निजी कुंजियां दिखाएं" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "मैं समझता हूं कि जब तक मैं अपने सीक्रेट रिकवरी फ्रेज का बैकअप नहीं लेता, मैं अपने खाते और उनकी सभी संपत्ति खो सकता हूं।" }, + "smartContracts": { + "message": "स्मार्ट कॉन्ट्रैक्ट्स" + }, + "smartSwap": { + "message": "स्मार्ट स्वैप" + }, + "smartSwapsAreHere": { + "message": "स्मार्ट स्वैप यहां हैं!" + }, + "smartSwapsDescription": { + "message": "MetaMask के स्वैप अब और अधिक स्मार्ट हो गए हैं! इन हेतु सहायता के लिए स्मार्ट स्वैप को सक्षम करने से MetaMask आपके स्वैप को प्रोग्रामेटिक रूप से ऑप्टिमाइज कर पाएगा:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "स्मार्ट स्वैप के लिए पर्याप्त फंड नहीं है।" + }, + "smartSwapsErrorUnavailable": { + "message": "स्मार्ट स्वैप अस्थायी रूप से अनुपलब्ध हैं।" + }, + "smartSwapsSubDescription": { + "message": "* स्मार्ट स्वैप कई बार आपके लेन-देन को निजी तौर पर सबमिट करने का प्रयास करेगा। यदि सभी प्रयास विफल हो जाते हैं, तो यह सुनिश्चित करने के लिए लेन-देन सार्वजनिक रूप से प्रसारित किया जाएगा कि आपका स्वैप सफलतापूर्वक पूरा हो गया है।" + }, + "snapConfigure": { + "message": "कॉन्फिगर करें" + }, + "snapConnectionWarning": { + "message": "$1 चाहता है कि $2 के साथ जुड़े. तभी आगे बढ़ें अगर आप इस वेबसाइट पर भरोसा करते हैं।", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "यह सामग्री $1 से आ रही है", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "MetaMask स्नैप्स का उपयोग करके अपने नए खाते को सुरक्षित करने का तरीका चुनें।" + }, + "snapCreateAccountTitle": { + "message": "एक $1 वाला खाता बनाएं", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "स्नैप", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "MetaMask द्वारा" + }, + "snapDetailAudits": { + "message": "ऑडिट करें" + }, + "snapDetailDeveloper": { + "message": "डेवलपर" + }, + "snapDetailLastUpdated": { + "message": "अपडेट किया गया" + }, + "snapDetailManageSnap": { + "message": "स्नैप को प्रबंधित करें" + }, + "snapDetailTags": { + "message": "टैग" + }, + "snapDetailVersion": { + "message": "संस्करण" + }, + "snapDetailWebsite": { + "message": "वेबसाइट" + }, + "snapDetailsCreateASnapAccount": { + "message": "एक स्नैप खाता बनाएं" + }, + "snapDetailsInstalled": { + "message": "इंस्टॉल किया गया" + }, "snapError": { "message": "स्नैप एरर: '$1'. एरर कोड: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "स्नैप इंस्टाल करें" }, + "snapInstallRequest": { + "message": "$1 इंस्टॉल करने से इसे निम्नलिखित अनुमतियां मिलती हैं। अगर आप $1 पर भरोसा करते हैं तो ही जारी रखें।", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "इंस्टॉलेशन समाप्त" + }, "snapInstallWarningCheck": { "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं।", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं:", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "सावधानी से आगे बढ़ें" + }, "snapInstallWarningKeyAccess": { "message": "आप स्नैप \"$1\" के लिए $2 कुंजी का एक्सेस प्रदान कर रहे हैं। यह अपरिवर्तनीय है और आपके $2 खातों और संपत्तियों पर \"$1\" नियंत्रण प्रदान करता है। आगे बढ़ने से पहले सुनिश्चित करें कि आप \"$1\" पर भरोसा करते हैं।", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "$1 को $2 की सार्वजनिक कुंजी का एक्सेस प्रदान करें", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 इंस्टॉल नहीं किया जा सका.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "इंस्टॉलेशन विफल रहा", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "ऑडिट किया गया" + }, + "snapResultError": { + "message": "त्रुटि" + }, + "snapResultSuccess": { + "message": "सफल" + }, + "snapResultSuccessDescription": { + "message": "$1 उपयोग के लिए तैयार है" + }, "snapUpdate": { "message": "स्नैप अपडेट करें" }, + "snapUpdateAvailable": { + "message": "अपडेट उपलब्ध है" + }, + "snapUpdateErrorDescription": { + "message": "$1 अपडेट नहीं किया जा सका।", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "अपडेट विफल रहा", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 $2 को $3 से अपडेट करना चाहते हैं जो इसे निम्नलिखित अनुमतियां देती है। यदि आप $2 पर भरोसा करते हैं तो ही जारी रखें।", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "अपडेट समाप्त" + }, "snaps": { "message": "स्नैप्स" }, "snapsInsightLoading": { "message": "ट्रांजैक्शन इनसाइट लोड हो रही है..." }, + "snapsInvalidUIError": { + "message": "स्नैप द्वारा निर्दिष्ट किया गया UI अमान्य है।" + }, "snapsNoInsight": { "message": "स्नैप ने कोई इनसाइट नहीं लौटाई" }, + "snapsPrivacyWarningFirstMessage": { + "message": "आप स्वीकार करते हैं कि आप जिस स्नैप को इंस्टॉल करने जा रहे हैं वह एक तृतीय पक्ष सेवा है जैसा कि Consensys $1 में परिभाषित किया गया है। तृतीय पक्ष सेवाओं का आपका उपयोग तृतीय पक्ष सेवा प्रदाता द्वारा निर्धारित अलग नियमों और शर्तों द्वारा नियंत्रित होता है। आप अपने जोखिम पर तृतीय पक्ष सेवा का एक्सेस करते हैं, उस पर भरोसा करते हैं या उसका उपयोग करते हैं। तृतीय पक्ष सेवाओं के आपके उपयोग के कारण किसी भी नुकसान के लिए Consensys सभी जिम्मेदारियों और उत्तरदायित्वों को अस्वीकार करता है।", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "तृतीय पक्ष सेवाओं के साथ आप जो भी सूचना साझा करते हैं, उसे उन तृतीय पक्ष सेवाओं द्वारा उनकी अपनी गोपनीयता नीतियों के अनुसार सीधे एकत्र की जाएगी। अधिक जानकारी के लिए कृपया उनकी गोपनीयता नीतियां देखें।", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys के पास उस सूचना का एक्सेस नहीं है जो आप इन तृतीय पक्षों से साझा करते हैं।", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "अपने स्नैप्स प्रबंधित करें" }, + "snapsTermsOfUse": { + "message": "उपयोग की शर्तें" + }, "snapsToggle": { "message": "कोई स्नैप तभी चलेगा जब उसे सक्षम किया गया हो" }, "snapsUIError": { - "message": "स्नैप द्वारा विनिर्दिष्टत UI अमान्य है।", + "message": "अधिक सहायता के लिए $1 के निर्माताओं से संपर्क करें।", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "केवल वही संख्या दर्ज करें जिसे आप अभी या भविष्य में $1 तक ऐक्सेस करने में सहज महसूस करते हैं। आप टोकन सीमा को बाद में कभी भी बढ़ा सकते हैं।", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "आपके $1 के लिए खर्च की सीमा का अनुरोध" + }, "srpInputNumberOfWords": { "message": "मेरे पास एक $1-शब्द का फ़्रेज़ है", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "शुरू करें" }, + "srpSecurityQuizImgAlt": { + "message": "बीच में एक कीहोल वाली एक आई और तीन फ्लोटिंग पासवर्ड फील्ड" + }, "srpSecurityQuizIntroduction": { "message": "अपना सीक्रेट रिकवरी फ्रेज़ प्रकट करने के लिए, आपको दो प्रश्नों का सही उत्तर देना होगा" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "स्थिर" }, + "stake": { + "message": "हिस्सेदारी" + }, "stateLogError": { "message": "स्टेट लॉग को पुनर्प्राप्त करने में त्रुटि।" }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "कनेक्ट नहीं है" }, + "statusNotConnectedAccount": { + "message": "कोई खाता जुड़ा नहीं है" + }, "step1LatticeWallet": { "message": "अपना Lattice1 कनेक्ट करें" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "यह आपको प्राप्त होने वाली न्यूनतम राशि है। आप स्लिपेज के आधार पर अधिक प्राप्त कर सकते हैं।" }, + "swapAnyway": { + "message": "कैसे भी स्वैप करें" + }, "swapApproval": { "message": "स्वैप के लिए $1 अनुमोदित करें", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "इस स्वैप को पूरा करने के लिए आपको अधिक $1 और $2 की आवश्यकता होगी", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "क्या आप अभी भी हैं?" + }, + "swapAreYouStillThereDescription": { + "message": "जब आप जारी रखना चाहते हैं तो हम आपको नवीनतम उद्धरण दिखाने के लिए तैयार हैं" + }, "swapBuildQuotePlaceHolderText": { "message": "$1 के मिलान वाले कोई भी टोकन उपलब्ध नहीं हैं", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "अपने हार्डवेयर वॉलेट से पुष्टि करें" }, + "swapContinueSwapping": { + "message": "स्वैप करना जारी रखें" + }, "swapContractDataDisabledErrorDescription": { "message": "अपने Ledger पर Ethereum ऐप में, \"सेटिंग\" पर जाएं और अनुबंध डेटा की अनुमति दें। फिर, अपने स्वैप का पुनः प्रयास करें।" }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "सीमा संपादित करें" }, + "swapEditTransactionSettings": { + "message": "लेन-देन सेटिंग्स को संपादित करें" + }, "swapEnableDescription": { "message": "यह आवश्यक है और MetaMask को आपके $1 को स्वैप करने की अनुमति देता है।", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "विनिमय के लिए यह $1 होगा", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "रकम दर्ज करें" + }, "swapEstimatedNetworkFees": { "message": "अनुमानित नेटवर्क शुल्क" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "स्वैप विफल रहा" }, + "swapFetchingQuote": { + "message": "उद्धरण प्राप्त करना" + }, "swapFetchingQuoteNofN": { "message": "$2 का $1 उद्धरण लाया जा रहा है", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "$1% MetaMask शुल्क शामिल है।", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "$1% MetaMask शुल्क शामिल है - $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "स्वैप के बारे में ज्यादा जानें" + }, "swapLowSlippageError": { "message": "लेनदेन विफल हो सकता है, अधिकतम स्लिपेज बहुत कम है।" }, @@ -3626,6 +4533,10 @@ "message": "$1 में नए उद्धरण", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "$1 के मिलान वाले कोई भी टोकन उपलब्ध नहीं हैं", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "यह लेनदेन संसाधित होने के बाद आपका $1 आपके खाते में जोड़ दिया जाएगा।", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "उद्धरण का विवरण" }, + "swapQuoteNofM": { + "message": "$2 में से $1", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "उद्धरण का स्रोत" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "उद्धरणों का समय समाप्त" }, + "swapQuotesNotAvailableDescription": { + "message": "अपने ट्रेड का आकार कम करें या एक अलग टोकन का उपयोग करें।" + }, "swapQuotesNotAvailableErrorDescription": { "message": "राशि या स्लिपेज सेटिंग को समायोजित करने का प्रयास करें और फिर से प्रयास करें।" }, @@ -3693,21 +4611,57 @@ "message": "एक उद्धरण का चयन करें" }, "swapSelectAToken": { - "message": "एक टोकन का चयन करें" + "message": "टोकन चुनें" }, "swapSelectQuotePopoverDescription": { "message": "नीचे दिए गए सभी उद्धरण कई चलनिधि स्रोतों से एकत्र किए गए हैं।" }, + "swapSelectToken": { + "message": "टोकन चुनें" + }, + "swapShowLatestQuotes": { + "message": "सबसे नया उद्धरण दिखाएं" + }, "swapSlippageNegative": { "message": "स्लिपेज शून्य से अधिक या बराबर होना चाहिए" }, + "swapSlippageNegativeDescription": { + "message": "स्लिपेज शून्य से अधिक या बराबर होना चाहिए" + }, + "swapSlippageNegativeTitle": { + "message": "जारी रखने के लिए स्लिपेज बढ़ाएं" + }, + "swapSlippageOverLimitDescription": { + "message": "स्लिपेज टोलरेंस 15% या उससे कम होनी चाहिए। कुछ भी अधिक खराब दर में बदल जाएगा।" + }, + "swapSlippageOverLimitTitle": { + "message": "जारी रखने के लिए स्लिपेज घटाएं" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "अधिकतम स्लिपेज बहुत कम है जिसके कारण आपका लेन-देन विफल हो सकता है।" + }, + "swapSlippageTooLowTitle": { + "message": "लेन-देन की विफलता से बचने के लिए स्लिपेज बढ़ाएं" + }, "swapSlippageTooltip": { "message": "यदि आपके ऑर्डर किए जाने और पुष्टि किए जाने के समय के बीच मूल्य में परिवर्तन होता है, तो इसे \"स्लिपेज\" कहा जाता है। यदि स्लिपेज आपकी \"स्लिपेज टॉलरेंस\" सेटिंग से अधिक हो जाता है, तो आपका स्वैप स्वतः रद्द हो जाएगा।" }, + "swapSlippageVeryHighDescription": { + "message": "दर्ज किया गया स्लिपेज बहुत अधिक माना जाता है और इसका परिणाम खराब दर हो सकता है" + }, + "swapSlippageVeryHighTitle": { + "message": "बहुत अधिक स्लिपेज" + }, + "swapSlippageZeroDescription": { + "message": "शून्य-स्लिपेज उद्धरण प्रदाता कम हैं, जिसके परिणामस्वरूप कम प्रतिस्पर्धी उद्धरण होगा।" + }, + "swapSlippageZeroTitle": { + "message": "शून्य-स्लिपेज प्रदाताओं की सोर्सिंग करना" + }, "swapSource": { "message": "चलनिधि का स्रोत" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "अपने हार्डवेयर वॉलेट से पुष्टि करने के लिए" }, + "swapTokenAddedManuallyDescription": { + "message": "इस टोकन को $1 पर सत्यापित करें और सुनिश्चित करें कि यह वही टोकन है जिससे आप व्यापार करना चाहते हैं।", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "टोकन मैन्युअल रूप से जोड़ा गया" + }, "swapTokenAvailable": { "message": "आपके खाते में आपका $1 जोड़ दिया गया है।", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "$1 स्रोतों पर सत्यापित।", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 केवल 1 स्रोत पर सत्यापित है। आगे बढ़ने से पहले इसे $2 पर सत्यापित करने पर विचार करें।", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "संभावित रूप से अप्रामाणिक टोकन" + }, "swapTooManyDecimalsError": { "message": "$1अनुमति देता है दशमलव $2 तक की", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "इस लेनदेन को पूरा करने के लिए पर्याप्त $1 नहीं है", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "पर्याप्त $1 नहीं", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "गतिविधि में देखें" }, + "switch": { + "message": "बदलें" + }, "switchEthereumChainConfirmationDescription": { "message": "इससे चयनित नेटवर्क को MetaMask के भीतर पहले से जोड़े गए नेटवर्क में बदल दिया जाएगा:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "आपने स्विच कर लिया है" }, + "switcherTitle": { + "message": "नेटवर्क स्विचर" + }, + "switcherTourDescription": { + "message": "नेटवर्क स्विच करने या नया नेटवर्क जोड़ने के लिए आइकन पर क्लिक करें" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "नेटवर्क स्विच करने से सभी लंबित पुष्टिकरण रद्द हो जाएंगे" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "सेवा की शर्तें" }, + "termsOfUse": { + "message": "उपयोग की शर्तें" + }, + "termsOfUseAgreeText": { + "message": " मैं उपयोग की शर्तों से सहमत हूं, जो MetaMask और इसकी सभी फीचर्स के मेरे उपयोग पर लागू होती हैं" + }, + "termsOfUseFooterText": { + "message": "सभी वर्गों को पढ़ने के लिए कृपया स्क्रॉल करें" + }, + "termsOfUseTitle": { + "message": "हमारी उपयोग की शर्तों को अपडेट कर दिया गया है" + }, "testNetworks": { "message": "टेस्ट नेटवर्क्स" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "ध्यान रखने योग्य बातें" }, + "thirdPartySoftware": { + "message": "तृतीय-पक्ष सॉफ्टवेयर नोटिस", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "यह संग्रह" + }, + "thisServiceIsExperimental": { + "message": "यह सेवा प्रायोगिक है। इस सुविधा को सक्षम करके, आप OpenSea के $1 से सहमत होते हैं।", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "समय" }, @@ -3863,11 +4867,44 @@ "message": "प्रति: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "आपको फ़िशिंग हमलों का जोखिम है। Eth_sign को बंद करके अपनी सुरक्षा करें।" + }, "toggleEthSignDescriptionField": { - "message": "Eth_sign अनुरोधों का उपयोग करके dapps को आपके हस्ताक्षर का अनुरोध करने देने के लिए इसे चालू करें। eth_sign एक ओपन-एंडेड साइनिंग विधि है जो आपको मनमाने ढंग से हैश पर हस्ताक्षर करने देती है, जिससे यह एक खतरनाक फ़िशिंग जोखिम बन जाता है। eth_sign अनुरोधों पर केवल तभी हस्ताक्षर करें यदि आप पढ़ सकते हैं कि आप क्या हस्ताक्षर कर रहे हैं और अनुरोध के मूल पर भरोसा करते हैं।" + "message": "यदि आप इस सेटिंग को एनेबल करते हैं, तो आपको हस्ताक्षर अनुरोध प्राप्त हो सकते हैं जो पढ़ने योग्य नहीं हैं। एक ऐसे संदेश पर हस्ताक्षर करके जिसे आप समझ नहीं पा रहे हैं, आप अपने फंड और एनएफटी दे देने के लिए सहमत हो सकते हैं।" }, "toggleEthSignField": { - "message": "Eth_sign अनुरोधों को टॉगल करें" + "message": "Eth_sign अनुरोध" + }, + "toggleEthSignModalBannerBoldText": { + "message": " आपके साथ धोखा हो सकता है" + }, + "toggleEthSignModalBannerText": { + "message": "यदि आपसे यह सेटिंग चालू करने के लिए कहा गया है," + }, + "toggleEthSignModalCheckBox": { + "message": "मैं समझता हूं कि अगर मैं eth_sign अनुरोधों को सक्षम करता हूं, तो मैं अपने सभी फंड और NFT's खो सकता हूं। " + }, + "toggleEthSignModalDescription": { + "message": "eth_sign अनुरोधों को अनुमति देने से आप फ़िशिंग हमलों के प्रति असुरक्षित हो सकते हैं। URL की हमेशा समीक्षा करें और कोड वाले संदेशों पर हस्ताक्षर करते समय सावधान रहें।" + }, + "toggleEthSignModalFormError": { + "message": "टेक्स्ट गलत है" + }, + "toggleEthSignModalFormLabel": { + "message": "जारी रखने के लिए \"मैं केवल वही हस्ताक्षर करता हूं जो मुझे समझ में आता है\" दर्ज करें" + }, + "toggleEthSignModalFormValidation": { + "message": "मैं केवल वही हस्ताक्षर करता हूं जो मुझे समझ में आता है" + }, + "toggleEthSignModalTitle": { + "message": "अपने जोखिम पर उपयोग करें" + }, + "toggleEthSignOff": { + "message": "बंद (अनुशंसित)" + }, + "toggleEthSignOn": { + "message": "चालू (अनुशंसित नहीं)" }, "token": { "message": "टोकन" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "टोकन का प्रतीक" }, + "tokens": { + "message": "टोकन" + }, "tokensFoundTitle": { "message": "$1 नए टोकन मिले", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "मैं समझता हूं" }, + "tooltipSatusConnected": { + "message": "जुड़े हुए" + }, + "tooltipSatusNotConnected": { + "message": "जुड़े नहीं हैं" + }, "total": { "message": "कुलयोग" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "लेनदेन में त्रुटि हुई।" }, + "transactionFailed": { + "message": "लेन-देन विफल रहा" + }, "transactionFee": { "message": "लेनदेन शुल्क" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "कुल गैस शुल्क" }, + "transactionNote": { + "message": "लेन-देन टिप्पणी" + }, "transactionResubmitted": { "message": "$2 गैस शुल्क में $1 वृद्धि के साथ लेनदेन फिर से सबमिट किया गया" }, "transactionSecurityCheck": { - "message": "लेन-देन सुरक्षा प्रदाताओं को सक्षम करें" + "message": "सुरक्षा अलर्ट एनेबल करें" }, "transactionSecurityCheckDescription": { "message": "आपके हस्ताक्षर करने से पहले अहस्ताक्षरित लेन-देन और हस्ताक्षर अनुरोधों में शामिल जोखिमों का पता लगाने और प्रदर्शित करने के लिए हम तृतीय-पक्ष एपीआई का उपयोग करते हैं। इन सेवाओं के पास आपके अहस्ताक्षरित लेनदेन और हस्ताक्षर अनुरोध, आपके खाते का पता और आपकी पसंदीदा भाषा तक पहुंच होगी।" }, + "transactionSettings": { + "message": "लेन-देन संबंधी सेटिंग्स" + }, "transactionSubmitted": { "message": "$2 पर $1 के गैस शुल्क के साथ लेनदेन सबमिट किया गया।" }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "इससे स्थानांतरित करें" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "हमें आपके लेजर को जोड़ने में समस्या आ रही है। $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "हमारे हार्डवेयर वॉलेट कनेक्शन मार्गदर्शिका की समीक्षा करें और पुनः प्रयास करें।", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "यदि आप फ़ायरफ़ॉक्स के नवीनतम संस्करण का उपयोग कर रहे हैं, तो आप फ़ायरफ़ॉक्स से U2F समर्थन छोड़ने से संबंधित समस्या का अनुभव कर सकते हैं। इस समस्या को ठीक करने का तरीका जानें $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "यहाँ", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "हमें आपके $1 से कनेक्ट करने में परेशानी हुई, $2 की समीक्षा करने का प्रयास करें और दोबारा कोशिश करें।", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "अप ऐरो" }, + "update": { + "message": "अपडेट करें" + }, "updatedWithDate": { "message": "अपडेट किया गया $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "यह URL वर्तमान में $1 नेटवर्क द्वारा उपयोग किया जाता है।" }, + "use4ByteResolution": { + "message": "स्मार्ट कॉन्ट्रैक्ट्स को डीकोड करें" + }, + "use4ByteResolutionDescription": { + "message": "उपयोगकर्ता के अनुभव को बेहतर बनाने के लिए, आपके द्वारा इंटरैक्ट किए गए स्मार्ट कॉन्ट्रैक्ट्स के आधार पर हम गतिविधि टैब को संदेशों के साथ कस्टमाइज़ करते हैं। डेटा को डीकोड करने और आसानी से पढ़े जा सकने वाले स्मार्ट कॉन्ट्रैक्ट्स का एक संस्करण आपको दिखाने के लिए MetaMask एक सर्विस इस्तेमाल करता है जिसका नाम 4byte.directory है। इससे आपके द्वारा बुरी नीयत वाले स्मार्ट कॉन्ट्रैक्ट एक्शन को मंजूरी देने की संभावनाओं को कम करने में मदद मिलती है। हालांकि, इसमें आपका IP एड्रेस शेयर होने का खतरा हो सकता है।" + }, "useMultiAccountBalanceChecker": { "message": "खाता के शेष राशि के अनुरोधों को बैच करें" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "खाता शेष अनुरोधों को बैच कर तेजी से शेष राशि अपडेट प्राप्त करें। इससे हमें आपके खाते की शेष राशि एक साथ मिल जाती है, जिससे आपको बेहतर अनुभव के लिए जल्द अपडेट प्राप्त होते हैं। जब यह सुविधा बंद हो जाती है, तो तृतीय पक्षों द्वारा आपके खातों को एक-दूसरे के साथ संबद्ध करने की संभावना कम हो जाती है।" + }, "useNftDetection": { "message": "एनएफटी को ऑटो-डिटेक्ट करें" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Ethereum उपयोगकर्ताओं को लक्षित करने वाले फिशिंग डोमेन के लिए एक चेतावनी प्रदर्शित करें" }, + "useSiteSuggestion": { + "message": "साइट के सुझाव का प्रयोग करें" + }, "useTokenDetectionPrivacyDesc": { "message": "आपके खाते में भेजे गए टोकन को स्वचालित रूप से प्रदर्शित करने में थर्ड पार्टी के सर्वर्स के साथ संचार शामिल रहेगा, जो टोकन के चित्रों को लाने का काम करते हैं। वे सर्वर्स आपके IP एड्रेस को एक्सेस कर पाएंगे।" }, @@ -4170,7 +5256,7 @@ "message": "उपयोगकर्ता" }, "verifyContractDetails": { - "message": "अनुबंध विवरण सत्यापित करें" + "message": "थर्ड-पार्टी विवरण सत्यापित करें" }, "verifyThisTokenDecimalOn": { "message": "टोकन दशमलव $1 पर पाया जा सकता है", @@ -4184,12 +5270,18 @@ "message": "इस टोकन को $1 पर सत्यापित करें और सुनिश्चित करें कि यह वही टोकन है जिससे आप व्यापार करना चाहते हैं।", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "संस्करण" + }, "view": { "message": "देखें" }, "viewAllDetails": { "message": "सभी विवरण देखें" }, + "viewAllQuotes": { + "message": "सभी उद्धरण को देखें" + }, "viewContact": { "message": "संपर्क देखें" }, @@ -4213,9 +5305,18 @@ "message": "Etherscan पर $1 देखें", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "एक्सप्लोरर पर देखें" + }, "viewOnOpensea": { "message": "Opensea पर देखें" }, + "viewPortfolioDashboard": { + "message": "पोर्टफोलियो डैशबोर्ड देखें" + }, + "viewinCustodianApp": { + "message": "कस्टोडियन ऐप में देखें" + }, "viewinExplorer": { "message": "एक्सप्लोरर में $1 देखें", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "इस नेटवर्क को जोड़ना चाहते हैं?" }, + "wantsToAddThisAsset": { + "message": "$1 इस संपत्ति को आपके वॉलेट में जोड़ना चाहता है", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "चेतावनी" }, "warningTooltipText": { - "message": "$1 अनुबंध आगे की सूचना या सहमति के बिना आपकी संपूर्ण टोकन शेष राशि खर्च कर सकता है। कम खर्च करने की सीमा को समायोजित करके अपनी सुरक्षा करें।", + "message": "$1 थर्ड पार्टी आपकी संपूर्ण टोकन शेष राशि को बिना किसी सूचना या सहमति के खर्च कर सकता है। खर्च की कम सीमा को अनुकूलित करके खुद को सुरक्षित रखें।", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "आप हस्ताक्षर कर रहे हैं" }, + "yourAccounts": { + "message": "आपके खाते" + }, "yourFundsMayBeAtRisk": { "message": "आपके फंड खतरे में हो सकते हैं" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 62e53b537..19a037be0 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Tentang" }, + "accept": { + "message": "Terima" + }, "acceptTermsOfUse": { "message": "Saya telah membaca dan menyetujui $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Tambahkan nama panggilan" }, + "addAccount": { + "message": "Tambahkan akun" + }, "addAcquiredTokens": { "message": "Tambahkan token yang Anda peroleh menggunakan MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Tambahkan dari daftar jaringan populer atau tambahkan jaringan secara manual. Lakukan interaksi hanya dengan entitas yang aman." }, + "addHardwareWallet": { + "message": "Tambahkan dompet perangkat keras" + }, + "addIPFSGateway": { + "message": "Tambahkan gateway IPFS pilihan Anda" + }, "addMemo": { "message": "Tambahkan memo" }, @@ -244,6 +256,21 @@ "message": "Koneksi jaringan ini mengandalkan pihak ketiga. Koneksi ini mungkin kurang bisa diandalkan atau memungkinkan pihak ketiga melacak aktivitas. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Tambahkan token baru" + }, + "addNft": { + "message": "Tambahkan NFT" + }, + "addNfts": { + "message": "Tambahkan NFT" + }, + "addSnapAccountModalDescription": { + "message": "Temukan opsi untuk menjaga keamanan akun Anda dengan MetaMask Snap" + }, + "addSuggestedNFTs": { + "message": "Tambahkan NFT yang disarankan" + }, "addSuggestedTokens": { "message": "Tambahkan token yang disarankan" }, @@ -254,6 +281,9 @@ "message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Menambahkan Jaringan" + }, "address": { "message": "Alamat" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Biaya prioritas (alias “tip penambang”) langsung masuk ke penambang dan memberi insentif kepada mereka untuk memprioritaskan transaksi Anda." }, + "agreeTermsOfUse": { + "message": "Saya menyetujui $1 MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "Brankas AirGap" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Peringatan" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Anda telah menghubungkan semua akun kustodian atau tidak memiliki akun untuk terhubung ke MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Tidak ada akun yang tersedia untuk dihubungkan" + }, "allOfYour": { "message": "Seluruh $1 Anda", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Seluruh NFT dari $1 Anda", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Izinkan ekstensi eksternal ini untuk:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Izinkan situs ini untuk:" }, + "allowThisSnapTo": { + "message": "Izinkan snap ini untuk:" + }, "allowWithdrawAndSpend": { "message": "Izinkan $1 untuk ditarik dan digunakan hingga jumlah berikut:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Jumlah" }, + "apiUrl": { + "message": "URL API" + }, "appDescription": { "message": "Dompet Ethereum pada Peramban Anda", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Berikan izin untuk mengakses dan mentransfer seluruh $1 Anda?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Berikan izin untuk mengakses dan mentransfer seluruh NFT dari $1 Anda?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Setujui" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Hal ini memungkinkan pihak ketiga untuk mengakses dan mentransfer NFT berikut tanpa pemberitahuan lebih lanjut sampai Anda mencabut aksesnya." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Hal ini memungkinkan pihak ketiga untuk mengakses dan mentransfer seluruh NFT dari $1 Anda tanpa pemberitahuan lebih lanjut sampai Anda mencabut aksesnya.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Berikan izin untuk mengakses dan mentransfer $1 Anda?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Jika Anda mencoba untuk mengirim aset secara langsung dari satu jaringan ke jaringan lain, aset Anda berpotensi hilang secara permanen. Pastikan untuk menggunakan penghubung." }, + "attemptToCancelSwap": { + "message": "Mencoba membatalkan pertukaran dengan biaya ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Mencoba terhubung ke blockchain." }, @@ -411,6 +473,9 @@ "average": { "message": "Rata-rata" }, + "awaitingApproval": { + "message": "Menunggu persetujuan..." + }, "back": { "message": "Kembali" }, @@ -454,6 +519,9 @@ "message": "Ini merupakan versi beta. Harap laporkan kerusakan $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Versi Beta MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Versi Beta MetaMask" }, @@ -488,9 +556,45 @@ "message": "Lihat akun di $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Jika Anda menyetujui permintaan ini, pihak ketiga yang terdeteksi melakukan penipuan dapat mengambil semua aset Anda." + }, + "blockaidDescriptionBlurFarming": { + "message": "Jika Anda menyetujui permintaan ini, seseorang dapat mencuri aset Anda yang terdaftar di Blur." + }, + "blockaidDescriptionFailed": { + "message": "Karena terjadi kesalahan, permintaan ini tidak diverifikasi oleh penyedia keamanan. Lanjutkan dengan hati-hati." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Anda berinteraksi dengan domain berbahaya. Jika Anda menyetujui permintaan ini, aset Anda kemungkinan akan hilang." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Jika Anda menyetujui permintaan ini, aset Anda kemungkinan akan hilang." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Jika Anda menyetujui permintaan ini, seseorang dapat mencuri aset Anda yang terdaftar di OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Jika Anda menyetujui permintaan ini, pihak ketiga yang terdeteksi melakukan penipuan akan mengambil semua aset Anda." + }, + "blockaidTitleDeceptive": { + "message": "Ini adalah permintaan tipuan" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Permintaan mungkin tidak aman" + }, + "blockaidTitleSuspicious": { + "message": "Ini adalah permintaan yang mencurigakan" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Jembatan" + }, "browserNotSupported": { "message": "Peramban Anda tidak didukung..." }, @@ -510,6 +614,10 @@ "message": "Beli $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Beli lebih banyak $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Beli Sekarang" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Tindakan ini mereset nonce akun dan menghapus data dari tab aktivitas di dompet Anda. Hanya akun dan jaringan saat ini yang akan terpengaruh. Saldo dan transaksi masuk Anda tidak akan berubah." }, + "click": { + "message": "Klik" + }, "clickToConnectLedgerViaWebHID": { "message": "Klik di sini untuk menghubungkan Ledger Anda melalui WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Saat ini Anda meninggalkan MetaMask untuk mengonfigurasi snap ini." + }, + "configureSnapPopupInstallDescription": { + "message": "Saat ini Anda meninggalkan MetaMask untuk menginstal snap ini." + }, + "configureSnapPopupInstallTitle": { + "message": "Instal snap" + }, + "configureSnapPopupLink": { + "message": "Klik tautan ini untuk melanjutkan:" + }, + "configureSnapPopupTitle": { + "message": "Konfigurasikan snap" + }, "confirm": { "message": "Konfirmasikan" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Hubungkan akun atau buat baru" }, + "connectCustodialAccountMenu": { + "message": "Hubungkan Akun Kustodian" + }, + "connectCustodialAccountMsg": { + "message": "Pilih kustodian yang ingin Anda hubungkan untuk menambah atau menyegarkan token." + }, + "connectCustodialAccountTitle": { + "message": "Akun Kustodian" + }, "connectManually": { "message": "Hubungkan ke situs saat ini secara manual" }, + "connectSnap": { + "message": "Hubungkan $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Hubungkan ke $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Menghubungkan ke jaringan uji Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Menghubungkan ke Linea Mainnet" + }, "connectingToMainnet": { "message": "Menghubungkan ke Ethereum Mainnet" }, "connectingToSepolia": { "message": "Menghubungkan ke jaringan uji Sepolia" }, + "connectionFailed": { + "message": "Koneksi gagal" + }, + "connectionFailedDescription": { + "message": "Pengambilan $1 gagal, periksa jaringan Anda dan coba lagi.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Permintaan koneksi" + }, "contactUs": { "message": "Hubungi kami" }, @@ -708,7 +860,7 @@ "message": "Pengalokasian kontrak" }, "contractDescription": { - "message": "Untuk melindungi diri Anda dari penipu, luangkan waktu sejenak untuk memverifikasi detail kontrak." + "message": "Untuk melindungi diri Anda dari penipu, luangkan waktu sejenak untuk memverifikasi detail pihak ketiga." }, "contractInteraction": { "message": "Interaksi kontrak" @@ -717,16 +869,16 @@ "message": "Kontrak NFT" }, "contractRequestingAccess": { - "message": "Kontrak meminta akses" + "message": "Pihak ketiga meminta akses" }, "contractRequestingSignature": { - "message": "Kontrak meminta tanda tangan" + "message": "Pihak ketiga meminta tanda tangan" }, "contractRequestingSpendingCap": { - "message": "Kontrak meminta batas penggunaan" + "message": "Pihak ketiga meminta batas pengeluaran" }, "contractTitle": { - "message": "Detail kontrak" + "message": "Detail pihak ketiga" }, "contractToken": { "message": "Kontrak token" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Grafik estimasi gas pasar" }, + "custodian": { + "message": "Kustodian" + }, + "custodianAccount": { + "message": "Akun kustodian" + }, + "custodianAccountAddedDesc": { + "message": "Kini Anda dapat menggunakan akun kustodian di MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Akun kustodian terpilih telah ditambahkan." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Buka $1 dan klik tombol 'Hubungkan ke MMI' di antarmuka pengguna untuk menghubungkan kembali akun Anda ke MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Kini Anda dapat menggunakan akun kustodian di MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Token kustodian Anda telah disegarkan" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Ini akan menggantikan token kustodian untuk alamat berikut:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Ganti token kustodian" + }, + "custodyApiUrl": { + "message": "URL API $1" + }, + "custodyDeeplinkDescription": { + "message": "Setujui transaksi di aplikasi $1. Setelah semua persetujuan kustodian yang diperlukan telah dilakukan, transaksi akan selesai. Periksa status aplikasi $1 Anda." + }, + "custodyRefreshTokenModalDescription": { + "message": "Buka $1 dan klik tombol 'Hubungkan ke MMI' di antarmuka pengguna untuk menghubungkan kembali akun Anda ke MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Kustodian Anda mengeluarkan token yang mengautentikasi ekstensi MetaMask Institutional yang memungkinkan untuk menghubungkan akun Anda." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Token ini kedaluwarsa setelah jangka waktu tertentu karena alasan keamanan. Anda harus menghubungkannya kembali ke MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Mengapa saya melihat hal ini?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Sesi kustodian Anda telah kedaluwarsa" + }, + "custodySessionExpired": { + "message": "Sesi kustodian kedaluwarsa." + }, + "custodyWrongChain": { + "message": "Akun ini tidak diatur untuk digunakan bersama $1" + }, "custom": { "message": "Lanjutan" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "dukungan pelanggan" }, + "dappRequestedSpendingCap": { + "message": "Batas pengeluaran yang diminta situs" + }, "dappSuggested": { "message": "Situs yang disarankan" }, @@ -851,6 +1060,12 @@ "message": "$1 telah menyarankan harga ini.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Situs yang disarankan" + }, + "dappSuggestedHighShortLabel": { + "message": "Situs (tinggi)" + }, "dappSuggestedShortLabel": { "message": "Situs" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Hapus" }, + "deleteContact": { + "message": "Hapus kontak" + }, "deleteNetwork": { "message": "Hapus jaringan?" }, + "deleteNetworkIntro": { + "message": "Jika menghapus jaringan ini, Anda harus menambahkannya lagi untuk melihat aset Anda di jaringan ini" + }, + "deleteNetworkTitle": { + "message": "Hapus jaringan $1?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Deposit" }, @@ -917,6 +1142,10 @@ "description": { "message": "Deskripsi" }, + "descriptionFromSnap": { + "message": "Deskripsi dari $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Kesalahan ini dapat terjadi secara berkala, jadi coba mulai ulang ekstensi atau nonaktifkan MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Lewatkan pengingat pencadangan Frasa Pemulihan Rahasia" }, + "displayNftMedia": { + "message": "Tampilkan media NFT" + }, + "displayNftMediaDescription": { + "message": "Alamat IP Anda dapat diketahui oleh OpenSea atau pihak ketiga lainnya saat Anda menampilkan media dan data NFT. Ini memungkinkan penyerang menghubungkan alamat IP dengan alamat Ethereum Anda. Deteksi otomatis NFT bergantung pada pengaturan ini, dan tidak akan tersedia saat dinonaktifkan." + }, "domain": { "message": "Domain" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Aktifkan deteksi otomatis" }, + "enableForAllNetworks": { + "message": "Aktifkan untuk semua jaringan" + }, "enableFromSettings": { "message": " Aktifkan dari Pengaturan." }, + "enableSmartSwaps": { + "message": "Aktifkan pertukaran pintar" + }, + "enableSnap": { + "message": "Aktifkan" + }, "enableToken": { "message": "aktifkan $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Diaktifkan" + }, "encryptionPublicKeyNotice": { "message": "$1 menginginkan kunci enkripsi publik Anda. Dengan menyetujui, situs ini akan dapat membuat pesan terenkripsi untuk Anda.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Saat ini deteksi token yang ditingkatkan tersedia di $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask memungkinkan Anda melihat domain ENS seperti \"https://metamask.eth\" tepat di bilah alamat peramban Anda. Berikut cara kerjanya:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Peramban biasa umumnya tidak menangani alamat ENS atau IPFS, tetapi MetaMask dapat menanganinya. Alamat IP Anda dapat dibagikan kepada layanan pihak ketiga IPFS saat menggunakan fitur ini." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask memeriksa kontrak ENS Ethereum untuk menemukan kode yang terhubung ke nama ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Jika kode ditautkan ke IPFS, maka kode tersebut mendapatkan konten dari jaringan IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Kemudian, Anda dapat melihat kontennya, umumnya dalam bentuk situs web atau yang serupa." + }, + "ensDomainsSettingTitle": { + "message": "Tampilkan domain ENS di bilah alamat" + }, "ensIllegalCharacter": { "message": "Karakter tidak sah untuk ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Masukkan angka" }, + "enterCustodianToken": { + "message": "Masukkan token $1 atau tambahkan token baru" + }, "enterMaxSpendLimit": { "message": "Masukkan batas penggunaan maksimum" }, + "enterOptionalPassword": { + "message": "Masukkan kata sandi opsional" + }, "enterPassword": { "message": "Masukkan kata sandi" }, "enterPasswordContinue": { "message": "Masukkan kata sandi untuk melanjutkan" }, + "enterTokenNameOrAddress": { + "message": "Masukkan nama token atau tempel alamat" + }, + "enterYourPassword": { + "message": "Masukkan kata sandi" + }, "errorCode": { "message": "Kode: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Tumpukan:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Kesalahan saat menghubungkan ke jaringan khusus." + }, + "errorWithSnap": { + "message": "Kesalahan pada $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Biaya gas cadangan diberikan karena layanan estimasi gas utama saat ini tidak tersedia." }, + "ethereumProviderAccess": { + "message": "Berikan penyedia Ethereum akses ke $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Alamat publik Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Eksperimental" }, + "exploreMetaMaskSnaps": { + "message": "Jelajahi MetaMask Snaps" + }, "exportPrivateKey": { "message": "Ekspor kunci privat" }, + "extendWalletWithSnaps": { + "message": "Perluas pengalaman dompet." + }, "externalExtension": { "message": "Ekstensi eksternal" }, @@ -1302,6 +1596,9 @@ "message": "Impor file tidak bekerja? Klik di sini!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Fail yang didrop terlalu besar." + }, "flaskWelcomeUninstall": { "message": "Anda harus menghapus ekstensi ini", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Umum" }, + "getStarted": { + "message": "Mulai" + }, + "globalTitle": { + "message": "Menu global" + }, + "globalTourDescription": { + "message": "Lihat portofolio, situs terhubung, pengaturan, dan lainnya" + }, "goBack": { "message": "Kembali" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Hubungkan dompet perangkat keras" }, + "hardwareWalletsInfo": { + "message": "Integrasi dompet perangkat keras menggunakan panggilan API ke server eksternal, yang dapat melihat alamat IP Anda dan alamat kontrak pintar tempat Anda berinteraksi." + }, "hardwareWalletsMsg": { "message": "Pilih dompet perangkat keras yang ingin Anda gunakan dengan MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "tetapi penipu akan mencoba memintanya.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Kunci Pribadi menyediakan $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "akses penuh ke dompet dan dana Anda.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "tahan untuk mengungkapkan lingkaran terkunci" + }, + "holdToRevealPrivateKey": { + "message": "Tahan untuk mengungkapkan Kunci Pribadi" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Amankan kunci pribadi Anda" + }, "holdToRevealSRP": { - "message": "Tahan untuk mengungkap FPR" + "message": "Tahan untuk mengungkapkan SRP" }, "holdToRevealSRPTitle": { - "message": "Jaga keamanan FPR Anda" + "message": "Amankan SRP Anda" + }, + "holdToRevealUnlockedLabel": { + "message": "tahan untuk mengungkapkan lingkaran terbuka" + }, + "id": { + "message": "Id" }, "ignoreAll": { "message": "Abaikan semua" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Galat saat mengimpor akun." }, + "importAccountErrorIsSRP": { + "message": "Anda telah memasukkan Frasa Pemulihan Rahasia (atau mnemonik). Untuk mengimpor akun di sini, Anda harus memasukkan kunci pribadi, yang merupakan string heksadesimal dengan panjang 64 karakter." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Ini bukan kunci pribadi yang valid. Anda telah memasukkan string heksadesimal, tetapi panjangnya harus 64 karakter." + }, + "importAccountErrorNotHexadecimal": { + "message": "Ini bukan kunci pribadi yang valid. Anda harus memasukkan string heksadesimal dengan panjang 64 karakter." + }, + "importAccountJsonLoading1": { + "message": "Impor JSON diperkirakan memerlukan waktu beberapa menit dan bekukan MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Mohon maaf, kami akan berupaya untuk membuatnya semakin cepat di masa mendatang." + }, "importAccountMsg": { - "message": "Akun yang diimpor tidak akan dikaitkan dengan Frasa Pemulihan Rahasia akun MetaMask yang asli dibuat. Pelajari selengkapnya tentang akun yang diimpor" + "message": "Akun yang diimpor tidak akan ditautkan dengan Frasa Pemulihan Rahasia MetaMask Anda. Pelajari selengkapnya tentang akun yang diimpor" }, "importMyWallet": { "message": "Impor dompet saya" @@ -1615,18 +1962,29 @@ "message": "Transaksi awal Anda dikonfirmasikan oleh jaringan. Klik Oke untuk kembali." }, "inputLogicEmptyState": { - "message": "Masukkan angka yang menurut Anda dapat digunakan kontrak sekarang atau di masa mendatang. Anda selalu dapat meningkatkan batas pengeluaran nanti." + "message": "Masukkan angka yang menurut Anda dapat digunakan pihak ketiga sekarang atau di masa mendatang. Anda selalu dapat meningkatkan batas pengeluaran nanti." }, "inputLogicEqualOrSmallerNumber": { - "message": "Ini memungkinkan kontrak menggunakan $1 dari saldo Anda saat ini.", + "message": "Ini memungkinkan pihak ketiga untuk menggunakan $1 dari saldo Anda saat ini.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Ini memungkinkan kontrak menggunakan semua saldo token Anda hingga mencapai batas atau Anda mencabut batas pengeluaran. Jika ini bukan yang dimaksudkan, pertimbangkan untuk menetapkan batas pengeluaran yang lebih rendah." + "message": "Ini memungkinkan pihak ketiga menggunakan semua saldo token Anda hingga mencapai batas atau Anda mencabut batas pengeluaran. Jika ini bukan yang dimaksudkan, pertimbangkan untuk menetapkan batas pengeluaran yang lebih rendah." + }, + "insightsFromSnap": { + "message": "Wawasan dari $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Instal" }, + "installOrigin": { + "message": "Instal asal" + }, + "installedOn": { + "message": "Diinstal di $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Saldo tidak cukup." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Masukan tidak valid! Frasa Pemulihan Rahasia peka terhadap huruf besar/kecil." }, + "ipfsGateway": { + "message": "Gateway IPFS" + }, + "ipfsGatewayDescription": { + "message": "MetaMask menggunakan layanan pihak ketiga untuk menampilkan gambar NFT Anda yang disimpan di IPFS, menampilkan informasi terkait alamat ENS yang dimasukkan di bilah alamat peramban Anda, serta mengambil ikon untuk token yang berbeda. Alamat IP Anda mungkin tidak memberikan perlindungan terhadap layanan ini saat Anda menggunakannya." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Kami menggunakan layanan pihak ketiga untuk menampilkan gambar NFT Anda yang disimpan di IPFS, menampilkan informasi terkait alamat ENS yang dimasukkan di bilah alamat peramban Anda, serta mengambil ikon untuk token yang berbeda. Alamat IP Anda mungkin tidak memberikan perlindungan terhadap layanan ini saat Anda menggunakannya." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Resolusi IPFS akan aktif saat Anda memilih Konfirmasi. Anda dapat menonaktifkannya di $1 kapan saja.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Pengaturan > Keamanan dan privasi" + }, "jazzAndBlockies": { "message": "Jazzicons dan Blockies merupakan dua gaya ikon unik yang berbeda untuk membantu Anda mengidentifikasi akun dengan cepat." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Terakhir terjual" }, + "layer1Fees": { + "message": "Biaya lapis 1" + }, "learnCancelSpeeedup": { "message": "Pelajari cara $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Ingin $1 seputar gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Pelajari Selengkapnya" + }, "learnMoreUpperCase": { "message": "Pelajari selengkapnya" }, @@ -1765,16 +2145,16 @@ "message": "Sebelum mengeklik konfirmasi:" }, "ledgerConnectionInstructionStepFour": { - "message": "Aktifkan \"data kontrak pintar\" atau \"penandatanganan buta\" pada perangkat Ledger Anda" + "message": "Aktifkan \"data kontrak pintar\" atau \"penandatanganan buta\" pada perangkat Ledger Anda." }, "ledgerConnectionInstructionStepOne": { - "message": "Aktifkan Gunakan Ledger Live di bawah Pengaturan > Lanjutan" + "message": "Aktifkan Gunakan Ledger Live di bawah Pengaturan > Lanjutan." }, "ledgerConnectionInstructionStepThree": { - "message": "Sambungkan perangkat Ledger Anda dan pilih aplikasi Ethereum" + "message": "Pastikan Ledger terhubung dan untuk memilih aplikasi Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Buka dan buka Aplikasi Ledger Live" + "message": "Akses dan buka Aplikasi Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Sesuaikan cara Anda menghubungkan Ledger ke MetaMask. Kami merekomendasikan $1, tetapi opsi lain tersedia. Baca selengkapnya di sini: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Jaringan uji Linea Goerli" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "Tautan" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Kunci" }, + "lockMetaMask": { + "message": "Kunci MetaMask" + }, + "lockTimeInvalid": { + "message": "Waktu kunci harus berupa angka antara 0 dan 10080" + }, "logo": { "message": "Logo $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Tombol status koneksi menunjukkan apakah situs web yang Anda kunjungi terhubung ke akun Anda yang dipilih saat ini." }, + "metamaskInstitutionalVersion": { + "message": "Versi MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swap sedang dalam pemeliharaan. Harap periksa kembali nanti." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Metrik" }, + "mismatchAccount": { + "message": "Akun yang Anda pilih ($1) berbeda dengan akun yang mencoba menandatangani ($2)" + }, "mismatchedChainLinkText": { "message": "memverifikasi detail jaringan", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Simbol mata uang yang dikirimkan tidak sesuai dengan yang kami harapkan untuk ID rantai ini." }, + "mismatchedRpcChainId": { + "message": "ID rantai yang dipulihkan oleh jaringan khusus tidak cocok dengan ID rantai yang dikirimkan." + }, "mismatchedRpcUrl": { "message": "Menurut catatan kami, nilai URL RPC yang dikirimkan tidak sesuai dengan penyedia yang dikenal untuk ID rantai ini." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Minta di sini" }, + "mmiAddToken": { + "message": "Halaman di $1 ingin mengotorisasi token kustodian berikut di MetaMask Institutional" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional dirancang dan dibangun di seluruh dunia." + }, + "more": { + "message": "selengkapnya" + }, "moreComingSoon": { - "message": "Selanjutnya akan segera hadir..." + "message": "Penyedia lainnya akan segera hadir" + }, + "multipleSnapConnectionWarning": { + "message": "$1 ingin terhubung dengan $2 snap. Hanya lanjutkan jika Anda memercayai situs web ini.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Harus memilih minimal 1 token." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Jaringan sibuk. Harga gas tinggi dan estimasinya kurang akurat." }, + "networkMenu": { + "message": "Menu Jaringan" + }, + "networkMenuHeading": { + "message": "Pilih jaringan" + }, "networkName": { "message": "Nama jaringan" }, @@ -2033,6 +2450,10 @@ "message": "Biaya gas relatif $1 dalam 72 jam terakhir.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Kami tidak dapat terhubung ke $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL Jaringan" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Dimiliki Sebelumnya" + }, "nickname": { "message": "Nama panggilan" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Nilai konversi tidak tersedia" }, + "noNFTs": { + "message": "Belum ada NFT" + }, + "noNetworksFound": { + "message": "Tidak ditemukan jaringan untuk kueri pencarian yang diberikan" + }, "noSnaps": { - "message": "Belum ada Snap yang diinstal" + "message": "Belum ada snap yang diinstal." }, "noThanksVariant2": { "message": "Tidak, terima kasih." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Apa ini akun yang benar? Ini berbeda dari akun yang saat ini dipilih di dompet Anda" }, + "notEnoughBalance": { + "message": "Saldo tidak cukup" + }, "notEnoughGas": { "message": "Gas tidak cukup" }, + "note": { + "message": "Catatan" + }, + "notePlaceholder": { + "message": "Pemberi persetujuan akan melihat catatan ini saat menyetujui transaksi di kustodian." + }, + "notificationTransactionFailedMessage": { + "message": "Transaksi $1 gagal! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Transaksi gagal! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Transaksi gagal", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Transaksi $1 dikonfirmasi!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Transaksi terkonfirmasi", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Lihat di $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Notifikasi" + }, "notifications10ActionText": { "message": "Lihat di Pengaturan", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Penggabungan Ethereum telah tiba!" }, + "notifications18ActionText": { + "message": "Aktifkan peringatan keamanan" + }, + "notifications18DescriptionOne": { + "message": "Dapatkan peringatan dari pihak ketiga saat Anda menerima permintaan yang dapat membahayakan.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Selalu pastikan untuk melakukan uji tuntas sendiri sebelum menyetujui permintaan apa pun.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea merupakan penyedia pertama untuk fitur ini. Penyedia lainnya akan segera hadir!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Tetap aman bersama peringatan keamanan" + }, + "notifications19ActionText": { + "message": "Aktifkan deteksi otomatis NFT" + }, + "notifications19DescriptionOne": { + "message": "Dua cara untuk memulai:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Saat ini kami hanya mendukung ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Tambahkan NFT secara manual, atau aktifkan deteksi otomatis NFT di Pengaturan > Eksperimental.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "NFT Anda berbeda dari sebelumnya" + }, "notifications1Description": { "message": "Pengguna MetaMask Mobile kini bisa menukar token di dalam dompet seluler mereka. Pindai kode QR untuk mendapatkan aplikasi seluler dan mulai menukar.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Penukaran pada perangkat seluler telah hadir!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Pelajari selengkapnya", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Jika menggunakan Firefox versi terbaru, Anda dapat mengalami masalah terkait Firefox yang menghentikan dukungan U2F.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Pengguna Ledger dan Firefox Mengalami Masalah Koneksi", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Cobalah" + }, + "notifications21Description": { + "message": "Kami telah memperbarui Swap di ekstensi MetaMask agar lebih mudah dan lebih cepat digunakan.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Memperkenalkan Swap baru dan segar!" + }, + "notifications22ActionText": { + "message": "Mengerti" + }, + "notifications22Description": { + "message": "💡 Cukup klik menu global atau menu akun untuk menemukannya!" + }, + "notifications22Title": { + "message": "Mencari detail akun Anda atau URL block explorer?" + }, + "notifications23ActionText": { + "message": "Aktifkan peringatan keamanan" + }, + "notifications23DescriptionOne": { + "message": "Hindari penipuan yang terdeteksi sambil tetap menjaga privasi Anda dengan peringatan keamanan yang didukung oleh Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Jika Anda mengaktifkan peringatan keamanan dari OpenSea, kami telah memindahkan Anda ke fitur ini." + }, + "notifications23DescriptionTwo": { + "message": "Selalu lakukan uji tuntas sendiri sebelum menyetujui permintaan." + }, + "notifications23Title": { + "message": "Tetap aman bersama peringatan keamanan" + }, "notifications3ActionText": { "message": "Baca selengkapnya", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Hanya hubungkan ke situs yang Anda percayai." }, "openFullScreenForLedgerWebHid": { - "message": "Buka MetaMask dalam layar penuh untuk menghubungkan ledger Anda melalui WebHID.", + "message": "Buka layar penuh untuk menghubungkan Ledger Anda.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Buka di block explorer" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Pengoperasian Gagal" + }, "optional": { "message": "Opsional" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Kata Sandi Tidak Cocok" }, + "pasteJWTToken": { + "message": "Tempel atau jatuhkan token di sini:" + }, "pastePrivateKey": { "message": "Tempel string kunci privat Anda di sini:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Akses internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Izinkan snap untuk mengakses internet. Ini dapat digunakan untuk mengirim dan menerima data dari server pihak ketiga.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Hubungkan ke Snap $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Izinkan situs web atau snap untuk berinteraksi dengan $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Jadwalkan dan lakukan tindakan berkala.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Izinkan snap untuk melakukan tindakan yang diatur secara berkala pada waktu, tanggal, atau interval yang tetap. Ini dapat digunakan untuk memicu interaksi atau pemberitahuan yang peka terhadap waktu.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Tampilkan jendela dialog di MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Izinkan snap untuk menampilkan sembulan MetaMask dengan teks khusus, kolom input, dan tombol untuk menyetujui atau menolak suatu tindakan.\nDapat digunakan untuk membuat, misalnya, peringatan, konfirmasi, dan alur keikutsertaan untuk snap.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Lihat alamat, saldo akun, aktivitas, dan mulai transaksi", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Akses penyedia Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Izinkan snap untuk berkomunikasi dengan MetaMask secara langsung, agar dapat membaca data dari blockchain serta menyarankan pesan dan transaksi.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Dapatkan kunci arbitrer unik untuk snap ini.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Izinkan snap untuk mendapatkan kunci arbitrer unik untuk snap ini, tanpa mengeksposnya. Kunci ini terpisah dari akun MetaMask dan tidak terkait dengan kunci pribadi atau Frasa Pemulihan Rahasia. Snap lain tidak dapat mengakses informasi ini.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Gunakan lifecycle hook.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Izinkan snap menggunakan lifecycle hook untuk menjalankan kode pada waktu tertentu selama siklus hidupnya.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Operasikan sepanjang waktu.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Izinkan snap beroperasi tanpa batas saat, sebagai contoh, memproses data dalam jumlah besar.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Tambah dan kontrol akun Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Kontrol akun dan aset Anda dengan $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Izinkan snap untuk mendapatkan pasangan kunci BIP-32 berdasarkan Frasa Pemulihan Rahasia tanpa mengeksposnya. Ini memberikan akses penuh ke semua akun dan aset di $1.\nDengan kemampuan untuk mengelola kunci, snap dapat mendukung berbagai protokol blockchain selain Ethereum (EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Kontrol akun dan aset \"$1\" Anda.", + "message": "Kontrol akun dan aset $1 Anda.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Izinkan snap untuk mendapatkan pasangan kunci BIP-44 berdasarkan Frasa Pemulihan Rahasia tanpa mengeksposnya. Ini memberikan akses penuh ke semua akun dan aset di $1.\nDengan kemampuan untuk mengelola kunci, snap dapat mendukung berbagai protokol blockchain selain Ethereum (EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Kontrol akun dan aset $1 Anda.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Simpan dan kelola datanya di perangkat Anda.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Izinkan snap untuk menyimpan, memperbarui, dan memulihkan data secara aman dengan enkripsi. Snap lain tidak dapat mengakses informasi ini.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Tampilkan pemberitahuan.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Izinkan snap untuk menampilkan pemberitahuan dalam MetaMask. Teks pemberitahuan singkat dapat dipicu oleh snap untuk informasi yang dapat ditindaklanjuti atau peka terhadap waktu.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Izinkan $1 untuk terhubung langsung dengan snap ini.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Izinkan $1 untuk mengirim pesan ke snap dan menerima tanggapan dari snap.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Dapatkan dan tampilkan wawasan transaksi.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Izinkan snap untuk membaca kode transaksi dan menampilkan wawasan dalam UI MetaMask. Ini dapat digunakan sebagai solusi antipengelabuan dan keamanan.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Lihat asal situs web yang mengirimkan transaksi", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Izinkan snap untuk melihat asal (URI) situs web yang menyarankan transaksi. Ini dapat digunakan sebagai solusi antipengelabuan dan keamanan.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Izin tak dikenal: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Lihat kunci publik Anda untuk $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Izinkan snap untuk melihat kunci publik (dan alamat) untuk $1. Ini tidak memberikan kendali atas akun atau aset.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Lihat kunci publik Anda untuk $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Mendukung WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Izinkan snap untuk mengakses lingkungan pelaksanaan tingkat rendah melalui WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Izin" }, + "permissionsTitle": { + "message": "Izin" + }, + "permissionsTourDescription": { + "message": "Temukan akun yang terhubung dan kelola izin di sini" + }, "personalAddressDetected": { "message": "Alamat pribadi terdeteksi. Masukkan alamat kontrak token." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portofolio" }, + "portfolioDashboard": { + "message": "Dasbor Portofolio" + }, "preferredLedgerConnectionType": { "message": "Jenis koneksi Ledger Pilihan", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Kunci Privat", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Kunci pribadi untuk $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Peringatan: Jangan ungkapkan kunci ini. Siapa pun yang memiliki kunci privat Anda dapat mencuri aset yang disimpan di akun Anda." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Antrean" }, + "quoteRate": { + "message": "Tingkat kuotasi" + }, "reAddAccounts": { "message": "tambahkan kembali akun lain" }, @@ -2751,7 +3401,7 @@ "message": "Terima" }, "recipientAddressPlaceholder": { - "message": "Cari, alamat publik (0x), atau ENS" + "message": "Masukkan alamat publik (0x) atau nama ENS" }, "recommendedGasLabel": { "message": "Direkomendasikan" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Akun ini akan dihapus dari dompet Anda. Pastikan Anda memiliki Frasa Pemulihan Rahasia asli atau kunci privat untuk akun impor ini sebelum melanjutkan. Anda dapat mengimpor atau membuat akun lagi dari akun drop down. " }, + "removeJWT": { + "message": "Hapus token kustodian" + }, + "removeJWTDescription": { + "message": "Yakin ingin menghapus token ini? Semua akun yang ditetapkan ke token ini juga akan dihapus dari ekstensi:" + }, "removeNFT": { "message": "Hapus NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Anda dapat memulihkan pengaturan pengguna yang berisi preferensi dan alamat akun dari berkas JSON yang dicadangkan sebelumnya." }, + "resultPageError": { + "message": "Galat" + }, + "resultPageErrorDefaultMessage": { + "message": "Pengoperasian gagal." + }, + "resultPageSuccess": { + "message": "Berhasil" + }, + "resultPageSuccessDefaultMessage": { + "message": "Pengoperasian berhasil dilakukan." + }, "retryTransaction": { "message": "Coba lagi transaksi" }, @@ -2939,16 +3607,27 @@ "message": "Cabut izin untuk mengakses dan mentransfer seluruh $1 Anda?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Cabut izin untuk mengakses dan mentransfer seluruh NFT dari $1 Anda?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Hal ini mencabut izin pihak ketiga untuk mengakses dan mentransfer seluruh $1 Anda tanpa pemberitahuan lebih lanjut.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Hal ini mencabut izin pihak ketiga untuk mengakses dan mentransfer seluruh NFT dari $1 Anda tanpa pemberitahuan lebih lanjut.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Cabut izin" + }, "revokeSpendingCap": { "message": "Cabut batas pengeluaran untuk $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Kontrak ini tidak akan dapat mempergunakan token Anda saat ini atau di masa mendatang." + "message": "Pihak ketiga ini tidak akan dapat mempergunakan token Anda saat ini atau di masa mendatang." }, "rpcUrl": { "message": "URL RPC Baru" @@ -2986,9 +3665,28 @@ "security": { "message": "Keamanan" }, + "securityAlert": { + "message": "Peringatan keamanan dari $1 dan $2" + }, + "securityAlerts": { + "message": "Peringatan keamanan" + }, + "securityAlertsDescription1": { + "message": "Fitur ini memberi tahu Anda seputar aktivitas berbahaya dengan meninjau transaksi dan permintaan tanda tangan Anda secara lokal. Data Anda tidak dibagikan kepada pihak ketiga yang menyediakan layanan ini. Selalu lakukan uji tuntas sendiri sebelum menyetujui permintaan apa pun. Tidak ada jaminan bahwa fitur ini akan mendeteksi semua aktivitas berbahaya." + }, + "securityAlertsDescription2": { + "message": "Pastikan untuk selalu melakukan uji tuntas sebelum menyetujui permintaan apa pun. Tidak ada jaminan semua aktivitas berbahaya akan terdeteksi oleh fitur ini." + }, "securityAndPrivacy": { "message": "Keamanan & privasi" }, + "securityProviderAdviceBy": { + "message": "Saran keamanan dari $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Lihat detailnya" + }, "seedPhraseConfirm": { "message": "Konfirmasikan Frasa Pemulihan Rahasia" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Tuliskan Frasa Pemulihan Rahasia Anda" }, + "select": { + "message": "Pilih" + }, "selectAccounts": { "message": "Pilih akun" }, + "selectAccountsForSnap": { + "message": "Pilih akun yang akan digunakan bersama snap ini" + }, "selectAll": { "message": "Pilih semua" }, + "selectAllAccounts": { + "message": "Pilih semua akun" + }, "selectAnAccount": { "message": "Pilih akun" }, "selectAnAccountAlreadyConnected": { "message": "Akun ini sudah terhubung ke MetaMask." }, + "selectAnAccountHelp": { + "message": "Pilih akun kustodian untuk digunakan di MetaMask Institutional." + }, "selectHdPath": { "message": "Pilih path HD" }, + "selectJWT": { + "message": "Pilih token" + }, "selectNFTPrivacyPreference": { "message": "Aktifkan deteksi NFT pada Pengaturan" }, @@ -3113,6 +3826,9 @@ "message": "Setujui $1 tanpa batas penggunaan", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Tambahkan akun snap" + }, "settings": { "message": "Pengaturan" }, @@ -3141,9 +3857,21 @@ "message": "Pilih ini untuk menggunakan Etherscan untuk menampilkan transaksi yang masuk di daftar transaksi", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Ini bergantung pada setiap jaringan yang akan memiliki akses ke alamat Ethereum dan alamat IP Anda." + }, + "showMore": { + "message": "Tampilkan selengkapnya" + }, + "showNft": { + "message": "Tampilkan NFT" + }, "showPermissions": { "message": "Tampilkan Izin" }, + "showPrivateKey": { + "message": "Tampilkan kunci pribadi" + }, "showPrivateKeys": { "message": "Tampilkan Kunci Privat" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Saya memahami bahwa sampai saya mencadangkan Frasa Pemulihan Rahasia, saya dapat kehilangan akun saya dan semua aset yang ada." }, + "smartContracts": { + "message": "Kontrak pintar" + }, + "smartSwap": { + "message": "Pertukaran pintar" + }, + "smartSwapsAreHere": { + "message": "Smart Swap telah hadir!" + }, + "smartSwapsDescription": { + "message": "Swap MetaMask kini semakin pintar! Mengaktifkan Smart Swap akan mengizinkan MetaMask mengoptimalkan Swap secara terprogram untuk membantu:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Dana tidak cukup untuk pertukaran pintar." + }, + "smartSwapsErrorUnavailable": { + "message": "Smart Swap tidak tersedia untuk sementara waktu." + }, + "smartSwapsSubDescription": { + "message": "* Smart Swap akan mencoba mengirimkan transaksi Anda secara pribadi, beberapa kali. Jika semua upaya gagal, transaksi akan disiarkan secara publik untuk memastikan Swap telah berhasil dilakukan." + }, + "snapConfigure": { + "message": "Konfigurasikan" + }, + "snapConnectionWarning": { + "message": "$1 ingin terhubung ke $2. Hanya lanjutkan jika Anda memercayai situs web ini.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Konten ini berasal dari $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Pilih cara mengamankan akun baru Anda menggunakan MetaMask Snap." + }, + "snapCreateAccountTitle": { + "message": "Buat akun $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Oleh MetaMask" + }, + "snapDetailAudits": { + "message": "Audit" + }, + "snapDetailDeveloper": { + "message": "Pengembang" + }, + "snapDetailLastUpdated": { + "message": "Diperbarui" + }, + "snapDetailManageSnap": { + "message": "Kelola snap" + }, + "snapDetailTags": { + "message": "Tag" + }, + "snapDetailVersion": { + "message": "Versi" + }, + "snapDetailWebsite": { + "message": "Situs web" + }, + "snapDetailsCreateASnapAccount": { + "message": "Buat Akun Snap" + }, + "snapDetailsInstalled": { + "message": "Terinstal" + }, "snapError": { "message": "Galat Snap: '$1'. Kode Galat: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Instal Snap" }, + "snapInstallRequest": { + "message": "Menginstal $1 memberinya izin berikut. Hanya lanjutkan jika Anda memercayai $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Instalasi selesai" + }, "snapInstallWarningCheck": { "message": "Untuk mengonfirmasikan bahwa Anda sudah paham, centang kotaknya.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Untuk mengonfirmasikan bahwa Anda memahaminya, centang semua kotak.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Lanjutkan dengan hati-hati" + }, "snapInstallWarningKeyAccess": { "message": "Anda memberikan $2 akses kunci ke snap \"$1\". Tindakan ini tidak dapat dibatalkan dan memberikan kendali \"$1\" atas akun dan aset $2 Anda. Sebelum melanjutkan, pastikan \"$1\" aman.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Berikan $2 akses kunci publik ke $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 tidak dapat diinstal.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Instalasi gagal", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Diaudit" + }, + "snapResultError": { + "message": "Galat" + }, + "snapResultSuccess": { + "message": "Berhasil" + }, + "snapResultSuccessDescription": { + "message": "$1 siap digunakan" + }, "snapUpdate": { "message": "Perbarui Snap" }, + "snapUpdateAvailable": { + "message": "Pembaruan tersedia" + }, + "snapUpdateErrorDescription": { + "message": "$1 tidak dapat diperbarui.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Pembaruan gagal", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 ingin memperbarui $2 menjadi $3 yang memberinya izin berikut. Hanya lanjutkan jika Anda memercayai $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Pembaruan selesai" + }, "snaps": { "message": "Snap" }, "snapsInsightLoading": { "message": "Memuat wawasan transaksi..." }, + "snapsInvalidUIError": { + "message": "UI yang ditentukan oleh snap tidak valid." + }, "snapsNoInsight": { "message": "Snap tidak mengembalikan wawasan apa pun" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Anda menyatakan bahwa snap yang akan diinstal adalah Layanan Pihak Ketiga sebagaimana dijelaskan dalam $1 Consensys. Penggunaan Layanan Pihak Ketiga diatur oleh syarat dan ketentuan terpisah yang ditetapkan oleh penyedia Layanan Pihak Ketiga. Anda mengakses, mengandalkan, atau menggunakan Layanan Pihak Ketiga dengan risiko Anda sendiri. Consensys menafikan semua tanggung jawab dan kewajiban atas kerugian yang timbul karena penggunaan Layanan Pihak Ketiga.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Setiap informasi yang Anda bagikan kepada Layanan Pihak Ketiga akan dikumpulkan langsung oleh Layanan Pihak Ketiga tersebut sesuai dengan kebijakan privasinya. Baca kebijakan privasinya untuk informasi lebih lanjut.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys tidak memiliki akses ke informasi yang Anda bagikan kepada pihak ketiga ini.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Kelola Snap Anda" }, + "snapsTermsOfUse": { + "message": "Persyaratan Penggunaan" + }, "snapsToggle": { "message": "Snap hanya akan beroperasi jika diaktifkan" }, "snapsUIError": { - "message": "UI yang ditentukan oleh snap tidak valid.", + "message": "Hubungi pembuat $1 untuk dukungan lebih lanjut.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Masukkan angka yang menurut Anda dapat diakses $1 sekarang atau di masa mendatang. Anda selalu dapat meningkatkan batas token nanti.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Permintaan batas pengeluaran untuk $1 Anda" + }, "srpInputNumberOfWords": { "message": "Frasa milik saya memiliki $1-kata", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Mulai" }, + "srpSecurityQuizImgAlt": { + "message": "Mata dengan lubang kunci di tengah, dan tiga bidang kata sandi melayang" + }, "srpSecurityQuizIntroduction": { "message": "Untuk mengungkapkan Frasa Pemulihan Rahasia, Anda perlu menjawab dua pertanyaan dengan benar" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "stabil" }, + "stake": { + "message": "Stake" + }, "stateLogError": { "message": "Galat pada log status pengambilan." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Tidak terhubung" }, + "statusNotConnectedAccount": { + "message": "Tidak ada akun yang terhubung" + }, "step1LatticeWallet": { "message": "Hubungkan Lattice1 Anda" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Ini merupakan jumlah minimum yang akan Anda terima. Anda bisa mendapatkan lebih banyak lagi tergantung pada slippage." }, + "swapAnyway": { + "message": "Tetap tukar" + }, "swapApproval": { "message": "Setujui swap $1", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Anda memerlukan $1 $2 lagi untuk menyelesaikan swap", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Masih di sana?" + }, + "swapAreYouStillThereDescription": { + "message": "Kami siap menampilkan kuotasi terbaru jika Anda ingin melanjutkan" + }, "swapBuildQuotePlaceHolderText": { "message": "Tidak ada token yang cocok yang tersedia $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Konfirmasikan dengan dompet perangkat keras Anda" }, + "swapContinueSwapping": { + "message": "Lanjutkan pertukaran" + }, "swapContractDataDisabledErrorDescription": { "message": "Di aplikasi Ethereum di Ledger Anda, buka \"Pengaturan\" dan izinkan data kontrak. Lalu, coba lagi swap Anda." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Edit batas" }, + "swapEditTransactionSettings": { + "message": "Edit pengaturan transaksi" + }, "swapEnableDescription": { "message": "Ini diwajibkan dan memberikan MetaMask izin untuk menukar $1 Anda.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Ini akan $1 untuk ditukar", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Masukkan jumlah" + }, "swapEstimatedNetworkFees": { "message": "Estimasi biaya jaringan" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Swap gagal" }, + "swapFetchingQuote": { + "message": "Mengambil kuotasi" + }, "swapFetchingQuoteNofN": { "message": "Mengambil kuotasi $1 dari $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Termasuk $1% biaya MetaMask.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Termasuk $1% biaya MetaMask – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Pelajari selengkapnya seputar Swap" + }, "swapLowSlippageError": { "message": "Transaksi berpotensi gagal, slippage maks terlalu rendah." }, @@ -3626,6 +4533,10 @@ "message": "Kuotasi baru di $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Token yang cocok dengan $1 tidak tersedia", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "$1 akan ditambahkan ke akun Anda setelah transaksi ini diproses.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Detail kuotasi" }, + "swapQuoteNofM": { + "message": "$1 dari $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Sumber kuotasi" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Waktu kuotasi habis" }, + "swapQuotesNotAvailableDescription": { + "message": "Kurangi ukuran perdagangan Anda atau gunakan token yang berbeda." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Cobalah untuk menyesuaikan pengaturan slippage atau jumlahnya dan coba lagi." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Di bawah ini merupakan semua kuotasi yang dikumpulkan dari beberapa sumber likuiditas." }, + "swapSelectToken": { + "message": "Pilih token" + }, + "swapShowLatestQuotes": { + "message": "Tampilkan kuotasi terbaru" + }, "swapSlippageNegative": { "message": "Slippage harus lebih besar atau sama dengan nol" }, + "swapSlippageNegativeDescription": { + "message": "Selip harus lebih besar atau sama dengan nol" + }, + "swapSlippageNegativeTitle": { + "message": "Tingkatkan selip untuk melanjutkan" + }, + "swapSlippageOverLimitDescription": { + "message": "Toleransi selip harus 15% atau kurang. Nilai yang lebih tinggi akan menghasilkan tarif yang tidak efektif." + }, + "swapSlippageOverLimitTitle": { + "message": "Kurangi selip untuk melanjutkan" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Selip maks terlalu rendah sehingga dapat menyebabkan transaksi gagal." + }, + "swapSlippageTooLowTitle": { + "message": "Tingkatkan selip untuk menghindari kegagalan transaksi" + }, "swapSlippageTooltip": { "message": "Jika harga berubah antara waktu penempatan dan konfirmasi order Anda, ini disebut “slippage”. Swap Anda akan otomatis dibatalkan jika slippage melebihi pengaturan “slippage tolerance”." }, + "swapSlippageVeryHighDescription": { + "message": "Selip yang dimasukkan dianggap sangat tinggi dan dapat mengakibatkan tarif yang tidak efektif" + }, + "swapSlippageVeryHighTitle": { + "message": "Selip sangat tinggi" + }, + "swapSlippageZeroDescription": { + "message": "Ada lebih sedikit penyedia kuotasi nol selip sehingga akan menghasilkan kuotasi yang kurang kompetitif." + }, + "swapSlippageZeroTitle": { + "message": "Sumber penyedia nol selip" + }, "swapSource": { "message": "Sumber likuiditas" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "untuk mengonfirmasikan dengan dompet perangkat keras Anda" }, + "swapTokenAddedManuallyDescription": { + "message": "Verifikasikan token ini di $1 dan pastikan ini adalah token yang ingin Anda perdagangkan.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token ditambahkan secara manual" + }, "swapTokenAvailable": { "message": "$1 telah ditambahkan ke akun Anda.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Diverifikasi di $1 sumber.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 hanya diverifikasi pada 1 sumber. Pertimbangkan untuk memverifikasinya di $2 sebelum melanjutkan.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Token berpotensi tidak autentik" + }, "swapTooManyDecimalsError": { "message": "$1 memungkinkan hingga $2 desimal", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "$1 tidak cukup untuk menyelesaikan transaksi ini", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "$1 tidak cukup", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Lihat di laman aktivitas" }, + "switch": { + "message": "Alihkan" + }, "switchEthereumChainConfirmationDescription": { "message": "Ini akan mengalihkan jaringan yang dipilih dalam MetaMask ke jaringan yang ditambahkan sebelumnya:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Anda telah beralih ke" }, + "switcherTitle": { + "message": "Pengalih jaringan" + }, + "switcherTourDescription": { + "message": "Klik ikon untuk beralih jaringan atau menambahkan jaringan baru" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Mengalihkan jaringan akan membatalkan semua konfirmasi yang tertunda" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Ketentuan layanan" }, + "termsOfUse": { + "message": "persyaratan penggunaan" + }, + "termsOfUseAgreeText": { + "message": " Saya menyetujui Syarat Penggunaan, yang berlaku untuk penggunaan atas MetaMask dan semua fiturnya" + }, + "termsOfUseFooterText": { + "message": "Gulirkan layar untuk membaca semua bagian" + }, + "termsOfUseTitle": { + "message": "Syarat Penggunaan kami telah diperbarui" + }, "testNetworks": { "message": "Jaringan pengujian" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Hal-hal yang perlu diingat:" }, + "thirdPartySoftware": { + "message": "Pemberitahuan perangkat lunak pihak ketiga", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "koleksi ini" + }, + "thisServiceIsExperimental": { + "message": "Layanan ini bersifat eksperimental. Dengan mengaktifkan fitur ini, Anda menyetujui $1 OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Waktu" }, @@ -3863,11 +4867,44 @@ "message": "Ke: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Anda berisiko terkena serangan pengelabuan. Lindungi diri dengan menonaktifkan eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Aktifkan fitur ini agar aplikasi terdesentralisasi dapat meminta tanda tangan Anda menggunakan permintaan eth_sign. eth_sign adalah metode penandatanganan terbuka untuk menandatangani hash arbitrer. Oleh karenanya, terdapat risiko pengelabuan yang berbahaya. Hanya tanda tangani permintaan eth_sign jika Anda dapat membaca yang ditandatangani dan mengenali asal permintaan tersebut." + "message": "Jika mengaktifkan pengaturan ini, Anda akan mendapatkan permintaan tanda tangan yang tidak terbaca. Dengan menandatangani pesan yang tidak Anda pahami, Anda mungkin setuju untuk memberikan dana dan NFT Anda." }, "toggleEthSignField": { - "message": "Alihkan permintaan eth_sign" + "message": "Permintaan et_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " Anda mungkin ditipu" + }, + "toggleEthSignModalBannerText": { + "message": "Jika Anda diminta mengaktifkan pengaturan ini," + }, + "toggleEthSignModalCheckBox": { + "message": "Saya memahami bahwa saya dapat kehilangan semua dana dan NFT jika mengaktifkan permintaan eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Mengizinkan permintaan eth_sign dapat membuat Anda rentan terhadap serangan pengelabuan. Selalu tinjau URL dan berhati-hatilah saat menandatangani pesan yang berisi kode." + }, + "toggleEthSignModalFormError": { + "message": "Teks salah" + }, + "toggleEthSignModalFormLabel": { + "message": "Masukkan \"Saya hanya menandatangani yang saya pahami\" untuk melanjutkan" + }, + "toggleEthSignModalFormValidation": { + "message": "Saya hanya menandatangani yang saya pahami" + }, + "toggleEthSignModalTitle": { + "message": "Gunakan dengan risiko Anda sendiri" + }, + "toggleEthSignOff": { + "message": "NONAKTIF (Disarankan)" + }, + "toggleEthSignOn": { + "message": "AKTIF (Tidak disarankan)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Simbol token" }, + "tokens": { + "message": "Token" + }, "tokensFoundTitle": { "message": "$1 token baru ditemukan", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Saya paham" }, + "tooltipSatusConnected": { + "message": "terhubung" + }, + "tooltipSatusNotConnected": { + "message": "Tidak terhubung" + }, "total": { "message": "Total" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "Transaksi mengalami kesalahan." }, + "transactionFailed": { + "message": "Transaksi Gagal" + }, "transactionFee": { "message": "Biaya Transaksi" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Total biaya gas" }, + "transactionNote": { + "message": "Catatan transaksi" + }, "transactionResubmitted": { "message": "Transaksi dikirim kembali dengan estimasi biaya gas naik $1 pada $2" }, "transactionSecurityCheck": { - "message": "Aktifkan penyedia keamanan transaksi" + "message": "Aktifkan peringatan keamanan" }, "transactionSecurityCheckDescription": { "message": "Kami menggunakan API pihak ketiga untuk mendeteksi dan menampilkan berbagai risiko yang muncul dalam transaksi yang belum ditandatangani dan permintaan tanda tangan sebelum Anda menandatanganinya. Layanan ini akan dapat mengakses transaksi yang belum ditandatangani dan permintaan tanda tangan, alamat akun, serta bahasa pilihan Anda." }, + "transactionSettings": { + "message": "Pengaturan transaksi" + }, "transactionSubmitted": { "message": "Transaksi dikirim dengan estimasi biaya gas sebesar $1 pada $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transfer dari" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Kami mengalami masalah saat menghubungkan Ledger Anda. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Tinjau panduan koneksi dompet perangkat keras kami dan coba lagi.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Jika menggunakan Firefox versi terbaru, Anda dapat mengalami masalah terkait Firefox yang menghentikan dukungan U2F. Pelajari cara memperbaiki masalah ini $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "di sini", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Kami mengalami masalah saat mencoba terhubung ke $1 Anda, tinjau kembali $2 dan coba lagi.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "panah atas" }, + "update": { + "message": "Perbarui" + }, "updatedWithDate": { "message": "Diperbarui $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "URL ini saat ini digunakan oleh jaringan $1." }, + "use4ByteResolution": { + "message": "Dekode kontrak pintar" + }, + "use4ByteResolutionDescription": { + "message": "Untuk meningkatkan pengalaman pengguna, kami menyesuaikan tab aktivitas dengan pesan berdasarkan kontrak pintar yang berinteraksi dengan Anda. MetaMask menggunakan layanan yang disebut 4byte.directory untuk mendekode data dan menampilkan versi kontrak pintar yang lebih mudah dibaca. Ini membantu mengurangi peluang Anda untuk menyetujui tindakan kontrak pintar yang berbahaya, tetapi dapat menyebabkan alamat IP Anda tersebar." + }, "useMultiAccountBalanceChecker": { "message": "Kelompokkan permintaan saldo akun" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Dapatkan pembaruan saldo yang lebih cepat dengan mengelompokkan permintaan saldo akun. Ini membantu kami untuk mengambil saldo akun Anda secara bersamaan, sehingga Anda mendapatkan pembaruan lebih cepat untuk pengalaman yang lebih baik. Saat fitur ini dinonaktifkan, pihak ketiga kemungkinan kecil akan menghubungkan akun Anda yang satu dengan yang lainnya." + }, "useNftDetection": { "message": "Deteksi otomatis NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Menampilkan peringatan untuk domain pengelabuan yang menargetkan pengguna Ethereum" }, + "useSiteSuggestion": { + "message": "Gunakan saran situs" + }, "useTokenDetectionPrivacyDesc": { "message": "Menampilkan token yang dikirim ke akun Anda secara otomatis yang melibatkan komunikasi dengan server pihak ketiga untuk mengambil gambar token. Server tersebut akan memiliki akses ke alamat IP Anda." }, @@ -4170,7 +5256,7 @@ "message": "Nama pengguna" }, "verifyContractDetails": { - "message": "Verifikasi detail kontrak" + "message": "Verifikasikan detail pihak ketiga" }, "verifyThisTokenDecimalOn": { "message": "Desimal token dapat ditemukan di $1", @@ -4184,12 +5270,18 @@ "message": "Verifikasikan token ini di $1 dan pastikan ini adalah token yang ingin Anda perdagangkan.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Versi" + }, "view": { "message": "Lihat" }, "viewAllDetails": { "message": "Lihat semua detail" }, + "viewAllQuotes": { + "message": "tampilkan semua kuotasi" + }, "viewContact": { "message": "Lihat kontak" }, @@ -4213,9 +5305,18 @@ "message": "Lihat $1 di Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Lihat pada explorer" + }, "viewOnOpensea": { "message": "Lihat di Opensea" }, + "viewPortfolioDashboard": { + "message": "Tampilkan Dasbor Portofolio" + }, + "viewinCustodianApp": { + "message": "Lihat di aplikasi kustodian" + }, "viewinExplorer": { "message": "Lihat $1 di explorer", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Ingin menambahkan jaringan ini?" }, + "wantsToAddThisAsset": { + "message": "$1 ingin menambahkan aset ini ke dompet Anda", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Peringatan" }, "warningTooltipText": { - "message": "$1 Kontrak dapat mempergunakan seluruh saldo token Anda tanpa pemberitahuan atau persetujuan lebih lanjut. Lindungi diri Anda dengan menyesuaikan batas pengeluaran yang lebih rendah.", + "message": "$1 Pihak ketiga dapat mempergunakan seluruh saldo token Anda tanpa pemberitahuan atau persetujuan lebih lanjut. Lindungi diri Anda dengan menyesuaikan batas pengeluaran yang lebih rendah.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Anda sudah masuk" }, + "yourAccounts": { + "message": "Akun Anda" + }, "yourFundsMayBeAtRisk": { "message": "Dana Anda mungkin berisiko" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 1c4c4827e..162e67f2e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -106,6 +106,9 @@ "about": { "message": "バージョン情報" }, + "accept": { + "message": "同意する" + }, "acceptTermsOfUse": { "message": "$1を読んで同意しました", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "ニックネームを追加" }, + "addAccount": { + "message": "アカウントを追加" + }, "addAcquiredTokens": { "message": "MetaMaskを使用して取得したトークンを追加します" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "人気のネットワークのリストから追加するか、ネットワークを手動で追加します。信頼できる相手と以外はやり取りしないようにしてください。" }, + "addHardwareWallet": { + "message": "ハードウェアウォレットを追加" + }, + "addIPFSGateway": { + "message": "優先 IPFS ゲートウェイを追加" + }, "addMemo": { "message": "メモを追加" }, @@ -244,6 +256,21 @@ "message": "このネットワーク接続はサードパーティに依存しているため、信頼性が低かったり、サードパーティによるアクティビティの追跡が可能になる可能性があります。$1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "新しいトークンを追加" + }, + "addNft": { + "message": "NFT を追加" + }, + "addNfts": { + "message": "NFT を追加" + }, + "addSnapAccountModalDescription": { + "message": "MetaMask Snaps でアカウントのセキュリティを確保する方法をご覧ください" + }, + "addSuggestedNFTs": { + "message": "推奨された NFT を追加" + }, "addSuggestedTokens": { "message": "推奨されたトークンを追加" }, @@ -254,6 +281,9 @@ "message": "トークンが見つからない場合、アドレスをペーストして手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "ネットワークを追加中" + }, "address": { "message": "アドレス" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "優先手数料 (別名「マイナーチップ」) はマイナーに直接支払われ、トランザクションを優先するインセンティブとなります。" }, + "agreeTermsOfUse": { + "message": "MetaMask の $1 に同意します", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Vault" }, @@ -302,10 +336,20 @@ "alerts": { "message": "アラート" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "すでにすべてのカストディアンアカウントを接続したか、MetaMask Institutional に接続するアカウントがありません。" + }, + "allCustodianAccountsConnectedTitle": { + "message": "接続できるアカウントがありません" + }, "allOfYour": { "message": "すべての $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "$1 のすべてのNFT", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "この外部拡張機能に次の操作を許可します" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "このサイトに次の操作を許可します" }, + "allowThisSnapTo": { + "message": "このスナップに次の操作を許可します:" + }, "allowWithdrawAndSpend": { "message": "$1に以下の額までの引き出しと使用を許可します。", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "金額" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "ブラウザにあるイーサリアムウォレット", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "すべての $1 へのアクセスとその送金を許可しますか?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "$1 のすべてのNFTへのアクセスとそれらの転送を許可しますか?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "承認" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "これにより、アクセス許可を取り消すまで、第三者が今後通知なしに次の NFT にアクセスし、転送できるようになります。" }, + "approveTokenDescriptionWithoutSymbol": { + "message": "これにより、アクセス許可を取り消すまで、第三者が今後通知なしに $1 のすべての NFT にアクセスし、それらを転送できるようになります。", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "$1 へのアクセスと転送を許可しますか?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "1 つのネットワークから別のネットワークに直接アセットを送ろうとすると、アセットが永久に失われる可能性があります。必ずブリッジを使用してください。" }, + "attemptToCancelSwap": { + "message": "~$1 でスワップのキャンセルを試行", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "ブロックチェーンへの接続を試みています。" }, @@ -411,6 +473,9 @@ "average": { "message": "平均" }, + "awaitingApproval": { + "message": "承認待ちです..." + }, "back": { "message": "戻る" }, @@ -454,6 +519,9 @@ "message": "これはベータ版です。バグは報告してください $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "MetaMask Institutional ベータバージョン" + }, "betaMetamaskVersion": { "message": "MetaMaskベータバージョン" }, @@ -488,9 +556,45 @@ "message": "$1のアカウントを表示", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "この要求を承認すると、不正行為が判明しているサードパーティに資産をすべて奪われる可能性があります。" + }, + "blockaidDescriptionBlurFarming": { + "message": "この要求を承認すると、Blur に登録されている資産を誰かに盗まれる可能性があります。" + }, + "blockaidDescriptionFailed": { + "message": "エラーが発生したため、このリクエストはセキュリティプロバイダーにより確認されませんでした。慎重に進めてください。" + }, + "blockaidDescriptionMaliciousDomain": { + "message": "悪質なドメインとやり取りしています。この要求を承認すると、資産を失う可能性があります。" + }, + "blockaidDescriptionMightLoseAssets": { + "message": "この要求を承認すると、資産を失う可能性があります。" + }, + "blockaidDescriptionSeaportFarming": { + "message": "この要求を承認すると、OpenSea に登録されている資産を誰かに盗まれる可能性があります。" + }, + "blockaidDescriptionTransferFarming": { + "message": "この要求を承認すると、不正行為が判明しているサードパーティに資産をすべて奪われます。" + }, + "blockaidTitleDeceptive": { + "message": "これは虚偽の要求です" + }, + "blockaidTitleMayNotBeSafe": { + "message": "リクエストは安全でない可能性があります" + }, + "blockaidTitleSuspicious": { + "message": "これは不審な要求です" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "ブリッジ" + }, "browserNotSupported": { "message": "ご使用のブラウザはサポートされていません..." }, @@ -510,6 +614,10 @@ "message": "$1 を購入", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "$1 を追加購入", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "今すぐ購入" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "これによりアカウントのノンスがリセットされ、ウォレットのアクティビティタブからデータが消去されます。現在のアカウントとネットワークだけが影響を受けます。残高と受信トランザクションへの変更はありません。" }, + "click": { + "message": "クリックして" + }, "clickToConnectLedgerViaWebHID": { "message": "ここをクリックして、WebHIDでLedgerを接続します", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "MetaMask から移動してこのスナップを構成します。" + }, + "configureSnapPopupInstallDescription": { + "message": "MetaMask から移動してこのスナップをインストールします。" + }, + "configureSnapPopupInstallTitle": { + "message": "スナップをインストール" + }, + "configureSnapPopupLink": { + "message": "続けるにはこのリンクをクリックしてください:" + }, + "configureSnapPopupTitle": { + "message": "スナップを構成" + }, "confirm": { "message": "確認" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "アカウントを接続するか、または新規に作成します" }, + "connectCustodialAccountMenu": { + "message": "カストディアルアカウントを接続" + }, + "connectCustodialAccountMsg": { + "message": "トークンを追加または更新するには、接続するカストディアンを選択してください。" + }, + "connectCustodialAccountTitle": { + "message": "カストディアルアカウント" + }, "connectManually": { "message": "現在のサイトに手動で接続" }, + "connectSnap": { + "message": "$1 を接続", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "$1に接続", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Linea Goerli テストネットワークに接続中" }, + "connectingToLineaMainnet": { + "message": "Linea メインネットに接続中" + }, "connectingToMainnet": { "message": "イーサリアムメインネットに接続中" }, "connectingToSepolia": { "message": "Sepolia テストネットワークに接続中" }, + "connectionFailed": { + "message": "接続できませんでした" + }, + "connectionFailedDescription": { + "message": "$1 を取得できませんでした。ネットワークを確認してもう一度お試しください。", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "接続要求" + }, "contactUs": { "message": "お問い合わせ" }, @@ -708,7 +860,7 @@ "message": "コントラクトの展開" }, "contractDescription": { - "message": "不正行為から身を守るため、コントラクトの詳細を確認してください。" + "message": "不正行為から身を守るため、サードパーティの詳細を確認してください。" }, "contractInteraction": { "message": "コントラクトインタラクション" @@ -717,16 +869,16 @@ "message": "NFT コントラクト" }, "contractRequestingAccess": { - "message": "コントラクトがアクセスを要求しています" + "message": "サードパーティががアクセスを要求しています" }, "contractRequestingSignature": { - "message": "コントラクトに署名が必要です" + "message": "サードパーティが署名を要求しています" }, "contractRequestingSpendingCap": { - "message": "使用上限を求めるコントラクト" + "message": "サードパーティが使用上限を要求しています" }, "contractTitle": { - "message": "コントラクトの詳細" + "message": "サードパーティの詳細" }, "contractToken": { "message": "トークンコントラクト" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "市場のガス代見積もりグラフ" }, + "custodian": { + "message": "カストディアン" + }, + "custodianAccount": { + "message": "カストディアンアカウント" + }, + "custodianAccountAddedDesc": { + "message": "カストディアンアカウントを MetaMask Institutional で使えるようになりました。" + }, + "custodianAccountAddedTitle": { + "message": "選択されたカストディアンアカウントが追加されました。" + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "$1 に移動して、ユーザーインターフェースの [MMI に接続] ボタンをクリックし、アカウントを MMI に再接続します。" + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "これでカストディアンアカウントを MetaMask Institutional で使えるようになります。" + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "カストディアントークンが更新されました" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "これにより、次のアドレスのカストディアントークンが置き換えられます:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "カストディアントークンを置き換える" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "$1 アプリでトランザクションを承認してください。必要なカストディ承認がすべて行われると、トランザクションが完了します。ステータスは $1 アプリで確認できます。" + }, + "custodyRefreshTokenModalDescription": { + "message": "$1 に移動して、ユーザーインターフェースの [MMI に接続] ボタンをクリックし、アカウントを MMI に再接続します。" + }, + "custodyRefreshTokenModalDescription1": { + "message": "カストディアンがMetaMask Institutional拡張機能を認証するトークンを発行し、アカウントを接続できるようになります。" + }, + "custodyRefreshTokenModalDescription2": { + "message": "このトークンはセキュリティ上の理由により、一定期間後に失効します。その場合、MMIへの再接続が必要になります。" + }, + "custodyRefreshTokenModalSubtitle": { + "message": "このメッセージが表示される理由" + }, + "custodyRefreshTokenModalTitle": { + "message": "カストディアンセッションが期限切れになりました" + }, + "custodySessionExpired": { + "message": "カストディアンセッションが期限切れになりました。" + }, + "custodyWrongChain": { + "message": "このアカウントは $1 で使用できるように設定されていません" + }, "custom": { "message": "高度な設定" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "カスタマーサポート" }, + "dappRequestedSpendingCap": { + "message": "サイトが使用上限を要求しました" + }, "dappSuggested": { "message": "サイト提案" }, @@ -851,6 +1060,12 @@ "message": "$1はこの価格を提案しています。", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "提案されたサイト" + }, + "dappSuggestedHighShortLabel": { + "message": "サイト (高)" + }, "dappSuggestedShortLabel": { "message": "サイト" }, @@ -902,9 +1117,19 @@ "delete": { "message": "削除" }, + "deleteContact": { + "message": "連絡先を削除" + }, "deleteNetwork": { "message": "ネットワークを削除しますか?" }, + "deleteNetworkIntro": { + "message": "このネットワークを削除した場合、このネットワーク内の資産を見るには、再度ネットワークの追加が必要になります。" + }, + "deleteNetworkTitle": { + "message": "$1 ネットワークを削除しますか?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "入金" }, @@ -917,6 +1142,10 @@ "description": { "message": "説明" }, + "descriptionFromSnap": { + "message": "$1 からの説明", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "このエラーは一時的なものかもしれないので、拡張機能を再起動するか、MetaMask Desktop を無効にしてみてください。" }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "リカバリーフレーズバックアップのリマインダーを解除" }, + "displayNftMedia": { + "message": "NFT メディアの表示" + }, + "displayNftMediaDescription": { + "message": "NFT のメディアとデータを表示した場合、IP アドレスが OpenSea をはじめとするサードパーティに公開されます。その結果、攻撃者がユーザーの IP アドレスとイーサリアムアドレスを関連付けられるようになる可能性があります。NFT の自動検出はこの設定に依存しており、この設定を無効にすると利用できなくなります。" + }, "domain": { "message": "ドメイン" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " 自動検出を有効にする" }, + "enableForAllNetworks": { + "message": "すべてのネットワークで有効にする" + }, "enableFromSettings": { "message": " 設定で有効にします。" }, + "enableSmartSwaps": { + "message": "スマートスワップを有効にする" + }, + "enableSnap": { + "message": "有効にする" + }, "enableToken": { "message": "$1を有効にする", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "有効" + }, "encryptionPublicKeyNotice": { "message": "$1は公開暗号鍵を必要とします。同意することによって、このサイトは暗号化されたメッセージを作成できます。", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "改善されたトークン検出は現在 $1 で利用可能です。$2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask は、「https://metamask.eth」などの ENS ドメインをブラウザのアドレスバーに直接表示します。使い方は次の通りです:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "一般的なブラウザは大抵 ENS や IPFS アドレスを扱えませんが、MetaMask はそれをサポートします。この機能を使うと、IPFS サードパーティサービスに IP アドレスが共有される可能性があります。" + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask はイーサリアムの ENS コントラクトを確認し、ENS 名に接続されたコードを取得します。" + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "コードが IPFS にリンクされている場合、IPFS ネットワークからコンテンツを取得します。" + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "その後コンテンツが表示され、通常 Web サイトやその他同様のコンテンツとなります。" + }, + "ensDomainsSettingTitle": { + "message": "アドレスバーに ENS ドメインを表示する" + }, "ensIllegalCharacter": { "message": "ENSにサポートされていない文字が使用されています。" }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "数字を入力してください" }, + "enterCustodianToken": { + "message": "$1 トークンを入力するか、新しいトークンを追加してください" + }, "enterMaxSpendLimit": { "message": "使用限度額の最大値を入力してください" }, + "enterOptionalPassword": { + "message": "オプションのパスワードを入力してください" + }, "enterPassword": { "message": "パスワードを入力してください" }, "enterPasswordContinue": { "message": "続行するには、パスワードを入力してください" }, + "enterTokenNameOrAddress": { + "message": "トークン名を入力するか、アドレスを貼り付けてください" + }, + "enterYourPassword": { + "message": "パスワードを入力してください" + }, "errorCode": { "message": "コード: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "スタック:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "カスタムネットワークへの接続中にエラーが発生しました。" + }, + "errorWithSnap": { + "message": "$1 でエラーが発生しました", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "現在メインのガスの見積もりサービスが利用できないため、バックアップのガス代が提供されています。" }, + "ethereumProviderAccess": { + "message": "Ethereumプロバイダーに $1 へのアクセス権を付与する", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "パブリックイーサリアムアドレス" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "実験的" }, + "exploreMetaMaskSnaps": { + "message": "MetaMask スナップを閲覧" + }, "exportPrivateKey": { "message": "秘密鍵のエクスポート" }, + "extendWalletWithSnaps": { + "message": "ウォレットのエクスペリエンスを拡張します。" + }, "externalExtension": { "message": "外部拡張機能" }, @@ -1302,6 +1596,9 @@ "message": "ファイルのインポートが機能していない場合ここをクリックしてください!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "ドロップされたファイルが大きすぎます。" + }, "flaskWelcomeUninstall": { "message": "この拡張機能はアンインストールしてください", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "一般" }, + "getStarted": { + "message": "開始" + }, + "globalTitle": { + "message": "グローバルメニュー" + }, + "globalTourDescription": { + "message": "ポートフォリオ、接続されたサイト、設定などを確認できます" + }, "goBack": { "message": "戻る" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "ハードウェアウォレットを接続" }, + "hardwareWalletsInfo": { + "message": "ハードウェアウォレットの統合には、外部サーバーへの API 呼び出しを使用します。外部サーバーはこれにより、あなたがやり取りした IP アドレスとスマートコントラクトアドレスを把握できます。" + }, "hardwareWalletsMsg": { "message": "MetaMaskに接続するハードウェアウォレットを選択してください。" }, @@ -1534,18 +1843,41 @@ "description": "$1 is a message from 'holdToRevealContent4' and $2 is a text link with the message from 'holdToRevealContent5'" }, "holdToRevealContent4": { - "message": "MetaMask サポートがこの情報を尋ねることはなく", + "message": "MetaMask サポートがこの情報を尋ねることはなく、", "description": "Part of 'holdToRevealContent3'" }, "holdToRevealContent5": { "message": "もし尋ねられた場合はフィッシング詐欺の可能性があります。", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "秘密鍵は$1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "ウォレットと資金への完全アクセスを提供します。", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "長押ししてロックされた円を表示します" + }, + "holdToRevealPrivateKey": { + "message": "長押しして秘密鍵を表示します" + }, + "holdToRevealPrivateKeyTitle": { + "message": "秘密鍵は安全に保管してください" + }, "holdToRevealSRP": { - "message": "長押ししてSRPを表示" + "message": "長押しして SRP を表示します" }, "holdToRevealSRPTitle": { - "message": "SRPは安全に保管してください" + "message": "SRP は安全に保管してください" + }, + "holdToRevealUnlockedLabel": { + "message": "長押ししてロックが解除された円を表示します" + }, + "id": { + "message": "ID" }, "ignoreAll": { "message": "すべて無視" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "アカウントのインポート中にエラーが発生しました。" }, + "importAccountErrorIsSRP": { + "message": "シークレットリカバリーフレーズ (またはヒント) が入力されました。ここにアカウントをインポートするには、秘密鍵を入力する必要があります。秘密鍵は64文字の16進数の文字列です。" + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "これは有効な秘密鍵ではありません。16進数の文字列を入力しましたが、64文字でなければなりません。" + }, + "importAccountErrorNotHexadecimal": { + "message": "これは有効な秘密鍵ではありません。16進数の64文字の文字列を入力する必要があります。" + }, + "importAccountJsonLoading1": { + "message": "このJSONのインポートには数分かかり、MetaMaskがフリーズします。" + }, + "importAccountJsonLoading2": { + "message": "申し訳ございません。今後高速化できるよう取り組みます。" + }, "importAccountMsg": { - "message": " インポートされたアカウントは、最初に作成したMetaMaskアカウントのシークレットリカバリーフレーズと関連付けられません。インポートされたアカウントの詳細を表示" + "message": "インポートされたアカウントは、MetaMaskアカウントのシークレットリカバリーフレーズと関連付けられません。インポートされたアカウントの詳細を表示" }, "importMyWallet": { "message": "ウォレットをインポート" @@ -1615,18 +1962,29 @@ "message": "最初のトランザクションはネットワークによって確認されました。戻るには [OK] をクリックします。" }, "inputLogicEmptyState": { - "message": "現在または今後コントラクトが使用しても構わない額のみを入力してください。使用上限は後でいつでも増額できます。" + "message": "現在または今後サードパーティが使用しても構わない額のみを入力してください。使用上限は後でいつでも増額できます。" }, "inputLogicEqualOrSmallerNumber": { - "message": "これにより、コントラクトが現在の残高から $1 使用できるようになります。", + "message": "これにより、サードパーティが現在の残高から $1 使用できるようになります。", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "これにより、上限に達するか使用上限が取り消されるまで、コントラクトがトークン残高全額を使用できるようになります。これを制限したい場合は、使用上限を低めに設定することをお勧めします。" + "message": "これにより、上限に達するか使用上限が取り消されるまで、サードパーティがトークン残高全額を使用できるようになります。これを意図していない場合は、使用上限を低めに設定することをお勧めします。" + }, + "insightsFromSnap": { + "message": "$1 からのインサイト", + "description": "$1 represents the name of the snap" }, "install": { "message": "インストール" }, + "installOrigin": { + "message": "インストール元" + }, + "installedOn": { + "message": "$1 にインストール", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "残高が不十分です。" }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "入力値が無効です!秘密のリカバリーフレーズは大文字・小文字が区別されます。" }, + "ipfsGateway": { + "message": "IPFS ゲートウェイ" + }, + "ipfsGatewayDescription": { + "message": "MetaMask はサードパーティサービスを使用して IPFS に保管されている NFT の画像の表示、ブラウザのアドレスバーに入力された ENS アドレスに関する情報の表示、様々なトークンのアイコンの取得を行います。これらのサービスの使用時には、IP アドレスが当該サービスに公開される可能性があります。" + }, + "ipfsToggleModalDescriptionOne": { + "message": "MetaMask はサードパーティサービスを使用して IPFS に保管されている NFT の画像の表示、ブラウザのアドレスバーに入力された ENS アドレスに関する情報の表示、様々なトークンのアイコンの取得を行います。これらのサービスの使用時には、IP アドレスが当該サービスに公開される可能性があります。" + }, + "ipfsToggleModalDescriptionTwo": { + "message": "[確認] を選択すると、IPFS 解決がオンになります。これは $1 でいつでもオフにできます。", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "[設定] > [セキュリティとプライバシー]" + }, "jazzAndBlockies": { "message": "Jazzicon と Blockie は、アカウントを一目で見分けるためのユニークなアイコンであり、2 つの異なるスタイルが特徴です。" }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "前回の売却" }, + "layer1Fees": { + "message": "レイヤー 1 手数料" + }, "learnCancelSpeeedup": { "message": "$1の方法を学ぶ", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "ガスに関する$1をご希望ですか?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "詳細" + }, "learnMoreUpperCase": { "message": "詳細" }, @@ -1765,16 +2145,16 @@ "message": "確認をクリックする前:に:" }, "ledgerConnectionInstructionStepFour": { - "message": "Ledgerデバイスで「スマートコントラクトデータ」または「ブラインド署名」を有効にしてください" + "message": "Ledger デバイスで「スマートコントラクトデータ」または「ブラインド署名」を有効にしてください。" }, "ledgerConnectionInstructionStepOne": { - "message": "[設定] > [高度な設定] でLedger Liveを有効にしてください" + "message": "[設定] > [高度な設定] で Ledger Live を有効にしてください。" }, "ledgerConnectionInstructionStepThree": { - "message": "Ledgerデバイスを接続し、Ethereumアプリを選択してください" + "message": "Ledger が接続されていて、Ethereum アプリを選択していることを確認してください。" }, "ledgerConnectionInstructionStepTwo": { - "message": "Ledger Liveアプリを開いてロックを解除してください" + "message": "Ledger Live アプリを開いてロックを解除してください。" }, "ledgerConnectionPreferenceDescription": { "message": "LedgerをMetaMaskに接続する方法をカスタマイズします。$1が推奨されますが、他のオプションも利用できます。詳細はこちらをご覧ください: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea Goerli テストネットワーク" }, + "lineaMainnet": { + "message": "Linea メインネット" + }, "link": { "message": "リンク" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "ロック" }, + "lockMetaMask": { + "message": "MetaMaskをロック" + }, + "lockTimeInvalid": { + "message": "ロック時間は 0 ~ 10080 の間の数字で設定する必要があります" + }, "logo": { "message": "$1 ロゴ", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "訪問しているWebサイトが現在選択しているアカウントに接続されている場合、接続ステータスボタンが表示されます。" }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Institutional バージョン" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swapsはメンテナンス中です。後でもう一度確認してください。" }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "メトリクス" }, + "mismatchAccount": { + "message": "選択されたアカウント ($1) は署名しようとしているアカウント ($2) と異なります" + }, "mismatchedChainLinkText": { "message": "ネットワークの詳細の確認", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "送信された通貨記号がこのチェーン ID に関して予想されるものと一致していません。" }, + "mismatchedRpcChainId": { + "message": "カスタムネットワークにより返されたチェーンIDが、送信されたチェーンIDと一致しません。" + }, "mismatchedRpcUrl": { "message": "弊社の記録によると、送信された RPC URL の値がこのチェーン ID の既知のプロバイダーと一致しません。" }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "ここからリクエスト" }, + "mmiAddToken": { + "message": "$1 のページがMetaMask Institutionalで次のカストディアントークンの承認を求めています" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask は、世界中でデザイン・開発されています。" + }, + "more": { + "message": "他" + }, "moreComingSoon": { - "message": "さらに近日追加予定..." + "message": "他のプロバイダーも近日追加予定" + }, + "multipleSnapConnectionWarning": { + "message": "$1 が $2 個のスナップとの接続を要求しています。この Web サイトが信頼できる場合にのみ続行してください。", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "トークンを1つ以上選択する必要があります。" @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "ネットワークが混み合っています。ガス代が高く、見積もりはあまり正確ではありません。" }, + "networkMenu": { + "message": "ネットワークメニュー" + }, + "networkMenuHeading": { + "message": "ネットワークを選択" + }, "networkName": { "message": "ネットワーク名" }, @@ -2033,6 +2450,10 @@ "message": "ガス代は過去72時間と比較して$1です。", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "$1 に接続できません", + "description": "$1 represents the network name" + }, "networkURL": { "message": "ネットワークURL" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "以前保有" + }, "nickname": { "message": "ニックネーム" }, @@ -2138,6 +2562,12 @@ "noConversionRateAvailable": { "message": "利用可能な換算レートがありません" }, + "noNFTs": { + "message": "NFTはまだありません" + }, + "noNetworksFound": { + "message": "入力された検索クエリでネットワークが見つかりません" + }, "noSnaps": { "message": "スナップがインストールされていません" }, @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "これは正しいアカウントですか? ウォレットで現在選択されているアカウントと異なっています" }, + "notEnoughBalance": { + "message": "残高が不十分です" + }, "notEnoughGas": { "message": "ガスが不足しています" }, + "note": { + "message": "備考" + }, + "notePlaceholder": { + "message": "承認者がカストディアンでトランザクションを承認する際に、この備考が表示されます。" + }, + "notificationTransactionFailedMessage": { + "message": "トランザクション $1 に失敗しました!$2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "トランザクションに失敗しました!$1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "トランザクション失敗", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "トランザクション $1 が確認されました!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "トランザクションの確認完了", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "$1 で表示", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "通知" + }, "notifications10ActionText": { "message": "設定に移動", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Ethereum のマージ (Merge) が完了しました!" }, + "notifications18ActionText": { + "message": "セキュリティアラートを有効にする" + }, + "notifications18DescriptionOne": { + "message": "悪質なリクエストを受けた可能性がある場合、サードパーティからアラートを受信します。", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "リクエストを承認する前に、必ず独自のデューデリジェンスを行ってください。", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea は、この機能の最初のプロバイダーです。他のプロバイダーも近日追加予定です!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "セキュリティアラートで安全を確保" + }, + "notifications19ActionText": { + "message": "NFTの自動検出を有効にする" + }, + "notifications19DescriptionOne": { + "message": "始めるには次の2通りの方法があります:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "現在当社はERC-721のみをサポートしています。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "手動でNFTを追加するか、[設定] > [試験運用]でNFTの自動検出をオンにします。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "NFTが今までにない形で表示されます" + }, "notifications1Description": { "message": "MetaMask Mobileのユーザーが、モバイルウォレット内でトークンを交換できるようになりました。QRコードをスキャンしてモバイルアプリを取得し、スワップを開始します。", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "モバイルでのスワッピングが可能になりました!\n", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "詳細", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Firefox の最新バージョンを使用している場合、Firefox の U2F サポート廃止に関連した問題が発生する可能性があります。", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Ledger と Firefox のユーザーに発生している接続の問題について", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "試す" + }, + "notifications21Description": { + "message": "MetaMask 拡張機能の Swaps をアップデートして、より素早く簡単に使えるようにしました。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "新しくなった Swaps をご紹介します!" + }, + "notifications22ActionText": { + "message": "了解" + }, + "notifications22Description": { + "message": "💡 グローバルメニューまたはアカウントメニューをクリックするだけで見つかります!" + }, + "notifications22Title": { + "message": "アカウント情報またはブロックエクスプローラー URL をお探しですか?" + }, + "notifications23ActionText": { + "message": "セキュリティアラートを有効にする" + }, + "notifications23DescriptionOne": { + "message": "Blockaid によるセキュリティアラートを使用して、プライバシーを守りつつ既知の詐欺を回避しましょう。" + }, + "notifications23DescriptionThree": { + "message": "OpenSea のセキュリティアラートを有効にしていた場合、この機能に移行されました。" + }, + "notifications23DescriptionTwo": { + "message": "要求を承認する前に、必ず独自のデューデリジェンスを行ってください。" + }, + "notifications23Title": { + "message": "セキュリティアラートで安全を確保" + }, "notifications3ActionText": { "message": "続きを表示", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "信頼するサイトにのみ接続してください。" }, "openFullScreenForLedgerWebHid": { - "message": "WebHIDでLedgerを接続するには、MetaMaskを全画面モードで開いてください。", + "message": "全画面モードにして Ledger を接続します。", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "ブロックエクスプローラーで開く" }, "openSea": { - "message": "OpenSea (ベータ)" + "message": "OpenSea + Blockaid (ベータ)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "操作に失敗しました" + }, "optional": { "message": "任意" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "パスワードが一致しません" }, + "pasteJWTToken": { + "message": "ここにトークンを張り付けるかドロップしてください:" + }, "pastePrivateKey": { "message": "秘密鍵の文字列をここに貼り付けます:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "インターネットにアクセスします。", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "スナップによるインターネットへのアクセスを許可します。これはサードパーティサーバーとのデータの送受信の両方に使用できます。", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "$1 スナップに接続します。", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Webサイトまたはスナップによる $1 とのやり取りを許可します。", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "定期的なアクションのスケジュール設定と実行。", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "スナップが、一定の時刻、日付、または間隔で定期的に実行されるアクションを実行することを許可します。これは、時間依存のやり取りや通知のトリガーに使用できます。", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "MetaMask にダイアログウィンドウを表示します。", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "スナップによる、カスタムテキスト、入力フィールド、アクションを承認または拒否するボタンを含むMetaMaskポップアップの表示を許可します。これは、スナップのアラート、確認、オプトインフローなどを作成するのに使用できます。", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "アドレス、アカウント残高、アクティビティを表示してトランザクションを開始", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Ethereum プロバイダーにアクセスします。", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "ブロックチェーンのデータを読み込みメッセージやトランザクションを提案するために、スナップによるMetaMaskとの直接の通信を許可します。", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "このスナップに一意の無作為なキーを取得します。", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "スナップが、このスナップに固有の任意のキーを公開せずに取得することを許可します。これらのキーはMetaMaskアカウントとは別で、秘密鍵やシークレットリカバリーフレーズとは関連性がありません。他のスナップはこの情報にアクセスできません。", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "ライフサイクルフックを使用します。", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "snap がライフサイクルフックを使用して、ライフサイクルの特定のタイミングでコードを実行することを許可します。", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "無期限で実行。", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "大きなデータの処理中など、スナップが無期限で実行されることを許可します。", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Ethereum アカウントを追加して管理します", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "$1 ($2) のアカウントとアセットを管理します。", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "スナップがシークレットリカバリーフレーズに基づき、公開することなくBIP-32キーペアを取得することを許可します。これにより、$1 のすべてのアカウントとアセットへの完全アクセスが許可されます。\\nキーの管理権限を与えることで、スナップはイーサリアム(EVM)以外にもさまざまなブロックチェーンプロトコルをサポートできるようになります。", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "「$1」アカウントとアセットをコントロールします。", + "message": "$1 アカウントとアセットをコントロールします。", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "スナップがシークレットリカバリーフレーズに基づき、公開することなくBIP-44キーペアを取得することを許可します。これにより、$1 のすべてのアカウントとアセットへの完全アクセスが許可されます。\\nキーの管理権限を与えることで、スナップはイーサリアム(EVM)以外にもさまざまなブロックチェーンプロトコルをサポートできるようになります。", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "$1 アカウントとアセットをコントロールします。", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "デバイスにデータを保管し管理します。", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "スナップが暗号化を使用して安全にデータを保管、更新、取得することを許可します。他のスナップはこの情報にアクセスできません。", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "通知を表示します。", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "スナップがMetaMask内に通知を表示することを許可します。スナップは、行動を促す情報や緊急性の高い情報に関する短い通知テキストをトリガーできます。", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "$1によるこのスナップとの直接のやり取りを許可してください。", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "$1 によるスナップへのメッセージの送信とスナップからの応答の受信を許可します。", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "トランザクションインサイトを取得して表示します。", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "スナップによるトランザクションのデコードと、MetaMask UI内でのインサイトの表示を許可します。これは、フィッシング対策やセキュリティソリューションに使用できます。", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "トランザクションを提案している Web サイトの提供元を確認します", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "スナップが、トランザクションを提案するWebサイトの転送元(URI)を確認することを許可します。これは、フィッシング対策やセキュリティソリューションに使用できます。", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "不明なパーミッション: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "$1 ($2) の公開鍵を表示します。", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "スナップが、$1 の公開鍵(およびアドレス)を表示することを許可します。これは、アカウントやアセットのコントロールを許可するものでは一切ありません。", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "$1 の公開鍵を表示します。", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "WebAssemblyのサポート", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "WebAssemblyを介して低レベルの実行環境にアクセスすることを許可します。", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "許可" }, + "permissionsTitle": { + "message": "許可" + }, + "permissionsTourDescription": { + "message": "ここで接続されたアカウントを見つけて許可の管理を行います" + }, "personalAddressDetected": { "message": "個人アドレスが検出されました。トークンコントラクトアドレスを入力してください。" }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "ポートフォリオ" }, + "portfolioDashboard": { + "message": "ポートフォリオダッシュボード" + }, "preferredLedgerConnectionType": { "message": "優先Ledger接続タイプ", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "秘密鍵", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "$1 の秘密鍵", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "警告:この鍵は絶対に公開しないでください。秘密鍵を持つ人は誰でも、アカウントに保持されているアセットを盗むことができます。" }, @@ -2738,6 +3385,9 @@ "queued": { "message": "キュー待ち" }, + "quoteRate": { + "message": "クォートレート" + }, "reAddAccounts": { "message": "他のアカウントを再度追加" }, @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "このアカウントはウォレットから削除されます。続行する前に、インポートしたアカウントの元のシークレットリカバリーフレーズまたは秘密鍵を持っていることを確認してください。アカウントはアカウントドロップダウンから再度インポートまたは作成できます。" }, + "removeJWT": { + "message": "カストディアントークンを削除" + }, + "removeJWTDescription": { + "message": "このトークンを削除してよろしいですか?このトークンに割り当てられているすべてのアカウントが、同時に拡張機能から削除されます:" + }, "removeNFT": { "message": "NFTを削除" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "以前バックアップされた JSON ファイルから、設定とアカウントアドレスを含むユーザー設定を復元できます。" }, + "resultPageError": { + "message": "エラー" + }, + "resultPageErrorDefaultMessage": { + "message": "操作に失敗しました。" + }, + "resultPageSuccess": { + "message": "成功" + }, + "resultPageSuccessDefaultMessage": { + "message": "操作が正常に完了しました。" + }, "retryTransaction": { "message": "トランザクションを再試行" }, @@ -2939,16 +3607,27 @@ "message": "すべての $1 へのアクセスおよび転送許可を取り消しますか?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "$1 のすべてNFTへのアクセスおよびそれらの転送の許可を取り消しますか?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "これにより、別途通知なしで第三者によるユーザーの $1 へのアクセスおよび転送の許可が取り消されます。", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "これにより、第三者が別途通知なしで $1 のすべてのNFTにアクセスし、それらを転送する許可が取り消されます。", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "許可を取り消す" + }, "revokeSpendingCap": { "message": "$1 の使用上限を取り消す", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "このコントラクトは、現在または今後のトークンをこれ以上使用できなくなります。" + "message": "このサードパーティは、現在または今後のトークンをこれ以上使用できなくなります。" }, "rpcUrl": { "message": "新しいRPC URL" @@ -2986,9 +3665,28 @@ "security": { "message": "セキュリティ" }, + "securityAlert": { + "message": "$1 と $2 からのセキュリティアラート" + }, + "securityAlerts": { + "message": "、セキュリティアラート" + }, + "securityAlertsDescription1": { + "message": "この機能は、トランザクションと署名要求をローカルで確認することで、悪質な行為に対する警告を発します。ユーザーのデータはこのサービスを提供するサードパーティと共有されません。要求を承認する前には、必ず独自のデューデリジェンスを行ってください。この機能がすべての悪質な行為を検出するという保証はありません。" + }, + "securityAlertsDescription2": { + "message": "要求を承認する前に、常に独自のデューデリジェンスを行うようにしてください。この機能によりすべての悪質な行為の検出が保証されているわけではありません。" + }, "securityAndPrivacy": { "message": "セキュリティとプライバシー" }, + "securityProviderAdviceBy": { + "message": "$1 によるセキュリティアドバイス", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "詳細を表示" + }, "seedPhraseConfirm": { "message": "シークレットリカバリーフレーズの確認" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "シークレットリカバリーフレーズを書き留めてください" }, + "select": { + "message": "選択" + }, "selectAccounts": { "message": "このサイトに使用するアカウントを選択してください" }, + "selectAccountsForSnap": { + "message": "このスナップで使用するアカウントを選択してください" + }, "selectAll": { "message": "すべて選択" }, + "selectAllAccounts": { + "message": "すべてのアカウントを選択" + }, "selectAnAccount": { "message": "アカウントを選択してください" }, "selectAnAccountAlreadyConnected": { "message": "このアカウントはすでにMetaMaskに接続されています" }, + "selectAnAccountHelp": { + "message": "MetaMask Institutional で使用するカストディアンアカウントを選択します。" + }, "selectHdPath": { "message": "HDパスを選択" }, + "selectJWT": { + "message": "トークンを選択" + }, "selectNFTPrivacyPreference": { "message": "設定でNFTの検出をオンにします" }, @@ -3113,6 +3826,9 @@ "message": "使用限度額なしで $1 を承認", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "スナップアカウントを追加" + }, "settings": { "message": "設定" }, @@ -3141,9 +3857,21 @@ "message": "これを選択すると、Etherscanを使用して受信トランザクションがトランザクションリストに表示されます", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "これは、ユーザーのイーサリアムアドレスおよび IP アドレスにアクセスする各ネットワークに依存します。" + }, + "showMore": { + "message": "他を表示" + }, + "showNft": { + "message": "NFT を表示" + }, "showPermissions": { "message": "表示許可" }, + "showPrivateKey": { + "message": "秘密鍵を表示" + }, "showPrivateKeys": { "message": "秘密鍵を表示" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "私は、シークレットリカバリーフレーズをバックアップするまで、アカウントとそのアセットのすべてを失う可能性があることを理解しています。" }, + "smartContracts": { + "message": "スマートコントラクト" + }, + "smartSwap": { + "message": "スマートスワップ" + }, + "smartSwapsAreHere": { + "message": "スマートスワップの登場です!" + }, + "smartSwapsDescription": { + "message": "MetaMask Swaps がはるかに賢くなりました!スマートスワップを有効にすると、MetaMask がプログラムに従ってスワップを最適化できるようになるため、以下のようなメリットがあります。" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "スマートスワップに必要な資金が不足しています。" + }, + "smartSwapsErrorUnavailable": { + "message": "スマートスワップは一時的にご利用いただけません。" + }, + "smartSwapsSubDescription": { + "message": "* スマートスワップは、トランザクションの送信を非公開で複数回試行します。すべての試行に失敗した場合、スワップが正常に行われるように、トランザクションが一般公開されます。" + }, + "snapConfigure": { + "message": "構成" + }, + "snapConnectionWarning": { + "message": "$1 が $2 への接続を要求しています。この Web サイトが信頼できる場合にのみ続行してください。", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "このコンテンツは $1 からのものです", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "MetaMask Snaps で新規アカウントのセキュリティを確保する方法を選択してください。" + }, + "snapCreateAccountTitle": { + "message": "$1 アカウントの作成", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "スナップ", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "By MetaMask" + }, + "snapDetailAudits": { + "message": "監査" + }, + "snapDetailDeveloper": { + "message": "開発者" + }, + "snapDetailLastUpdated": { + "message": "更新済み" + }, + "snapDetailManageSnap": { + "message": "スナップの管理" + }, + "snapDetailTags": { + "message": "タグ" + }, + "snapDetailVersion": { + "message": "バージョン" + }, + "snapDetailWebsite": { + "message": "Web サイト" + }, + "snapDetailsCreateASnapAccount": { + "message": "スナップアカウントの作成" + }, + "snapDetailsInstalled": { + "message": "インストール済み" + }, "snapError": { "message": "スナップエラー:「$1」。エラーコード:「$2」", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "スナップをインストール" }, + "snapInstallRequest": { + "message": "$1 をインストールすることで、次のアクセス許可が付与されます。$1 を信頼できる場合にのみ続行してください。", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "インストール完了" + }, "snapInstallWarningCheck": { "message": "理解したことを確認するために、次の項目にチェックを入れてください.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "理解したことを確認するために、すべての項目にチェックを入れてください。", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "慎重に進めてください" + }, "snapInstallWarningKeyAccess": { "message": "スナップ「$1」に $2 へのキーアクセスを許可しようとしています。この操作は取り消し不能であり、$2 アカウントとアセットのコントロールを「$1」に許可することになります。続行する前に、必ず「$1」が信頼できることを確認してください。", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "$2 に $1 への公開鍵アクセス権を付与する", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 をインストールできませんでした。", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "インストールに失敗しました", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "監査済み" + }, + "snapResultError": { + "message": "エラー" + }, + "snapResultSuccess": { + "message": "成功" + }, + "snapResultSuccessDescription": { + "message": "$1 を使用する準備が整いました" + }, "snapUpdate": { "message": "スナップを更新" }, + "snapUpdateAvailable": { + "message": "アップデートが利用できます" + }, + "snapUpdateErrorDescription": { + "message": "$1 を更新できませんでした。", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "更新失敗", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 が $2 の $3 への更新を要求しています。これにより、次のアクセス許可が付与されます。$2 を信頼できる場合にのみ続行してください。", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "更新完了" + }, "snaps": { "message": "スナップ" }, "snapsInsightLoading": { "message": "トランザクションインサイトを読み込み中..." }, + "snapsInvalidUIError": { + "message": "スナップに指定された UI が無効です。" + }, "snapsNoInsight": { "message": "スナップがインサイトを返しませんでした" }, + "snapsPrivacyWarningFirstMessage": { + "message": "ユーザーは、インストールしようとしているスナップが Consensys $1 で定義されているサードパーティサービスであることを認めたものとみなされます。サードパーティサービスの使用には、当該サードパーティサービスのプロバイダーにより定められた、別の諸条件が適用されます。サードパーティサービスへのアクセス、依存、使用は、ユーザーの自己責任で行うものとします。Consensys は、サードパーティサービスの使用によりアカウントで発生する損失について、一切責任および賠償責任を負いません。", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "サードパーティサービスと共有する情報は、当該サードパーティサービスにより、それぞれのプライバシーポリシーに従い直接収集されます。詳細は、各サードパーティサービスのプライバシーポリシーをご覧ください。", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys は、ユーザーがこれらのサードパーティと共有した情報に一切アクセスできません。", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "スナップの管理" }, + "snapsTermsOfUse": { + "message": "利用規約" + }, "snapsToggle": { "message": "スナップは有効になっている場合にのみ実行されます" }, "snapsUIError": { - "message": "スナップにより指定された UI が無効です。", + "message": "今後のサポートは、$1 の作成者にお問い合わせください。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "現在または今後 $1 がアクセスしても構わない額のみを入力してください。トークン上限は後でいつでも増額できます。", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "$1 の使用上限の要求" + }, "srpInputNumberOfWords": { "message": "$1 語のフレーズがあります", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "開始" }, + "srpSecurityQuizImgAlt": { + "message": "目の中央に鍵穴があり、3 つのパスワード入力欄がフローティング表示されている画像" + }, "srpSecurityQuizIntroduction": { "message": "秘密のリカバリーフレーズを表示するには、2 つの質問に正しく答える必要があります。" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "安定" }, + "stake": { + "message": "ステーク" + }, "stateLogError": { "message": "ステータスログの取得中にエラーが発生しました。" }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "未接続" }, + "statusNotConnectedAccount": { + "message": "アカウントが接続されていません" + }, "step1LatticeWallet": { "message": "Lattice1を接続する" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "これは受け取る最低額です。スリッページによりそれ以上の額を受け取ることもあります。" }, + "swapAnyway": { + "message": "スワップを続ける" + }, "swapApproval": { "message": "$1のスワップを承認", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "このスワップを完了させるには、さらに$1の$2が必要です。", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "まだご利用中ですか?" + }, + "swapAreYouStillThereDescription": { + "message": "続ける際には、最新のクォートを表示する準備ができています" + }, "swapBuildQuotePlaceHolderText": { "message": "$1と一致するトークンがありません", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "ハードウェアウォレットで確定" }, + "swapContinueSwapping": { + "message": "スワップを続ける" + }, "swapContractDataDisabledErrorDescription": { "message": "Ledgerのイーサリアムアプリで、「設定」に移動し、コントラクトデータを許可します。次に、スワップを再度試します。" }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "限度額を編集" }, + "swapEditTransactionSettings": { + "message": "トランザクション設定を編集" + }, "swapEnableDescription": { "message": "これは必須であり、MetaMaskに$1をスワップする許可を付与します。", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "これはスワップ用に$1", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "金額を入力してください" + }, "swapEstimatedNetworkFees": { "message": "推定ネットワーク手数料" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "スワップに失敗しました" }, + "swapFetchingQuote": { + "message": "クォートを取得中" + }, "swapFetchingQuoteNofN": { "message": "$2 件中 $1 件の見積もりを取得中", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "$1%のMetaMask手数料が含まれています。", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "$1% の MetaMask 手数料が含まれています – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Swaps の詳細" + }, "swapLowSlippageError": { "message": "トランザクションが失敗する可能性があります。最大スリッページが低すぎます。" }, @@ -3626,6 +4533,10 @@ "message": "$1の新規の見積もり", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "$1 と一致するトークンがありません", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "このトランザクションの処理が完了すると、$1がアカウントに追加されます。", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "見積もりの詳細" }, + "swapQuoteNofM": { + "message": "$1/$2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "見積もりのソース" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "見積もりのタイムアウト" }, + "swapQuotesNotAvailableDescription": { + "message": "トレードのサイズを減らすか、別のトークンを使用してください。" + }, "swapQuotesNotAvailableErrorDescription": { "message": "額の調整またはスリッページの設定を試してから、もう一度実行してください。" }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "以下は複数の流動性ソースから収集したすべての見積もりです。" }, + "swapSelectToken": { + "message": "トークンを選択" + }, + "swapShowLatestQuotes": { + "message": "最新のクォートを表示" + }, "swapSlippageNegative": { "message": "スリッページは0以上でなければなりません。" }, + "swapSlippageNegativeDescription": { + "message": "スリッページは 0 以上でなければなりません" + }, + "swapSlippageNegativeTitle": { + "message": "続けるにはスリッページを増やしてください" + }, + "swapSlippageOverLimitDescription": { + "message": "スリッページの許容範囲は 15% 以下でなければなりません。それを超えると不利なレートになります。" + }, + "swapSlippageOverLimitTitle": { + "message": "続けるにはスリッページを減らしてください" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "最大スリッページが低すぎ、トランザクションが失敗する可能性があります。" + }, + "swapSlippageTooLowTitle": { + "message": "トランザクションの失敗を防ぐために、スリッページを増やしてください。" + }, "swapSlippageTooltip": { "message": "注文から確定までの間に価格が変動することを「スリッページ」といいます。スリッページが「スリッページ許容範囲」の設定を超えた場合、スワップは自動的にキャンセルされます。" }, + "swapSlippageVeryHighDescription": { + "message": "入力されたスリッページが非常に高いため、不利なレートになる可能性があります" + }, + "swapSlippageVeryHighTitle": { + "message": "非常に高いスリッページ" + }, + "swapSlippageZeroDescription": { + "message": "スリッページがゼロのプロバイダーは少ないため、不利なクォートになる可能性があります。" + }, + "swapSlippageZeroTitle": { + "message": "スリッページがゼロのプロバイダーを使用中" + }, "swapSource": { "message": "流動性ソース" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "ハードウェアウォレットで確認" }, + "swapTokenAddedManuallyDescription": { + "message": "このトークンを $1 で検証して、取引したいトークンであることを確認してください。", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "トークンが手動で追加されました" + }, "swapTokenAvailable": { "message": "$1がアカウントに追加されました。", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "$1個のソースで検証済みです。", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 は 1 つのソースでしか検証されていません。進める前に $2 で検証することをご検討ください。", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "偽物のトークンの可能性" + }, "swapTooManyDecimalsError": { "message": "$1は小数点以下$2桁まで使用できます", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "トランザクションを完了させるには、$1が不足しています", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "$1 が不足しています", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "アクティビティに表示" }, + "switch": { + "message": "切り替える" + }, "switchEthereumChainConfirmationDescription": { "message": "これによりMetaMask内で選択されたネットワークが、以前に追加されたものに切り替わります。" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "次に変更しました:" }, + "switcherTitle": { + "message": "ネットワークスイッチャー" + }, + "switcherTourDescription": { + "message": "アイコンをクリックしてネットワークを切り替えるか、新しいネットワークを追加します" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "ネットワークを切り替えると、保留中の確認がすべてキャンセルされます" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "サービス規約" }, + "termsOfUse": { + "message": "利用規約" + }, + "termsOfUseAgreeText": { + "message": " MetaMaskおよびそのすべての機能の利用に適用される利用規約に同意します。" + }, + "termsOfUseFooterText": { + "message": "スクロールしてすべてのセクションをお読みください" + }, + "termsOfUseTitle": { + "message": "利用規約が更新されました" + }, "testNetworks": { "message": "テストネットワーク" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "留意点:" }, + "thirdPartySoftware": { + "message": "サードパーティソフトウェアに関する通知", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "このコレクション" + }, + "thisServiceIsExperimental": { + "message": "このサービスは試験運用中です。この機能を有効にすることで、OpenSea の $1 に同意したものとみなされます。", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "時間" }, @@ -3863,11 +4867,44 @@ "message": "移動先: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "フィッシング攻撃のリスクがあります。eth_sign を無効にして自分の身を守ってください。" + }, "toggleEthSignDescriptionField": { - "message": "Dapps による eth_sign 要求を使用した署名のリクエストを許可する場合は、これをオンにします。eth_sign は任意ハッシュの署名が可能なオープンエンドの署名方法で、危険なフィッシングのリスクをもたらします。eth_sign 要求は、署名する内容が読め、要求元が信頼できる場合以外は署名しないでください。" + "message": "この設定を有効にすると、読めない署名要求を受ける可能性があります。理解できないメッセージに署名すると、資金や NFT の提供に同意してしまう可能性があります。" }, "toggleEthSignField": { - "message": "eth_sign 要求の設定" + "message": "Eth_sign 要求" + }, + "toggleEthSignModalBannerBoldText": { + "message": "騙されている可能性があります" + }, + "toggleEthSignModalBannerText": { + "message": "この設定を有効にするよう求められた場合、" + }, + "toggleEthSignModalCheckBox": { + "message": "私は、eth_sign 要求を有効にすると、すべての資金と NFT を失う可能性があることを理解しています。" + }, + "toggleEthSignModalDescription": { + "message": "eth_sign 要求を許可すると、フィッシング攻撃を受けやすくなる可能性があります。常に URL を確認し、コードを含むメッセージに署名する際には注意してください。" + }, + "toggleEthSignModalFormError": { + "message": "テキストが正しくありません" + }, + "toggleEthSignModalFormLabel": { + "message": "続行するには、「私は理解できるものにしか署名しません」と入力してください" + }, + "toggleEthSignModalFormValidation": { + "message": "私は理解できるものにしか署名しません" + }, + "toggleEthSignModalTitle": { + "message": "自己責任でご利用ください" + }, + "toggleEthSignOff": { + "message": "オフ (推奨)" + }, + "toggleEthSignOn": { + "message": "オン (非推奨)" }, "token": { "message": "トークン" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "トークンシンボル" }, + "tokens": { + "message": "トークン" + }, "tokensFoundTitle": { "message": "$1 種類の新しいトークンが見つかりました", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "理解しました" }, + "tooltipSatusConnected": { + "message": "接続済み" + }, + "tooltipSatusNotConnected": { + "message": "未接続" + }, "total": { "message": "合計" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "トランザクションでエラーが発生しました。" }, + "transactionFailed": { + "message": "トランザクションに失敗しました" + }, "transactionFee": { "message": "トランザクション手数料" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "ガス代合計" }, + "transactionNote": { + "message": "トランザクション備考" + }, "transactionResubmitted": { "message": "推定のガス代を$2で$1に増加し、トランザクションを再送信しました" }, "transactionSecurityCheck": { - "message": "トランザクションのセキュリティプロバイダーを有効にする" + "message": "セキュリティアラートを有効にする" }, "transactionSecurityCheckDescription": { "message": "当社はサードパーティ API を使用して、ユーザーが署名する前に未署名のトランザクションおよび署名のリクエストに関するリスクを検出・表示します。このようなサービスは、ユーザーの未署名のトランザクションおよび署名のリクエスト、アカウントアドレス、希望言語にアクセスできます。" }, + "transactionSettings": { + "message": "トランザクション設定" + }, "transactionSubmitted": { "message": "$1の推定ガス代が$2でトランザクションが送信されました。" }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "送金元" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Ledger の接続に問題が発生しました。$1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "ハードウェアウォレットの接続ガイドを確認し、もう一度お試しください。", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Firefox の最新バージョンを使用している場合、Firefox の U2F サポート廃止に関連した問題が発生する可能性があります。$1でこの問題を解決する方法をご覧ください。", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "こちら", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "$1に接続できませんでした。$2を確認してから、もう一度実行してください。", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "上矢印" }, + "update": { + "message": "更新" + }, "updatedWithDate": { "message": "$1が更新されました" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "このURLは現在$1ネットワークで使用されています。" }, + "use4ByteResolution": { + "message": "スマートコントラクトのデコード" + }, + "use4ByteResolutionDescription": { + "message": "ユーザーエクスペリエンスの向上のため、ユーザーがやり取りするスマートコントラクトに応じたメッセージで、アクティビティタブをカスタマイズします。MetaMaskは、4byte.directoryと呼ばれるサービスを利用してデータをデコードし、より読みやすいバージョンのスマートコントラクトを表示します。これにより、悪質なスマートコントラクトの操作を承認する可能性は減りますが、IPアドレスが公開されます。" + }, "useMultiAccountBalanceChecker": { "message": "アカウント残高の一括リクエスト" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "アカウントの残高要求を一斉に行うことで、より素早く残高更新を取得できます。これにより、アカウントの残高を一斉に取得できるので、更新がより迅速になり、エクスペリエンスが向上します。この機能がオフの場合、サードパーティがユーザーのアカウントをお互いに関連付けなくなる可能性があります。" + }, "useNftDetection": { "message": "NFTを自動検出" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "イーサリアムユーザーを対象としたドメインのフィッシングに対して警告を表示します" }, + "useSiteSuggestion": { + "message": "サイトの提案を使用" + }, "useTokenDetectionPrivacyDesc": { "message": "アカウントに送られたトークンを自動的に表示するには、サードパーティーサーバーと通信し、トークンの画像を取得する必要があります。これらのサーバーはユーザーの IP アドレスにアクセスできます。" }, @@ -4170,7 +5256,7 @@ "message": "ユーザー名" }, "verifyContractDetails": { - "message": "コントラクトの詳細を確認" + "message": "サードパーティの詳細を確認" }, "verifyThisTokenDecimalOn": { "message": "トークンの10進数は$1にあります", @@ -4184,12 +5270,18 @@ "message": "このトークンを$1で検証して、取引したいトークンであることを確認してください。", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "バージョン" + }, "view": { "message": "表示" }, "viewAllDetails": { "message": "すべての詳細の表示" }, + "viewAllQuotes": { + "message": "すべてのクォートを表示" + }, "viewContact": { "message": "連絡先を表示" }, @@ -4213,9 +5305,18 @@ "message": "$1をEtherscanで表示", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "エクスプローラーで表示" + }, "viewOnOpensea": { "message": "Openseaで表示" }, + "viewPortfolioDashboard": { + "message": "ポートフォリオダッシュボードを表示" + }, + "viewinCustodianApp": { + "message": "カストディアンアプリで表示" + }, "viewinExplorer": { "message": "$1をエクスプローラーで表示", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "このネットワークを追加しますか?" }, + "wantsToAddThisAsset": { + "message": "$1 がこのアセットのウォレットへの追加を要求しています", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "警告" }, "warningTooltipText": { - "message": "$1 このコントラクトは今後、通知や承諾なしにトークン残高全額を使用できます。使用限度をより低い金額にカスタマイズして、自分の身を守りましょう。", + "message": "$1 このサードパーティは今後、通知や承諾なしにトークン残高全額を使用できます。使用上限をより低い金額にカスタマイズして、自分の身を守りましょう。", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "著名しています" }, + "yourAccounts": { + "message": "アカウント" + }, "yourFundsMayBeAtRisk": { "message": "資金が危険にさらされている可能性があります" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 741f9de5a..745963142 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -106,6 +106,9 @@ "about": { "message": "정보" }, + "accept": { + "message": "동의" + }, "acceptTermsOfUse": { "message": "$1의 내용을 읽고 이에 동의합니다.", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "닉네임 추가" }, + "addAccount": { + "message": "계정 추가" + }, "addAcquiredTokens": { "message": "MetaMask를 이용해 얻은 토큰 추가" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "주요 네트워크 목록에서 추가하거나 네트워크를 직접 추가합니다. 신뢰할 수 있는 엔터티만 이용하세요." }, + "addHardwareWallet": { + "message": "하드웨어 지갑 추가" + }, + "addIPFSGateway": { + "message": "선호하는 IPFS 게이트웨이를 추가하세요" + }, "addMemo": { "message": "메모 추가" }, @@ -244,6 +256,21 @@ "message": "이 네트워크 연결은 타사 서비스를 이용합니다. 연결의 보안이 취약하거나 타사에 연결 내용이 노출될 수 있습니다. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "신규 토큰 추가" + }, + "addNft": { + "message": "NFT 추가" + }, + "addNfts": { + "message": "NFT 추가" + }, + "addSnapAccountModalDescription": { + "message": "MetaMask 스냅으로 계정 보안을 유지할 수 있는 옵션을 확인하세요" + }, + "addSuggestedNFTs": { + "message": "추천 NFT 추가" + }, "addSuggestedTokens": { "message": "추천 토큰 추가" }, @@ -254,6 +281,9 @@ "message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "네트워크 추가" + }, "address": { "message": "주소" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "우선 요금(일명 \"채굴자 팁\")이란 나와 먼저 거래한 것에 대한 인센티브로 채굴자에게 직접 전달되는 금액입니다." }, + "agreeTermsOfUse": { + "message": "MetaMask의 $1에 동의합니다", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "에어갭 볼트" }, @@ -302,10 +336,20 @@ "alerts": { "message": "경고" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "현재 모든 관리인 계정이 연결되어 있거나 MetaMask Institutional에 연결할 계정이 없습니다." + }, + "allCustodianAccountsConnectedTitle": { + "message": "연결할 계정 없음" + }, "allOfYour": { "message": "내 모든 $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "$1의 모든 NFT", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "이 외부 확장을 통해 다음을 허용:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "이 사이트에서 다음을 하도록 허용:" }, + "allowThisSnapTo": { + "message": "이 스냅을 다음으로 허용:" + }, "allowWithdrawAndSpend": { "message": "$1에서 다음 금액까지 인출 및 지출하도록 허용:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "금액" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "브라우저의 이더리움 지갑", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "내 모든 $1에 액세스 및 전송할 수 있는 권한을 부여할까요?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "$1에 액세스하여 모든 NFT를 전송하는 것을 허용하시겠습니까?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "승인" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "이렇게 하면 사용자가 액세스를 취소할 때까지 제삼자가 가 통지 없이 다음과 같은 NFT에 액세스하고 이를 전송할 수 있습니다." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "이렇게 하면 회원님이 해당 액세스를 취소할 때까지 타사가 별도의 통보 없이 회원님의 $1에 액세스하여 모든 NFT를 전송하게 됩니다.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "내 $1에 액세스 및 전송할 수 있는 권한을 부여할까요?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "한 네트워크에서 다른 네트워크로 자산을 직접 전송하면 자산이 영구적으로 손실될 수 있습니다. 반드시 브릿지를 이용하세요." }, + "attemptToCancelSwap": { + "message": "~$1 비용으로 스왑 취소 시도", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "블록체인에 연결 중입니다." }, @@ -411,6 +473,9 @@ "average": { "message": "평균" }, + "awaitingApproval": { + "message": "승인 대기 중..." + }, "back": { "message": "뒤로" }, @@ -454,6 +519,9 @@ "message": "베타 버전입니다. 버그는 $1로 보고하세요", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "MetaMask Institutional 베타 버전" + }, "betaMetamaskVersion": { "message": "MetaMask 베타 버전" }, @@ -488,9 +556,45 @@ "message": "$1의 계정 보기", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "이 요청을 승인하면 스캠과 같은 제삼자가 회원님의 모든 자산을 가져갈 수 있습니다." + }, + "blockaidDescriptionBlurFarming": { + "message": "이 요청을 승인하면, Blur에 있는 자산을 타인이 갈취할 수 있습니다." + }, + "blockaidDescriptionFailed": { + "message": "오류로 인해 보안업체에서 이 요청을 확인하지 못했습니다. 유의하여 진행하세요." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "악성 도메인과 상호 작용하고 있습니다. 이 요청을 승인하면 본인의 자산을 잃을 수도 있습니다." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "이 요청을 승인하면, 자산을 잃을 수도 있습니다." + }, + "blockaidDescriptionSeaportFarming": { + "message": "이 요청을 승인하면 OpenSea에 있는 자산을 타인이 갈취할 수 있습니다." + }, + "blockaidDescriptionTransferFarming": { + "message": "이 요청을 승인하면 스캠과 같은 제삼자가 회원님의 모든 자산을 가져갈 수 있습니다." + }, + "blockaidTitleDeceptive": { + "message": "사기성 요청입니다" + }, + "blockaidTitleMayNotBeSafe": { + "message": "요청이 안전하지 않을 수 있습니다" + }, + "blockaidTitleSuspicious": { + "message": "수상한 요청입니다" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "브리지" + }, "browserNotSupported": { "message": "지원되지 않는 브라우저입니다..." }, @@ -510,6 +614,10 @@ "message": "$1 구매", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "$1 추가 구매", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "지금 구매" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "이는 계정의 논스를 초기화하고 지갑의 활동 탭에 있는 데이터를 지웁니다. 이렇게 하면 현재 계정과 네트워크만 변경될 뿐 잔액과 입금 거래에는 영향을 미치지 않습니다." }, + "click": { + "message": "클릭" + }, "clickToConnectLedgerViaWebHID": { "message": "WebHID를 통해 Ledger을 연결하려면 여기를 클릭하세요.", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "이제 스냅 구성을 위해 MetaMask 페이지를 떠나게 됩니다." + }, + "configureSnapPopupInstallDescription": { + "message": "이제 스냅 설치를 위해 MetaMask 페이지를 떠나게 됩니다." + }, + "configureSnapPopupInstallTitle": { + "message": "스냅 설치" + }, + "configureSnapPopupLink": { + "message": "계속하려면 다음 링크를 클릭:" + }, + "configureSnapPopupTitle": { + "message": "스냅 구성" + }, "confirm": { "message": "확인" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "계정 연결 또는 새 계정 만들기" }, + "connectCustodialAccountMenu": { + "message": "관리인 계정 연결" + }, + "connectCustodialAccountMsg": { + "message": "연결을 원하는 관리인 계정을 선택하여 토큰을 추가하거나 새로고침하세요." + }, + "connectCustodialAccountTitle": { + "message": "관리인 계정" + }, "connectManually": { "message": "현재 사이트에 수동으로 연결" }, + "connectSnap": { + "message": "$1 연결", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "$1에 연결", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Linea Goerli 테스트 네트워크에 연결 중" }, + "connectingToLineaMainnet": { + "message": "Linea 메인넷에 연결 중" + }, "connectingToMainnet": { "message": "이더리움 메인넷에 연결 중" }, "connectingToSepolia": { "message": "Sepolia 테스트 네트워크에 연결 중" }, + "connectionFailed": { + "message": "연결 실패" + }, + "connectionFailedDescription": { + "message": "$1 불러오기에 실패했습니다. 네트워크를 확인하고 다시 시도하세요", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "연결 요청" + }, "contactUs": { "message": "문의하기" }, @@ -708,7 +860,7 @@ "message": "계약 배포" }, "contractDescription": { - "message": "사기를 방지하려면 잠시 시간을 내어 계약의 세부 사항을 확인하세요." + "message": "사기를 방지하려면 잠시 시간을 내어 타사에 대한 세부 정보를 확인하세요." }, "contractInteraction": { "message": "계약 상호 작용" @@ -717,16 +869,16 @@ "message": "NFT 계약" }, "contractRequestingAccess": { - "message": "액세스 필요 계약" + "message": "타사의 액세스 요청" }, "contractRequestingSignature": { - "message": "서명 필수 계약" + "message": "타사의 서명 요청" }, "contractRequestingSpendingCap": { - "message": "지출 한도 필요 계약" + "message": "타사의 지출 한도 요청" }, "contractTitle": { - "message": "계약 세부 사항" + "message": "타사 세부 정보" }, "contractToken": { "message": "토큰 계약" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "시장 가스비 추정치 그래프" }, + "custodian": { + "message": "관리자" + }, + "custodianAccount": { + "message": "관리자 계정" + }, + "custodianAccountAddedDesc": { + "message": "이제 MetaMask Institutional에서 관리인 계정을 사용할 수 있습니다." + }, + "custodianAccountAddedTitle": { + "message": "선택한 관리인 계정을 추가했습니다." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "계정을 MMI에 다시 연결하려면 $1에서 해당 사용자 인터페이스를 찾아 'MMI로 연결(Connect to MMI)' 버튼을 클릭하면 됩니다." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "이제 MetaMask Institutional에서 관리인 계정을 사용할 수 있습니다." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "관리인 토큰이 새로고침되었습니다" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "이렇게 하면 관리인 토큰이 다음 주소로 대체됩니다:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "관리인 토큰 대체" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "$1 앱에서 거래를 승인하세요. 필요한 관리인 승인이 모두 완료되면 거래가 완료됩니다. $1 앱 상태를 확인하세요." + }, + "custodyRefreshTokenModalDescription": { + "message": "계정을 MMI에 다시 연결하려면 $1에서 해당 사용자 인터페이스를 찾아 'MMI로 연결(Connect to MMI)' 버튼을 클릭하면 됩니다." + }, + "custodyRefreshTokenModalDescription1": { + "message": "수탁 관리자가 MetaMask Institutional 확장을 인증하는 토큰을 발행하면 계정을 연결할 수 있습니다." + }, + "custodyRefreshTokenModalDescription2": { + "message": "이 토큰은 보안상의 이유로 일정 기간이 지나면 만료됩니다. 그러면 MMI에 다시 연결해야 합니다." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "이것은 무엇인가요?" + }, + "custodyRefreshTokenModalTitle": { + "message": "수탁 관리자 세션이 만료되었습니다" + }, + "custodySessionExpired": { + "message": "관리인 세션이 만료되었습니다." + }, + "custodyWrongChain": { + "message": "이 계정은 $1 사용으로 설정되어 있지 않습니다" + }, "custom": { "message": "고급" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "고객 지원" }, + "dappRequestedSpendingCap": { + "message": "사이트에서 지출 한도를 요청했습니다" + }, "dappSuggested": { "message": "추천 사이트" }, @@ -851,6 +1060,12 @@ "message": "$1에서 이 가격을 제안했습니다.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "추천 사이트" + }, + "dappSuggestedHighShortLabel": { + "message": "사이트(높음)" + }, "dappSuggestedShortLabel": { "message": "사이트" }, @@ -880,10 +1095,10 @@ "message": "소수점 이하 자릿수는 0 이상, 36 이하여야 합니다." }, "decrypt": { - "message": "복호화" + "message": "암호 해독" }, "decryptCopy": { - "message": "복호화된 메시지 복사" + "message": "암호 해독된 메시지 복사" }, "decryptInlineError": { "message": "다음 오류 때문에 이 메시지를 해독할 수 없습니다: $1", @@ -902,9 +1117,19 @@ "delete": { "message": "삭제" }, + "deleteContact": { + "message": "연락처 삭제" + }, "deleteNetwork": { "message": "네트워크를 삭제할까요?" }, + "deleteNetworkIntro": { + "message": "이 네트워크를 삭제하면 나중에 이 네트워크에 있는 자산을 보려면 네트워크를 다시 추가해야 합니다" + }, + "deleteNetworkTitle": { + "message": "$1 네트워크를 삭제할까요?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "예치" }, @@ -917,6 +1142,10 @@ "description": { "message": "설명" }, + "descriptionFromSnap": { + "message": "$1 설명", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "이는 간헐적인 오류일 수 있습니다. 확장 프로그램을 다시 시작하거나 MetaMask Desktop을 비활성화해 보세요." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "비밀 복구 구문 백업 알림 해지" }, + "displayNftMedia": { + "message": "NFT 미디어 표시" + }, + "displayNftMediaDescription": { + "message": "NFT 미디어와 데이터를 표시하면 IP 주소가 OpenSea나 다른 타사에 노출될 수 있습니다. 그러면 이더리움 주소와 연관된 IP 주소가 공격 세력에 의해 이용될 수 있습니다. NFT 자동 감지 기능에는 이 설정이 필요합니다. 이 설정을 비활성화하면 해당 기능도 사용할 수 없습니다." + }, "domain": { "message": "도메인" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " 자동 감지 활성화" }, + "enableForAllNetworks": { + "message": "모든 네트워크에 활성화" + }, "enableFromSettings": { "message": " 설정에서 이 기능을 활성화합니다." }, + "enableSmartSwaps": { + "message": "스마트 스왑 활성화" + }, + "enableSnap": { + "message": "활성화" + }, "enableToken": { "message": "$1 활성화", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "활성화됨" + }, "encryptionPublicKeyNotice": { "message": "$1에서 귀하의 공개 암호화 키를 요구합니다. 동의를 받으면 이 사이트에서 암호화된 메시지를 작성하여 귀하에게 전송할 수 있습니다.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "$1. $2에서 향상된 토큰 감지를 사용할 수 있습니다." }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask를 사용하면 브라우저의 주소창에서 \"https://metamask.eth\"과 같은 ENS 도메인을 바로 볼 수 있습니다. 방법은 다음과 같습니다." + }, + "ensDomainsSettingDescriptionOutro": { + "message": "일반 브라우저에서는 ENS나 IPFS 주소를 취급하지 않습니다. 하지만 MetaMask에서는 가능합니다. 이 기능을 사용하시면 회원님의 IP 주소를 IPFS 타사 서비스와 공유할 수 있습니다." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask에서는 이더리움의 ENS 계약을 확인하여 해당 ENS 이름과 연결되어 있는 코드를 찾습니다." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "해당 코드가 IPFS와 링크되어 있으면 이를 통해 IPFS 네트워크에서 콘텐츠를 불러올 수 있습니다." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "그러면 보통 웹사이트 또는 이와 유사한 형태로 콘텐츠를 볼 수 있습니다." + }, + "ensDomainsSettingTitle": { + "message": "주소창에 ENS 도메인 표시하기" + }, "ensIllegalCharacter": { "message": "ENS에 맞지 않는 문자입니다." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "금액 입력" }, + "enterCustodianToken": { + "message": "$1 토큰을 입력하거나 새로운 토큰을 추가하세요" + }, "enterMaxSpendLimit": { "message": "최대 지출 한도 입력" }, + "enterOptionalPassword": { + "message": "선택적 비밀번호를 입력하세요" + }, "enterPassword": { "message": "비밀번호 입력" }, "enterPasswordContinue": { "message": "계속하려면 비밀번호를 입력하세요" }, + "enterTokenNameOrAddress": { + "message": "토큰 이름 입력 또는 주소 붙여넣기" + }, + "enterYourPassword": { + "message": "비밀번호를 입력하세요" + }, "errorCode": { "message": "코드: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "스택:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "사용자 맞춤 네트워크 연결 중에 오류가 발생했습니다." + }, + "errorWithSnap": { + "message": "$1 오류", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "현재 주요 가스 견적 서비스를 사용할 수 없으므로 백업 가스 가격을 제공합니다." }, + "ethereumProviderAccess": { + "message": "이더리움 공급업체에 $1 엑세스 허용", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "이더리움 공개 주소" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "실험적" }, + "exploreMetaMaskSnaps": { + "message": "MetaMask 스냅 탐색" + }, "exportPrivateKey": { "message": "비공개 키 내보내기" }, + "extendWalletWithSnaps": { + "message": "지갑 경험 확장" + }, "externalExtension": { "message": "외부 확장" }, @@ -1302,6 +1596,9 @@ "message": "파일 가져오기가 작동하지 않나요? 여기를 클릭하세요.", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "드롭한 파일 크기가 너무 큽니다." + }, "flaskWelcomeUninstall": { "message": "이 확장 프로그램을 삭제해야 합니다", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "일반" }, + "getStarted": { + "message": "시작하기" + }, + "globalTitle": { + "message": "글로벌 메뉴" + }, + "globalTourDescription": { + "message": "포트폴리오, 연결된 사이트, 설정 등을 확인하세요" + }, "goBack": { "message": "뒤로 가기" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "하드웨어 지갑 연결" }, + "hardwareWalletsInfo": { + "message": "하드웨어 지갑을 통합하면 외부 서버에 API 호출을 사용하며, 이 서버는 사용자의 IP 주소와 사용자와 상호작용하는 스마트 계약의 주소를 볼 수 있습니다." + }, "hardwareWalletsMsg": { "message": "MetaMask와 함께 사용할 하드웨어 지갑을 선택하세요." }, @@ -1541,11 +1850,34 @@ "message": "오히려 피싱 사기꾼들이 요구할 수 있으니 주의가 필요합니다.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "개인 키가 있으면 $1 기능을 사용할 수 있습니다", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "지갑과 자금 모두에 액세스하세요.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "눌러서 잠긴 서클 보기" + }, + "holdToRevealPrivateKey": { + "message": "눌러서 개인 키 보기" + }, + "holdToRevealPrivateKeyTitle": { + "message": "개인 키를 안전하게 보관하세요" + }, "holdToRevealSRP": { - "message": "눌러서 SRP 정보를 확인하세요" + "message": "눌러서 SRP 확인" }, "holdToRevealSRPTitle": { - "message": "SRP 정보를 안전하게 보관하세요" + "message": "SRP를 안전하게 보관하세요" + }, + "holdToRevealUnlockedLabel": { + "message": "눌러서 잠금해제된 서클 보기" + }, + "id": { + "message": "ID" }, "ignoreAll": { "message": "모두 무시" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "계정 가져오기 오류" }, + "importAccountErrorIsSRP": { + "message": "비밀 복구 구문 (또는 니모닉)을 입력하셨습니다. 계정을 이곳으로 불러오려면 64자의 16진수 문자열인 개인 키를 입력해야 합니다." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "유효한 개인 키가 아닙니다. 16진수 문자열을 입력했지만 64자리가 아닙니다." + }, + "importAccountErrorNotHexadecimal": { + "message": "유효한 개인 키가 아닙니다. 반드시 64자의 16진수 문자열인 개인 키를 입력해야 합니다." + }, + "importAccountJsonLoading1": { + "message": "이 JSON 파일을 가져오려면 몇 분 정도 걸리며 MetaMask가 정지됩니다." + }, + "importAccountJsonLoading2": { + "message": "죄송합니다. 향후에는 더 빠르게 개선하겠습니다." + }, "importAccountMsg": { - "message": "가져온 계정은 본래 생성한 MetaMask 계정 비밀 복구 구문과 연결하지 못합니다. 가져온 계정에 대해 자세히 알아보기" + "message": "가져온 계정은 MetaMask 계정 비밀 복구 구문과 연결하지 못합니다. 가져온 계정에 대해 자세히 알아보기" }, "importMyWallet": { "message": "내 지갑 가져오기" @@ -1615,18 +1962,29 @@ "message": "최초 거래를 네트워크에서 확인했습니다. 돌아가려면 확인을 클릭하세요." }, "inputLogicEmptyState": { - "message": "계약에서 현재나 추후 지출하기에 무리가 없는 금액만 입력하세요. 지출 한도는 나중에 언제든지 상향할 수 있습니다." + "message": "타사에서 현재나 추후 지출하기에 무리가 없는 금액만 입력하세요. 지출 한도는 나중에 언제든지 상향할 수 있습니다." }, "inputLogicEqualOrSmallerNumber": { - "message": "계약이 현재 잔액에서 $1만큼 지출할 수 있게 됩니다.", + "message": "이렇게 하면 타사가 현재 잔액에서 $1만큼 지출할 수 있게 됩니다.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "토큰 잔액이 한도에 도달하거나 지출 한도를 철회할 때까지 계약이 모든 토큰 잔액을 지출할 수 있게 됩니다. 이를 원하지 않는다면 지출 한도를 하향하세요." + "message": "이렇게 하면 토큰 잔액이 한도에 도달하거나 지출 한도를 철회할 때까지 타사가 모든 토큰 잔액을 지출할 수 있게 됩니다. 이를 원하지 않는다면 지출 한도를 하향하세요." + }, + "insightsFromSnap": { + "message": "$1 인사이트", + "description": "$1 represents the name of the snap" }, "install": { "message": "설치" }, + "installOrigin": { + "message": "오리진 설치" + }, + "installedOn": { + "message": "$1에 설치됨", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "잔액이 부족합니다." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "입력 오류: 비밀 복구 구문은 대소문자를 구분해야 합니다." }, + "ipfsGateway": { + "message": "IPFS 게이트웨이" + }, + "ipfsGatewayDescription": { + "message": "MetaMask는 타사 서비스를 이용하여 IPFS에 저장된 NFT 이미지를 표시하고, 브라우저 주소창에 입력한 ENS 주소 관련 정보도 표시하며, 다른 토큰의 아이콘도 가져옵니다. 이 기능을 이용하면 IP 주소가 해당 서비스에 노출될 수 있습니다." + }, + "ipfsToggleModalDescriptionOne": { + "message": "당사는 타사 서비스를 사용하여 IPFS에 저장된 NFT 이미지를 표시하고, 브라우저 주소창에 입력된 ENS 주소와 관련된 정보를 표시하며, 다양한 토큰의 아이콘을 가져옵니다. 이러한 서비스를 사용하면 귀하의 IP 주소가 해당 서비스에 노출될 수 있습니다." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "확인을 선택하면 IPFS 레졸루션이 활성화됩니다. 언제든지 $1에서 비활성화할 수 있습니다.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "설정 > 보안 및 개인정보" + }, "jazzAndBlockies": { "message": "Jazzicons와 Blockies는 계정을 한눈에 식별할 수 있게 도와주는 두 가지 고유한 아이콘 스타일입니다." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "최근 판매" }, + "layer1Fees": { + "message": "레이어 1 요금" + }, "learnCancelSpeeedup": { "message": "$1하는 방법 알아보기", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "가스에 대해 $1하시겠습니까?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "자세히 알아보기" + }, "learnMoreUpperCase": { "message": "자세히 알아보기" }, @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea Goerli 테스트 네트워크" }, + "lineaMainnet": { + "message": "Linea 메인넷" + }, "link": { "message": "링크" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "잠금" }, + "lockMetaMask": { + "message": "MetaMask 고정" + }, + "lockTimeInvalid": { + "message": "잠금 시간은 반드시 0에서 10080 사이여야 합니다" + }, "logo": { "message": "$1 로고", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "방문 중인 웹사이트가 현재 선택한 계정에 연결되어 있다면 연결 상태 버튼이 표시됩니다." }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Institutional 버전" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps가 점검 중입니다. 나중에 다시 확인하세요." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "메트릭" }, + "mismatchAccount": { + "message": "선택한 계정($1)이 서명하려는 계정($2)과 다릅니다" + }, "mismatchedChainLinkText": { "message": "네트워크 세부 정보 검증", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "제출한 화폐 기호가 이 체인 ID의 화폐 기호와 일치하지 않습니다." }, + "mismatchedRpcChainId": { + "message": "사용자 맞춤 네트워크의 체인 ID가 제출한 체인 ID와 일치하지 않습니다." + }, "mismatchedRpcUrl": { "message": "기록에 따르면 제출한 RPC URL 값이 이 체인 ID의 알려진 공급업체와 일치하지 않습니다." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "여기에서 요청하세요" }, + "mmiAddToken": { + "message": "$1의 페이지는 다음과 같은 MetaMask 기관 관리자 토큰을 승인하려고 합니다" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional은 전 세계적으로 설계 및 구축되었습니다." + }, + "more": { + "message": "그 외" + }, "moreComingSoon": { - "message": "더 추가 예정..." + "message": "더 많은 공급업체가 곧 추가됩니다" + }, + "multipleSnapConnectionWarning": { + "message": "$1에서 $2개의 스냅 연결을 원합니다. 해당 웹사이트를 신뢰할 수 있는 경우에만 진행하세요.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "토큰을 1개 이상 선택해야 합니다." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "네트워크 사용량이 많습니다. 가스비가 높고 견적의 정확도도 떨어집니다." }, + "networkMenu": { + "message": "네트워크 메뉴" + }, + "networkMenuHeading": { + "message": "네트워크 선택" + }, "networkName": { "message": "네트워크 이름" }, @@ -2033,6 +2450,10 @@ "message": "가스 요금이 지난 72시간에 비해 $1입니다.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "$1 연결이 불가능합니다", + "description": "$1 represents the network name" + }, "networkURL": { "message": "네트워크 URL" }, @@ -2090,7 +2511,7 @@ "message": "다음" }, "nextNonceWarning": { - "message": "논스값이 권장 논스값인 $1보다 큽니다.", + "message": "임시값이 권장 임시값인 $1보다 큽니다.", "description": "The next nonce according to MetaMask's internal logic" }, "nftAddFailedMessage": { @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "이전 소유" + }, "nickname": { "message": "닉네임" }, @@ -2138,6 +2562,12 @@ "noConversionRateAvailable": { "message": "사용 가능한 환율 없음" }, + "noNFTs": { + "message": "아직 NFT가 없음" + }, + "noNetworksFound": { + "message": "검색어에 해당하는 네트워크가 없습니다." + }, "noSnaps": { "message": "설치된 스냅이 없습니다" }, @@ -2154,13 +2584,13 @@ "message": "웹캠을 찾을 수 없음" }, "nonce": { - "message": "논스" + "message": "임시값" }, "nonceField": { - "message": "거래 논스 맞춤화" + "message": "거래 임시값 맞춤화" }, "nonceFieldDescription": { - "message": "이 기능을 켜면 확인 화면에서 논스(거래 번호)를 변경할 수 있습니다. 이는 고급 기능으로, 주의해서 사용해야 합니다." + "message": "이 기능을 켜면 확인 화면에서 임시값(거래 번호)을 변경할 수 있습니다. 이는 고급 기능으로, 주의해서 사용해야 합니다." }, "nonceFieldHeading": { "message": "커스텀 논스" @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "올바른 계정인가요? 현재 지갑에서 선택된 계정과 다릅니다." }, + "notEnoughBalance": { + "message": "잔액이 부족합니다" + }, "notEnoughGas": { "message": "가스 부족" }, + "note": { + "message": "메모" + }, + "notePlaceholder": { + "message": "수탁 기관에서 트랜잭션이 승인되면 승인자가 이 참고 사항을 보게 됩니다." + }, + "notificationTransactionFailedMessage": { + "message": "$1 거래에 실패했습니다! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "거래에 실패했습니다! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "실패한 거래", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "$1 거래가 확인되었습니다!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "확인된 거래", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "$1에서 보기", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "알림" + }, "notifications10ActionText": { "message": "설정으로 이동하기", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "이더리움 머지가 완료되었습니다!" }, + "notifications18ActionText": { + "message": "보안 알림 활성화" + }, + "notifications18DescriptionOne": { + "message": "악성 요청을 받을 때마다 타사로부터 경고 알림을 받으세요.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "모든 요청을 승인하기 전에 주의 깊게 직접 확인하세요.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea는 이러한 기능을 제공하는 최초 업체입니다. 다른 업체도 곧 지원될 예정입니다.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "알림을 통해 보안을 유지하세요" + }, + "notifications19ActionText": { + "message": "NFT 자동 감지 활성화" + }, + "notifications19DescriptionOne": { + "message": "두 가지 방법으로 시작할 수 있습니다.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "현재 ERC-721만 지원됩니다.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "NFT를 직접 추가하거나 설정(Settings) > 실험 기능(Experimental)에서 NFT 자동 감지 기능(NFT autodetection)을 켜세요.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "NFT를 전보다 더욱 쉽게 찾아 보세요" + }, "notifications1Description": { "message": "MetaMask 모바일 사용자는 이제 모바일 지갑에서 토큰을 스왑할 수 있습니다. QR 코드를 스캔하여 모바일 앱을 설치하고 스왑을 시작하세요.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "모바일 스왑은 여기서 진행됩니다!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "자세히 알아보기", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Firefox 최신 버전을 사용하신다면 Firefox에서 U2F 지원 중단과 관련된 문제가 발생할 수 있습니다.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Ledger 및 Firefox 사용자의 연결 문제점 경험", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "체험해 보세요" + }, + "notifications21Description": { + "message": "더욱 쉽고 빠르게 사용하도록 MetaMask 확장의 스왑을 업데이트했습니다", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "새롭게 업데이트한 스왑을 소개합니다" + }, + "notifications22ActionText": { + "message": "확인" + }, + "notifications22Description": { + "message": "💡 글로벌 메뉴나 계정 메뉴를 클릭하여 찾으세요!" + }, + "notifications22Title": { + "message": "계정 상세 정보나 블록 탐색기 URL을 찾고 계신가요?" + }, + "notifications23ActionText": { + "message": "보안 알림 활성화" + }, + "notifications23DescriptionOne": { + "message": "Blockaid가 제공하는 보안 알림을 통해 개인정보를 보호하면서 알려진 사기를 피하세요." + }, + "notifications23DescriptionThree": { + "message": "OpenSea에서 보안 알림을 활성화하면, 이 기능으로 이동합니다." + }, + "notifications23DescriptionTwo": { + "message": "요청을 승인하기 전에 항상 직접 실사하세요." + }, + "notifications23Title": { + "message": "알림을 통해 보안을 유지하세요" + }, "notifications3ActionText": { "message": "더 읽어보기", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2257,7 +2805,7 @@ "description": "The 'call to action' on the button, or link, of the 'Swap on Binance Smart Chain!' notification. Upon clicking, users will be taken to a page where then can swap tokens on Binance Smart Chain." }, "notifications4Description": { - "message": "토큰 스왑 최고가를 지갑에서 바로 이용하세요. MetaMask는 이제 바이낸스 스마트 체인의 여러 탈중앙화 거래소 애그리게이터 및 투자전문기관과 연결됩니다.", + "message": "토큰 스왑 최고가를 지갑에서 바로 이용하세요. MetaMask는 이제 바이낸스 스마트 체인의 여러 분산형 교환 애그리게이터 및 투자전문기관과 연결됩니다.", "description": "Description of a notification in the 'See What's New' popup." }, "notifications4Title": { @@ -2486,18 +3034,21 @@ "message": "신뢰하는 사이트만 연결하세요." }, "openFullScreenForLedgerWebHid": { - "message": "전체 화면에서 MetaMask를 열어 WebHID를 통해 Ledger를 연결합니다.", + "message": "전체 화면으로 이동하여 Ledger를 연결하세요.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "블록 탐색기 열기" }, "openSea": { - "message": "OpenSea (베타)" + "message": "OpenSea+ Blockaid(베타)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "작업에 실패했습니다" + }, "optional": { "message": "옵션" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "비밀번호가 일치하지 않습니다." }, + "pasteJWTToken": { + "message": "토큰을 여기 드롭하거나 붙여 넣으세요:" + }, "pastePrivateKey": { "message": "여기에 비공개 키 문자열을 붙여넣으세요.", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "인터넷에 액세스합니다.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "스냅의 인터넷 액세스를 허용합니다. 이는 타사에 데이터를 전송 및 수신하는 데 사용됩니다.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "$1 스냅에 연결하세요.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "웹사이트나 스냅이 $1 스냅과 인터렉션하는 것을 허용합니다.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "정기적 활동 예약 및 실행", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "스냅이 고정된 시간, 날짜 또는 간격으로 주기적으로 실행되는 작업을 수행하도록 허용합니다. 이는 기한이 필요한 인터렉션이나 알람 트리거에 사용됩니다.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "MetaMask 대화창 표시", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "스냅이 특정 메시지와 입력란을 허용 또는 거부 클릭 버튼과 함께 MetaMask 팝업을 표시하도록 허용합니다.\n이는 알림, 확인 및 스냅에 필요한 옵트인 플로우 등을 만드는 데 사용할 수 있습니다.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "허용되는 계정의 주소 보기(필수)", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "이더리움 공급자에 액세스합니다.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "스냅이 MetaMask와 직접 통신하여 블록체인에서 데이터를 읽고 메시지 및 트랜잭션을 제안하도록 허용합니다.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "이 스냅에 대해 고유한 키를 임의로 파생합니다.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "스냅이 노출되지 않고 이 스냅에 고유한 임의의 키를 파생하도록 허용합니다. 이 키는 MetaMask 계정과 별개이며 개인 키 또는 비밀 복구 구문과 관련이 없습니다. 다른 스냅은 이 정보에 액세스할 수 없습니다.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "라이프사이클 후크를 사용하세요.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "스냅으로 라이프사이클 후크를 사용하도록 허용하면 라이프사이클 동안 코드를 특정 횟수만큼 실행합니다.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "무기한 운용됩니다.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "예를 들어 많은 양의 데이터를 처리하는 동안 스냅이 무기한 실행되도록 허용합니다.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "이더리움 계정 추가 및 제어", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "'$1'($2) 하에서 계정과 자산을 통제합니다.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "스냅이 비밀 복구 문구를 노출하지 않고 이를 기반으로 BIP-32 키 쌍을 파생하도록 허용합니다. 이렇게 하면 $1의 모든 계정과 자산에 대한 전체 액세스 권한이 부여됩니다.\n스냅은 키 관리 기능을 통해 이더리움(EVM)을 넘어 다양한 블록체인 프로토콜을 지원할 수 있습니다.", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "'$1' 계정과 자산을 통제합니다.", + "message": "$1 계정과 자산을 통제합니다.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "스냅이 비밀 복구 문구를 노출하지 않고 이를 기반으로 BIP-44 키 쌍을 파생하도록 허용합니다. 이렇게 하면 $1의 모든 계정과 자산에 대한 전체 액세스 권한이 부여됩니다.\n스냅은 키 관리 기능을 통해 이더리움(EVM)을 넘어 다양한 블록체인 프로토콜을 지원할 수 있습니다.", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "$1 계정과 자산을 관리하세요.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "기기의 데이터를 저장하고 관리합니다.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "스냅이 암호화를 통해 데이터를 안전하게 저장, 업데이트 및 검색하도록 허용합니다. 다른 스냅은 이 정보에 액세스할 수 없습니다.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "알림을 표시합니다.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "스냅이 MetaMask에서 알림을 디스플레이하도록 허용합니다. 스냅은 작업이 필요하거나 기한이 필요한 정보를 짧은 알림으로 트리거합니다.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "$1에서 이 스냅으로 커뮤니케이션하도록 허용합니다.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "$1에서 스냅으로 메시지를 전송하고 응답을 받도록 허용합니다.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "거래 인사이트를 가져오고 표시하세요.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "스냅이 트랜잭션을 디코딩하고 MetaMask UI에서 인사이트를 디스플레이하도록 허용합니다. 이는 피싱 방지와 보안 솔루션에 사용할 수 있습니다.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "거래 추천 웹사이트 출처 보기", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "스냅이 거래를 제안하는 웹사이트의 원본(URI)을 볼 수 있도록 허용합니다. 이는 피싱 방지 및 보안 솔루션에 활용할 수 있습니다.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "알 수 없는 권한: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "$1 공개 키($2) 보기.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "스냅이 $1에 대한 공개 키(및 주소)를 볼 수 있도록 허용합니다. 이 허용이 계정이나 자산에 대한 통제권을 부여하는 것은 아닙니다.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "$1에 관한 공개 키 보기", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "WebAssembly 지원", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "스냅이 WebAssembly를 통해 하위 레벨 실행 환경에 액세스하도록 허용합니다.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "권한" }, + "permissionsTitle": { + "message": "권한" + }, + "permissionsTourDescription": { + "message": "연결된 계정을 찾아 여기서 권한을 관리하세요" + }, "personalAddressDetected": { "message": "개인 주소가 발견되었습니다. 토큰 계약 주소를 입력하세요." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "포트폴리오" }, + "portfolioDashboard": { + "message": "포트폴리오 대시보드" + }, "preferredLedgerConnectionType": { "message": "선호하는 Ledger 연결 유형", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "비공개 키", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "$1 비공개 키", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "경고: 이 키를 노출하지 마세요. 비공개 키가 있는 사람이라면 누구든 귀하의 계정에 있는 자산을 훔칠 수 있습니다." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "대기열에 지정됨" }, + "quoteRate": { + "message": "견적 비율" + }, "reAddAccounts": { "message": "다른 계정을 다시 추가" }, @@ -2751,7 +3401,7 @@ "message": "받기" }, "recipientAddressPlaceholder": { - "message": "검색, 공개 주소(0x) 또는 ENS" + "message": "공개 주소(0x) 또는 ENS 제목 입력" }, "recommendedGasLabel": { "message": "권장됨" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "이 계정이 지갑에서 제거됩니다. 계속하기 전에 가져온 이 계정에 대한 원본 비밀 복구 구문이나 비공개 키가 있는지 확인하세요. 계정 드롭다운에서 계정을 가져오거나 다시 만들 수 있습니다. " }, + "removeJWT": { + "message": "관리인 토큰 제거" + }, + "removeJWTDescription": { + "message": "정말 이 토큰을 삭제하시겠습니까? 이 토큰에 할당된 모든 계정이 확장 프로그램에서도 제거됩니다." + }, "removeNFT": { "message": "NFT 제거" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "이전에 백업한 JSON 파일에서 기본 설정과 계정 주소가 포함된 사용자 설정을 복원할 수 있습니다." }, + "resultPageError": { + "message": "오류" + }, + "resultPageErrorDefaultMessage": { + "message": "작업에 실패했습니다." + }, + "resultPageSuccess": { + "message": "성공" + }, + "resultPageSuccessDefaultMessage": { + "message": "작업을 완료했습니다." + }, "retryTransaction": { "message": "트랜잭션 재시도" }, @@ -2939,16 +3607,27 @@ "message": "모든 $1에 액세스할 수 있는 권한을 철회할까요?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "$1에 액세스하여 모든 NFT를 전송하는 것을 취소하시겠습니까?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "권한을 철회하면 제삼자가 통보 없이 $1에 액세스하거나 이를 전송할 수 없습니다.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "이렇게 하면 타사가 별도의 통보 없이 회원님의 $1에 액세스하여 모든 NFT를 전송하는 권한이 취소됩니다.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "허용 철회" + }, "revokeSpendingCap": { "message": "$1에 대한 지출 한도 취소", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "이 계약은 현재나 미래의 토큰을 더 이상 사용할 수 없습니다." + "message": "타사가 현재는 물론 미래에도 토큰을 더 이상 사용할 수 없습니다." }, "rpcUrl": { "message": "새 RPC URL" @@ -2986,9 +3665,28 @@ "security": { "message": "보안" }, + "securityAlert": { + "message": "$1 및 $2의 보안 경고" + }, + "securityAlerts": { + "message": "보안 알림" + }, + "securityAlertsDescription1": { + "message": "이 기능은 거래 및 서명 요청을 로컬에서 검토하여 악의적인 활동이 있는 경우 경고합니다. 사용자 데이터는 이 서비스를 제공하는 제3자와 공유되지 않습니다. 요청을 승인하기 전에 항상 직접 실사하세요. 이 기능이 모든 악성 활동 탐지를 보장하지는 않습니다." + }, + "securityAlertsDescription2": { + "message": "요청을 승인하기 전에 항상 직접 확인하세요. 이 기능을 통해 모든 악의적인 활동이 감지된다는 보장이 없습니다." + }, "securityAndPrivacy": { "message": "보안 및 프라이버시" }, + "securityProviderAdviceBy": { + "message": "$1의 보안 권고", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "세부 정보 보기" + }, "seedPhraseConfirm": { "message": "비밀 복구 구문 확인" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "비밀 복구 구문 기록" }, + "select": { + "message": "선택" + }, "selectAccounts": { "message": "계정 선택" }, + "selectAccountsForSnap": { + "message": "이 스냅을 사용할 계정을 선택하세요" + }, "selectAll": { "message": "모두 선택" }, + "selectAllAccounts": { + "message": "모든 계정 선택" + }, "selectAnAccount": { "message": "계정 선택" }, "selectAnAccountAlreadyConnected": { "message": "이 계정은 이미 MetaMask와 연결되어 있습니다." }, + "selectAnAccountHelp": { + "message": "MetaMask Institutional에서 사용할 관리인 계정을 선택하세요." + }, "selectHdPath": { "message": "HD 경로 선택" }, + "selectJWT": { + "message": "토큰 선택" + }, "selectNFTPrivacyPreference": { "message": "설정에서 NFT 감지 켜기" }, @@ -3113,6 +3826,9 @@ "message": "$1 무제한 지출 승인", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "스냅 계정 추가" + }, "settings": { "message": "설정" }, @@ -3141,9 +3857,21 @@ "message": "이 항목을 선택하면 Etherscan을 사용해 거래 목록에 수신 거래를 표시할 수 있습니다.", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "이 기능을 이용하려면 이더리움 주소와 IP 주소에 접근 가능한 네트워크가 필요합니다." + }, + "showMore": { + "message": "더 보기" + }, + "showNft": { + "message": "NFT 표시" + }, "showPermissions": { "message": "권한 표시" }, + "showPrivateKey": { + "message": "개인 키 표시" + }, "showPrivateKeys": { "message": "비공개 키 표시" }, @@ -3169,7 +3897,7 @@ "message": "본 메시지에 서명하는 행위는 위험의 가능성을 내포하고 있습니다. 본 서명을 통해 메시지 발신 당사자에게 귀하의 계정 및 모든 자산에 대해 완전한 권한을 부여할 수 있기 때문입니다. 이를 통해 계정의 모든 잔액을 인출할 수 있기도 하다는 뜻입니다. 주의하여 진행하세요. $1" }, "signed": { - "message": "서명 완료" + "message": "서명완료" }, "signin": { "message": "로그인" @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "본인은 본인의 비밀 복구 구문을 백업하지 않는 한 본인의 계정과 모든 자산을 잃을 수 있다는 사실을 이해합니다." }, + "smartContracts": { + "message": "스마트 계약" + }, + "smartSwap": { + "message": "스마트 스왑" + }, + "smartSwapsAreHere": { + "message": "스마트 스왑이 시작되었습니다!" + }, + "smartSwapsDescription": { + "message": "MetaMask 스왑이 더욱 스마트해졌습니다! 스마트 스왑을 활성화하면 MetaMask가 프로그램을 통해 스왑을 최적화하여 다음과 같은 활동에 도움을 드립니다." + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "스마트 스왑 자금 부족" + }, + "smartSwapsErrorUnavailable": { + "message": "스마트 스왑을 잠시 사용할 수 없습니다." + }, + "smartSwapsSubDescription": { + "message": "*스마트 스왑은 비공개로 거래 제출을 여러 번 시도할 것입니다. 모든 시도가 실패하면 성공적인 스왑을 위해 거래가 공개적으로 브로드캐스트될 것입니다." + }, + "snapConfigure": { + "message": "구성" + }, + "snapConnectionWarning": { + "message": "$1에서 $2 스냅 연결을 원합니다. 해당 웹사이트를 신뢰할 수 있는 경우에만 진행하세요.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "콘텐츠 출처: $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "MetaMask 스냅으로 새 계정의 보안을 유지하는 방법을 선택하세요." + }, + "snapCreateAccountTitle": { + "message": "$1 계정 생성", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "스냅", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "MetaMask 제공" + }, + "snapDetailAudits": { + "message": "감사" + }, + "snapDetailDeveloper": { + "message": "개발자" + }, + "snapDetailLastUpdated": { + "message": "업데이트됨" + }, + "snapDetailManageSnap": { + "message": "스냅 관리" + }, + "snapDetailTags": { + "message": "태그" + }, + "snapDetailVersion": { + "message": "버전" + }, + "snapDetailWebsite": { + "message": "웹사이트" + }, + "snapDetailsCreateASnapAccount": { + "message": "스냅 계정 만들기" + }, + "snapDetailsInstalled": { + "message": "설치됨" + }, "snapError": { "message": "스냅 오류: '$1'. 오류 코드: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "스냅 설치" }, + "snapInstallRequest": { + "message": "$1 설치는 다음과 같은 권한을 허용합니다. $1 스냅을 신뢰하는 경우에만 계속 진행하세요.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "설치 완료" + }, "snapInstallWarningCheck": { "message": "이해하셨으면 모두 체크해 주세요.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "이해하셨으면 모든 란에 체크하세요.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "주의하여 진행하세요" + }, "snapInstallWarningKeyAccess": { "message": "'$1' 스냅 이용에 필요한 $2 키 액세스 권한을 부여하고 있습니다. 이 작업은 사용자의 $2 계정과 자산에 '$1' 제어 권한을 부여하며 취소가 불가능합니다. '$1의 신뢰성을 확인한 후에 진행하세요.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "$1에서 $2 공개 키 액세스 허용", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 스냅을 설치할 수 없습니다.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "설치 실패", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "감사 완료됨" + }, + "snapResultError": { + "message": "오류" + }, + "snapResultSuccess": { + "message": "성공" + }, + "snapResultSuccessDescription": { + "message": "$1 사용 가능" + }, "snapUpdate": { "message": "스냅 업데이트" }, + "snapUpdateAvailable": { + "message": "업데이트 가능" + }, + "snapUpdateErrorDescription": { + "message": "$1 스냅을 업데이트할 수 없습니다.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "업데이트 실패", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1에서 $2 스냅을 $3 버전으로 업데이트하기 원하여 다음 권한을 요청합니다. $2 스냅을 신뢰하는 경우에만 진행하세요.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "업데이트 완료" + }, "snaps": { "message": "스냅" }, "snapsInsightLoading": { "message": "거래 인사이트를 가져오는 중..." }, + "snapsInvalidUIError": { + "message": "스냅에서 지정한 UI가 올바르지 않습니다." + }, "snapsNoInsight": { "message": "스냅이 인사이트를 가져오지 못했습니다" }, + "snapsPrivacyWarningFirstMessage": { + "message": "귀하는 현재 설치하려고 하는 스냅이 Consensys $1에 정의된 바와 같은 타사 서비스임에 해당한다는 사실을 인정합니다. 타사 서비스 이용은 해당 타사 서비스 공급업체의 이용 약관에 따라 별도로 관리됩니다. 타사 서비스에 대한 액세스나 의존 또는 사용에 대한 책임은 전적으로 사용자에게 부과됩니다. Consensys는 타사 서비스 이용으로 인해 귀하의 계정에 발생하는 그 어떤 손실에 대해 모든 책임과 법적 의무를 지지 않습니다.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "타사와 공유하는 모든 정보는 해당 타사의 개인정보 보호 방침에 따라 직접 수집됩니다. 더 자세한 내용은 해당 회사의 개인정보 보호 방침을 참고하시기 바랍니다.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys는 귀하가 타사와 공유한 정보에 대한 접근 권한이 없습니다.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "스냅 관리" }, + "snapsTermsOfUse": { + "message": "이용 약관" + }, "snapsToggle": { "message": "스냅은 활성화된 상태에서만 작동합니다." }, "snapsUIError": { - "message": "스냅에서 지정한 UI가 올바르지 않습니다.", + "message": "$1 작성자에게 연락하여 향후 지원을 요청하세요.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "$1에서 현재나 추후 지출하기에 무리가 없는 금액만 입력하세요. 지출 한도는 나중에 언제든지 상향할 수 있습니다.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "$1 관련 지출 한도 요청" + }, "srpInputNumberOfWords": { "message": "제 구문은 $1개의 단어로 이루어져 있습니다", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "시작하기" }, + "srpSecurityQuizImgAlt": { + "message": "중앙의 열쇠 구멍에 있는 눈과 세 개의 유동 비밀번호 필드" + }, "srpSecurityQuizIntroduction": { "message": "비밀 복구 구문을 찾으려면 두 가지 질문에 올바르게 답해야 합니다" }, @@ -3336,7 +4209,7 @@ "message": "맞습니다! 비밀 복구 구문은 아무와도 공유하면 안 됩니다" }, "srpSecurityQuizQuestionTwoWrongAnswer": { - "message": "주어야 합니다" + "message": "그들에게 이것을 주어야 합니다" }, "srpSecurityQuizQuestionTwoWrongAnswerDescription": { "message": "비밀 복구 구문이 필요하다고 하는 사람은 모두 거짓말쟁이입니다. 그런 자들과 비밀 복구 구문을 공유하면 자산을 도둑맞게 됩니다." @@ -3345,7 +4218,7 @@ "message": "맞습니다! 비밀 복구 구문은 절대로 아무와도 공유하면 안 됩니다" }, "srpSecurityQuizTitle": { - "message": "보안 퀴즈" + "message": "보안 질문" }, "srpToggleShow": { "message": "비밀 복구 구문 중에서 이 단어 공개하기/숨기기", @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "안정적" }, + "stake": { + "message": "스테이크" + }, "stateLogError": { "message": "상태 로그를 가져오는 도중 오류가 발생했습니다." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "연결되지 않음" }, + "statusNotConnectedAccount": { + "message": "계정 연결되지 않음" + }, "step1LatticeWallet": { "message": "Lattice1을 연결하세요." }, @@ -3509,7 +4388,10 @@ "message": "보장 금액" }, "swapAmountReceivedInfo": { - "message": "수신하는 최소 금액입니다. 슬리피지에 따라 추가 금액을 받을 수도 있습니다." + "message": "수신하는 최소 금액입니다. 슬리패지에 따라 추가 금액을 받을 수도 있습니다." + }, + "swapAnyway": { + "message": "스왑 계속 진행" }, "swapApproval": { "message": "스왑을 위해 $1 승인", @@ -3519,6 +4401,12 @@ "message": "이 스왑을 완료하려면 $1개의 추가 $2이(가) 필요합니다.", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "아직 이용 중인가요?" + }, + "swapAreYouStillThereDescription": { + "message": "계속하기 원하시면 최신 견적을 보여드리겠습니다" + }, "swapBuildQuotePlaceHolderText": { "message": "$1와(과) 일치하는 토큰이 없습니다.", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "하드웨어 지갑으로 확인합니다." }, + "swapContinueSwapping": { + "message": "스왑 계속" + }, "swapContractDataDisabledErrorDescription": { "message": "Ledger의 이더리움 앱에서 \"설정 \"으로 이동하여 계약 데이터를 허용한 후, 스왑을 다시 시도하세요." }, @@ -3536,7 +4427,7 @@ "message": "맞춤형" }, "swapDecentralizedExchange": { - "message": "탈중앙화 거래소" + "message": "분산형 교환" }, "swapDirectContract": { "message": "직접 계약" @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "한도 편집" }, + "swapEditTransactionSettings": { + "message": "거래 설정 편집" + }, "swapEnableDescription": { "message": "MetaMask에게 $1 스왑 권한을 부여하는 것으로, 이는 필수입니다.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "스왑하려면 $1이(가) 필요합니다.", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "금액 입력" + }, "swapEstimatedNetworkFees": { "message": "예상 네트워크 수수료" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "스왑 실패" }, + "swapFetchingQuote": { + "message": "견적 가져오기" + }, "swapFetchingQuoteNofN": { "message": "$2 중 $1 견적 가져오기", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3599,17 +4499,24 @@ "description": "$1 is the selected network, e.g. Ethereum or BSC" }, "swapHighSlippageWarning": { - "message": "슬리피지 금액이 아주 큽니다." + "message": "슬리패지 금액이 아주 큽니다." }, "swapIncludesMMFee": { "message": "$1%의 MetaMask 요금이 포함됩니다.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "$1% MetaMask 요금 - $2 포함", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "스왑 자세히 알아보기" + }, "swapLowSlippageError": { - "message": "거래가 실패할 수도 있습니다. 최대 슬리피지가 너무 낮습니다." + "message": "거래가 실패할 수도 있습니다. 최대 슬리패지가 너무 낮습니다." }, "swapMaxSlippage": { - "message": "최대 슬리피지" + "message": "최대 슬리패지" }, "swapMetaMaskFee": { "message": "MetaMask 수수료" @@ -3626,6 +4533,10 @@ "message": "$1의 새 견적", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "$1 단어와 일치하는 토큰이 없습니다", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "$1은(는) 이 거래가 처리되면 귀하의 계정에 추가됩니다.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3645,7 +4556,7 @@ "message": "시장 가격 데이터가 부족하여 가격 영향을 파악할 수 없습니다. 스왑하기 전에 받게 될 토큰 수가 만족스러운지 확인하시기 바랍니다." }, "swapPriceUnavailableTitle": { - "message": "진행하기 전에 비율 확인" + "message": "진행하기 전에 요율 확인" }, "swapProcessing": { "message": "처리 중" @@ -3653,29 +4564,36 @@ "swapQuoteDetails": { "message": "견적 세부 정보" }, + "swapQuoteNofM": { + "message": "$1/$2개", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "견적 소스" }, "swapQuotesExpiredErrorDescription": { - "message": "새 견적을 요청해 최신 비율을 확인하세요." + "message": "새 견적을 요청해 최신 요율을 확인하세요." }, "swapQuotesExpiredErrorTitle": { "message": "견적 시간 초과" }, + "swapQuotesNotAvailableDescription": { + "message": "거래 규모를 줄이거나 다른 토큰을 사용하세요." + }, "swapQuotesNotAvailableErrorDescription": { - "message": "금액 또는 슬리피지 설정을 조정한 후 다시 시도해 보세요." + "message": "금액 또는 슬리패지 설정을 조정한 후 다시 시도해 보세요." }, "swapQuotesNotAvailableErrorTitle": { "message": "사용 가능한 견적 없음" }, "swapRate": { - "message": "비율" + "message": "요율" }, "swapReceiving": { "message": "수신 중" }, "swapReceivingInfoTooltip": { - "message": "이것은 예상치입니다. 정확한 금액은 슬리피지에 따라 달라집니다." + "message": "이것은 예상치입니다. 정확한 금액은 슬리패지에 따라 달라집니다." }, "swapRequestForQuotation": { "message": "견적 요청" @@ -3698,21 +4616,57 @@ "swapSelectQuotePopoverDescription": { "message": "다음은 여러 유동성 소스에서 수집한 전체 견적입니다." }, + "swapSelectToken": { + "message": "토큰 선택" + }, + "swapShowLatestQuotes": { + "message": "최신 견적 표시" + }, "swapSlippageNegative": { + "message": "슬리패지는 0보다 크거나 같아야 합니다." + }, + "swapSlippageNegativeDescription": { "message": "슬리피지는 0보다 크거나 같아야 합니다." }, + "swapSlippageNegativeTitle": { + "message": "계속하려면 슬리피지를 높이세요" + }, + "swapSlippageOverLimitDescription": { + "message": "슬리피지 허용치는 반드시 15% 이하여야 합니다. 이 보다 높으면 비율이 나빠집니다." + }, + "swapSlippageOverLimitTitle": { + "message": "계속하려면 슬리피지를 낯추세요" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "최대 슬리피지가 너무 낮아 거래가 실패할 수 있습니다" + }, + "swapSlippageTooLowTitle": { + "message": "거래 실패를 막으려면 슬리피지를 높이세요" + }, "swapSlippageTooltip": { - "message": "주문 시점과 확인 시점 사이에 가격이 변동되는 현상을 \"슬리피지\"라고 합니다. 슬리피지가 \"최대 슬리피지\" 설정을 초과하면 스왑이 자동으로 취소됩니다." + "message": "주문 시점과 확인 시점 사이에 가격이 변동되는 현상을 \"슬리패지\"라고 합니다. 슬리패지가 \"최대 슬리패지\" 설정을 초과하면 스왑이 자동으로 취소됩니다." + }, + "swapSlippageVeryHighDescription": { + "message": "입력한 슬리피지는 너무 높아 가격이 낮아질 수 있습니다" + }, + "swapSlippageVeryHighTitle": { + "message": "너무 높은 슬리피지" + }, + "swapSlippageZeroDescription": { + "message": "제로 슬리피지 견적 제공자가 거의 없어 견적의 경쟁력이 약화될 수 있습니다" + }, + "swapSlippageZeroTitle": { + "message": "제로 슬리피지 견적 제공자 소싱" }, "swapSource": { "message": "유동성 소스" }, "swapSourceInfo": { - "message": "저희는 여러 유동성 소스(교환, 애그리게이터, 투자전문기관)를 검색하여 최상의 비율과 최저 네트워크 수수료를 찾아드립니다." + "message": "저희는 여러 유동성 소스(교환, 애그리게이터, 투자전문기관)를 검색하여 최상의 요율과 최저 네트워크 수수료를 찾아드립니다." }, "swapSuggested": { "message": "제안 스왑" @@ -3724,7 +4678,7 @@ "message": "다음에서 스왑" }, "swapSwapSwitch": { - "message": "토큰에서 또는 토큰으로 전환" + "message": "토큰 주문 변환" }, "swapSwapTo": { "message": "다음으로 스왑" @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "하드웨어 지갑으로 확인하기 위해" }, + "swapTokenAddedManuallyDescription": { + "message": "$1에서 이 토큰이 거래할 토큰이 맞는지 확인하세요.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "토큰 직접 추가" + }, "swapTokenAvailable": { "message": "$1이(가) 계정에 추가되었습니다.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "$1개 소스에서 확인되었습니다.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 토큰은 1 소스에서만 확인됩니다. 계속 진행하기 전에 $2에서도 확인하세요.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "잠재적 모조 토큰" + }, "swapTooManyDecimalsError": { "message": "$1은(는) 소수점 이하 $2까지 허용됩니다.", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3780,13 +4748,13 @@ "description": "Tells the user how much of a token they have in their balance. $1 is a decimal number amount of tokens, and $2 is a token symbol" }, "swapZeroSlippage": { - "message": "0% 슬리피지" + "message": "0% 슬리패지" }, "swapsAdvancedOptions": { "message": "고급 옵션" }, "swapsExcessiveSlippageWarning": { - "message": "슬리피지 금액이 너무 커서 전환율이 좋지 않습니다. 슬리피지 허용치를 15% 값 이하로 줄이세요." + "message": "슬리패지 금액이 너무 커서 전환율이 좋지 않습니다. 슬리패지 허용치를 15% 값 이하로 줄이세요." }, "swapsMaxSlippage": { "message": "슬리피지 허용치" @@ -3795,9 +4763,16 @@ "message": "$1이(가) 부족하여 이 거래를 완료할 수 없습니다.", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "$1 부족", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "활동에서 보기" }, + "switch": { + "message": "전환" + }, "switchEthereumChainConfirmationDescription": { "message": "이는 MetaMask에서 선택한 네트워크를 이전에 추가한 다음 네트워크로 전환하게 됩니다:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "다음으로 변경했습니다:" }, + "switcherTitle": { + "message": "네트워크 전환기" + }, + "switcherTourDescription": { + "message": "아이콘을 클릭하면 네트워크가 변경되거나 새로운 네트워크가 추가됩니다" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "네트워크를 전환하면 대기 중인 모든 확인 작업이 취소됩니다." }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "서비스 약관" }, + "termsOfUse": { + "message": "이용 약관" + }, + "termsOfUseAgreeText": { + "message": " 본인은 MetaMask의 사용 및 관련 모든 기능에 적용되는 이용약관에 동의합니다" + }, + "termsOfUseFooterText": { + "message": "스크롤하여 모든 섹션의 내용을 확인하세요" + }, + "termsOfUseTitle": { + "message": "이용약관이 개정되었습니다" + }, "testNetworks": { "message": "테스트 네트워크" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "유의 사항:" }, + "thirdPartySoftware": { + "message": "타사 소프트웨어 알림", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "이 컬렉션" + }, + "thisServiceIsExperimental": { + "message": "이 서비스는 시험 기능입니다. 이 기능을 활성화하면 OpenSea의 $1에 동의하는 것입니다.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "시간" }, @@ -3863,11 +4867,44 @@ "message": "수신: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "피싱 공격의 위험이 있습니다. eth_sign을 비활성화해 자신을 보호하세요." + }, "toggleEthSignDescriptionField": { - "message": "이 기능을 켜면 eth_sign 요청을 통해 dapp이 서명을 요청할 수 있습니다. eth_sign은 임의의 해시에 서명할 수 있는 개방형 서명 방법이므로 피싱의 위험이 있습니다. 서명한 내용을 읽을 수 있고 요청의 출처를 신뢰할 수 있는 경우에만 eth_sign 요청에 서명하세요." + "message": "이 설정을 사용하면 읽을 수 없는 요청에 서명하게 될 위험이 있습니다. 이해할 수 없는 메시지에 서명하면 자금이나 NFT 손실에 동의할 수도 있습니다." }, "toggleEthSignField": { - "message": "eth_sign 요청 토글" + "message": "Eth_서명 요청" + }, + "toggleEthSignModalBannerBoldText": { + "message": " 사기 가능성이 있습니다" + }, + "toggleEthSignModalBannerText": { + "message": "이 설정을 켜라는 요청을 받았다면," + }, + "toggleEthSignModalCheckBox": { + "message": "eth_sign 요청을 활성화하면 내 모든 자금과 NFT를 잃을 수도 있다는 사실을 이해합니다. " + }, + "toggleEthSignModalDescription": { + "message": "eth_sign 요청을 허용하면 피싱 공격에 취약해질 수 있습니다. 항상 URL을 검토하고 코드가 포함된 메시지에 서명할 때는 주의하세요." + }, + "toggleEthSignModalFormError": { + "message": "텍스트가 잘못되었습니다" + }, + "toggleEthSignModalFormLabel": { + "message": "계속하려면 \"본인은 이해한 사항에만 서명합니다\"라고 입력하세요" + }, + "toggleEthSignModalFormValidation": { + "message": "본인은 이해한 사항에만 서명합니다" + }, + "toggleEthSignModalTitle": { + "message": "위험을 감수하고 사용하기" + }, + "toggleEthSignOff": { + "message": "끄기(권장 사항)" + }, + "toggleEthSignOn": { + "message": "켜기(비권장 사항)" }, "token": { "message": "토큰" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "토큰 기호" }, + "tokens": { + "message": "토큰" + }, "tokensFoundTitle": { "message": "$1개의 새 토큰을 찾았습니다", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "이해했습니다" }, + "tooltipSatusConnected": { + "message": "연결됨" + }, + "tooltipSatusNotConnected": { + "message": "연결되지 않음" + }, "total": { "message": "합계" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "거래에서 오류가 발생했습니다." }, + "transactionFailed": { + "message": "거래에 실패했습니다" + }, "transactionFee": { "message": "거래 수수료" }, @@ -4014,6 +5063,9 @@ "transactionHistoryTotalGasFee": { "message": "총 가스 수수료" }, + "transactionNote": { + "message": "트랜잭션 메모" + }, "transactionResubmitted": { "message": "$2에서 가스 수수료가 $1(으)로 증가한 거래가 다시 제출되었습니다." }, @@ -4023,6 +5075,9 @@ "transactionSecurityCheckDescription": { "message": "서명하기 전에 해당 트랜잭션과 서명 요청 관련 위험을 탐지하고 표시하기 위해 타사 API를 이용합니다. 이러한 서비스를 이용하시면 해당 서비스가 아직 서명하지 않은 트랜젝션, 서명 요청, 계정 주소 및 기본 언어에 액세스할 수 있습니다." }, + "transactionSettings": { + "message": "거래 설정" + }, "transactionSubmitted": { "message": "$2에서 가스 수수료가 $1인 거래가 제출되었습니다." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "전송 위치" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Ledger 연결에 오류가 발생했습니다. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "하드웨어 지갑 연결 가이드를 확인하고 다시 시도하세요.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Firefox 최신 버전을 사용하신다면 Firefox에서 U2F 지원 중단과 관련된 문제가 발생할 수 있습니다. $1 문제 해결 방법을 알아보세요.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "여기에서", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "$1 연결 도중 문제가 발생했습니다. $2을(를) 검토하고 다시 시도해 보세요.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "상승 화살표" }, + "update": { + "message": "업데이트" + }, "updatedWithDate": { "message": "$1에 업데이트됨" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "이 URL은 현재 $1 네트워크에서 사용됩니다." }, + "use4ByteResolution": { + "message": "스마트 계약 디코딩" + }, + "use4ByteResolutionDescription": { + "message": "인터렉션하는 스마트 계약에 따라 메시지를 이용하여 활동 탭을 사용자 맞춤하여 사용자 경험을 개선합니다. MetaMask는 4byte.directory라는 서비스를 통해 데이터를 디코딩하여 스마트 계약을 읽기 쉬운 버전으로 보여 줍니다. 이는 악의적인 스마트 계약을 승인할 가능성을 줄이는 데 도움이 되지만 IP 주소가 공유될 수 있습니다." + }, "useMultiAccountBalanceChecker": { "message": "일괄 계정 잔액 요청" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "계좌 잔액 요청을 일괄 처리하여 잔액을 더 빠르게 업데이트하세요. 이렇게 하면 계좌 잔액을 일괄적으로 가져올 수 있으므로 업데이트가 빨라져 더 나은 경험을 할 수 있습니다. 이 기능을 비활성화하면 제삼자가 회원님의 계정을 서로 연결할 가능성이 낮아질 수 있습니다." + }, "useNftDetection": { "message": "NFT 자동 감지" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "이더리움 사용자를 노리는 피싱 도메인에 대한 경고를 표시합니다" }, + "useSiteSuggestion": { + "message": "사이트 제안 사용" + }, "useTokenDetectionPrivacyDesc": { "message": "계정으로 전송된 토큰이 자동으로 표시되도록 하려면 타사 서버와의 통신을 통해 토큰 이미지를 불러와야 합니다. 이를 위해 타사 서버는 사용자의 IP 주소에 액세스하게 됩니다." }, @@ -4173,7 +5259,7 @@ "message": "연락처 정보 인증" }, "verifyThisTokenDecimalOn": { - "message": "토큰 소수점은 $1에서 찾을 수 있습니다.", + "message": "토큰 십진수는 $1에서 찾을 수 있습니다.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, "verifyThisTokenOn": { @@ -4184,12 +5270,18 @@ "message": "$1에서 이 토큰이 거래할 토큰이 맞는지 확인하세요.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "버전" + }, "view": { "message": "보기" }, "viewAllDetails": { "message": "모든 세부 정보 보기" }, + "viewAllQuotes": { + "message": "모든 견적 보기" + }, "viewContact": { "message": "연락처 보기" }, @@ -4213,9 +5305,18 @@ "message": "Etherscan에서 $1 보기", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Explorer에서 보기" + }, "viewOnOpensea": { "message": "Opensea에서 보기" }, + "viewPortfolioDashboard": { + "message": "포트폴리오 대시보드 보기" + }, + "viewinCustodianApp": { + "message": "관리자 앱에서 보기" + }, "viewinExplorer": { "message": "Explorer에서 $1 보기", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "이 네트워크를 추가할까요?" }, + "wantsToAddThisAsset": { + "message": "$1에서 이 자산을 지갑으로 추가하기 원합니다", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "경고" }, "warningTooltipText": { - "message": "$1 계약은 추가 통보나 동의 없이도 남은 토큰 전체를 사용할 수 있습니다. 보호를 위해 최소 지출 한도를 설정하세요.", + "message": "$1 타사는 추가 통보나 동의 없이도 남은 토큰 전체를 사용할 수 있습니다. 보호를 위해 지출 한도를 하향하세요.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "서명 중입니다." }, + "yourAccounts": { + "message": "계정" + }, "yourFundsMayBeAtRisk": { "message": "자금이 위험할 수 있습니다" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 540d8d39a..638975d19 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Sobre" }, + "accept": { + "message": "Aceitar" + }, "acceptTermsOfUse": { "message": "Eu li e concordo com os $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Adicionar um apelido" }, + "addAccount": { + "message": "Adicionar conta" + }, "addAcquiredTokens": { "message": "Adicione os tokens que você adquiriu usando a MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Adicione a partir de uma lista de redes populares ou adicione uma rede manualmente. Interaja apenas com entidades de sua confiança." }, + "addHardwareWallet": { + "message": "Adicionar carteira de hardware" + }, + "addIPFSGateway": { + "message": "Adicione seu gateway IPFS preferencial" + }, "addMemo": { "message": "Adicionar observação" }, @@ -244,6 +256,21 @@ "message": "Esta conexão de rede depende de terceiros. Esta conexão pode ser menos confiável ou permitir que terceiros rastreiem atividades. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Adicionar novo token" + }, + "addNft": { + "message": "Adicionar NFT" + }, + "addNfts": { + "message": "Adicionar NFTs" + }, + "addSnapAccountModalDescription": { + "message": "Descubra opções para manter sua conta segura com o MetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Adicionar NFTs sugeridos" + }, "addSuggestedTokens": { "message": "Adicionar tokens sugeridos" }, @@ -251,9 +278,12 @@ "message": "Adicionar token" }, "addTokenByContractAddress": { - "message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1", + "message": "Não consegue encontrar um token? Você pode adicioná-lo manualmente colando seu endereço. Os endereços de contrato do token se encontram em $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Adicionar rede" + }, "address": { "message": "Endereço" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "A taxa de prioridade (ou seja, \"gorjeta do minerador\") vai diretamente para os mineradores e os incentiva a priorizar a sua transação." }, + "agreeTermsOfUse": { + "message": "Concordo com os $1 da MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Vault" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Alertas" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Você já conectou todas as sua contas custodiantes ou não tem nenhuma conta para conectar ao MetaMask Institucional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Nenhuma conta disponível para conectar" + }, "allOfYour": { "message": "Todos os seus $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Todos os seus NFTs de $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Permitir que essa extensão externa:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Permitir que esse site:" }, + "allowThisSnapTo": { + "message": "Permitir que esse snap:" + }, "allowWithdrawAndSpend": { "message": "Permitir que $1 saque e gaste até o seguinte valor:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Valor" }, + "apiUrl": { + "message": "URL da API" + }, "appDescription": { "message": "Extensão para o browser de Ethereum", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Permitir acesso a todos os seus $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Permitir acesso e transferir todos os seus NFTs de $1?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Aprovar" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Isso permite que terceiros acessem e transfiram os seguintes NFTs sem aviso prévio até que você revogue o acesso." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Isso permite que um terceiro acesse e transfira todos os seus NFTs de $1 sem aviso até que você revogue esse acesso.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Permitir acesso e transferência de seu $1?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Se você tentar enviar ativos diretamente de uma rede para outra, isso poderá resultar na perda permanente deles. Certifique-se de usar uma ponte." }, + "attemptToCancelSwap": { + "message": "Tentar cancelar a troca por ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "A tentar ligar à blockchain." }, @@ -411,6 +473,9 @@ "average": { "message": "Média" }, + "awaitingApproval": { + "message": "Aguardando aprovação..." + }, "back": { "message": "Voltar" }, @@ -454,6 +519,9 @@ "message": "Esta é uma versão beta. Pedimos que relatem os bugs $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Versão beta do MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Versão Beta da MetaMask" }, @@ -488,9 +556,45 @@ "message": "Exibir conta em $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Se você aprovar essa solicitação, algum terceiro conhecido por aplicar golpes poderá tomar todos os seus ativos." + }, + "blockaidDescriptionBlurFarming": { + "message": "Se você aprovar essa solicitação, alguém poderá roubar seus ativos listados na Blur." + }, + "blockaidDescriptionFailed": { + "message": "Em razão de um erro, essa solicitação não foi confirmada pelo provedor de segurança. Prossiga com cautela." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Você está interagindo com um domínio mal-intencionado. Se você aprovar essa solicitação, poderá perder seus ativos." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Se você aprovar essa solicitação, poderá perder seus ativos." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Se você aprovar essa solicitação, alguém poderá roubar seus ativos listados na OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Se você aprovar essa solicitação, algum terceiro conhecido por aplicar golpes poderá tomar todos os seus ativos." + }, + "blockaidTitleDeceptive": { + "message": "Esta solicitação é enganosa" + }, + "blockaidTitleMayNotBeSafe": { + "message": "A solicitação pode não ser segura" + }, + "blockaidTitleSuspicious": { + "message": "Esta solicitação é suspeita" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Ponte" + }, "browserNotSupported": { "message": "Seu navegador não é compatível..." }, @@ -510,6 +614,10 @@ "message": "Comprar $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Comprar mais $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Comprar agora" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Isso redefinirá o nonce da conta e apagará os dados da aba de atividades em sua carteira. Somente a conta e rede atuais serão afetadas. Seus saldos e transações recebidas não mudarão." }, + "click": { + "message": "Clique" + }, "clickToConnectLedgerViaWebHID": { "message": "Clique aqui para conectar seu Ledger por meio do WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Você está saindo da MetaMask para configurar esse snap." + }, + "configureSnapPopupInstallDescription": { + "message": "Você está saindo da MetaMask para instalar esse snap." + }, + "configureSnapPopupInstallTitle": { + "message": "Instalar snap" + }, + "configureSnapPopupLink": { + "message": "Clique neste link para continuar:" + }, + "configureSnapPopupTitle": { + "message": "Configurar snap" + }, "confirm": { "message": "Confirmar" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Conectar conta ou criar nova" }, + "connectCustodialAccountMenu": { + "message": "Conectar conta custodiada" + }, + "connectCustodialAccountMsg": { + "message": "Escolha a custodiante que deseja conectar para adicionar ou atualizar um token." + }, + "connectCustodialAccountTitle": { + "message": "Contas custodiadas" + }, "connectManually": { "message": "Conectar manualmente ao site atual" }, + "connectSnap": { + "message": "Conectar $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Conectar a $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Conectando à rede de teste Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Conectando-se à mainnet do Linea" + }, "connectingToMainnet": { "message": "Conectando à mainnet do Ethereum" }, "connectingToSepolia": { "message": "Conectando à rede de teste Sepolia" }, + "connectionFailed": { + "message": "Falha na conexão" + }, + "connectionFailedDescription": { + "message": "Falha ao buscar $1. Verifique sua rede e tente de novo.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Solicitação de conexão" + }, "contactUs": { "message": "Fale conosco" }, @@ -708,7 +860,7 @@ "message": "Implementação do contrato" }, "contractDescription": { - "message": "Para se proteger contra golpistas, reserve um momento para verificar os detalhes do contrato." + "message": "Para se proteger contra golpistas, reserve um momento para verificar os detalhes do terceiro." }, "contractInteraction": { "message": "Interação com o contrato" @@ -717,16 +869,16 @@ "message": "Contrato de NFT" }, "contractRequestingAccess": { - "message": "Contrato solicitando acesso" + "message": "Terceiro solicitando acesso" }, "contractRequestingSignature": { - "message": "Contrato pendente de assinatura" + "message": "Terceiro solicitando assinatura" }, "contractRequestingSpendingCap": { - "message": "Contrato solicitando limite de gastos" + "message": "Terceiro solicitando limite de gastos" }, "contractTitle": { - "message": "Detalhes do contrato" + "message": "Detalhes do terceiro" }, "contractToken": { "message": "Contrato do token" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Gráfico de estimativa de gás do mercado" }, + "custodian": { + "message": "Custodiante" + }, + "custodianAccount": { + "message": "Conta custodiante" + }, + "custodianAccountAddedDesc": { + "message": "Agora você pode usar suas contas de custódia no MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "As contas de custódia selecionadas foram adicionadas." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Acesse $1 e clique no botão \"Conectar ao MMI\" dentro da respectiva interface de usuário para reconectar suas contas ao MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Agora você pode usar suas contas custodiantes no MetaMask Institucional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Seu token custodiante foi atualizado" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Isso substituirá o token custodiante para o seguinte endereço:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Substituir token custodiante" + }, + "custodyApiUrl": { + "message": "URL da API $1" + }, + "custodyDeeplinkDescription": { + "message": "Aprove a transação no app $1. Quando forem dadas todas as aprovações de custódia exigidas, a transação será concluída. Veja o status no seu app $1." + }, + "custodyRefreshTokenModalDescription": { + "message": "Por favor, acesse $1 e clique no botão \"Conectar ao MMI\" dentro da respectiva interface de usuário para conectar suas contas ao MMI novamente." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Seu custodiante emite um token que autentica a extensão MetaMask Institucional, permitindo que você conecte suas contas." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Esse token expira após um determinado período por motivos de segurança. Isso exige que você reconecte ao MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Por que estou vendo isso?" + }, + "custodyRefreshTokenModalTitle": { + "message": "A sessão do seu custodiante expirou" + }, + "custodySessionExpired": { + "message": "Sessão de custodiante expirada." + }, + "custodyWrongChain": { + "message": "Essa conta não está configurada para uso com $1" + }, "custom": { "message": "Avançado" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "suporte ao cliente" }, + "dappRequestedSpendingCap": { + "message": "Limite de gastos solicitado pelo site" + }, "dappSuggested": { "message": "Site sugerido" }, @@ -851,6 +1060,12 @@ "message": "$1 sugeriu esse preço.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Site sugerido" + }, + "dappSuggestedHighShortLabel": { + "message": "Site (alto)" + }, "dappSuggestedShortLabel": { "message": "Site" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Excluir" }, + "deleteContact": { + "message": "Excluir contato" + }, "deleteNetwork": { "message": "Excluir rede?" }, + "deleteNetworkIntro": { + "message": "Se excluir essa rede, você precisará adicioná-la novamente para ver seus ativos nela" + }, + "deleteNetworkTitle": { + "message": "Excluir rede $1?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Depositar" }, @@ -917,6 +1142,10 @@ "description": { "message": "Descrição" }, + "descriptionFromSnap": { + "message": "Descrição de $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Esse erro pode ser intermitente. Por isso, tente reiniciar a extensão ou desative a MetaMask para desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Descartar o lembrete de backup da Frase de Recuperação Secreta" }, + "displayNftMedia": { + "message": "Exibir mídias de NFTs" + }, + "displayNftMediaDescription": { + "message": "Exibir mídias e dados de NFTs expõe seu endereço IP à OpenSea ou outros terceiros. Isso pode possibilitar que invasores associem seu endereço IP ao seu endereço Ethereum. A detecção automática de NFTs depende dessa configuração e ficará indisponível quando ela estiver desativada." + }, "domain": { "message": "Domínio" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Ativar detecção automática" }, + "enableForAllNetworks": { + "message": "Ativar para todas as redes" + }, "enableFromSettings": { "message": " Habilite-a nas configurações." }, + "enableSmartSwaps": { + "message": "Ativar trocas inteligentes" + }, + "enableSnap": { + "message": "Ativar" + }, "enableToken": { "message": "ativar $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Ativado" + }, "encryptionPublicKeyNotice": { "message": "$1 gostaria da sua chave pública de criptografia. Ao consentir, este site conseguirá redigir mensagens criptografadas para você.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "A detecção aprimorada de tokens está disponível no momento em $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "A MetaMask permite que você veja domínios ENS, como \"https://metamask.eth\", direto da barra de endereço do seu navegador. Veja como funciona:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Os navegadores comuns normalmente não lidam com endereços ENS ou IPFS, mas a MetaMask ajuda nisso. Usar esse recurso pode compartilhar seu endereço IP com serviços terceirizados de IPFS." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "A MetaMask verifica com o contrato ENS da Ethereum para encontrar o código conectado ao nome ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Se o código está vinculado ao IPFS, ele recebe o conteúdo da rede IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Então, você poderá ver o conteúdo, geralmente um website ou algo similar." + }, + "ensDomainsSettingTitle": { + "message": "Exibir domínios ENS na barra de endereço" + }, "ensIllegalCharacter": { "message": "Caractere inválido para ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Insira um número" }, + "enterCustodianToken": { + "message": "Insira seu token $1 ou adicione um novo" + }, "enterMaxSpendLimit": { "message": "Digite um limite máximo de gastos" }, + "enterOptionalPassword": { + "message": "Insira a senha opcional" + }, "enterPassword": { "message": "Introduza palavra-passe" }, "enterPasswordContinue": { "message": "Insira a senha para continuar" }, + "enterTokenNameOrAddress": { + "message": "Insira o nome do token ou cole o endereço" + }, + "enterYourPassword": { + "message": "Insira sua senha" + }, "errorCode": { "message": "Código: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Lista:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Erro ao conectar à rede personalizada." + }, + "errorWithSnap": { + "message": "Erro com $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "O preço de backup do gás é fornecido porque a estimativa de gás principal está indisponível no momento." }, + "ethereumProviderAccess": { + "message": "Conceder ao Ethereum acesso de provedor a $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Endereço público do Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Experimental" }, + "exploreMetaMaskSnaps": { + "message": "Explorar os snaps da MetaMask" + }, "exportPrivateKey": { "message": "Exportar chave privada" }, + "extendWalletWithSnaps": { + "message": "Amplie a experiência da carteira." + }, "externalExtension": { "message": "Extensão externa" }, @@ -1302,6 +1596,9 @@ "message": "A importação de ficheiro não está a funcionar? Carregue aqui!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "O arquivo inserido é grande demais." + }, "flaskWelcomeUninstall": { "message": "você deve desinstalar essa extensão", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Geral" }, + "getStarted": { + "message": "Começar" + }, + "globalTitle": { + "message": "Menu global" + }, + "globalTourDescription": { + "message": "Veja seu portfólio, sites conectados, configurações e mais" + }, "goBack": { "message": "Voltar" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Conecte uma carteira de hardware" }, + "hardwareWalletsInfo": { + "message": "As integrações com carteiras de hardware usam chamadas de API para servidores externos, os quais podem ver seu endereço IP e os endereços de contratos inteligentes com os quais você interage." + }, "hardwareWalletsMsg": { "message": "Selecione uma carteira de hardware que você gostaria de usar com a MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "mas os phishers talvez solicitem.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Sua chave privada oferece a $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "acesso total à sua carteira e fundos.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "círculo \"segure para revelar\" bloqueado" + }, + "holdToRevealPrivateKey": { + "message": "Segure para revelar a chave privada" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Mantenha sua chave privada em segurança" + }, "holdToRevealSRP": { - "message": "Mantenha pressionado para revelar FRS" + "message": "Segure para revelar a FRS" }, "holdToRevealSRPTitle": { - "message": "Mantenha sua FRS protegida" + "message": "Mantenha sua FRS em segurança" + }, + "holdToRevealUnlockedLabel": { + "message": "círculo \"segure para revelar\" desbloqueado" + }, + "id": { + "message": "Id" }, "ignoreAll": { "message": "Ignorar tudo" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Erro de importação de conta." }, + "importAccountErrorIsSRP": { + "message": "Você inseriu uma Frase de Recuperação Secreta (ou mnemônica). Para importar uma conta aqui, você precisa inserir uma chave privada, que é uma sequência hexadecimal de 64 caracteres." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Essa chave privada não é válida. Você inseriu uma sequência hexadecimal, mas seu tamanho deve ser de 64 caracteres." + }, + "importAccountErrorNotHexadecimal": { + "message": "Essa chave privada não é válida. Você deve inserir uma sequência hexadecimal de 64 caracteres." + }, + "importAccountJsonLoading1": { + "message": "É esperado que a importação do JSON leve alguns minutos e trave a MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Pedimos desculpas, e vamos acelerar esse processo futuramente." + }, "importAccountMsg": { - "message": "Contas importadas não irão ser associadas com a frase seed da conta criada originalmente pelo MetaMask. Saiba mais sobre contas importadas." + "message": "Contas importadas não serão associadas à sua Frase Secreta de Recuperação na MetaMask. Saiba mais sobre contas importadas" }, "importMyWallet": { "message": "Importar minha carteira" @@ -1615,18 +1962,29 @@ "message": "Sua transação inicial foi confirmada pela rede. Clique em OK para voltar." }, "inputLogicEmptyState": { - "message": "Somente insira um número com o qual esteja confortável de o contrato gastar agora ou no futuro. Você pode aumentar o limite de gastos a qualquer momento." + "message": "Somente insira um número com o qual esteja confortável de o terceiro gastar agora ou no futuro. Você pode aumentar o limite de gastos a qualquer momento." }, "inputLogicEqualOrSmallerNumber": { - "message": "Isso permite que o contrato gaste $1 de seu saldo atual.", + "message": "Isso permite que o terceiro gaste $1 do seu saldo atual.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Isso permite que o contrato gaste todo o seu saldo de tokens até atingir o limite ou até você revogar o limite de gastos. Se a intenção não é essa, considere definir um limite de gastos menor." + "message": "Isso permite que o terceiro gaste todo o seu saldo de tokens até atingir o limite ou até você revogar o limite de gastos. Se a intenção não é essa, considere definir um limite de gastos menor." + }, + "insightsFromSnap": { + "message": "Insights de $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Instalar" }, + "installOrigin": { + "message": "Origem da instalação" + }, + "installedOn": { + "message": "Instalado em $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Saldo insuficiente." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Entrada inválida! A frase secreta de recuperação diferencia maiúsculas e minúsculas." }, + "ipfsGateway": { + "message": "Gateway IPFS" + }, + "ipfsGatewayDescription": { + "message": "A MetaMask usa serviços terceirizados para exibir imagens de seus NFTs armazenados no IPFS, exibir informações relacionadas a endereços ENS inseridos na barra de endereço do seu navegador e buscar ícones para diferentes tokens. Seu endereço IP pode ser exposto a esses serviços ao usá-los." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Usamos serviços terceirizados para exibir imagens de seus NFTs armazenados no IPFS, exibir informações relacionadas a endereços ENS inseridos na barra de endereço do seu navegador e buscar ícones para diferentes tokens. Seu endereço IP pode ser exposto a esses serviços durante o uso deles." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Ao selecionar Confirmar, é ativada a resolução IPFS. Você pode desativá-la a qualquer momento em $1.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Configurações > Segurança e privacidade" + }, "jazzAndBlockies": { "message": "Jazzicons e Blockies são dois estilos diferentes de ícones únicos que ajudam você a identificar uma conta num relance." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Última venda" }, + "layer1Fees": { + "message": "Taxas de camada 1" + }, "learnCancelSpeeedup": { "message": "Saiba como $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Quer $1 sobre gás?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Saiba mais" + }, "learnMoreUpperCase": { "message": "Saiba mais" }, @@ -1765,16 +2145,16 @@ "message": "Antes de clicar em confirmar:" }, "ledgerConnectionInstructionStepFour": { - "message": "Ative \"dados de contrato inteligente\" ou \"assinatura cega\" no seu dispositivo Ledger" + "message": "Ative \"dados de contrato inteligente\" ou \"assinatura cega\" no seu dispositivo Ledger." }, "ledgerConnectionInstructionStepOne": { - "message": "Ative Utilizar Ledger Live em Configurações > Avançado" + "message": "Ative Utilizar Ledger Live em Configurações > Avançado." }, "ledgerConnectionInstructionStepThree": { - "message": "Conecte o seu dispositivo Ledger e selecione o app Ethereum" + "message": "Certifique-se de conectar o seu dispositivo Ledger e de selecionar o app Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Abra e desbloqueie o app Ledger Live" + "message": "Abra e desbloqueie o app Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Personalize como conectar sua Ledger à MetaMask. Recomenda-se utilizar $1, mas há outras opções. Leia mais aqui: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Rede de teste Linea Goerli" }, + "lineaMainnet": { + "message": "Mainnet do Linea" + }, "link": { "message": "Link" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Sair" }, + "lockMetaMask": { + "message": "Trancar MetaMask" + }, + "lockTimeInvalid": { + "message": "O tempo de bloqueio deve ser um número entre 0 e 10080" + }, "logo": { "message": "Logotipo do $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "O botão de status da conexão mostra se o website que você está visitando está conectado à conta selecionada no momento." }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Institutional versão" + }, "metamaskSwapsOfflineDescription": { "message": "O recurso de swaps do MetaMask está em manutenção. Verifique novamente mais tarde." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Métricas" }, + "mismatchAccount": { + "message": "Sua conta selecionada ($1) é diferente da conta que está tentando assinar ($2)" + }, "mismatchedChainLinkText": { "message": "verifique os detalhes da rede", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "O símbolo de moeda enviado não corresponde ao esperado para a ID desta cadeia." }, + "mismatchedRpcChainId": { + "message": "A rede personalizada retornou um ID de cadeia que não coincide com o ID de cadeia enviado." + }, "mismatchedRpcUrl": { "message": "De acordo com os nossos registros, o valor da URL da RPC enviado não corresponde a um provedor conhecido da ID desta cadeia." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Solicite aqui" }, + "mmiAddToken": { + "message": "A página em $1 gostaria de autorizar o seguinte token de custodiante na MetaMask Institucional" + }, + "mmiBuiltAroundTheWorld": { + "message": "O MetaMask Institutional é projetado e desenvolvido ao redor do mundo." + }, + "more": { + "message": "mais" + }, "moreComingSoon": { - "message": "Mais em breve..." + "message": "Mais provedores em breve" + }, + "multipleSnapConnectionWarning": { + "message": "$1 deseja conectar-se a $2 snaps. Prossiga apenas se você confia no site.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Deve escolher no mínimo 1 token." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "A rede está ocupada. Os preços de gás estão altos e as estimativas estão menos exatas." }, + "networkMenu": { + "message": "Menu da rede" + }, + "networkMenuHeading": { + "message": "Selecione uma rede" + }, "networkName": { "message": "Nome da rede" }, @@ -2033,6 +2450,10 @@ "message": "As taxas de gás estão $1 em relação às últimas 72 horas.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Não podemos conectar a $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL da rede" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFTs" }, + "nftsPreviouslyOwned": { + "message": "Detidos anteriormente" + }, "nickname": { "message": "Apelido" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Não há uma taxa de conversão disponível" }, + "noNFTs": { + "message": "Nenhum NFT até agora" + }, + "noNetworksFound": { + "message": "Nenhuma rede encontrada para a consulta de pesquisa fornecida" + }, "noSnaps": { - "message": "Nenhum snap instalado" + "message": "Você não tem nenhum snap instalado." }, "noThanksVariant2": { "message": "Não, obrigado." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Essa é a conta correta? É diferente da conta atualmente selecionada na sua carteira" }, + "notEnoughBalance": { + "message": "Saldo insuficiente" + }, "notEnoughGas": { "message": "Não há gás suficiente" }, + "note": { + "message": "Observação" + }, + "notePlaceholder": { + "message": "O aprovador verá essa observação ao aprovar a transação no custodiante." + }, + "notificationTransactionFailedMessage": { + "message": "Falha na transação $1! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Falha na transação! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Falha na transação", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Transação $1 confirmada!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Transação confirmada", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Ver em $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Notificações" + }, "notifications10ActionText": { "message": "Visite nas configurações", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "A fusão do Ethereum chegou!" }, + "notifications18ActionText": { + "message": "Ativar alertas de segurança" + }, + "notifications18DescriptionOne": { + "message": "Receba alertas de terceiros quando houver a possibilidade de ter recebido uma solicitação mal-intencionada.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Sempre se certifique de fazer sua devida diligência antes de aprovar qualquer solicitação.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "O OpenSea é o primeiro provedor desse recurso. Mais provedores em breve!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Fique protegido com alertas de segurança" + }, + "notifications19ActionText": { + "message": "Ativar detecção automática de NFTs" + }, + "notifications19DescriptionOne": { + "message": "Há duas maneiras de começar:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Oferecemos suporte apenas ao ERC-721 no momento.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Adicione manualmente seus NFTs ou ative a detecção automática de NFTs em Configurações > Experimental.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Veja seus NFTs como nunca viu antes" + }, "notifications1Description": { "message": "Usuários da MetaMask Mobile agora podem trocar tokens dentro de sua carteira mobile. Leia o QR code para obter o aplicativo para dispositivos móveis e comece a trocar.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Você já pode fazer trocas em dispositivos móveis!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Saiba mais", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Se você está usando a versão mais recente do Firefox, talvez esteja com um problema relacionado ao Firefox ter abandonado o suporte ao U2F.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Usuários do Ledger e Firefox com problemas de conexão", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Experimentar" + }, + "notifications21Description": { + "message": "Atualizamos as trocas na extensão da MetaMask para que seu uso seja mais fácil e rápido.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Conheça as novas trocas atualizadas!" + }, + "notifications22ActionText": { + "message": "Entendi" + }, + "notifications22Description": { + "message": "💡 Basta clicar no menu global ou menu da conta para encontrar!" + }, + "notifications22Title": { + "message": "Procurando os dados da sua conta ou o URL do explorador de blocos?" + }, + "notifications23ActionText": { + "message": "Ativar alertas de segurança" + }, + "notifications23DescriptionOne": { + "message": "Fique longe de golpes conhecidos enquanto preserva sua privacidade com alertas de segurança com tecnologia da Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Se você ativou os alertas de segurança da OpenSea, fizemos sua transição para esse recurso." + }, + "notifications23DescriptionTwo": { + "message": "Sempre faça sua própria devida diligência antes de aprovar solicitações." + }, + "notifications23Title": { + "message": "Fique protegido com os alertas de segurança" + }, "notifications3ActionText": { "message": "Ler mais", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Conecte-se somente com sites em que você confia." }, "openFullScreenForLedgerWebHid": { - "message": "Abra a MetaMask em tela cheia para conectar sua ledger por meio do WebHID.", + "message": "Abra o app em tela cheia para conectar seu Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Abrir no explorador de blocos" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Falha na operação" + }, "optional": { "message": "Opcional" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "As senhas não coincidem" }, + "pasteJWTToken": { + "message": "Cole ou solte seu token aqui:" + }, "pastePrivateKey": { "message": "Cole aqui a sua chave privada:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Acesse a internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Permitir que o snap acesse a internet. Isso pode ser usado para enviar e receber dados de servidores de terceiros.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Conecte-se ao snap $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Permitir que o site ou snap interaja com $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Agende e execute ações periódicas.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Permitir que o snap realize ações que são executadas periodicamente em horários, datas ou intervalos fixos. Isso pode ser usado para disparar interações ou notificações sensíveis ao tempo.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Exibir janelas de diálogo na MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Permitir que o snap exiba pop-ups da MetaMask com texto personalizado, campo para entrada de informações e botões para aprovar ou rejeitar uma ação.\nPode ser usado, por exemplo, para criar alertas, confirmações e fluxos de adesão para um snap.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Ver endereço, saldo da conta, atividade e iniciar transações", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Acesse o provedor do Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Permitir que o snap se comunique diretamente com a MetaMask, para que possa ler dados da blockchain e sugerir mensagens e transações.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Derive chaves arbitrárias únicas para este snap.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Permitir que o snap derive chaves arbitrárias únicas a esse snap sem as expor. Essas chaves são separadas das suas contas na MetaMask e não estão relacionadas às suas chaves privadas ou à Frase de Recuperação Secreta. Os outros snaps não podem acessar essas informações.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Usar ganchos de ciclo de vida.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Permita que o snap use ganchos de clico de vida para executar código em momentos específicos durante seu ciclo de vida.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Executar indefinidamente.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Permitir que o snap seja executado indefinidamente, por exemplo, enquanto processa grandes volumes de dados.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Adicionar e controlar contas Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Controle suas contas e ativos em $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Permitir que o snap derive pares de chaves BIP-32 baseadas na sua Frase de Recuperação Secreta sem a expor. Isso concede acesso total a todas as contas e ativos em $1.\nCom o poder para gerenciar chaves, o snap é compatível com vários protocolos da blockchain além do Ethereum (EVMs).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Controlar suas contas e ativos do \"$1\".", + "message": "Controle suas contas e ativos do $1.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Permitir que o snap derive pares de chaves BIP-44 baseadas na sua Frase de Recuperação Secreta sem a expor. Isso concede acesso total a todas as contas e ativos em $1.\nCom o poder para gerenciar chaves, o snap é compatível com vários protocolos da blockchain além do Ethereum (EVMs).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Controle suas contas e ativos em $1.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Armazenar e gerenciar dados pertinentes em seu dispositivo.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Permitir que o snap armazene, atualize e recupere dados de maneira segura usando criptografia. Os outros snaps não podem acessar essas informações.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Mostrar notificações.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Permitir que o snap exiba notificações dentro da MetaMask. Um breve texto de notificação pode ser disparado por um snap para informações acionáveis ou sensíveis ao tempo.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Permitir que $1 se comuniquem diretamente com esse snap.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Permitir que $1 enviem mensagens ao snap e recebam resposta dele.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Busque e exiba insights de transações.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Permitir que o snap decodifique transações e exiba informações dentro da interface da MetaMask. Isso pode ser usado para soluções de segurança e antiphishing.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Verá as origens dos sites que sugerem transações", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Permitir que o snap veja a origem (URI) dos sites que sugerirem transações. Isso pode ser usado para soluções de segurança e antiphishing.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Permissão desconhecida: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Ver sua chave pública para $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Permitir que o snap veja suas chaves públicas (e endereços) referentes a $1. Isso não concede nenhum tipo de controle das contas ou ativos.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Veja sua chave pública para $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Suporte a WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Permitir que o snap acesse ambientes de execução de baixo nível via WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Permissões" }, + "permissionsTitle": { + "message": "Permissões" + }, + "permissionsTourDescription": { + "message": "Encontre suas contas conectadas e gerencie permissões aqui" + }, "personalAddressDetected": { "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portfólio" }, + "portfolioDashboard": { + "message": "Painel do portfólio" + }, "preferredLedgerConnectionType": { "message": "Tipo de conexão preferencial com o Ledger", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Chave Privada", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Chave privada de $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Atenção: Nunca revele esta chave. Qualquer pessoa com acesso à sua chave privada pode roubar os bens que esta contém." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Na fila" }, + "quoteRate": { + "message": "Taxa de cotação" + }, "reAddAccounts": { "message": "readicione outras contas" }, @@ -2751,7 +3401,7 @@ "message": "Receber" }, "recipientAddressPlaceholder": { - "message": "Pesquisa, endereço público (0x) ou ENS" + "message": "Insira o endereço público (0x) ou o nome ENS" }, "recommendedGasLabel": { "message": "Recomendado" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Essa conta será removida da sua carteira. Antes de continuar, você precisa garantir que tem a Frase de Recuperação Secreta original ou chave privada para essa conta importada. Você pode importar ou criar contas novamente a partir do menu suspenso da conta. " }, + "removeJWT": { + "message": "Remover token custodiante" + }, + "removeJWTDescription": { + "message": "Tem certeza de que deseja remover este token? Todas as contas atribuídas a ele também serão removidas da extensão: " + }, "removeNFT": { "message": "Remover NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Você pode restaurar as configurações do usuário contendo preferências e endereços de contas a partir de um arquivo de backup JSON." }, + "resultPageError": { + "message": "Erro" + }, + "resultPageErrorDefaultMessage": { + "message": "A operação falhou." + }, + "resultPageSuccess": { + "message": "Sucesso" + }, + "resultPageSuccessDefaultMessage": { + "message": "A operação foi concluída com sucesso." + }, "retryTransaction": { "message": "Tentar transação novamente" }, @@ -2939,16 +3607,27 @@ "message": "Revogar permissão de acesso e transferência de todos os seus $1?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Revogar a permissão para acessar e transferir todos os seus NFTs de $1?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Isso revoga a permissão de terceiros para acessar e transferir todos os seus $1 sem aviso prévio.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Isso revoga a permissão de um terceiro para acessar e transferir todos os seus NFTs de $1 sem aviso.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Revogar permissão" + }, "revokeSpendingCap": { "message": "Revogar limite de gastos de seu $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Este contrato não poderá gastar mais nenhum de seus tokens atuais ou futuros." + "message": "Esse terceiro não poderá gastar mais nenhum dos seus tokens atuais ou futuros." }, "rpcUrl": { "message": "Novo URL da RPC" @@ -2986,9 +3665,28 @@ "security": { "message": "Segurança" }, + "securityAlert": { + "message": "Alerta de segurança de $1 e $2" + }, + "securityAlerts": { + "message": "Alertas de segurança" + }, + "securityAlertsDescription1": { + "message": "Esse recurso alerta sobre atividades mal-intencionadas ao analisar localmente suas transações e solicitações de assinatura. Seus dados não são compartilhados com os terceiros prestadores do serviço. Sempre faça sua própria devida diligência antes de aprovar qualquer solicitação. Não há garantia de que esse recurso detectará toda e qualquer atividade mal-intencionada." + }, + "securityAlertsDescription2": { + "message": "Sempre se certifique de fazer a devida diligência por conta própria antes de aprovar eventuais solicitações. Não há garantia de que toda e qualquer atividade mal-intencionada será detectada por esse recurso." + }, "securityAndPrivacy": { "message": "Segurança e privacidade" }, + "securityProviderAdviceBy": { + "message": "Conselhos de segurança de $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Ver detalhes" + }, "seedPhraseConfirm": { "message": "Confirmar Frase Secreta de Recuperação" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Anote sua Frase Secreta de Recuperação" }, + "select": { + "message": "Selecionar" + }, "selectAccounts": { "message": "Selecione a(s) conta(s) para usar nesse site" }, + "selectAccountsForSnap": { + "message": "Selecione a(s) conta(s) para usar com esse snap" + }, "selectAll": { "message": "Selecionar tudo" }, + "selectAllAccounts": { + "message": "Selecionar todas as contas" + }, "selectAnAccount": { "message": "Selecione uma conta" }, "selectAnAccountAlreadyConnected": { "message": "Essa conta já foi conectada à MetaMask" }, + "selectAnAccountHelp": { + "message": "Selecione as contas custodiantes para usar no MetaMask Institucional." + }, "selectHdPath": { "message": "Selecione o caminho do disco rígido" }, + "selectJWT": { + "message": "Selecionar token" + }, "selectNFTPrivacyPreference": { "message": "Ative a detecção de NFTs nas configurações" }, @@ -3113,6 +3826,9 @@ "message": "Aprovar $1 sem limite de gastos", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Adicionar conta de snaps" + }, "settings": { "message": "Definições" }, @@ -3141,9 +3857,21 @@ "message": "Selecione essa opção para usar o Etherscan e mostrar as transações recebidas na lista de transações", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Isso depende de cada rede que terá acesso ao seu endereço Ethereum e seu endereço IP." + }, + "showMore": { + "message": "Exibir mais" + }, + "showNft": { + "message": "Exibir NFT" + }, "showPermissions": { "message": "Mostrar permissões" }, + "showPrivateKey": { + "message": "Exibir chave privada" + }, "showPrivateKeys": { "message": "Mostrar Chaves Privadas" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Compreendo que, até fazer o backup da minha Frase de Recuperação Secreta, poderei perder minhas contas e todos os ativos contidos nela." }, + "smartContracts": { + "message": "Contratos inteligentes" + }, + "smartSwap": { + "message": "Troca inteligente" + }, + "smartSwapsAreHere": { + "message": "As trocas inteligentes chegaram!" + }, + "smartSwapsDescription": { + "message": "As trocas na MetaMask ficaram muito mais inteligentes! Ativar as trocas inteligentes permitirá que a MetaMask otimize programaticamente sua troca para ajudar:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Fundos insuficientes para uma troca inteligente." + }, + "smartSwapsErrorUnavailable": { + "message": "As trocas inteligentes estão temporariamente indisponíveis." + }, + "smartSwapsSubDescription": { + "message": "* As trocas inteligentes tentarão enviar sua transação de forma privada, diversas vezes. Se todas as tentativas falharem, a transação será transmitida publicamente para garantir que sua troca ocorra com sucesso." + }, + "snapConfigure": { + "message": "Configurar" + }, + "snapConnectionWarning": { + "message": "$1 deseja conectar-se a $2. Continue apenas se você confia no site.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Esse conteúdo vem de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Escolha como proteger sua nova conta usando o MetaMask Snaps." + }, + "snapCreateAccountTitle": { + "message": "Criar uma conta $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Da MetaMask" + }, + "snapDetailAudits": { + "message": "Auditoria" + }, + "snapDetailDeveloper": { + "message": "Desenvolvedor" + }, + "snapDetailLastUpdated": { + "message": "Atualizado" + }, + "snapDetailManageSnap": { + "message": "Gerenciar snap" + }, + "snapDetailTags": { + "message": "Tags" + }, + "snapDetailVersion": { + "message": "Versão" + }, + "snapDetailWebsite": { + "message": "Site" + }, + "snapDetailsCreateASnapAccount": { + "message": "Criar uma conta de snaps" + }, + "snapDetailsInstalled": { + "message": "Instalado" + }, "snapError": { "message": "Erro no snap: '$1'. Código de erro: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Instalar snap" }, + "snapInstallRequest": { + "message": "Instalar $1 concede as permissões a seguir. Continue apenas se você confia em $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Instalação concluída" + }, "snapInstallWarningCheck": { "message": "Para confirmar que você entende, marque a caixa.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Para confirmar que você entende, marque todas as caixas.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Prossiga com cautela" + }, "snapInstallWarningKeyAccess": { "message": "Você está concedendo ao snap \"$1\" acesso à sua chave $2. Isso é irrevogável e concede a \"$1\" controle de suas contas e ativos $2. Certifique-se de que confia em \"$1\" antes de prosseguir.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Conceder a $1 acesso à chave pública de $2", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 não pôde ser instalado.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Falha na instalação", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Auditado" + }, + "snapResultError": { + "message": "Erro" + }, + "snapResultSuccess": { + "message": "Sucesso" + }, + "snapResultSuccessDescription": { + "message": "$1 está pronto para ser usado" + }, "snapUpdate": { "message": "Atualizar snap" }, + "snapUpdateAvailable": { + "message": "Atualização disponível" + }, + "snapUpdateErrorDescription": { + "message": "$1 não pôde ser atualizado.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Falha na atualização", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 deseja atualizar $2 para $3, o que concede as permissões a seguir. Continue apenas se você confia em $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Atualização concluída" + }, "snaps": { "message": "Snaps" }, "snapsInsightLoading": { "message": "Carregando insight da transação..." }, + "snapsInvalidUIError": { + "message": "A IU especificada pelo snap é inválida." + }, "snapsNoInsight": { "message": "O snap não retornou nenhum insight" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Você reconhece que o snap que está prestes a instalar é um Serviço de Terceiros, conforme definido nos $1 da Consensys. O uso de Serviços de Terceiros é regido por termos e condições separados, definidos pelo prestador de Serviços de Terceiros. Você acessa, confia ou usa o Serviço de Terceiros por sua conta e risco. A Consensys se isenta de todas as responsabilidades por perdas relacionadas ao seu uso de Serviços de Terceiros.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Informações compartilhadas com Serviços de Terceiros serão coletadas diretamente por eles, de acordo com políticas de privacidade próprias. Por favor, consulte-as para obter mais informações.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "A Consensys não tem acesso às informações que você compartilha com terceiros.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Gerencie seus snaps" }, + "snapsTermsOfUse": { + "message": "Termos de Uso" + }, "snapsToggle": { "message": "O snap só será executado se estiver ativado" }, "snapsUIError": { - "message": "A IU especificada pelo snap é inválida.", + "message": "Contate os criadores de $1 para receber mais suporte.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Somente insira um número com o qual esteja confortável de $1 acessar agora ou no futuro. Você pode aumentar o limite de tokens a qualquer momento.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Solicitação de limite de gastos para seu $1" + }, "srpInputNumberOfWords": { "message": "Eu tenho uma frase com $1 palavras", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,11 +4169,14 @@ "srpSecurityQuizGetStarted": { "message": "Começar" }, + "srpSecurityQuizImgAlt": { + "message": "Um olho com um buraco de fechadura no centro, e três campos de senha flutuando" + }, "srpSecurityQuizIntroduction": { - "message": "Para revelar sua Frase de Recuperação Secreta, você precisa responder corretamente duas perguntas" + "message": "Para revelar sua Frase Secreta de Recuperação, você precisa responder corretamente duas perguntas" }, "srpSecurityQuizQuestionOneQuestion": { - "message": "Se você perder sua Frase de Recuperação Secreta, a MetaMask..." + "message": "Se você perder sua Frase Secreta de Recuperação, a MetaMask..." }, "srpSecurityQuizQuestionOneRightAnswer": { "message": "Não poderá ajudar" @@ -3312,37 +4185,37 @@ "message": "Anote-a, grave em metal ou guarde-a em diversos lugares secretos para que nunca a perca. Se perdê-la, é para sempre." }, "srpSecurityQuizQuestionOneRightAnswerTitle": { - "message": "Certo! Ninguém pode ajudar a recuperar sua Frase de Recuperação Secreta" + "message": "Certo! Ninguém pode ajudar a recuperar sua Frase Secreta de Recuperação" }, "srpSecurityQuizQuestionOneWrongAnswer": { "message": "Poderá recuperá-la para você" }, "srpSecurityQuizQuestionOneWrongAnswerDescription": { - "message": "Se você perder sua Frase de Recuperação Secreta, é para sempre. Ninguém consegue ajudar a recuperá-la, não importa o que digam." + "message": "Se você perder sua Frase Secreta de Recuperação, é para sempre. Ninguém consegue ajudar a recuperá-la, não importa o que digam." }, "srpSecurityQuizQuestionOneWrongAnswerTitle": { - "message": "Errado! Ninguém consegue recuperar sua Frase de Recuperação Secreta" + "message": "Errado! Ninguém consegue recuperar sua Frase Secreta de Recuperação" }, "srpSecurityQuizQuestionTwoQuestion": { - "message": "Se alguém, até mesmo um atendente do suporte, pedir sua Frase de Recuperação Secreta..." + "message": "Se alguém, até mesmo um atendente do suporte, pedir sua Frase Secreta de Recuperação..." }, "srpSecurityQuizQuestionTwoRightAnswer": { "message": "Você estará sendo vítima de um golpe" }, "srpSecurityQuizQuestionTwoRightAnswerDescription": { - "message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados." + "message": "Qualquer pessoa que afirme precisar da sua Frase Secreta de Recuperação está mentindo. Se você compartilhar com ela, seus ativos serão roubados." }, "srpSecurityQuizQuestionTwoRightAnswerTitle": { - "message": "Correto! Compartilhar sua Frase de Recuperação Secreta nunca é uma boa ideia" + "message": "Correto! Compartilhar sua Frase Secreta de Recuperação nunca é uma boa ideia" }, "srpSecurityQuizQuestionTwoWrongAnswer": { "message": "Você deverá revelar" }, "srpSecurityQuizQuestionTwoWrongAnswerDescription": { - "message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados." + "message": "Qualquer pessoa que afirme precisar da sua Frase Secreta de Recuperação está mentindo. Se você compartilhar com ela, seus ativos serão roubados." }, "srpSecurityQuizQuestionTwoWrongAnswerTitle": { - "message": "Não! Não compartilhe sua Frase de Recuperação Secreta com ninguém, nunca" + "message": "Não! Não compartilhe sua Frase Secreta de Recuperação com ninguém, nunca" }, "srpSecurityQuizTitle": { "message": "Quiz de segurança" @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "estável" }, + "stake": { + "message": "Stake" + }, "stateLogError": { "message": "Erro ao recuperar os registros de estado." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Não conectado" }, + "statusNotConnectedAccount": { + "message": "Nenhuma conta conectada" + }, "step1LatticeWallet": { "message": "Conecte seu Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Esse é o valor mínimo que você receberá. Você pode receber mais, dependendo do slippage." }, + "swapAnyway": { + "message": "Trocar mesmo assim" + }, "swapApproval": { "message": "Aprovar $1 para trocas", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Você precisa de mais $1 $2 para concluir essa troca", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Ainda está aí?" + }, + "swapAreYouStillThereDescription": { + "message": "Estamos prontos para mostrar as últimas cotações quando quiser continuar" + }, "swapBuildQuotePlaceHolderText": { "message": "Nenhum token disponível correspondente a $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Confirme com sua carteira de hardware" }, + "swapContinueSwapping": { + "message": "Continuar troca" + }, "swapContractDataDisabledErrorDescription": { "message": "No aplicativo do Ethereum em seu Ledger, vá para \"Configurações\" e habilite os dados do contrato. Em seguida, tente sua troca novamente." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Editar limite" }, + "swapEditTransactionSettings": { + "message": "Editar configurações de transação" + }, "swapEnableDescription": { "message": "Isso é obrigatório e dá à MetaMask permissão para trocar o seu $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Isso vai $1 para trocas", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Insira um valor" + }, "swapEstimatedNetworkFees": { "message": "Taxas de rede estimadas" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Falha na troca" }, + "swapFetchingQuote": { + "message": "Buscando cotação" + }, "swapFetchingQuoteNofN": { "message": "Obtendo cotação $1 de $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Inclui uma taxa de $1% da MetaMask.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Inclui uma taxa de $1% da MetaMask – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Saiba mais sobre as trocas" + }, "swapLowSlippageError": { "message": "A transação pode falhar; o slippage máximo está baixo demais." }, @@ -3626,6 +4533,10 @@ "message": "Novas cotações em $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Nenhum token disponível correspondente a $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Seu $1 será adicionado à sua conta quando essa transação for processada.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Detalhes da cotação" }, + "swapQuoteNofM": { + "message": "$1 de $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Fonte da cotação" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Cotações vencidas" }, + "swapQuotesNotAvailableDescription": { + "message": "Reduza o tamanho da sua negociação ou use um token diferente." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Experimente ajustar a quantidade ou as configurações de slippage e tente novamente." }, @@ -3693,21 +4611,57 @@ "message": "Selecione uma cotação" }, "swapSelectAToken": { - "message": "Selecionar um token" + "message": "Selecionar token" }, "swapSelectQuotePopoverDescription": { "message": "Abaixo estão todas as cotações reunidas de diversas fontes de liquidez." }, + "swapSelectToken": { + "message": "Selecionar token" + }, + "swapShowLatestQuotes": { + "message": "Mostrar últimas cotações" + }, "swapSlippageNegative": { "message": "O slippage deve ser maior ou igual a zero" }, + "swapSlippageNegativeDescription": { + "message": "O slippage deve ser maior ou igual a zero" + }, + "swapSlippageNegativeTitle": { + "message": "Aumente o slippage para continuar" + }, + "swapSlippageOverLimitDescription": { + "message": "A tolerância ao slippage deve ser de 15% ou menos. Qualquer valor superior resultará em uma taxa ruim." + }, + "swapSlippageOverLimitTitle": { + "message": "Reduza o slippage para continuar" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "O slippage máximo está muito baixo, o que pode fazer sua transação falhar." + }, + "swapSlippageTooLowTitle": { + "message": "Aumente o slippage para evitar falha na transação" + }, "swapSlippageTooltip": { "message": "Chamamos de \"slippage\" quando o preço muda entre o momento de realização da ordem e sua confirmação. Seu swap será cancelado automaticamente se o slippage exceder sua configuração de \"tolerância a slippage\"." }, + "swapSlippageVeryHighDescription": { + "message": "O slippage inserido é considerado muito alto e pode resultar em uma taxa ruim" + }, + "swapSlippageVeryHighTitle": { + "message": "Slippage muito alto" + }, + "swapSlippageZeroDescription": { + "message": "Há menos provedores de cotação com slippage zero, o que resultará em uma cotação menos competitiva." + }, + "swapSlippageZeroTitle": { + "message": "Buscando provedores de slippage zero" + }, "swapSource": { "message": "Fonte de liquidez" }, @@ -3724,7 +4678,7 @@ "message": "Trocar de" }, "swapSwapSwitch": { - "message": "Trocar de e para tokens" + "message": "Trocar ordem dos tokens" }, "swapSwapTo": { "message": "Trocar por" @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "para confirmar com a sua carteira de hardware" }, + "swapTokenAddedManuallyDescription": { + "message": "Verifique esse token em $1 e certifique-se de que é o token que você deseja negociar.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token adicionado manualmente" + }, "swapTokenAvailable": { "message": "Seu $1 foi adicionado à sua conta.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Verificado em $1 fontes.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 só foi verificado em 1 fonte. Considere verificá-lo em $2 antes de prosseguir.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Token potencialmente inautêntico" + }, "swapTooManyDecimalsError": { "message": "$1 permite até $2 decimais", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Não há $1 suficiente para concluir essa transação", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "$1 insuficiente", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Ver na atividade" }, + "switch": { + "message": "Alternar" + }, "switchEthereumChainConfirmationDescription": { "message": "Isso mudará a rede selecionada dentro da MetaMask para uma rede adicionada anteriormente:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Você mudou para" }, + "switcherTitle": { + "message": "Alternador de redes" + }, + "switcherTourDescription": { + "message": "Clique no ícone para alternar as redes ou adicionar uma nova" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "A troca de redes cancelará todas as confirmações pendentes" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Termos de Serviço" }, + "termsOfUse": { + "message": "termos de uso" + }, + "termsOfUseAgreeText": { + "message": " Eu concordo com os Termos de Uso, que se aplicam ao meu uso da MetaMask e de todos os seus recursos" + }, + "termsOfUseFooterText": { + "message": "Favor rolar para ler todas as seções" + }, + "termsOfUseTitle": { + "message": "Nossos Termos de Uso foram atualizados" + }, "testNetworks": { "message": "Redes de teste" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Informações importantes:" }, + "thirdPartySoftware": { + "message": "Aviso de software de terceiros", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "esta coleção" + }, + "thisServiceIsExperimental": { + "message": "Esse serviço é experimental. Ao habilitar esse recurso, você concorda com os $1 do OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Hora" }, @@ -3863,11 +4867,44 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Você está vulnerável a ataques de phishing. Proteja-se desativando eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Ative para permitir que dapps peçam sua assinatura usando solicitações eth_sign. O eth_sign é um método de entrada aberto que permite que você assine uma hash aleatória, apresentando um perigoso risco de phishing. Assine solicitações eth_sign somente se conseguir ler o que está assinando e confiar na origem da solicitação." + "message": "Se você ativar essa configuração, poderá receber solicitações de assinatura ilegíveis. Ao assinar uma mensagem que não entende, você poderá estar concordando em ceder seus fundos e NFTs." }, "toggleEthSignField": { - "message": "Ativar/desativar solicitações eth_sign" + "message": "Solicitações eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " você pode estar sendo vítima de um golpe" + }, + "toggleEthSignModalBannerText": { + "message": "Se pediram para você ativar essa configuração," + }, + "toggleEthSignModalCheckBox": { + "message": "Entendo que poderei perder todos os meus fundos e NFTs se eu ativar solicitações eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Permitir solicitações eth_sign pode deixar você vulnerável a ataques de phishing. Sempre revise o URL e tenha cuidado ao assinar mensagens que contenham código." + }, + "toggleEthSignModalFormError": { + "message": "O texto está incorreto" + }, + "toggleEthSignModalFormLabel": { + "message": "Insira \"Eu só assino o que eu entendo\" para continuar" + }, + "toggleEthSignModalFormValidation": { + "message": "Eu só assino o que eu entendo" + }, + "toggleEthSignModalTitle": { + "message": "Use por sua conta e risco" + }, + "toggleEthSignOff": { + "message": "DESLIGADO (recomendado)" + }, + "toggleEthSignOn": { + "message": "LIGADO (não recomendado)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Símbolo do token" }, + "tokens": { + "message": "Tokens" + }, "tokensFoundTitle": { "message": "$1 novos tokens encontrados", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Eu compreendo" }, + "tooltipSatusConnected": { + "message": "conectada" + }, + "tooltipSatusNotConnected": { + "message": "não conectada" + }, "total": { "message": "Total" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "A transação encontrou um erro." }, + "transactionFailed": { + "message": "Falha na transação" + }, "transactionFee": { "message": "Taxa de transação" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Taxa de gás total" }, + "transactionNote": { + "message": "Observação da transação" + }, "transactionResubmitted": { "message": "Transação reenviada com taxa de gás aumentada para $1 às $2" }, "transactionSecurityCheck": { - "message": "Habilitar provedores de segurança para transações" + "message": "Habilitar alertas de segurança" }, "transactionSecurityCheckDescription": { "message": "Usamos APIs de terceiros para detectar e exibir riscos envolvidos em solicitações de assinatura e de transações não assinadas antes que você as assine. Esses serviços terão acesso às suas solicitações de assinatura e de transações não assinadas, ao endereço de sua conta e ao seu idioma preferencial." }, + "transactionSettings": { + "message": "Configurações da transação" + }, "transactionSubmitted": { "message": "Transação enviada com taxa de gás estimada de $1 às $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transferir de" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Estamos com problemas para conectar o seu Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Revise nosso guia de conexão de carteiras de hardware e tente de novo.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Se você está usando a versão mais recente do Firefox, talvez esteja com um problema relacionado ao Firefox ter abandonado o suporte ao U2F. Saiba como corrigir esse problema $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "aqui", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Tivemos dificuldade para conectar-nos à sua $1. Revise $2 e tente novamente.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "seta para cima" }, + "update": { + "message": "Atualizar" + }, "updatedWithDate": { "message": "Atualizado em $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "O ID da cadeia está sendo usado pela rede $1." }, + "use4ByteResolution": { + "message": "Decodificar contratos inteligentes" + }, + "use4ByteResolutionDescription": { + "message": "Para melhorar a experiência do usuário, personalizamos a guia de atividades com mensagens baseadas nos contratos inteligentes com os quais você interage. A MetaMask usa um serviço chamado 4byte.directory para decodificar os dados e mostrar a você uma versão de um contrato inteligente que é mais fácil de ler. Isso ajuda a reduzir suas chances de aprovar ações de contratos inteligentes mal-intencionados, mas pode resultar no compartilhamento do seu endereço IP." + }, "useMultiAccountBalanceChecker": { "message": "Agrupar solicitações de saldo de contas" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Obtenha atualizações de saldo mais rápidas ao reunir solicitações de saldo de conta. Isso nos permite buscar seus saldos de conta em conjunto, assim você recebe atualizações mais ágeis e tem uma experiência melhorada. Quando esse recurso está desativado, terceiros podem ter menor probabilidade de associar suas contas umas às outras." + }, "useNftDetection": { "message": "Detectar NFTs automaticamente" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Exibir uma advertência para os domínios de phishing destinados a usuários do Ethereum" }, + "useSiteSuggestion": { + "message": "Usar sugestão do site" + }, "useTokenDetectionPrivacyDesc": { "message": "A exibição automática de tokens enviados para a sua conta envolve a comunicação com servidores de terceiros para buscar as imagens dos tokens. Esses servidores terão acesso ao seu endereço IP." }, @@ -4170,7 +5256,7 @@ "message": "Nome de usuário" }, "verifyContractDetails": { - "message": "Verificar dados do contrato" + "message": "Verificar dados do terceiro" }, "verifyThisTokenDecimalOn": { "message": "Os decimais do token podem ser encontrados no $1", @@ -4184,12 +5270,18 @@ "message": "Verifique esse token no $1 e confirme que é o token que você deseja negociar.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Versão" + }, "view": { "message": "Ver" }, "viewAllDetails": { "message": "Ver todos os detalhes" }, + "viewAllQuotes": { + "message": "ver todas as cotações" + }, "viewContact": { "message": "Ver contato" }, @@ -4213,9 +5305,18 @@ "message": "Ver $1 no Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Ver no explorador" + }, "viewOnOpensea": { "message": "Ver no Opensea" }, + "viewPortfolioDashboard": { + "message": "Ver Painel do Portfólio" + }, + "viewinCustodianApp": { + "message": "Ver no app custodiante" + }, "viewinExplorer": { "message": "Ver $1 no explorador", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Desejar adicionar esta rede?" }, + "wantsToAddThisAsset": { + "message": "$1 deseja adicionar esse ativo à sua carteira", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Atenção" }, "warningTooltipText": { - "message": "$1 O contrato pode gastar todo o seu saldo de tokens sem aviso ou consentimento. Proteja-se personalizando um limite de gastos menor.", + "message": "$1 O terceiro pode gastar todo o seu saldo de tokens sem aviso ou consentimento. Proteja-se personalizando um limite de gastos menor.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Está a assinar" }, + "yourAccounts": { + "message": "Suas contas" + }, "yourFundsMayBeAtRisk": { "message": "Seus fundos podem estar em risco" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 22c7cff59..5207152f1 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Общие сведения" }, + "accept": { + "message": "Согласиться" + }, "acceptTermsOfUse": { "message": "Я прочитал(-а) $1 и согласен(-на) с ними", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Добавить ник" }, + "addAccount": { + "message": "Добавить счет" + }, "addAcquiredTokens": { "message": "Добавьте токены, которые вы приобрели с помощью MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Добавьте из списка популярных сетей или добавьте сеть вручную. Взаимодействуйте только с теми организациями, которым доверяете." }, + "addHardwareWallet": { + "message": "Добавить аппаратный кошелек" + }, + "addIPFSGateway": { + "message": "Добавьте предпочтительный шлюз IPFS" + }, "addMemo": { "message": "Добавить примечание" }, @@ -244,6 +256,21 @@ "message": "Это сетевое подключение зависит от третьих сторон. Оно может быть менее надежным или позволять третьим лицам отслеживать активность. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Добавить новый токен" + }, + "addNft": { + "message": "Добавить NFT" + }, + "addNfts": { + "message": "Добавить NFT" + }, + "addSnapAccountModalDescription": { + "message": "Откройте для себя варианты обеспечения безопасности своего счета с помощью MetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Добавить рекомендованные NFT" + }, "addSuggestedTokens": { "message": "Добавить рекомендованные токены" }, @@ -254,6 +281,9 @@ "message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Добавление сети" + }, "address": { "message": "Адрес" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Плата за приоритет (также известная как «чаевые майнеру») направляется непосредственно майнерам, чтобы они уделили приоритетное внимание вашей транзакции." }, + "agreeTermsOfUse": { + "message": "Я соглашаюсь с $1 MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "Хранилище AirGap" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Предупреждения" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Вы либо уже подключили все свои счета депозитария, либо у вас нет счета для подключения к MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Нет доступных счетов для подключения" + }, "allOfYour": { "message": "Все ваши $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Все ваши NFT из $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Разрешить этому внешнему расширению:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Разрешить этому сайту:" }, + "allowThisSnapTo": { + "message": "Разрешить эту привязку к:" + }, "allowWithdrawAndSpend": { "message": "Разрешить $1 снять и потратить до следующей суммы:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Сумма" }, + "apiUrl": { + "message": "URL API" + }, "appDescription": { "message": "Кошелек Ethereum в вашем браузере", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Разрешить доступ ко всем вашим $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Разрешить доступ к вашим NFT из $1 и перевести их?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Одобрить" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Это позволяет третьей стороне получать доступ и передавать следующие NFT без дополнительного уведомления, пока вы не отзовете ее доступ." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Это позволяет третьей стороне получать доступ и передавать все ваши NFT из $1 без дополнительного уведомления, пока вы не закроете ей доступ.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Разрешить доступ и перевод ваших $1?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Попытка отправки активов напрямую из одной сети в другую может привести к необратимой потере активов. Следует использовать мост." }, + "attemptToCancelSwap": { + "message": "Попытка отменить своп на ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Попытка подключения к блокчейну..." }, @@ -411,6 +473,9 @@ "average": { "message": "Средний" }, + "awaitingApproval": { + "message": "Ожидание одобрения..." + }, "back": { "message": "Назад" }, @@ -454,6 +519,9 @@ "message": "Это бета-версия. Пожалуйста, сообщайте об ошибках $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Бета-версия MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Бета-версия MetaMask" }, @@ -488,9 +556,45 @@ "message": "Посмотреть счет в $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Если вы одобрите этот запрос, третья сторона, известная мошенничеством, может похитить все ваши активы." + }, + "blockaidDescriptionBlurFarming": { + "message": "Если вы одобрите этот запрос, кто-то может украсть ваши активы, указанные в Blur." + }, + "blockaidDescriptionFailed": { + "message": "Из-за ошибки этот запрос не был подтвержден поставщиком услуг безопасности. Действуйте осторожно." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Вы взаимодействуете с вредоносным доменом. Если вы одобрите этот запрос, вы можете потерять свои активы." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Если вы одобрите этот запрос, вы можете потерять свои активы." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Если вы одобрите этот запрос, кто-то может украсть ваши активы, указанные в OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Если вы одобрите этот запрос, третья сторона, известная мошенничеством, похитит все ваши активы." + }, + "blockaidTitleDeceptive": { + "message": "Это запрос с целью обмана" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Запрос может быть небезопасным" + }, + "blockaidTitleSuspicious": { + "message": "Это подозрительный запрос" + }, "blockies": { "message": "«Блокиз»" }, + "bridge": { + "message": "Мост" + }, "browserNotSupported": { "message": "Ваш браузер не поддерживается..." }, @@ -510,6 +614,10 @@ "message": "Купить $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Купить еще $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Купить сейчас" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Это приведет к сбросу одноразового пароля от счета и удалению данных со вкладки \"Активность\" в вашем кошельке. Это затронет только текущий счет и сеть. Ваши балансы и входящие транзакции не изменятся." }, + "click": { + "message": "Нажмите" + }, "clickToConnectLedgerViaWebHID": { "message": "Нажмите здесь, чтобы подключить свой леджер через WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Теперь вы покидаете MetaMask, чтобы настроить эту привязку." + }, + "configureSnapPopupInstallDescription": { + "message": "Теперь вы покидаете MetaMask, чтобы установить эту привязку." + }, + "configureSnapPopupInstallTitle": { + "message": "Установить привязку" + }, + "configureSnapPopupLink": { + "message": "Нажмите на эту ссылку, чтобы продолжить:" + }, + "configureSnapPopupTitle": { + "message": "Настроить привязку" + }, "confirm": { "message": "Подтвердить" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Подключите счет или создайте новый" }, + "connectCustodialAccountMenu": { + "message": "Подключить депозитарный счет" + }, + "connectCustodialAccountMsg": { + "message": "Выберите депозитария, к которому вы хотите подключиться, чтобы добавить или обновить токен." + }, + "connectCustodialAccountTitle": { + "message": "Депозитарные счета" + }, "connectManually": { "message": "Подключиться к текущему сайту вручную" }, + "connectSnap": { + "message": "Подключиться к $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Подключиться к $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Подключение к тестовой сети Linea..." }, + "connectingToLineaMainnet": { + "message": "Подключение к Linea Mainnet" + }, "connectingToMainnet": { "message": "Подключение к сети Ethereum Mainnet..." }, "connectingToSepolia": { "message": "Подключение к тестовой сети Sepolia..." }, + "connectionFailed": { + "message": "Ошибка подключения" + }, + "connectionFailedDescription": { + "message": "Не удалось получить $1, проверьте свою сеть и повторите попытку.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Запрос подключения" + }, "contactUs": { "message": "Свяжитесь с нами" }, @@ -708,7 +860,7 @@ "message": "Развертывание контракта" }, "contractDescription": { - "message": "Чтобы защитить себя от мошенников, найдите минутку, чтобы внимательно ознакомиться с контрактом." + "message": "Чтобы защитить себя от мошенников, найдите минутку, чтобы проверить данные третьей стороны." }, "contractInteraction": { "message": "Взаимодействие по контракту" @@ -717,16 +869,16 @@ "message": "NFT-контракт" }, "contractRequestingAccess": { - "message": "Контракт запрашивает доступ" + "message": "Третья сторона запрашивает доступ" }, "contractRequestingSignature": { - "message": "Контракт, требующий подписания" + "message": "Третья сторона запрашивает подписание" }, "contractRequestingSpendingCap": { - "message": "Контракт, требующий ограничения расходов" + "message": "Третья сторона запрашивает ограничение расходов" }, "contractTitle": { - "message": "Сведения о контракте" + "message": "Сведения о третьей стороне" }, "contractToken": { "message": "Контракт на токен" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "График рыночной оценки газа" }, + "custodian": { + "message": "Депозитарий" + }, + "custodianAccount": { + "message": "Депозитарный счет" + }, + "custodianAccountAddedDesc": { + "message": "Теперь вы можете использовать свои депозитарные счета в MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Добавлены выбранные депозитарные счета." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Перейдите к $1 и нажмите кнопку «Подключиться к MMI» в их пользовательском интерфейсе, чтобы снова подключить свои счета к MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Теперь вы можете использовать свои депозитарные счета в MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Ваш токен депозитария обновлен" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Он заменит токен депозитария для следующего адреса:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Заменить токен депозитария" + }, + "custodyApiUrl": { + "message": "URL-адрес $1 API" + }, + "custodyDeeplinkDescription": { + "message": "Подтвердите транзакцию в приложении $1. После того как все необходимые разрешения на хранение будут предоставлены, транзакция будет завершена. Проверьте статус своего приложения $1." + }, + "custodyRefreshTokenModalDescription": { + "message": "Перейдите к $1 и нажмите кнопку «Подключиться к MMI» в их пользовательском интерфейсе, чтобы снова подключить свои счета к MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Ваш депозитарий выпускает токен, который аутентифицирует институциональное расширение MetaMask, позволяя вам подключать свои счета." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Срок действия этого токена истекает через определенный срок по соображениям безопасности. Это требует повторного подключения к MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Почему я это вижу?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Срок сеанса вашего депозитария истек" + }, + "custodySessionExpired": { + "message": "Сеанс депозитария истек." + }, + "custodyWrongChain": { + "message": "Этот счет не настроен для использования с $1" + }, "custom": { "message": "Дополнительно" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "поддержка клиентов" }, + "dappRequestedSpendingCap": { + "message": "Сайт запросил лимит расходов" + }, "dappSuggested": { "message": "Рекомендовано сайтом" }, @@ -851,6 +1060,12 @@ "message": "$1 рекомендовал эту цену.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Рекомендовано сайтом" + }, + "dappSuggestedHighShortLabel": { + "message": "Сайт (высокий)" + }, "dappSuggestedShortLabel": { "message": "Сайт" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Удалить" }, + "deleteContact": { + "message": "Удалить контакт" + }, "deleteNetwork": { "message": "Удалить сеть?" }, + "deleteNetworkIntro": { + "message": "Если вы удалите эту сеть, вам нужно будет добавить ее снова, чтобы просмотреть свои активы в этой сети" + }, + "deleteNetworkTitle": { + "message": "Удалить сеть $1?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Внести деньги" }, @@ -917,6 +1142,10 @@ "description": { "message": "Описание" }, + "descriptionFromSnap": { + "message": "Описание из $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Эта ошибка может быть периодической, поэтому попробуйте перезапустить расширение или отключить MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Отклонить напоминание о резервном копировании секретной фразы для восстановления" }, + "displayNftMedia": { + "message": "Показать NFT-носитель" + }, + "displayNftMediaDescription": { + "message": "Отображение носителя и данных NFT раскрывает ваш IP-адрес для OpenSea или других третьих лиц. Это может позволить злоумышленникам связать ваш IP-адрес с вашим адресом Ethereum. Автоопределение NFT зависит от этого параметра и будет недоступно, если он отключен." + }, "domain": { "message": "Домен" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Включить автообнаружение" }, + "enableForAllNetworks": { + "message": "Включить для всех сетей" + }, "enableFromSettings": { "message": " Включите его в Настройках." }, + "enableSmartSwaps": { + "message": "Включить смарт-свопы" + }, + "enableSnap": { + "message": "Включить" + }, "enableToken": { "message": "активирует для $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Включено" + }, "encryptionPublicKeyNotice": { "message": "$1 запрашивает ваш открытый ключ шифрования. После получения вашего согласия на это данный сайт сможет создавать зашифрованные сообщения для отправки в ваш адрес.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Улучшенное обнаружение токенов в настоящее время доступно на $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask позволяет вам видеть домены ENS, такие как https://metamask.eth, прямо в адресной строке вашего браузера. Вот как это работает:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Обычные браузеры обычно не работают с адресами ENS или IPFS, но MetaMask помогает с этим. При использовании этой функции ваш IP-адрес может быть передан сторонним службам IPFS." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask сверяется с контрактом ENS Ethereum, чтобы найти код, связанный с именем ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Если код связан с IPFS, он получает контент из сети IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Затем вы можете увидеть контент, обычно веб-сайт или что-то подобное." + }, + "ensDomainsSettingTitle": { + "message": "Показывать домены ENS в адресной строке" + }, "ensIllegalCharacter": { "message": "Недопустимый символ для ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Введите цифру" }, + "enterCustodianToken": { + "message": "Введите свой токен $1 или добавьте новый токен" + }, "enterMaxSpendLimit": { "message": "Введите максимальный лимит расходов" }, + "enterOptionalPassword": { + "message": "Введите необязательный пароль" + }, "enterPassword": { "message": "Введите пароль" }, "enterPasswordContinue": { "message": "Введите пароль, чтобы продолжить" }, + "enterTokenNameOrAddress": { + "message": "Введите имя токена или вставьте адрес" + }, + "enterYourPassword": { + "message": "Введите свой пароль" + }, "errorCode": { "message": "Код: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Стек:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Ошибка при подключении к пользовательской сети." + }, + "errorWithSnap": { + "message": "Ошибка с $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Указана резервная цена газа, поскольку основной сервис определения цены газа сейчас недоступен." }, + "ethereumProviderAccess": { + "message": "Предоставить поставщику Ethereum доступ к $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Открытый адрес Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Экспериментальный" }, + "exploreMetaMaskSnaps": { + "message": "Обзор привязок MetaMask" + }, "exportPrivateKey": { "message": "Экспорт закрытого ключа" }, + "extendWalletWithSnaps": { + "message": "Расширьте возможности кошелька." + }, "externalExtension": { "message": "Внешнее расширение" }, @@ -1302,6 +1596,9 @@ "message": "Импорт файлов не работает? Нажмите здесь!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Добавленный файл слишком велик." + }, "flaskWelcomeUninstall": { "message": "вам нужно должны удалить это расширение", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Общее" }, + "getStarted": { + "message": "Начало работы" + }, + "globalTitle": { + "message": "Глобальное меню" + }, + "globalTourDescription": { + "message": "Просматривайте свой портфель, подключенные сайты, настройки и многое другое" + }, "goBack": { "message": "Назад" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Подключить аппаратный кошелек" }, + "hardwareWalletsInfo": { + "message": "Интеграция с аппаратным кошельком использует вызовы API к внешним серверам, которые могут видеть ваш IP-адрес и адреса смарт-контрактов, с которыми вы взаимодействуете" + }, "hardwareWalletsMsg": { "message": "Выберите аппаратный кошелек, который хотите использовать с MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "но злоумышленники-фишеры могут.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Ваш закрытый ключ обеспечивает $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "полный доступ к вашему кошельку и средствам.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "удерживайте, чтобы показать заблокированный круг" + }, + "holdToRevealPrivateKey": { + "message": "Удерживайте, чтобы показать закрытый ключ" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Обеспечьте безопасность своего закрытого ключа" + }, "holdToRevealSRP": { - "message": "Удерживайте, чтобы показать СФВ" + "message": "Удерживайте для отображения СВФ" }, "holdToRevealSRPTitle": { - "message": "Храните СФВ в безопасности" + "message": "Обеспечьте безопасность своей СВФ" + }, + "holdToRevealUnlockedLabel": { + "message": "удерживайте, чтобы показать разблокированный круг" + }, + "id": { + "message": "Ид." }, "ignoreAll": { "message": "Игнорировать все" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Ошибка импорта счета." }, + "importAccountErrorIsSRP": { + "message": "Вы ввели секретную фразу для восстановления (или мнемоническую фразу). Чтобы импортировать счет сюда, нужно ввести закрытый ключ, который представляет собой шестнадцатеричную строку длиной 64 символа." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Это недействительный закрытый ключ. Вы ввели шестнадцатеричную строку, но она должна содержать 64 символа." + }, + "importAccountErrorNotHexadecimal": { + "message": "Это недействительный закрытый ключ. Вам нужно ввести шестнадцатеричную строку длиной 64 символа." + }, + "importAccountJsonLoading1": { + "message": "Ожидайте, что этот импорт JSON займет несколько минут и заблокирует MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Мы приносим свои извинения, и мы сделаем это быстрее в будущем." + }, "importAccountMsg": { - "message": "Импортированные счета не будут связаны с секретной фразой для восстановления вашего изначально созданного счета MetaMask. Узнайте больше об импортированных счетах" + "message": "Импортированные счета не будут связаны с вашей секретной фразой для восстановления MetaMask. Узнайте больше об импортированных счетах" }, "importMyWallet": { "message": "Импорт моего кошелька" @@ -1615,18 +1962,29 @@ "message": "Ваша первоначальная транзакция подтверждена сетью. Нажмите ОК, чтобы вернуться." }, "inputLogicEmptyState": { - "message": "Введите только ту сумму, которую вам комфортно тратить по контракту сейчас или в будущем. Вы всегда можете увеличить лимит расходов позже." + "message": "Введите только ту сумму, которую третья сторона, по вашему мнению, может тратить сейчас или в будущем. Вы всегда можете увеличить лимит расходов позже." }, "inputLogicEqualOrSmallerNumber": { - "message": "Это позволяет контракту потратить $1 с вашего текущего баланса.", + "message": "Это позволяет третьей стороне потратить $1 с вашего текущего баланса.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Это позволяет контракту расходовать весь ваш баланс токенов до тех пор, пока не будет достигнут лимит расходов или вы не отмените его. Если вы не хотите такого развития событий, подумайте о снижении лимита расходов." + "message": "Это позволяет третьей стороне расходовать весь ваш баланс токенов до тех пор, пока не будет достигнут лимит расходов или вы не отмените его. Если вы не хотите такого развития событий, подумайте о снижении лимита расходов." + }, + "insightsFromSnap": { + "message": "Аналитика от $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Установите," }, + "installOrigin": { + "message": "Источник установки" + }, + "installedOn": { + "message": "Установлено на $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Недостаточный баланс." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Неправильный ввод! Секретная фраза для восстановления чувствительна к регистру." }, + "ipfsGateway": { + "message": "Шлюз IPFS" + }, + "ipfsGatewayDescription": { + "message": "MetaMask использует сторонние службы для показа изображений ваших NFT, хранящихся в IPFS, отображения информации, связанной с адресами ENS, введенными в адресную строку вашего браузера, и получения значков для различных токенов. Ваш IP-адрес может быть открыт для этих служб, когда вы их используете." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Мы задействуем сторонние службы для показа изображений ваших NFT, хранящихся в IPFS, отображения информации, связанной с адресами ENS, введенными в адресную строку вашего браузера, и получения значков для различных токенов. При использовании вами этих служб они могут узнать ваш IP-адрес." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "При нажатии на «Подтвердить» включается разрешение IPFS. Вы можете отключить его в $1 в любое время.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Настройки > Безопасность и конфиденциальность" + }, "jazzAndBlockies": { "message": "«Джазиконы» и «Блокиз» — это два разных стиля уникальных значков, которые помогут вам с первого взгляда идентифицировать свой счет." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Последняя продажа" }, + "layer1Fees": { + "message": "Комиссии 1-го уровня" + }, "learnCancelSpeeedup": { "message": "Узнайте, как $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Хотите $1 о газе?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Подробнее" + }, "learnMoreUpperCase": { "message": "Подробнее" }, @@ -1765,16 +2145,16 @@ "message": "Перед нажатием «Подтвердить»:" }, "ledgerConnectionInstructionStepFour": { - "message": "Включите «данные смарт-контракта» или «слепую подпись» на своем леджере" + "message": "Включите «данные смарт-контракта» или «слепую подпись» в своем леджере." }, "ledgerConnectionInstructionStepOne": { - "message": "Включите «Использовать Ledger Live» в разделе «Настройки» > «Дополнительно»" + "message": "Включите «Использовать Ledger Live» в разделе «Настройки» > «Дополнительно»." }, "ledgerConnectionInstructionStepThree": { - "message": "Подключите леджер и выберите приложение Ethereum" + "message": "Убедитесь, что леджер подключен, и выберите приложение Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Откройте и разблокируйте приложение Ledger Live" + "message": "Откройте и разблокируйте приложение Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Настройте подключение леджера к MetaMask. Рекомендуется $1, но возможны и другие варианты. См. подробнее здесь: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Тестовая сеть Linea Goerli" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "Привязать" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Заблокировать" }, + "lockMetaMask": { + "message": "Заблокировать MetaMask" + }, + "lockTimeInvalid": { + "message": "Номер блокировки будет числом от 0 до 10080" + }, "logo": { "message": "логотип $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Кнопка статуса подключения показывает, подключен ли посещаемый вами веб-сайт, к выбранному вами в настоящее время счету." }, + "metamaskInstitutionalVersion": { + "message": "Версия MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "Сервис обмена MetaMask на техобслуживании. Зайдите позже." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Показатели" }, + "mismatchAccount": { + "message": "Выбранный вами счет ($1) отличается от счета, который вы пытаетесь подписать ($2)" + }, "mismatchedChainLinkText": { "message": "проверить сведения о сети", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Представленный символ валюты не соответствует тому, что мы ожидаем для этого идентификатора блокчейна." }, + "mismatchedRpcChainId": { + "message": "Идентификатор блокчейна, возвращенный пользовательской сетью, не соответствует отправленному идентификатору блокчейна." + }, "mismatchedRpcUrl": { "message": "Согласно нашим записям, отправленное значение URL-адреса RPC не соответствует известному поставщику для этого идентификатора блокчейна." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Запросите здесь" }, + "mmiAddToken": { + "message": "Страница в $1 хочет авторизовать следующий токен депозитария в MetaMask Institutional" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional разработан и создан с учетом потребностей в разных частях мира." + }, + "more": { + "message": "больше" + }, "moreComingSoon": { - "message": "Скоро появится больше..." + "message": "Скоро появятся другие поставщики" + }, + "multipleSnapConnectionWarning": { + "message": "$1 хочет подключиться с помощью привязок $2. Продолжайте, только если вы доверяете этому веб-сайту.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Необходимо выбрать хотя бы 1 токен." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Сеть занята. Цены на газ высоки, а оценки менее точны." }, + "networkMenu": { + "message": "Сетевое меню" + }, + "networkMenuHeading": { + "message": "Выбрать сеть" + }, "networkName": { "message": "Имя сети" }, @@ -2033,6 +2450,10 @@ "message": "Относительная плата за газ составляет $1 за последние 72 часа.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Нам не удается подключиться к $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL-адрес сети" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Ранее было во владении" + }, "nickname": { "message": "Ник" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Нет доступного обменного курса" }, + "noNFTs": { + "message": "Пока нет NFT-токенов" + }, + "noNetworksFound": { + "message": "По заданному поисковому запросу сети не найдены" + }, "noSnaps": { - "message": "Снапы не установлены" + "message": "Привязки не установлены." }, "noThanksVariant2": { "message": "Нет, спасибо." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Это правильный счет? Он отличается от счета, выбранного в настоящее время в вашем кошельке." }, + "notEnoughBalance": { + "message": "Нехватка средств" + }, "notEnoughGas": { "message": "Недостаточно газа" }, + "note": { + "message": "Примечание" + }, + "notePlaceholder": { + "message": "Утверждающее лицо увидит это примечание при утверждении транзакции у депозитария." + }, + "notificationTransactionFailedMessage": { + "message": "Ошибка транзакции $1! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Ошибка транзакции! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Неудачная транзакция", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Транзакция $1 подтверждена!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Подтвержденная транзакция", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Смотреть на $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Уведомления" + }, "notifications10ActionText": { "message": "Смотреть в настройках", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Ethereum Merge уже досутпно!" }, + "notifications18ActionText": { + "message": "Включить предупреждения безопасности" + }, + "notifications18DescriptionOne": { + "message": "Получайте оповещения от третьих лиц, когда вы, возможно, получаете вредоносный запрос.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Всегда обязательно проводите собственную комплексную проверку, прежде чем утверждать какие-либо запросы.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea является первым поставщиком услуг для этой функции. Скоро появятся новые поставщики!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Избегайте опасности с помощью оповещений безопасности" + }, + "notifications19ActionText": { + "message": "Включить автоопределение NFT" + }, + "notifications19DescriptionOne": { + "message": "Два способа начать работу:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "На данный момент мы поддерживаем только ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Добавьте свои NFT вручную или включите автоматическое определение NFT в меню «Настройки» > «Экспериментальное».", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Просматривайте свои NFT как никогда раньше" + }, "notifications1Description": { "message": "Теперь пользователи MetaMask Mobile могут обменивать токены в своем мобильном кошельке. Отсканируйте QR-код, чтобы скачать мобильное приложение и начать обмен.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Обмен на мобильном устройстве уже доступен!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Подробнее", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Если вы используете последнюю версию Firefox, у вас может возникнуть проблема, связанная с отказом Firefox от поддержки U2F.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Пользователи леджера и Firefox испытывают проблемы с подключением", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Попробовать" + }, + "notifications21Description": { + "message": "Мы обновили свопы в расширении MetaMask, чтобы их было проще и быстрее использовать.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Представляем новые и обновленные свопы!" + }, + "notifications22ActionText": { + "message": "Понятно" + }, + "notifications22Description": { + "message": "💡 Просто нажмите глобальное меню или меню счета, чтобы найти их!" + }, + "notifications22Title": { + "message": "Ищете данные своего счета или URL-адрес обозревателя блоков?" + }, + "notifications23ActionText": { + "message": "Включить оповещения безопасности" + }, + "notifications23DescriptionOne": { + "message": "Избегайте известных мошенников, сохраняя при этом свою конфиденциальность с помощью оповещений безопасности Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Если вы включили оповещения безопасности от OpenSea, мы активировали для вас эту функцию." + }, + "notifications23DescriptionTwo": { + "message": "Всегда проводите собственную комплексную проверку перед утверждением запросов." + }, + "notifications23Title": { + "message": "Избегайте угроз с помощью оповещений безопасности" + }, "notifications3ActionText": { "message": "Подробнее", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Подключайтесь только к сайтам, которым доверяете." }, "openFullScreenForLedgerWebHid": { - "message": "Откройте MetaMask в полноэкранном режиме, чтобы подключить свой леджер через WebHID.", + "message": "Перейдите в полноэкранный режим, чтобы подключить свой леджер.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Открыть в проводнике блоков" }, "openSea": { - "message": "OpenSea (бета-версия)" + "message": "OpenSea + Blockaid (бета-версия)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Ошибка операции" + }, "optional": { "message": "Необязательно" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Пароли не совпадают" }, + "pasteJWTToken": { + "message": "Вставьте или перетащите свой токен сюда:" + }, "pastePrivateKey": { "message": "Вставьте строку вашего закрытого ключа сюда:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Доступ в Интернет.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Разрешите привязке доступ к Интернету. Ее можно использовать как для отправки, так и для получения данных со сторонних серверов.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Подключение к спапу $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Разрешите веб-сайту и привязке взаимодействовать с $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Планируйте и выполняйте периодические действия.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Разрешите привязке выполнять действия, которые выполняются периодически в фиксированное время, даты или интервалы. Это можно использовать для запуска срочных взаимодействий или уведомлений.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Отображение диалоговых окон в MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Разрешите привязке отображать всплывающие окна MetaMask с настраиваемым текстом, полем ввода и кнопками для подтверждения или отклонения действия.\nМожет использоваться для создания, например, оповещения, подтверждения и процедуры получения согласия для привязки.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "См. адрес, баланс счета, активность и инициируйте транзакции", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Получите доступ к поставщику Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Разрешите привязке связываться с MetaMask напрямую, чтобы она могла считывать данные из блокчейна и предлагать сообщения и транзакции.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Извлекайте произвольные ключи, уникальные для этой привязки.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Разрешите привязке получать произвольные ключи, уникальные для этой привязки, не раскрывая их. Эти ключи отделены от вашего(-их) счета(-ов) MetaMask и не связаны с вашими закрытыми ключами или секретной фразой для восстановления. Другие привязки не могут получить доступ к этой информации.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Используйте хуки жизненного цикла.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Разрешите привязке использовать обработчики жизненного цикла для запуска кода в определенное время в течение ее жизненного цикла.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Выполнять бесконечно.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Разрешите привязке работать бесконечно, например, при обработке больших объемов данных.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Добавляйте счета Ethereum и управляйте ими", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Контролируйте свои счета и активы в $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Разрешите привязке получать пары ключей BIP-32 на основе вашей секретной фразы для восстановления, не раскрывая ее. Это дает полный доступ ко всем счетам и активам на $1.\nБлагодаря способности управлять ключами, привязка может поддерживать множество протоколов блокчейна, кроме Ethereum (EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { "message": "Контролируйте свои счета и активы «$1».", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Разрешите привязке получать пары ключей BIP-44 на основе вашей секретной фразы для восстановления, не раскрывая ее. Это дает полный доступ ко всем счетам и активам на $1.\nБлагодаря способности управлять ключами привязка может поддерживать множество протоколов блокчейна, кроме Ethereum (EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Контролируйте свои счета и активы $1.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Храните и управляйте его данными на вашем устройстве.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Разрешите привязке безопасно хранить, обновлять и извлекать данные с помощью шифрования. Другие привязки не могут получить доступ к этой информации.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Показать уведомления.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Разрешите привязке отображать уведомления в MetaMask. Текст короткого уведомления может отображаться привязкой для получения информации, требующей действий, или срочной информации.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Разрешите $1 напрямую взаимодействовать с этой привязкой.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Разрешите $1 отправлять сообщения приявзке и получать ответ от привязки.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Получайте и отображайте подробную информацию о транзакциях.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Разрешите привязке декодировать транзакции и показывать информацию в пользовательском интерфейсе MetaMask. Это можно использовать для защиты от фишинга и обеспечения безопасности.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Смотрите происхождение веб-сайтов, которые предлагают транзакции", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Разрешить привязке видеть происхождение (URI) веб-сайтов, предлагающих транзакции. Это можно использовать для защиты от фишинга и обеспечения безопасности.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Неизвестное разрешение: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Просмотрите свой открытый ключ для $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Разрешите привязке просматривать ваши открытые ключи (и адреса) для $1. Это не дает никакого контроля над счетами или активами.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Просмотрите свой открытый ключ для $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Поддержка WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Разрешите привязке доступ к низкоуровневым средам выполнения через WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Разрешения" }, + "permissionsTitle": { + "message": "Разрешения" + }, + "permissionsTourDescription": { + "message": "Найдите свои подключенные счета и управляйте разрешениями здесь" + }, "personalAddressDetected": { "message": "Обнаружен личный адрес. Введите адрес контракта токена." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Портфель" }, + "portfolioDashboard": { + "message": "Панель инструментов портфеля" + }, "preferredLedgerConnectionType": { "message": "Предпочтительный тип подключения к леджеру", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Закрытый ключ", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Закрытый ключ за $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Предупреждение: никогда не раскрывайте этот ключ. Любой, у кого есть ваши закрытые ключи, может украсть любые активы, хранящиеся на вашем счете." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "В очереди" }, + "quoteRate": { + "message": "Курс котировки" + }, "reAddAccounts": { "message": "повторно добавить любые другие счета" }, @@ -2751,7 +3401,7 @@ "message": "Получить" }, "recipientAddressPlaceholder": { - "message": "Поиск, открытый адрес (0x) или ENS" + "message": "Введите открытый адрес (0x) или имя ENS" }, "recommendedGasLabel": { "message": "Рекомендовано" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Этот счет будет удален из вашего кошелька. Перед продолжением убедитесь, что у вас есть секретная фраза для восстановления или закрытый ключ для этого импортированного счета. Вы можете импортировать или снова создать счета из раскрывающегося списка счетов. " }, + "removeJWT": { + "message": "Удалить токен депозитария" + }, + "removeJWTDescription": { + "message": "Уверены, что хотите удалить этот токен? Все счета, назначенные этому токену, также будут удалены из расширения: " + }, "removeNFT": { "message": "Удалить NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Вы можете восстановить пользовательские настройки, содержащие настройки и адреса аккаунтов, из ранее сохраненного файла JSON." }, + "resultPageError": { + "message": "Ошибка" + }, + "resultPageErrorDefaultMessage": { + "message": "Ошибка операции." + }, + "resultPageSuccess": { + "message": "Успех" + }, + "resultPageSuccessDefaultMessage": { + "message": "Операция выполнена." + }, "retryTransaction": { "message": "Повторить транзакцию" }, @@ -2939,16 +3607,27 @@ "message": "Отозвать разрешение на доступ ко всем вашим $1 и их перевод?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Отозвать разрешение на доступ ко всем вашим NFT из $1 и их перевод?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Это отменит разрешение третьей стороне на доступ ко всем вашим $1 и их перевод без дальнейшего уведомления.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Это отменит разрешение третьей стороне на доступ ко всем вашим NFT из $1 и их перевод без дальнейшего уведомления.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Отозвать разрешение" + }, "revokeSpendingCap": { "message": "Отменить верхний лимит расходов для вашего $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Этот контракт не позволит вам больше тратить ваши текущие или будущие токены." + "message": "Эта третья сторона больше не сможет тратить ваши текущие или будущие токены." }, "rpcUrl": { "message": "Новый URL-адрес RPC" @@ -2986,9 +3665,28 @@ "security": { "message": "Безопасность" }, + "securityAlert": { + "message": "Оповещение о безопасности от $1 и $2" + }, + "securityAlerts": { + "message": "Оповещения безопасности" + }, + "securityAlertsDescription1": { + "message": "Эта функция предупреждает вас о вредоносных действиях, локально просматривая ваши транзакции и запросы подписания. Ваши данные не передаются третьим сторонам, предоставляющим эту услугу. Всегда проводите комплексную проверку, прежде чем одобрять какие-либо запросы. Нет гарантии, что эта функция обнаружит все вредоносные действия." + }, + "securityAlertsDescription2": { + "message": "Всегда обязательно проводите собственную комплексную проверку, прежде чем одобрять какие-либо запросы. Нет никакой гарантии, что эта функция обнаружит все вредоносные действия." + }, "securityAndPrivacy": { "message": "Безопасность и конфиденциальность" }, + "securityProviderAdviceBy": { + "message": "Совет по безопасности от $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "См. подробности" + }, "seedPhraseConfirm": { "message": "Подтвердите секретную фразу для восстановления" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Запишите секретную фразу для восстановления" }, + "select": { + "message": "Выбрать" + }, "selectAccounts": { "message": "Выберите счета(-а) для использования на этом сайте" }, + "selectAccountsForSnap": { + "message": "Выберите счет(-а) для использования с этой привязкой" + }, "selectAll": { "message": "Выбрать все" }, + "selectAllAccounts": { + "message": "Выбрать все счета" + }, "selectAnAccount": { "message": "Выберите счет" }, "selectAnAccountAlreadyConnected": { "message": "Этот счет уже подключен к MetaMask" }, + "selectAnAccountHelp": { + "message": "Выберите депозитарные счета для использования в MetaMask Institutional." + }, "selectHdPath": { "message": "Выберите путь HD" }, + "selectJWT": { + "message": "Выбрать токен" + }, "selectNFTPrivacyPreference": { "message": "Включите обнаружение NFT в настройках" }, @@ -3113,6 +3826,9 @@ "message": "Одобрить $1 без ограничений по расходам", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Добавить счет привязки" + }, "settings": { "message": "Настройки" }, @@ -3141,9 +3857,21 @@ "message": "Выберите это, чтобы использовать Etherscan для отображения входящих транзакций в списке транзакций", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Это зависит от каждой сети, которая будет иметь доступ к вашему адресу Ethereum и вашему IP-адресу." + }, + "showMore": { + "message": "Показать больше" + }, + "showNft": { + "message": "Показать NFT" + }, "showPermissions": { "message": "Показать разрешения" }, + "showPrivateKey": { + "message": "Показать закрытый ключ" + }, "showPrivateKeys": { "message": "Показать закрытые ключи" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Я понимаю, что, если я не создам резервную копию своей секретной фразы для восстановления, я могу потерять доступ ко всем своим счетам и всем средствам на них." }, + "smartContracts": { + "message": "Смарт-контракты" + }, + "smartSwap": { + "message": "Смарт-своп" + }, + "smartSwapsAreHere": { + "message": "Появились смарт-свопы!" + }, + "smartSwapsDescription": { + "message": "Свопы MetaMask стали намного умнее! Включение смарт-свопов позволит MetaMask программно оптимизировать ваш своп, чтобы помочь:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Недостаточно средств для смарт-свопа." + }, + "smartSwapsErrorUnavailable": { + "message": "Смарт-свопы временно недоступны." + }, + "smartSwapsSubDescription": { + "message": "* Смарт-свопы попытаются отправить вашу транзакцию в частном порядке несколько раз. Если все попытки не увенчаются успехом, транзакция будет транслироваться публично, чтобы гарантировать успешное завершение вашего свопа." + }, + "snapConfigure": { + "message": "Настроить" + }, + "snapConnectionWarning": { + "message": "$1 хочет подключиться к $2. Продолжайте, только если вы доверяете этому сайту.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Этот контент поступает от $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Выберите способ защиты вашего нового счета с помощью MetaMask Snaps." + }, + "snapCreateAccountTitle": { + "message": "Создать новый счет $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "привязка", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "От MetaMask" + }, + "snapDetailAudits": { + "message": "Аудит" + }, + "snapDetailDeveloper": { + "message": "Разработчик" + }, + "snapDetailLastUpdated": { + "message": "Обновлено" + }, + "snapDetailManageSnap": { + "message": "Управление привязкой" + }, + "snapDetailTags": { + "message": "Теги" + }, + "snapDetailVersion": { + "message": "Версия" + }, + "snapDetailWebsite": { + "message": "Веб-сайт" + }, + "snapDetailsCreateASnapAccount": { + "message": "Создать новую привязку" + }, + "snapDetailsInstalled": { + "message": "Установлена" + }, "snapError": { "message": "Ошибка снапа: '$1'. Код ошибки: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Установить снап" }, + "snapInstallRequest": { + "message": "Установка $1 дает ему следующие разрешения. Продолжайте, только если доверяете $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Установка завершена" + }, "snapInstallWarningCheck": { "message": "Чтобы подтвердить, что вы понимаете, отметьте все.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Чтобы подтвердить, что вы понимаете, отметьте все ячейки.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Действуйте с осторожностью" + }, "snapInstallWarningKeyAccess": { "message": "Вы предоставляете ключ доступа $2 к привязке \"$1\". Это действие нельзя отменить, и оно предоставляет \"$1\" управление всеми счетами и активами $2. Перед тем как продолжить, убедитесь, что доверяете \"$1\".", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Предоставьте $2 доступ к открытому ключу к $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "Не удалось установить $1.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Ошибка установки", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Проверена" + }, + "snapResultError": { + "message": "Ошибка" + }, + "snapResultSuccess": { + "message": "Успех" + }, + "snapResultSuccessDescription": { + "message": "$1 готово к использованию" + }, "snapUpdate": { "message": "Обновить снап" }, + "snapUpdateAvailable": { + "message": "Доступно обновление" + }, + "snapUpdateErrorDescription": { + "message": "Не удалось обновить $1.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Ошибка обновления", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 хочет обновить $2 до $3, что дает ему следующие разрешения. Продолжайте, только если доверяете $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Обновление завершено" + }, "snaps": { "message": "Снапы" }, "snapsInsightLoading": { "message": "Загрузка аналитики по транзакции..." }, + "snapsInvalidUIError": { + "message": "Пользовательский интерфейс, указанный привязкой, недействителен." + }, "snapsNoInsight": { "message": "Эта привязка не выдала никакой аналитики" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Вы признаете, что привязка, которую вы собираетесь установить, является Сторонней службой, как она определена в $1 Consensys. Использование вами Сторонних служб регулируется отдельными положениями и условиями, установленными сторонним поставщиком услуг. Вы получаете доступ к Сторонней службе, полагаетесь на нее или используете его на свой страх и риск. Consensys отказывается от любой ответственности и ответственности за любые убытки, связанные с использованием вами сторонних услуг.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Любая информация, которую вы передаете Сторонним службам, будет собираться непосредственно этими Сторонними службами в соответствии с их политикой конфиденциальности. Пожалуйста, обратитесь к их политике конфиденциальности для получения дополнительной информации.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "У Consensys нет доступа к информации, которой вы делитесь с этими третьими сторонами.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Управление вашим снапами" }, + "snapsTermsOfUse": { + "message": "Условия использования" + }, "snapsToggle": { "message": "Снап будет работать только в том случае, если он включен" }, "snapsUIError": { - "message": "Пользовательский интерфейс, указанный привязкой, недействителен.", + "message": "Свяжитесь с авторами $1 для получения дополнительной поддержки.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Введите только то число в качестве лимита для $1, которое приемлемо для вас. Вы всегда можете увеличить лимит токенов позже.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Запрос лимита расходов для вашего $1" + }, "srpInputNumberOfWords": { "message": "У меня есть фраза из $1 слов(-а)", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3297,7 +4167,10 @@ "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, "srpSecurityQuizGetStarted": { - "message": "Начать" + "message": "Начало работы" + }, + "srpSecurityQuizImgAlt": { + "message": "Глаз с замочной скважиной в центре и три плавающих поля пароля" }, "srpSecurityQuizIntroduction": { "message": "Чтобы увидеть свою секретную фразу для восстановления, вам нужно правильно ответить на два вопроса" @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "стабильная" }, + "stake": { + "message": "Выполнить стейкинг" + }, "stateLogError": { "message": "Ошибка при получении журналов состояния." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Не подключено" }, + "statusNotConnectedAccount": { + "message": "Аккаунты не подключены" + }, "step1LatticeWallet": { "message": "Подключите Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Это минимальная сумма, которую вы получите. Вы можете получить больше в зависимости от проскальзывания." }, + "swapAnyway": { + "message": "Все равно выполнить своп" + }, "swapApproval": { "message": "Одобрить использование $1 для обмена", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Вам нужно еще $1 $2 для завершения этого обмена", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Вы все еще там?" + }, + "swapAreYouStillThereDescription": { + "message": "Мы готовы показать вам последние котировки, когда вы захотите продолжить" + }, "swapBuildQuotePlaceHolderText": { "message": "Нет доступных токенов, соответствующих $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Подтвердите с помощью аппаратного кошелька" }, + "swapContinueSwapping": { + "message": "Продолжить своп" + }, "swapContractDataDisabledErrorDescription": { "message": "В приложении Ethereum на леджере перейдите в раздел «Настройки» и разрешите использование данных о контракте. Затем попробуйте повторить обмен." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Изменить лимит" }, + "swapEditTransactionSettings": { + "message": "Изменить настройки транзакции" + }, "swapEnableDescription": { "message": "Это необходимо и дает MetaMask разрешение на обмен вашего $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Это $1 возможность обмена", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Введите сумму" + }, "swapEstimatedNetworkFees": { "message": "Примерные комиссии сети" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Обмен не удался" }, + "swapFetchingQuote": { + "message": "Получение котировки..." + }, "swapFetchingQuoteNofN": { "message": "Получение котировки $1 из $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Включает комиссию MetaMask в размере $1%.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Включает комиссию MetaMask в размере $1% — $2.", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Узнайте больше о свопах" + }, "swapLowSlippageError": { "message": "Возможно, не удастся выполнить транзакцию. Ммаксимальное проскальзывание слишком низкое." }, @@ -3626,6 +4533,10 @@ "message": "Новые котировки через $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Нет доступных подходящих токенов $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Ваш $1 будет зачислен на ваш счет после обработки этой транзакции.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Свдения о котировке" }, + "swapQuoteNofM": { + "message": "$1 из $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Источник котировки" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Таймаут котировок" }, + "swapQuotesNotAvailableDescription": { + "message": "Уменьшите размер своей сделки или используйте другой токен." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Попробуйте изменить настройки суммы или проскальзывания и повторить попытку." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Ниже приведены все котировки, собранные из нескольких источников ликвидности." }, + "swapSelectToken": { + "message": "Выберите токен" + }, + "swapShowLatestQuotes": { + "message": "Показать последние котировки" + }, "swapSlippageNegative": { "message": "Проскальзывание должно быть больше нуля или равно нулю" }, + "swapSlippageNegativeDescription": { + "message": "Проскальзывание должно быть больше или равно нулю" + }, + "swapSlippageNegativeTitle": { + "message": "Увеличьте проскальзывание, чтобы продолжить" + }, + "swapSlippageOverLimitDescription": { + "message": "Допуск на проскальзывание должен составлять 15% или менее. Все, что выше, приведет к неудачной ставке." + }, + "swapSlippageOverLimitTitle": { + "message": "Уменьшите проскальзывание, чтобы продолжить" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Максимальное проскальзывание слишком мало, что может привести к сбою транзакции." + }, + "swapSlippageTooLowTitle": { + "message": "Увеличьте проскальзывание, чтобы избежать неудачной транзакции" + }, "swapSlippageTooltip": { "message": "Изменение цены в период между размещением заказа и подтверждением называется «проскальзыванием». Обмен будет автоматически отменен, если фактическое проскальзывание превысит установленный «допуск проскальзывания»." }, + "swapSlippageVeryHighDescription": { + "message": "Введенное проскальзывание считается очень высоким и может привести к неудачной ставке" + }, + "swapSlippageVeryHighTitle": { + "message": "Очень высокое проскальзывание" + }, + "swapSlippageZeroDescription": { + "message": "Существует меньше поставщиков котировок с нулевым проскальзыванием, что приводит к менее конкурентоспособным котировкам." + }, + "swapSlippageZeroTitle": { + "message": "Поиск поставщиков с нулевым проскальзыванием" + }, "swapSource": { "message": "Источник ликвидности" }, @@ -3732,8 +4686,15 @@ "swapToConfirmWithHwWallet": { "message": "чтобы подтвердить с помощью аппаратного кошелька" }, + "swapTokenAddedManuallyDescription": { + "message": "Проверьте этот токен на $1 и убедитесь, что это именно тот токен, которым вы хотите торговать.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Токен добавлен вручную" + }, "swapTokenAvailable": { - "message": "Ваши $1 засичслены на ваш счет.", + "message": "Ваши $1 зачислены на ваш счет.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." }, "swapTokenBalanceUnavailable": { @@ -3758,6 +4719,13 @@ "message": "Токен проверен в $1 источниках.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 проверяется только на 1 источнике. Попробуйте проверить его на $2, прежде чем продолжить.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Потенциально неаутентичный токен" + }, "swapTooManyDecimalsError": { "message": "$1 позволяет использовать до $2 десятичных знаков", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Недостаточно $1 для выполнения этой транзакции", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Недостаточно $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Посмотреть в журнале активности" }, + "switch": { + "message": "Переключиться" + }, "switchEthereumChainConfirmationDescription": { "message": "В результате этого сеть, выбранная в MetaMask, будет изменена на ранее добавленную:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Вы переключились на" }, + "switcherTitle": { + "message": "Переключатель сети" + }, + "switcherTourDescription": { + "message": "Нажмите на значок для переключения сетей или добавления новой сети" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "В случае смены сетей все ожидающие подтверждения будут отменены" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Условия обслуживания" }, + "termsOfUse": { + "message": "условия использования" + }, + "termsOfUseAgreeText": { + "message": " Я согласен(-на) с Условиями использования, которые применяются к использованию мною MetaMask и всех его функций" + }, + "termsOfUseFooterText": { + "message": "Прокрутите, чтобы прочитать все разделы" + }, + "termsOfUseTitle": { + "message": "Наши Условия использования обновлены" + }, "testNetworks": { "message": "Протестировать сети" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Что нужно помнить:" }, + "thirdPartySoftware": { + "message": "Уведомление о стороннем ПО", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "эта коллекция" + }, + "thisServiceIsExperimental": { + "message": "Эта услуга является экспериментальной. Включив эту функцию, вы соглашаетесь с $1 OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Время" }, @@ -3863,11 +4867,44 @@ "message": "Адресат: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Вы подвержены риску фишинговых атак. Защитите себя, отключив eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Включите это, чтобы децентрализованные приложения могли запрашивать вашу подпись с помощью запросов eth_sign. eth_sign — это открытый метод подписи, который позволяет вам подписывать произвольный хеш, что делает его опасным для фишинга. Подписывайте запросы eth_sign только в том случае, если вы можете прочитать, что вы подписываете, и доверяете источнику запроса." + "message": "Если вы включите этот параметр, вы можете получать запросы подписи, которые невозможно прочитать. Подписав сообщение, которое вы не понимаете, вы можете согласиться отдать свои средства и NFT." }, "toggleEthSignField": { - "message": "Переключить запросы eth_sign" + "message": "Запросы eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " вас могут обмануть" + }, + "toggleEthSignModalBannerText": { + "message": "Если вас попросили включить этот параметр," + }, + "toggleEthSignModalCheckBox": { + "message": "Я понимаю, что могу потерять все свои средства и NFT, если включу запросы eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Разрешение запросов eth_sign может сделать вас уязвимыми для фишинговых атак. Всегда проверяйте URL-адрес и будьте осторожны при подписании сообщений, содержащих код." + }, + "toggleEthSignModalFormError": { + "message": "Текст неверный" + }, + "toggleEthSignModalFormLabel": { + "message": "Введите «Я подписываю только то, что понимаю», чтобы продолжить." + }, + "toggleEthSignModalFormValidation": { + "message": "Я подписываю только то, что понимаю" + }, + "toggleEthSignModalTitle": { + "message": "Используйте на свой риск" + }, + "toggleEthSignOff": { + "message": "ВЫКЛ. (рекомендуется)" + }, + "toggleEthSignOn": { + "message": "ВКЛ. (не рекомендуется)" }, "token": { "message": "Токен" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Символ токена" }, + "tokens": { + "message": "Токены" + }, "tokensFoundTitle": { "message": "Найдены $1 новых токена(-ов)", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Я понимаю" }, + "tooltipSatusConnected": { + "message": "подключено" + }, + "tooltipSatusNotConnected": { + "message": "не подключено" + }, "total": { "message": "Итого" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "Транзакция обнаружила ошибку." }, + "transactionFailed": { + "message": "Транзакция не удалась" + }, "transactionFee": { "message": "Комиссия за транзакцию" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Итого платы за газ" }, + "transactionNote": { + "message": "Примечание к транзакции" + }, "transactionResubmitted": { "message": "Транзакция отправлена повторно с платой за газ, увеличенной до $1, в $2" }, "transactionSecurityCheck": { - "message": "Включить поставщиков безопасности транзакций" + "message": "Включить оповещения безопасности" }, "transactionSecurityCheckDescription": { "message": "Мы используем сторонние API для обнаружения и отображения рисков, связанных с неподписанными транзакциями и запросами подписи, прежде чем вы их подпишете. Эти службы будут иметь доступ к вашим неподписанным транзакциям и запросам подписи, адресу вашей учетной записи и предпочитаемому вами языку." }, + "transactionSettings": { + "message": "Настройки транзакции" + }, "transactionSubmitted": { "message": "Транзакция отправлена с платой за газ в размере $1 в $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Перевести с" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "У нас возникли проблемы с подключением вашего леджера. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Ознакомьтесь с нашим руководством по подключению аппаратного кошелька и повторите попытку.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Если вы используете последнюю версию Firefox, у вас может возникнуть проблема, связанная с отказом Firefox от поддержки U2F. Узнайте, как решить эту проблему $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "здесь", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Не удалось подключиться к вашему $1, попробуйте проверить $2 и повторите попытку.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "стрелка «вверх»" }, + "update": { + "message": "Обновить" + }, "updatedWithDate": { "message": "Обновлено $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Это URL-адрес в настоящее время используется сетью $1." }, + "use4ByteResolution": { + "message": "Расшифровать смарт-контракты" + }, + "use4ByteResolutionDescription": { + "message": "Чтобы улучшить взаимодействие с пользователем, мы настраиваем вкладку действий, добавляя сообщения на основе смарт-контрактов, с которыми вы взаимодействуете. MetaMask использует службу под названием 4byte.directory для декодирования данных и показа версии смарт-контакта, которую легче читать. Это помогает снизить шансы того, что вы одобрите вредоносные действия смарт-контракта, но может привести к раскрытию вашего IP-адреса." + }, "useMultiAccountBalanceChecker": { "message": "Пакетные запросы баланса счета" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Получайте более быстрые обновления сведений о балансе, группируя запросы по балансу счета. Это позволяет нам извлекать совокупные данные об остатках на ваших счетах, чтобы вы могли быстрее получать обновления для улучшения впечатлений от использования. Когда эта функция отключена, снижается вероятность того, что третьи стороны свяжут ваши счета друг с другом." + }, "useNftDetection": { "message": "Автообнаружение NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Показывать предупреждение для фишинговых доменов, нацеленных на пользователей Ethereum" }, + "useSiteSuggestion": { + "message": "Использовать рекомендацию сайта" + }, "useTokenDetectionPrivacyDesc": { "message": "Автоматическое отображение токенов, отправленных на ваш счет, требует обмена данными со сторонними серверами для получения изображений токенов. Эти серверы получат доступ к вашему IP-адресу." }, @@ -4170,7 +5256,7 @@ "message": "Имя пользователя" }, "verifyContractDetails": { - "message": "Проверьте реквизиты контракта" + "message": "Проверьте информацию о третьей стороне" }, "verifyThisTokenDecimalOn": { "message": "Число десятичных знаков токена можно найти на $1", @@ -4184,12 +5270,18 @@ "message": "Проверьте этот токен на $1 и убедитесь, что это тот токен, которым вы хотите торговать.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Версия" + }, "view": { "message": "Просмотр" }, "viewAllDetails": { "message": "Смотреть все сведения" }, + "viewAllQuotes": { + "message": "смотреть все котировки" + }, "viewContact": { "message": "Смотреть контакт" }, @@ -4213,9 +5305,18 @@ "message": "Смотреть 1$ на Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Смотреть в Проводнике" + }, "viewOnOpensea": { "message": "Смотреть на Opensea" }, + "viewPortfolioDashboard": { + "message": "Просмотр панели инструментов портфеля" + }, + "viewinCustodianApp": { + "message": "Смотреть в приложении депозитария" + }, "viewinExplorer": { "message": "Смотреть $1 в Проводнике", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Хотите добавить эту сеть?" }, + "wantsToAddThisAsset": { + "message": "$1 хочет добавить этот актив в ваш кошелек", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Предупреждение" }, "warningTooltipText": { - "message": "$1 Контракт может потратить весь ваш баланс токенов без дополнительного уведомления или согласия. Защитите себя, установив более низкий лимит расходов.", + "message": "$1 Третья сторона может потратить весь ваш баланс токенов без дополнительного уведомления или согласия. Защитите себя, установив более низкий лимит расходов.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Вы подписываете" }, + "yourAccounts": { + "message": "Ваши счета" + }, "yourFundsMayBeAtRisk": { "message": "Ваши средства могут быть в опасности" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 82311d906..404594107 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Tungkol Dito" }, + "accept": { + "message": "Tanggapin" + }, "acceptTermsOfUse": { "message": "Nabasa ko at sumasang-ayon ako sa $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Magdagdag ng palayaw" }, + "addAccount": { + "message": "Magdagdag ng account" + }, "addAcquiredTokens": { "message": "Idagdag ang mga token na nakuha mo gamit ang MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Idagdag mula sa listahan ng mga sikat na network o mano-manong idagdag ang network. Makipag-ugnayan lamang sa mga entidad na iyong pinagkakatiwalaan." }, + "addHardwareWallet": { + "message": "Magdagdag ng hardware wallet" + }, + "addIPFSGateway": { + "message": "Idagdag ang mas gusto mong IPFS gateway" + }, "addMemo": { "message": "Magdagdag ng memo" }, @@ -244,6 +256,21 @@ "message": "Ang koneksyon sa network na ito ay umaasa sa mga third party. Ang koneksyon na ito ay maaaring hindi gaanong maaasahan o binibigyang-daan ang mga third-party na mag-track ng aktibidad. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Magdagdag ng bagong token" + }, + "addNft": { + "message": "Magdagdag ng NFT" + }, + "addNfts": { + "message": "Magdagdag ng mga NFT" + }, + "addSnapAccountModalDescription": { + "message": "Tuklasin ang mga opsyon upang mapanatiling ligtas ang iyong MetaMask Snaps" + }, + "addSuggestedNFTs": { + "message": "Magdagdag ng mga iminumungkahing NFT" + }, "addSuggestedTokens": { "message": "Magdagdag ng Mga Iminumungkahing Token" }, @@ -254,6 +281,9 @@ "message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng token contract ay matatagpuan sa $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Nagdaragdag ng Network" + }, "address": { "message": "Address" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Ang priority fee (kilala rin bilang “tip ng miner”) ay direktang napupunta sa mga miner at ginagawang insentibo ang mga ito upang unahin ang iyong mga transaksyon." }, + "agreeTermsOfUse": { + "message": "Sumasang-ayon ako sa $1 ng MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Vault" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Mga Alerto" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Alinman sa nakakonekta na ang lahat ng iyong custodian account o wala kang anumang account na nakakonekta sa MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Walang mga account na available para ikonekta" + }, "allOfYour": { "message": "Lahat ng iyong $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Lahat ng iyong NFT mula sa $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Payagan ang external extension na ito na:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Payagan ang site na ito na:" }, + "allowThisSnapTo": { + "message": "Payagan ang snap na ito sa:" + }, "allowWithdrawAndSpend": { "message": "Payagan ang $1 na mag-withdraw at gastusin ang sumusunod na halaga:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Halaga" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "Ethereum Wallet sa iyong Browser", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Payagan ang pag-access at paglipat ng lahat ng iyong $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Payagan ang access at ilipat ang lahat ng iyong NFT mula sa $1?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Aprubahan" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Nagbibigay-daan ito sa third party na i-access at ilipat ang mga sumusunod na NFT nang walang karagdagang abiso hanggang sa bawiin mo ang pag-access nito." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Nagbibigay-daan ito sa isang third party na i-access at ilipat ang lahat ng iyong NFT mula sa $1 nang walang karagdagang abiso hanggang sa bawiin mo ang pag-access nito.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Payagan ang pag-access at paglipat ng iyong $1?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Kung tatangkain mong magpadala ng mga asset nang direkta mula sa isang network papunta sa isa pa, maaari itong magresulta sa permanenteng pagkawala ng asset. Siguraduhing gumamit ng tulay." }, + "attemptToCancelSwap": { + "message": "Magtangkang kanselahin ang swap sa halagang ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Sinusubukang kumonekta sa blockchain." }, @@ -411,6 +473,9 @@ "average": { "message": "Average" }, + "awaitingApproval": { + "message": "Naghihintay ng pahintulot..." + }, "back": { "message": "Bumalik" }, @@ -454,6 +519,9 @@ "message": "Ito ay isang beta version. I-ulat ang mga bug $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Bersyong Beta ng MetaMask Institutional" + }, "betaMetamaskVersion": { "message": "Bersyon ng MetaMask Beta" }, @@ -488,9 +556,45 @@ "message": "Tingnan ang account sa $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Kapag pinahintulutan mo ang kahilingang ito, maaaring kunin ng third party na kilala sa mga scam ang lahat ng asset mo." + }, + "blockaidDescriptionBlurFarming": { + "message": "Kapag pinahintulutan mo ang kahilingang ito, maaaring nakawin ang mga asset mo na nakalista sa Blur." + }, + "blockaidDescriptionFailed": { + "message": "Dahil sa error, hindi na-verify ang hiling na ito ng tagapagbigay ng seguridad. Magpatuloy nang may pag-iingat." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Nakikipag-ugnayan ka sa malisyosong domain. Kapag pinahintulutan mo ang kahilingang ito, maaaring mawala ang mga asset mo." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Kapag pinahintulutan mo ang kahilingang ito, maaaring mawala ang mga asset mo." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Kapag pinahintulutan mo ang kahilingang ito, maaaring nakawin ang mga asset mo na nakalista sa OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Kapag pinahintulutan mo ang kahilingang ito, kukunin ng third party na kilala sa mga scam ang lahat ng asset mo." + }, + "blockaidTitleDeceptive": { + "message": "Ito ay mapanlinlang na kahilingan" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Maaaring hindi ligtas ang hiling" + }, + "blockaidTitleSuspicious": { + "message": "Ito ay kahina-hinalang kahilingan" + }, "blockies": { "message": "Mga Blocky" }, + "bridge": { + "message": "Tulay" + }, "browserNotSupported": { "message": "Hindi sinusuportahan ang iyong browser..." }, @@ -510,6 +614,10 @@ "message": "Bumili ng $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Bumili pa ng $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Bilhin Ngayon" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Nire-reset nito ang nonce ng account at binubura ang datos mula sa tab ng aktibidad sa iyong wallet. Tanging ang kasalukuyang account at network lamang ang maaapektuhan. Ang iyong mga balanse at mga papasok na transaksyon ay hindi magbabago." }, + "click": { + "message": "I-click" + }, "clickToConnectLedgerViaWebHID": { "message": "Mag-click dito upang ikonekta ang iyong Ledger sa pamamagitan ng WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Papaalis ka na ngayon sa MetaMask para ma-configure ang snap na ito." + }, + "configureSnapPopupInstallDescription": { + "message": "Papaalis ka na ngayon sa MetaMask para ma-install ang snap na ito." + }, + "configureSnapPopupInstallTitle": { + "message": "I-install ang snap" + }, + "configureSnapPopupLink": { + "message": "I-click ang link na ito para magpatuloy:" + }, + "configureSnapPopupTitle": { + "message": "I-configure ang snap" + }, "confirm": { "message": "Kumpirmahin" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Ikonekta ang account o gumawa ng bago" }, + "connectCustodialAccountMenu": { + "message": "Ikonekta ang Custodial Account" + }, + "connectCustodialAccountMsg": { + "message": "Pakipiliin ang custodian na gusto mong ikonekta para maidagdag o i-refresh ang token." + }, + "connectCustodialAccountTitle": { + "message": "Mga Custodial Account" + }, "connectManually": { "message": "Manu-manong kumonekta sa kasalukuyang site" }, + "connectSnap": { + "message": "Ikonekta ang $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Kumonekta sa $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Kumokonekta sa Linea Goerli test network" }, + "connectingToLineaMainnet": { + "message": "Kumokonekta sa Linea Mainnet" + }, "connectingToMainnet": { "message": "Kumokonekta sa Ethereum Mainnet" }, "connectingToSepolia": { "message": "Kumokonekta sa Sepolia test network" }, + "connectionFailed": { + "message": "Nabigo ang koneksyon" + }, + "connectionFailedDescription": { + "message": "Nabigo ang pagkuha ng $1, suriin ang iyong network at subukan ulit.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Kahilingan sa koneksyon" + }, "contactUs": { "message": "Makipag-ugnayan sa amin" }, @@ -708,7 +860,7 @@ "message": "Deployment ng Kontrata" }, "contractDescription": { - "message": "Para protektahan ang iyong sarili laban sa mga manloloko, maglaan ng ilang sandali upang i-verify ang mga detalye ng kontrata." + "message": "Para protektahan ang iyong sarili laban sa mga manloloko, maglaan ng ilang sandali upang i-verify ang mga detalye ng third-party." }, "contractInteraction": { "message": "Interaksyon ng kontrata" @@ -717,16 +869,16 @@ "message": "Kontrata ng NFT" }, "contractRequestingAccess": { - "message": "Kontrata na humihiling ng access" + "message": "Third party na humihiling ng access" }, "contractRequestingSignature": { - "message": "Kontratang humihiling ng lagda" + "message": "Third party na humihiling ng lagda" }, "contractRequestingSpendingCap": { - "message": "Humihiling ang kontrata ng limitasyon sa paggasta" + "message": "Third party na humihiling ng limitasyon sa paggastos" }, "contractTitle": { - "message": "Mga detalye ng kontrata" + "message": "Mga detalye ng third-party" }, "contractToken": { "message": "Kontrata ng token" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Grap ng Merkado sa pagtantiya sa gas" }, + "custodian": { + "message": "Tagapangalaga" + }, + "custodianAccount": { + "message": "Account ng tagapangalaga" + }, + "custodianAccountAddedDesc": { + "message": "Maaari mo na ngayong gamitin ang iyong mga custodian account sa MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Naidagdag na ang mga napiling custodian account." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Magpunta sa $1 at i-click ang button na 'Ikonekta sa MMI' sa loob ng kanilang user interface para ikonektang muli ang iyong mga account sa MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Maaari mo na ngayong gamitin ang mga custodian account mo sa MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Na-refresh na ang iyong custodian token" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Papalitan nito ang custodian token para sa mga sumusunod na address:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Palitan ang custodian token" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "Aprubahan ang transaksyon sa $1 app. Kapag naisagawa na ang lahat ng kailangang pahintulot sa kustodiya ang transaksyon ay makukumpleto. Tingnan ang iyong $1 app para sa status." + }, + "custodyRefreshTokenModalDescription": { + "message": "Mangyaring pumunta sa $1 at i-click ang button na 'Kumonekta sa MMI' sa loob ng kanilang user interface upang ikonekta muli ang iyong mga account sa MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Nag-isyu ng token ang iyong custodian na nagpapatotoo sa MetaMask Institutional extension, na nagbibigay-daan sa iyong ikonekta ang iyong mga account." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Mapapaso ang token na ito pagkatapos ng isang partikular na panahon sa mga kadahilanang pangseguridad. Kinakailangan nitong kumonekta muli sa MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Bakit nakikita ko ito?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Napaso na ang iyong sesyon sa custodian" + }, + "custodySessionExpired": { + "message": "Nag-expire na ang sesyon ng custodian." + }, + "custodyWrongChain": { + "message": "Ang account na ito ay hindi naka-set up para gamitin sa $1" + }, "custom": { "message": "Makabago" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "suporta sa kostumer" }, + "dappRequestedSpendingCap": { + "message": "Hiniling sa site na cap sa paggamit" + }, "dappSuggested": { "message": "Minungkahing site" }, @@ -851,6 +1060,12 @@ "message": "Minungkahi ng $1 ang presyong ito.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Iminungkahi ang site" + }, + "dappSuggestedHighShortLabel": { + "message": "Site (mataas)" + }, "dappSuggestedShortLabel": { "message": "Site" }, @@ -902,9 +1117,19 @@ "delete": { "message": "I-delete" }, + "deleteContact": { + "message": "Tanggalin ang contact" + }, "deleteNetwork": { "message": "I-delete ang network?" }, + "deleteNetworkIntro": { + "message": "Kapag binura mo ang network na ito, kakailanganin mo itong idagdag muli para makita ang mga asset mo sa network na ito" + }, + "deleteNetworkTitle": { + "message": "Burahin ang $1 network?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Deposito" }, @@ -917,6 +1142,10 @@ "description": { "message": "Deskripsyon" }, + "descriptionFromSnap": { + "message": "Deskripsyon mula sa $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Maaaring paulit-ulit ang error na ito, kaya subukang i-restart ang extension o huwag paganahin ang MetaMask Desktop." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "I-dismiss ang back up na paalala ng Secret Recovery Phrase" }, + "displayNftMedia": { + "message": "Ipakita ang NFT media" + }, + "displayNftMediaDescription": { + "message": "Ang pagpapakita ng NFT media at data ay naglalantad sa iyong IP address sa OpenSea o sa ibang mga third party. Maaari nitong payagan ang mga umaatake na iugnay ang iyong IP address sa iyong Ethereum address. Umaasa ang NFT autodetection sa setting na ito, at hindi magagamit kapag naka-off ito." + }, "domain": { "message": "Domain" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Paganahin ang autodetect" }, + "enableForAllNetworks": { + "message": "Paganahin para sa lahat ng network" + }, "enableFromSettings": { "message": " Paganahin ito mula sa Settings." }, + "enableSmartSwaps": { + "message": "I-enable ang mga smart swap" + }, + "enableSnap": { + "message": "Paganahin" + }, "enableToken": { "message": "paganahin ang $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Pinagana" + }, "encryptionPublicKeyNotice": { "message": "Kailangan ng $1 ang iyong pampublikong encryption key. Sa pamamagitan ng pagbibigay ng pahintulot, makakagawa ang site na ito ng mga naka-encrypt na mensahe para sa iyo.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Ang pinahusay na pagtuklas ng token ay kasalukuyang magagamit sa $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "Pinapahintulutan ka ng MetaMask na makita ang mga ENS domain gaya ng \"https://metamask.eth\" mismo sa address bar ng iyong browser. Narito kung paano ito ginagawa:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Ang mga regular na browser ay kadalasang hindi humahawak ng mga ENS o IPFS address, pero ang MetaMask ay tumutulong dito. Ang paggamit sa feature na ito ay maaring magbahagi sa iyong IP address gamit ang mga IPFS third-party na serbisyo." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "Sinusuri ng MetaMask ang ENS contract ng Ethereum para mahanap ang code na konektado sa pangalan ng ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Kapag ang code ay naka-link sa IPFS, kinukuha nito ang content mula sa IPFS network." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Pagkatapos, makikita mo ang content, na karaniwang website o isang bagay na katulad." + }, + "ensDomainsSettingTitle": { + "message": "Ipinapakita ang mga ENS domain sa address bar" + }, "ensIllegalCharacter": { "message": "Mga ilegal na character para sa ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Maglagay ng numero" }, + "enterCustodianToken": { + "message": "Ilagay ang iyong $1 na token o magdagdag ng bagong token" + }, "enterMaxSpendLimit": { "message": "Ilagay ang max na limitasyon sa paggastos" }, + "enterOptionalPassword": { + "message": "Ilagay ang opsyonal na password" + }, "enterPassword": { "message": "Ilagay ang password" }, "enterPasswordContinue": { "message": "Ilagay ang password para magpatuloy" }, + "enterTokenNameOrAddress": { + "message": "Ilagay ang pangalan ng token o i-paste ang address" + }, + "enterYourPassword": { + "message": "Ilagay ang iyong password" + }, "errorCode": { "message": "Code: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Stack:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Error habang kumokonekta sa custom na network." + }, + "errorWithSnap": { + "message": "Error sa $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Ang backup gas price ay inilalaan dahil ang pangunahing pagtantiya ng presyo ng gas ay hindi available sa ngayon." }, + "ethereumProviderAccess": { + "message": "Payagan ang pag-access sa Ethereum provider sa $1", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Pampublikong address ng ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Pinag-eeksperimentuhan" }, + "exploreMetaMaskSnaps": { + "message": "Galugarin ang MetaMask Snaps" + }, "exportPrivateKey": { "message": "I-export ang Pribadong Key" }, + "extendWalletWithSnaps": { + "message": "Palawakin ang karanasan sa wallet." + }, "externalExtension": { "message": "External Extension" }, @@ -1302,6 +1596,9 @@ "message": "Hindi gumagana ang pag-import ng file? Mag-click dito!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Napakalaki ng na-drop na file." + }, "flaskWelcomeUninstall": { "message": "dapat mong i-uninstall ang extension na ito", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Pangkalahatan" }, + "getStarted": { + "message": "Magsimula" + }, + "globalTitle": { + "message": "Global menu" + }, + "globalTourDescription": { + "message": "Tingnan ang iyong portfolio, mga nakakonektang site, setting at marami pang iba" + }, "goBack": { "message": "Bumalik" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Magkonekta ng hardware wallet" }, + "hardwareWalletsInfo": { + "message": "Ang pagsasama ng hardware wallet ay gumagamit ng mga API call sa mga external server, na nakakakita sa IP address mo at sa mga smart contract address kung saan ka nakipag-ugnayan." + }, "hardwareWalletsMsg": { "message": "Pumili ng hardware wallet na gusto mong gamitin kasama ng MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "ngunit maaring hingin ng mga phisher.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Nagbibigay ang iyong Pribadong Key ng $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "ganap na access sa iyong wallet at mga pondo.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "i-hold para ipakita ang bilog na naka-lock" + }, + "holdToRevealPrivateKey": { + "message": "I-hold para ipakita ang Pribadong Key" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Ingatan ang iyong pribadong key" + }, "holdToRevealSRP": { "message": "I-hold para ipakita ang SRP" }, "holdToRevealSRPTitle": { - "message": "Panatilihing ligtas ang iyong SRP" + "message": "Ingatan ang iyong SRP" + }, + "holdToRevealUnlockedLabel": { + "message": "i-hold para ipakita ang circle unlocked" + }, + "id": { + "message": "Id" }, "ignoreAll": { "message": "Huwag pansinin ang lahat" @@ -1563,6 +1895,21 @@ "importAccountError": { "message": "Error sa pag-import ng account." }, + "importAccountErrorIsSRP": { + "message": "Naglagay ka ng Secret Recovery Phrase (o mnemonic). Para mag-import ng account dito, kailangan mong ilagay ang private key, na isang hexadecimal string na may habang 64." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Ito ay hindi wastong private key. Naglagay ka ng hexadecimal string, ngunit dapat ito ay 64 na character ang haba." + }, + "importAccountErrorNotHexadecimal": { + "message": "Ito ay hindi wastong private key. Dapat kang maglagay ng hexadecimal string na 64 ang haba." + }, + "importAccountJsonLoading1": { + "message": "Asahan ang pag-import ng JSON na ito ay tatagal ng ilang minuto at i-freeze ang MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Humihingi kami ng paumanhin, at gagawin namin itong mas mabilis sa hinaharap." + }, "importAccountMsg": { "message": "Ang mga na-import na account ay hindi mauugnay sa orihinal mong nagawang Secret Recovery Phrase ng MetaMask account. Matuto pa tungkol sa mga na-import account" }, @@ -1615,18 +1962,29 @@ "message": "Nakumpirma na ng network ang iyong inisyal na transaksyon. I-click ang OK para bumalik." }, "inputLogicEmptyState": { - "message": "Maglagay lamang ng numero na komportable kang gastusin ng kontrata ngayon o sa hinaharap. Palagi mong madadagdagan ang limitasyon sa paggastos sa ibang pagkakataon." + "message": "Maglagay lamang ng numero na komportable ka sa paggastos ng third party ngayon o sa hinaharap. Maaari mong palaging taasan ang limitasyon sa paggastos sa ibang pagkakataon." }, "inputLogicEqualOrSmallerNumber": { - "message": "Pinapayagan nito ang kontrata na gumastos ng $1 mula sa iyong kasalukuyang balanse.", + "message": "Nagbibigay-daan ito sa third party na gumastos ng $1 mula sa iyong kasalukuyang balanse.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Pinapayagan nito ang kontrata na gastusin ang lahat ng balanse ng iyong token hanggang sa maabot nito ang limitasyon o ipawalang-bisa ang limitasyon sa paggastos. Kung hindi ito sinasadya, isaalang-alang na magtakda ng mas mababang limitasyon sa paggastos." + "message": "Pinapayagan nito ang third party na gastusin ang lahat ng balanse ng iyong token hanggang sa maabot nito ang limitasyon o bawiin mo ang limitasyon sa paggastos. Kung hindi ito nilayon, isaalang-alang ang pagtatakda ng mas mababang limitasyon sa paggastos." + }, + "insightsFromSnap": { + "message": "Mga kaalaman mula sa $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "I-install" }, + "installOrigin": { + "message": "I-install ang pinagmulan" + }, + "installedOn": { + "message": "In-install sa $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Hindi sapat ang balanse." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Di-wastong input! Ang Secret Recovery Phrase ay case sensitive." }, + "ipfsGateway": { + "message": "IPFS gateway" + }, + "ipfsGatewayDescription": { + "message": "Ang MetaMask ay gumagamit ng mga serbisyo ng third-party para ipakita ang mga imahe ng iyong mga NFT na nakaimbak sa IPFS, nagpapakita ng impormasyong nauugnay sa mga ENS address na ipinakita sa address bar ng iyong browser, at kinukuha ang mga icon para sa iba't ibang token. Ang iyong IP address ay maaaring ipakita sa mga serbisyong ito kapag ginagamit mo ang mga iyon." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Gumagamit kami ng mga serbisyo ng third-party para ipakita ang imahe ng mga NFT na inimbak mo sa IPFS, nagpapakita ng impormasyong nauugnay sa mga ENS address na inilagay sa address bar ng browser mo, at kinukuha ang mga icon para sa iba't ibang token. Ang Iyong IP address ay maaaring makita ng iba sa mga serbisyong ito kapag ginagamit mo ang mga ito." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Ang pagpili sa Kumpirmahin ay nagbubukas sa IPFS resolution. Maaari mo itong isara sa loob ng $1 kahit anong oras.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Settings > Seguridad at privacy" + }, "jazzAndBlockies": { "message": "Ang mga Jazzicon at Blockies ay dalawang magkaibang istilo ng mga natatanging icon na makakatulong sa iyong makilala ang isang account sa isang sulyap." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Huling naibenta" }, + "layer1Fees": { + "message": "Layer 1 na bayad" + }, "learnCancelSpeeedup": { "message": "Alamin kung paano sa $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Gusto mo bang $1 tungkol sa gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Matuto Pa" + }, "learnMoreUpperCase": { "message": "Matuto pa" }, @@ -1765,16 +2145,16 @@ "message": "Bago ang pag-click ang kumpirmahin ang:" }, "ledgerConnectionInstructionStepFour": { - "message": "Paganahin ang \"smart contract data\" o \"may takip na pagpirma\" sa iyong Ledger device" + "message": "Paganahin ang \"smart contract data\" o \"blind signing\" sa iyong Ledger device." }, "ledgerConnectionInstructionStepOne": { - "message": "Paganahin ang Gamitin ang Ledger Live sa ilalim ng Settings > Advanced" + "message": "Paganahin ang Gamitin ang Ledger Live sa ilalim ng Mga Setting > Advanced." }, "ledgerConnectionInstructionStepThree": { - "message": "I-plug in ang iyong Ledger device at piliin ang Ethereum app" + "message": "Tiyaking naka-plug in ang iyong Ledger at piliin ang Ethereum app." }, "ledgerConnectionInstructionStepTwo": { - "message": "Buksan at i-unlock ang Ledger Live App" + "message": "Buksan at i-unlock ang Ledger Live App." }, "ledgerConnectionPreferenceDescription": { "message": "I-customize kung paano ka kokonekta sa iyong Ledger sa MetaMask. Ang $1 ay nirerekomenda, pero ang ibang mga opsyon ay available. Magbasa pa dito: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea Goerli test network" }, + "lineaMainnet": { + "message": "Linea Mainnet" + }, "link": { "message": "Link" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "I-lock" }, + "lockMetaMask": { + "message": "I-lock ang MetaMask" + }, + "lockTimeInvalid": { + "message": "Ang lock time ay dapat na isang numero sa pagitan ng 0 at 10080" + }, "logo": { "message": "$1 logo", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Makikita sa button ng status ng koneksyon kung nakakonekta ang website na binibisita mo sa kasalukuyan mong napiling account." }, + "metamaskInstitutionalVersion": { + "message": "Bersyon ng MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "Kasalukuyang minementina ang MetaMask Swaps. Bumalik sa ibang pagkakataon." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Metrics" }, + "mismatchAccount": { + "message": "Ang pinili mong account na ($1) ay kakaiba sa account na sinusubukan mong mag-sign in ($2)" + }, "mismatchedChainLinkText": { "message": "i-verify ang mga detalye ng network", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Ang isinumiteng simbolo ng currency ay hindi tumutugma sa inaasahan namin para sa chain ID na ito." }, + "mismatchedRpcChainId": { + "message": "Ang Chain ID na ibinalik ng custom na network ay hindi tumutugma sa isinumiteng chain ID." + }, "mismatchedRpcUrl": { "message": "Ayon sa aming mga talaan, ang isinumiteng RPC URL value ay hindi tumutugma sa isang kilalang provider para sa chain ID na ito." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Hilingin dito" }, + "mmiAddToken": { + "message": "Ang pahina sa $1 ay nais pahintulutan ang sumusunod na tagapangalaga na token sa MetaMask Institutional" + }, + "mmiBuiltAroundTheWorld": { + "message": "Ang MetaMask Institutional ay dinisenyo at binuo sa buong mundo." + }, + "more": { + "message": "higit pa" + }, "moreComingSoon": { - "message": "Marami pang parating..." + "message": "Parating na ang mas maraming provider" + }, + "multipleSnapConnectionWarning": { + "message": "Gustong kumonekta ng $1 sa $2 (na) snap. Magpatuloy lang kung pinagkakatiwalaan mo ang website na ito.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Dapat pumili ng kahit 1 token lang." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Busy ang network. Ang presyo ng gas ay mataas at ang pagtantiya ay hindi gaanong tumpak." }, + "networkMenu": { + "message": "Network Menu" + }, + "networkMenuHeading": { + "message": "Pumili ng network" + }, "networkName": { "message": "Pangalan ng Network" }, @@ -2033,6 +2450,10 @@ "message": "Ang mga gas fee na $1 ay nauugnay sa huling 72 oras.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Hindi kami makakonekta sa $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL Network" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "Mga NFT" }, + "nftsPreviouslyOwned": { + "message": "Dating Pagmamay-ari" + }, "nickname": { "message": "Palayaw" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Hindi available ang rate ng conversion" }, + "noNFTs": { + "message": "Wala pang mga NFT" + }, + "noNetworksFound": { + "message": "Walang nakitang mga network para sa ibinigay na tanong ng paghahanap" + }, "noSnaps": { - "message": "Walang mga Snap na naka-install" + "message": "Wala kang naka-install na snaps." }, "noThanksVariant2": { "message": "Huwag na lang." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Ito ba ang tamang account? Iba ito sa kasalukuyang napiling account sa iyong wallet" }, + "notEnoughBalance": { + "message": "Hindi sapat ang balanse" + }, "notEnoughGas": { "message": "Hindi Sapat ang Gas" }, + "note": { + "message": "Tala" + }, + "notePlaceholder": { + "message": "Makikita ng nag-apruba ang tala na ito kapag inaprubahan ang transaksyon sa custodian." + }, + "notificationTransactionFailedMessage": { + "message": "Nabigo ang transaksyon $1! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Nabigo ang transaksyon! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Nabigong transaksyon", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Nakumpirma ang transaksyon $1!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Nakumpirmang transaksyon", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Tingnan sa $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Mga Abiso" + }, "notifications10ActionText": { "message": "Bisitahin sa mga setting", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Narito na ang Ethereum Merge!" }, + "notifications18ActionText": { + "message": "Paganahin ang mga alerto sa seguridad" + }, + "notifications18DescriptionOne": { + "message": "Makakuha ng mga alerto mula sa mga third party kapag maaaring nakatanggap ka ng kahina-hinalang kahilingan.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Laging siguraduhin na gawin ang iyong sariling makatwirang pagsusuri bago aprubahan ang anumang mga kahilingan.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "Ang OpenSea ay ang unang provider para sa tampok na ito. Marami pang mga provider ang paparating!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Manatiling ligtas sa mga alerto sa seguridad" + }, + "notifications19ActionText": { + "message": "Paganahin ang NFT autodetection" + }, + "notifications19DescriptionOne": { + "message": "Dalawang paraan upang makapagsimula:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Sinusuportahan lang namin ang ERC-721 sa ngayon.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Manu-manong idagdag ang iyong mga NFT, o i-on ang autodetection ng NFT sa Mga Setting > Eksperimento.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Tingnan ang iyong mga NFT nang hindi tulad ng dati" + }, "notifications1Description": { "message": "Ang mga user ng MetaMask Mobile ay maaari na ngayong mag-swap ng mga token sa loob ng kanilang mobile wallet. I-scan ang QR code para makuha ang mobile app at magsimulang mag-swap.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Narito ang pag-swap sa mobile!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Matuto pa", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Kung ikaw ay nasa pinakabagong bersyon ng Firefox, maaari kang makaranas ng isyu na nauugnay sa Firefox dropping U2F support.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Ang mga User ng Ledger at Firefox ay Nakakaranas ng Isyu sa Koneksyon", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Subukan ito" + }, + "notifications21Description": { + "message": "In-update namin ang mga Swap sa MetaMask extension para maging mas madali at mas mabilis na gamitin.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Ipinapakilala ang bago at ni-refresh na mga Swap!" + }, + "notifications22ActionText": { + "message": "Nakuha ko" + }, + "notifications22Description": { + "message": "💡 I-click lang ang pandaigdigang menu o account menu para mahanap ang mga iyon!" + }, + "notifications22Title": { + "message": "Naghahanap ka ba ng mga detalye ng account o i-block ang explorer URL?" + }, + "notifications23ActionText": { + "message": "Pinapagana ang mga alerto sa seguridad" + }, + "notifications23DescriptionOne": { + "message": "Iniiwasan ang mga kilalang scam habang pinapanatili pa rin ang pagpapanatili ng iyong privacy gamit ang mga alerto ng seguridad na pinatatakbo ng Blockaid." + }, + "notifications23DescriptionThree": { + "message": "Kung pinagana mo ang mga alerto ng seguridad mula sa OpenSea, inilipat ka namin sa feature na ito." + }, + "notifications23DescriptionTwo": { + "message": "Palaging gumawa ng sarili mong pagsusuri bago aprubahan ang mga kahilingan." + }, + "notifications23Title": { + "message": "Manatiling ligtas gamit ang mga alerto ng seguridad" + }, "notifications3ActionText": { "message": "Magbasa pa", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Kumonekta lang sa mga site na pinagkakatiwalaan mo." }, "openFullScreenForLedgerWebHid": { - "message": "Buksan ang MetaMask sa buong screen para ikonekta ang ledger mo sa pamamagitan ng WebHID.", + "message": "Pumunta sa full screen para ikonekta ang iyong Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Buksan sa block explorer" }, "openSea": { - "message": "OpenSea na (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Bigo ang Operasyon" + }, "optional": { "message": "Opsyonal" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Hindi Magkatugma ang Mga Password" }, + "pasteJWTToken": { + "message": "I-paste o i-drop ang iyong token dito:" + }, "pastePrivateKey": { "message": "I-paste ang string ng iyong pribadong key dito:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "I-access ang Internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Payagan ang snap na ma-access ang internet. Maaari itong gamitin para magpadala at tumanggap ng datos gamit ang mga third-party server.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Kumonekta sa $1 snap.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Payagan ang website o snap na makipag-ugnayan sa $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Mag-iskedyul at magsagawa ng mga pana-panahong mga aksyon.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Payagan ang snap na isagawa ang mga aksyon na madalas paganahin sa hindi nababagong oras, petsa, o agwat. Maaari itong gamitin para ma-trigger ang mga pakikipag-ugnayan o abiso na sensitibo sa oras.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Ipakita ang mga dialog window sa MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Payagan ang snap na ipakita ang mga MetaMask popup na may custom text, input field, at mga button upang maaprubahan o matanggihan ang aksyon.\nMaaaring gamitin para lumikha hal. ng mga alerto, kumpirmasyon, at opt-in flow para sa snap.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Tingnan ang mga address, balanse ng account, aktibidad at simulan ang iyong mga transaksyon", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "I-access ang provider ng Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Payagan ang snap na makipag-usap nang direkta sa MetaMask, para mabasa nito ang datos mula sa blockchain at magmungkahi ng mga mensahe at transaksyon.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Kumuha ng mga arbitrary key na natatangi sa snap na ito.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Payagan ang snap na makuha ang mga arbitraryong key na natatangi sa snap na ito, nang hindi sila ibinubunyag. Ang mga key na ito ay hiwalay mula sa iyong (mga) account sa MetaMask at hindi nauugnay sa iyong mga pribadong key o Secret Recovery Phrase. Ang ibang mga snap ay hindi maa-access ang impormasyong ito.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Gumagamit ng mga lifecycle hook.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Pinapayagan ang snap na gumamit ng mga lifecycle hook para paganahin ang code sa mga espisipikong panahon sa oras ng lifecycle nito.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Patakbuhin ng walang katapusan.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Payagan a ng snap para paganahin nang walang katiyakan, halimbawa, nagpoproseso ng malaking halaga ng datos.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Magdagdag at kontrolin ang mga Ethereum account", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Kontrolin ang iyong mga account at asset sa ilalim ng $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Payagan ang snap na kunin ang pares ng BIP-32 key batay sa iyong Secret Recovery Phrase nang hindi ito ibinubunyag. Magbibigay ito ng buong access sa lahat ng account at asset sa $1.\nGamit ang kapangyarihang pamahalaan ang mga key, ang snap ay maaaring sumuporta sa iba't ibang protocol ng blockchain higit sa Ethereum (mga EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Kontrolin ang iyong mga account at asset sa \"$1\".", + "message": "Kontrolin ang iyong mga account at asset sa $1.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Payagan ang snap na kunin ang mga pares ng BIP-44 key sa iyong Secret Recovery Phrase nang hindi ito ibinubunyag. Nagbibigay ito ng buong access sa lahat ng account at asset sa $1.\nGamit ang kapangyarihang pamahalaan ang mga key, ang snap ay maaaring sumuporta sa iba't ibang protocol ng blockchain higit sa Ethereum (mga EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Kontrolin ang iyong $1 na mga account at asset.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Iimbak at pamahalaan ang datos nito sa iyong device.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Payagan ang snap na mag-imbak, mag-update, at bawiin ang mga datos nang ligtas gamit ang pag-encrypt. Ang ibang mga snap ay hindi maa-access ang impormasyong ito.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Ipakita ang mga notipikasyon.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Payagan ang snap na ipakita ang mga abiso sa loob ng MetaMask. Ang maikling text ng abiso ay maaaring ma-trigger sa pamamagitan ng snap para sa impormasyong naaaksyunan o sensitibo sa oras.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Payagan ang $1 na direktang makipag-usap sa snap na ito.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Payagan ang $1 na magpadala ng mga mensahe sa snap at tumanggap ng mga tugon mula sa snap.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Kunin at ipakita ang mga insight sa transaksyon.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Payagan ang snap na mag-decode ng mga transaksyon at ipakita ang mga insight sa loob ng MetaMask UI. Magagamit ito para sa mga solusyon sa anti-phishing at seguridad.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Tingnan ang pinagmulan ng mga website na nagmumungkahi ng mga transaksyon", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Payagan ang snap na makita ang orihinal (URI) ng mga website na nagmumungkahi ng mga transaksyon. Magagamit ito para sa mga solusyon sa anti-phishing at seguridad.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Hindi kilalang pahintulot: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Tingnan ang iyong pampublikong key para sa $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Payagan ang snap na makita ang iyong mga pampublikong key (at mga address) para sa $1. Hindi ito nagbibigay ng anumang kontrol ng mga account o asset.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Tingnan ang iyong pampublikong key para sa $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Suporta para sa WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Payagan ang snap na ma-access ang mababang antas ng mga kapaligiran ng pagpapatupad sa pamamagitan ng WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Mga Pahintulot" }, + "permissionsTitle": { + "message": "Mga Pahintulot" + }, + "permissionsTourDescription": { + "message": "Hanapin ang mga nakakonekta mong account at pamahalaan ang mga pahintulot dito" + }, "personalAddressDetected": { "message": "Natukoy ang personal na address. Ilagay ang address ng kontrata ng token." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portfolio" }, + "portfolioDashboard": { + "message": "Dashboard ng Portfolio" + }, "preferredLedgerConnectionType": { "message": "Napiling Uri ng Ledger Connection", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Pribadong Key", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Private key para sa $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Babala: Huwag ipaalam ang key na ito. Ang sinumang mayroon ng iyong mga pribadong key ay maaaring magnakaw ng anumang asset sa iyong account." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Naka-queue" }, + "quoteRate": { + "message": "Quote rate" + }, "reAddAccounts": { "message": "idagdag muli ang anumang ibang mga account" }, @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Tatanggalin ang account na ito sa iyong wallet. Tiyaking nasa iyo ang orihinal na Secret Recovery Phrase o private key para sa na-import na account na ito bago magpatuloy. Puwede kang mag-import o gumawa ulit ng mga account mula sa drop-down ng account. " }, + "removeJWT": { + "message": "Tangggalin ang custodian token" + }, + "removeJWTDescription": { + "message": "Sigurado ka ba na gusto mong tanggalin ang token na ito? Ang lahat ng account na itinalaga sa token na ito ay tatanggalin din mula sa ekstensyon: " + }, "removeNFT": { "message": "Tanggalin ang NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Maaari mong ibalik ang mga setting ng user na naglalaman ng mga kagustuhan at mga address ng account mula sa isang dating na-back up na JSON file." }, + "resultPageError": { + "message": "Error" + }, + "resultPageErrorDefaultMessage": { + "message": "Nabigo ang operasyon." + }, + "resultPageSuccess": { + "message": "Tagumpay" + }, + "resultPageSuccessDefaultMessage": { + "message": "Matagumpay na nakumpleto ang operasyon." + }, "retryTransaction": { "message": "Subukan Ulit ang Transaksyon" }, @@ -2939,16 +3607,27 @@ "message": "Bawiin ang pahintulot na i-access at ilipat ang lahat ng iyong $1?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Bawiin ang pahintulot na i-access at ilipat ang lahat ng iyong NFT mula sa $1?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Binawi nito ang pahintulot para sa isang third party na i-access at ilipat ang lahat ng iyong $1 nang walang karagdagang abiso.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Binabawi nito ang pahintulot para sa isang third party na i-access at ilipat ang lahat ng iyong NFT mula sa $1 nang walang karagdagang abiso.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Bawiin ang pahintulot" + }, "revokeSpendingCap": { "message": "Bawiin ang limitasyon sa paggastos para sa iyong $1", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Hindi na magagawang gastusin pa ng kontratang ito ang iyong mga pangkasalukuyan o panghinaharap na mga token." + "message": "Ang third party na ito ay hindi na makakagastos pa sa iyong kasalukuyan o hinaharap na mga token." }, "rpcUrl": { "message": "Bagong RPC URL" @@ -2986,9 +3665,28 @@ "security": { "message": "Seguridad" }, + "securityAlert": { + "message": "Alerto ng seguridad mula sa $1 at $2" + }, + "securityAlerts": { + "message": "Mga alerto sa seguridad" + }, + "securityAlertsDescription1": { + "message": "Ang feature na ito ay nagbibigay ng alerto sa iyo sa malisyosong aktibidad sa pamamagitan ng lokal na pagsusuri sa iyong mga transaksyon at kahilingan sa pagpirma. Ang iyong data ay hindi ibinabahagi sa mga third party na nagbibigay ng serbisyong ito. Palaging gumawa ng sarili mong pagsusuri bago aprubahan ang anumang mga kahilingan. Walang garantiya na made-detect ng feature na ito ang lahat ng malisyosong aktibidad." + }, + "securityAlertsDescription2": { + "message": "Laging tiyakin na isasagawa ang sariling pagsusuri bago pahintulutan ang anumang kahilingan. Walang garantiya na ang lahat ng malisyosong aktibidad ay matutuklasan ng feature na ito." + }, "securityAndPrivacy": { "message": "Seguridad at Pagkapribado" }, + "securityProviderAdviceBy": { + "message": "Abiso sa seguridad ng $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Tingnan ang mga detalye" + }, "seedPhraseConfirm": { "message": "Kumpirmahin ang Secret Recovery Phrase" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Isulat ang iyong Secret Recovery Phrase" }, + "select": { + "message": "Piliin ang" + }, "selectAccounts": { "message": "Pumili ng (mga) account" }, + "selectAccountsForSnap": { + "message": "Piliin ang (mga) account para gamitin ang snap na ito" + }, "selectAll": { "message": "Piliin lahat" }, + "selectAllAccounts": { + "message": "Piliin ang lahat ng mga account" + }, "selectAnAccount": { "message": "Pumili ng Account" }, "selectAnAccountAlreadyConnected": { "message": "Ang acount na ito ay nakakonekta na sa MetaMask" }, + "selectAnAccountHelp": { + "message": "Piliin ang mga custodian account para gamitin sa MetaMask Institutional." + }, "selectHdPath": { "message": "Pumili ng HD Path" }, + "selectJWT": { + "message": "Pumili ng token" + }, "selectNFTPrivacyPreference": { "message": "I-on ang pag-detect ng NFT sa Settings" }, @@ -3113,6 +3826,9 @@ "message": "Aprubahan ang $1 nang walang limitasyon sa paggastos", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Magdagdag ng snap account" + }, "settings": { "message": "Mga Setting" }, @@ -3141,9 +3857,21 @@ "message": "Piliin ito para gamitin ang Etherscan sa pagpapakita ng mga papasok na transaksyon sa listahan ng mga transaksyon", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Umaasa ito sa bawat network na magkakaroon ng access sa iyong Ethereum address at sa iyong IP address." + }, + "showMore": { + "message": "Ipakita ang iba pa" + }, + "showNft": { + "message": "Ipakita ang NFT" + }, "showPermissions": { "message": "Ipakita ang mga pahintulot" }, + "showPrivateKey": { + "message": "Ipakita ang private key" + }, "showPrivateKeys": { "message": "Ipakita ang Mga Pribadong Key" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Nauunawaan ko na hanggang sa i-back up ko ang aking Secret Recovery Phrase, maaari kong maiwala ang aking mga account at lahat ng kanilang mga asset." }, + "smartContracts": { + "message": "Mga smart contract" + }, + "smartSwap": { + "message": "Smart swap" + }, + "smartSwapsAreHere": { + "message": "Nandito na ang mga Smart Swap!" + }, + "smartSwapsDescription": { + "message": "Mas humusay pa ang mga MetaMask Swap! Ang pag-enable sa mga Smart Swap ay magbibigay-daan sa MetaMask na i-optimize ang iyong Swap gamit ang program para makatulong na:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Hindi sapat ang pondo para sa smart swap." + }, + "smartSwapsErrorUnavailable": { + "message": "Pansamantalang hindi available ang mga Smart Swap." + }, + "smartSwapsSubDescription": { + "message": "* Susubukan ng mga Smart Swap na isumite nang pribado ang iyong transaksyon, maraming beses. Kapag nabigo ang lahat ng pagsubok, ipapakita sa publiko ang transaksyon upang matiyak na ang Swap ay naging matagupay." + }, + "snapConfigure": { + "message": "I-configure" + }, + "snapConnectionWarning": { + "message": "Gustong kumonekta ng $1 sa $2. Magpatuloy lang kung pinagkakatiwalaan mo ang website na ito.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Ang nilalamang ito ay nagmumula sa $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Piliin kung paano gawing ligtas ang iyong bagong account gamit ang mga MetaMask Snap." + }, + "snapCreateAccountTitle": { + "message": "Gumawa ng $1 account", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Sa pamamagitan ng MetaMask" + }, + "snapDetailAudits": { + "message": "I-audit" + }, + "snapDetailDeveloper": { + "message": "Developer" + }, + "snapDetailLastUpdated": { + "message": "In-update" + }, + "snapDetailManageSnap": { + "message": "Pamahalaan ang snap" + }, + "snapDetailTags": { + "message": "Mga Tag" + }, + "snapDetailVersion": { + "message": "Bersyon" + }, + "snapDetailWebsite": { + "message": "Website" + }, + "snapDetailsCreateASnapAccount": { + "message": "Gumawa ng Snap Account" + }, + "snapDetailsInstalled": { + "message": "Na-install" + }, "snapError": { "message": "Snap Error: '$1'. Error Code: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "I-install ang snap" }, + "snapInstallRequest": { + "message": "Ang pag-install ng $1 ay nagbibigay dito ng mga sumusunod na pahintulot. Magpatuloy lang kung pinagkakatiwalaan mo ang $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Tapos na ang pag-install" + }, "snapInstallWarningCheck": { "message": "Para kumpirmahing naunawaan mo, tsekan lahat.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Para kumpirmahin na naiintindihan mo, lagyan ng tsek ang lahat ng kahon.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Magpatuloy nang may pag-iingat" + }, "snapInstallWarningKeyAccess": { "message": "Binibigyan mo ang $2 ng key access sa snap na \"$1\". Hindi na ito mababawi at nagbibigay ito sa \"$1\" ng kontrol sa iyong mga $2 account at asset. Tiyaking pinagkakatiwalaan mo ang \"$1\" bago magpatuloy.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Bigyan ang $2 ng access sa pampublikong key sa $1", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "Hindi ma-install ang $1.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Nabigo ang pag-install", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "In-audit" + }, + "snapResultError": { + "message": "Error" + }, + "snapResultSuccess": { + "message": "Tagumpay" + }, + "snapResultSuccessDescription": { + "message": "Handa nang gamitin ang $1" + }, "snapUpdate": { "message": "I-update ang snap" }, + "snapUpdateAvailable": { + "message": "Available ang update" + }, + "snapUpdateErrorDescription": { + "message": "Hindi ma-update ang $1.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Nabigo ang pag-update", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "Gustong i-update ng $1 ang $2 papuntang $3 na nagbibigay dito ng mga sumusunod na pahintulot. Magpatuloy lang kung pinagkakatiwalaan mo ang $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Tapos na ang pag-update" + }, "snaps": { "message": "Mga Snap" }, "snapsInsightLoading": { "message": "Naglo-load ng insight sa transaksyon..." }, + "snapsInvalidUIError": { + "message": "Ang UI na tinukoy ng snap ay hindi wasto." + }, "snapsNoInsight": { "message": "Ang snap ay hindi nagbalik ng anumang insight" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Kinikilala mo na ang snap na ii-install mo ay isang Serbisyo ng Third Party gaya ng tinukoy sa $1 ng Consensys. Ang iyong paggamit ng mga Serbisyo ng Third Party ay pinapamahalaan ng hiwalay na mga tuntunin at kundisyon na itinakda ng tagapagbigay ng Serbisyo ng Third Party. Ina-access, pinagbabatayan, o ginagamit mo ang Serbisyo ng Third Party sa sarili mong pananganib. Itinatanggi ng Consensys ang lahat ng responsibilidad at pananagutan para sa anumang pagkalugi sa account dahil sa iyong paggamit ng mga Serbisyo ng Third Party.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Ang anumang impormasyong ibinabahagi mo sa mga Serbisyo ng Third Party ay direktang kokolektahin ng mga Serbisyo ng Third Party na iyon alinsunod sa kanilang mga patakaran sa pagkapribado. Pakitingnan ang kanilang mga patakaran sa pagkapribado para sa higit pang impormasyon.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Walang access ang Consensys sa impormasyong ibinabahagi mo sa mga third party na ito.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Pamahalaan ang iyong mga Snap" }, + "snapsTermsOfUse": { + "message": "Mga Tuntunin ng Paggamit" + }, "snapsToggle": { "message": "Tatakbo lamang ang snap kapag pinagana ito" }, "snapsUIError": { - "message": "Ang UI na tinukoy sa pamamagitan ng snap ay hindi wasto.", + "message": "Makipag-ugnayan sa mga tagalikha ng $1 para sa karagdagang suporta.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Maglagay lamang ng numero na komportable kang i-access ng $1 ngayon o sa hinaharap. Palagi mong madadagdagan ang limitasyon ng token sa ibang pagkakataon.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Hiniling na cap sa paggamit para sa iyong $1" + }, "srpInputNumberOfWords": { "message": "Mayroon akong word phrase ng $1", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Magsimula" }, + "srpSecurityQuizImgAlt": { + "message": "Isang mata na may susian sa sentro, at tatlong lumulutang na password field" + }, "srpSecurityQuizIntroduction": { "message": "Upang ipakita ang iyong Secret Recovery Phrase, kailangan mong sagutin nang tama ang dalawang tanong" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "stable" }, + "stake": { + "message": "Mag-stake" + }, "stateLogError": { "message": "Error sa pagkuha ng mga log ng estado." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Hindi konektado" }, + "statusNotConnectedAccount": { + "message": "Walang mga account na konektado" + }, "step1LatticeWallet": { "message": "Ikonekta ang iyong Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Ito ang minimum na halagang matatanggap mo. Maaari kang makatanggap ng mas malaki depende sa slippage." }, + "swapAnyway": { + "message": "I-swap pa rin" + }, "swapApproval": { "message": "Aprubahan ang $1 para sa mga pagpapalit", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Kailangan mo ng $1 pa $2 para makumpleto ang pag-swap na ito", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Nandyan ka pa ba?" + }, + "swapAreYouStillThereDescription": { + "message": "Handa kaming ipakita sa iyo ang mga pinakabagong quote kapag gusto mo ng magpatuloy" + }, "swapBuildQuotePlaceHolderText": { "message": "Walang available na token na tumutugma sa $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Kumpirmahin gamit ang iyong hardware wallet" }, + "swapContinueSwapping": { + "message": "Ituloy ang pag-swap" + }, "swapContractDataDisabledErrorDescription": { "message": "Sa Ethereum app sa iyong Ledger, magpunta sa \"Settings\" at payagan ang contract data. Pagkatapos ay subukan muli ang iyong pag-swap." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "I-edit ang limitasyon" }, + "swapEditTransactionSettings": { + "message": "I-edit ang mga setting ng transaksyon" + }, "swapEnableDescription": { "message": "Kinakailangan ito at nagbibigay ito ng pahintulot sa MetaMask na i-swap ang iyong $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Ito ay $1 para sa pag-swap", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Ilagay ang halaga" + }, "swapEstimatedNetworkFees": { "message": "Mga tinatayang bayarin sa network" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Hindi matagumpay ang pag-swap" }, + "swapFetchingQuote": { + "message": "Kinukuha ang mga quote" + }, "swapFetchingQuoteNofN": { "message": "Kinukuha ang quote $1 ng $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Kasama ang $1% MetaMask fee.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Kabilang ang $1% na bayad sa MetaMask – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Alamin pa ang tungkol sa mga Swap" + }, "swapLowSlippageError": { "message": "Maaaring hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage." }, @@ -3626,6 +4533,10 @@ "message": "Mga bagong quote sa $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Walang available na mga token na tumutugma sa $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Idaragdag ang iyong $1 sa account mo sa oras na maiproseso ang transaksyong ito.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Mga detalye ng quote" }, + "swapQuoteNofM": { + "message": "$1 ng $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Pinagkunan ng quote" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Pag-timeout ng mga quote" }, + "swapQuotesNotAvailableDescription": { + "message": "Bawasan ang laki ng iyong trade o gumamit ng ibang token." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Subukang i-adjust ang halaga o mga setting ng slippage at subukan ulit." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Makikita sa ibaba ang lahat ng quote na nakuha mula sa maraming pinagkukunan ng liquidity." }, + "swapSelectToken": { + "message": "Pumili ng token" + }, + "swapShowLatestQuotes": { + "message": "Ipakita ang mga pinakabagong quote" + }, "swapSlippageNegative": { "message": "Ang slippage ay dapat mas malaki o katumbas ng zero" }, + "swapSlippageNegativeDescription": { + "message": "Dapat na mas malaki o katumbas ng zero ang slippage" + }, + "swapSlippageNegativeTitle": { + "message": "Dagdagan ang slippage para magpatuloy" + }, + "swapSlippageOverLimitDescription": { + "message": "Dapat na 15% o mas mababa ang slippage tolerance. Ang anumang mas mataas ay magreresulta sa hindi magandang rate." + }, + "swapSlippageOverLimitTitle": { + "message": "Bawasan ang slippage para magpatuloy" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Masyadong mababa ang max slippage na maaaring maging sanhi para mabigo ang iyong transaksyon." + }, + "swapSlippageTooLowTitle": { + "message": "Dagdagan ang slippage para maiwasan ang pagkabigo ng transaksyon" + }, "swapSlippageTooltip": { "message": "Kung magbabago ang presyo sa pagitan ng oras na nailagay at nakumpirma ang iyong order, tinatawag itong \"slippage\". Awtomatikong makakansela ang iyong swap kung lalampas ang slippage sa iyong setting ng “pagpapahintulot sa slippage”." }, + "swapSlippageVeryHighDescription": { + "message": "Itinuturing na napakataas ng inilagay na slippage at maaari itong magresulta sa hindi magandang rate" + }, + "swapSlippageVeryHighTitle": { + "message": "Napakataas na slippage" + }, + "swapSlippageZeroDescription": { + "message": "Kakaunti ang mga tagapagbigay ng zero-slippage quote at maaari itong magresulta sa hindi gaanong mapagkumpitensyang quote." + }, + "swapSlippageZeroTitle": { + "message": "Nagso-source ng mga tagapagbigay ng zero-slippage" + }, "swapSource": { "message": "Pinagkunan ng liquidity" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "para kumpirmahin gamit ang iyong hardware wallet" }, + "swapTokenAddedManuallyDescription": { + "message": "I-verify ang token na ito sa $1 at siguraduhing ito ang token na gusto mong i-trade.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Mano-manong idinagdag ang token" + }, "swapTokenAvailable": { "message": "Naidagdag na ang $1 sa iyong account.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Na-verify sa $1 na source.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "Na-verify lang ang $1 sa 1 source. Isaalang-alang na i-verify ito sa $2 bago magpatuloy.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Potensyal na hindi tunay na token" + }, "swapTooManyDecimalsError": { "message": "Ang $1 ay nagpapahintulot sa hanggang $2 na decimal", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Hindi sapat ang $1 para makumpleto ang transaksyong ito", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Hindi sapat ang $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Tingnan sa aktibidad" }, + "switch": { + "message": "Lumipat" + }, "switchEthereumChainConfirmationDescription": { "message": "Maglilipat ito sa napiling network sa loob ng MetaMask sa dating idinagdag na network:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Lumipat ka sa" }, + "switcherTitle": { + "message": "Network switcher" + }, + "switcherTourDescription": { + "message": "I-click ang icon para ilipat ang mga network o magdagdag ng bagong network" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Ang paglipat ng mga network ay magkakansela ng lahat ng nakabinbin na mga kumpirmasyon" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Mga Tuntunin ng Serbisyo" }, + "termsOfUse": { + "message": "mga tuntunin ng paggamit" + }, + "termsOfUseAgreeText": { + "message": " Sumasang-ayon ako sa Mga Tuntunin sa Paggamit, na nalalapat sa aking paggamit ng MetaMask at lahat ng mga tampok nito" + }, + "termsOfUseFooterText": { + "message": "Mag-scroll upang basahin ang lahat ng seksyon" + }, + "termsOfUseTitle": { + "message": "Na-update ang aming Mga Tuntunin sa Paggamit" + }, "testNetworks": { "message": "Suriin ang mga network" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Mga bagay na dapat tandaan:" }, + "thirdPartySoftware": { + "message": "Paunawa ng third-party na software", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "koleksyong ito" + }, + "thisServiceIsExperimental": { + "message": "Ang serbisyong ito ay pang-eksperimento. Sa pamamagitan ng pagpapagana ng tampok na ito, sumasang-ayon ka sa $1 ng OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Oras" }, @@ -3863,11 +4867,44 @@ "message": "Para kay/sa: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Nanganganib ka sa mga phishing na pag-atake. Protektahan ang sarili mo sa pamamagitan ng pag-off sa eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "I-on ito para hayaan ang mga dapps na hilingin ang iyong lagda gamit ang mga kahilingan sa eth_sign. Ang eth_sign ay isang open-ended na paraan ng pagpirma na nagbibigay-daan sa iyong pirmahan ang isang arbitrary hash, na ginagawa itong isang delikadong panganib sa phishing. Pumirma lamang sa mga kahilingan sa eth_sign kung mababasa mo ang iyong pinipirmahan at mapagkakatiwalaan ang pinagmulan ng kahilingan." + "message": "Kung papaganahin mo ang setting na ito, maaari kang makakuha ng mga kahilingan sa lagda na hindi nababasa. Sa pamamagitan ng pagpirma sa isang mensahe na hindi mo naiintindihan, maaari kang sumang-ayon na ibigay ang iyong mga pondo at NFT." }, "toggleEthSignField": { - "message": "I-toggle ang mga kahilingan sa eth_sign" + "message": "Mga kahilingan sa Eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " baka ma-scam ka" + }, + "toggleEthSignModalBannerText": { + "message": "Kung hiniling sa iyo na i-on ang setting na ito," + }, + "toggleEthSignModalCheckBox": { + "message": "Nauunawaan ko na maaaring mawala ang aking pondo at mga NFT kapag pinagana ko ang mga kahilingan sa eth-sign. " + }, + "toggleEthSignModalDescription": { + "message": "Ang pagbibigay ng pahintulot sa mga kahilingan ng eth_sign ay magpapahina sa iyo upang atakehin ng phishing. Palaging i-review ang URL a t mag-ingat kapag nagsa-sign ng mga mensahe na naglalaman ng code." + }, + "toggleEthSignModalFormError": { + "message": "Mali ang text" + }, + "toggleEthSignModalFormLabel": { + "message": "Ilagay ang “pipirmahan ko lang kung ano ang nauunawaan ko” para magpatuloy" + }, + "toggleEthSignModalFormValidation": { + "message": "Pipirmahan ko lang kung ano ang nauunawaan ko" + }, + "toggleEthSignModalTitle": { + "message": "Gamitin sa sarili mong pananagutan" + }, + "toggleEthSignOff": { + "message": "I-OFF (Nirerekomenda)" + }, + "toggleEthSignOn": { + "message": "I-ON (Hindi nirerekomenda)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Simbolo ng Token" }, + "tokens": { + "message": "Mga Token" + }, "tokensFoundTitle": { "message": "$1 bagong token ang nakita", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Nauunawaan ko" }, + "tooltipSatusConnected": { + "message": "konektado" + }, + "tooltipSatusNotConnected": { + "message": "hindi konektado" + }, "total": { "message": "Kabuuan" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "Nagkaroon ng error sa transaksyon." }, + "transactionFailed": { + "message": "Bigo ang Transaksyon" + }, "transactionFee": { "message": "Bayarin sa Transaksyon" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Kabuuang Gas Fee" }, + "transactionNote": { + "message": "Tala ng transaksyon" + }, "transactionResubmitted": { "message": "Isinumite ulit ang transaksyon nang may bayarin sa gas na tumaas at naging $1 sa $2" }, "transactionSecurityCheck": { - "message": "Paganahin ang mga provider ng seguridad ng transaksyon" + "message": "Paganahin ang mga alerto sa seguridad" }, "transactionSecurityCheckDescription": { "message": "Gumagamit kami ng mga third-party na API para tumuklas at ipakita ang mga panganib na kasangkot sa hindi nalagdaang mga kahilingan sa transaksyon at lagda bago mo lagdaan ang mga ito. Ang mga serbisyong ito ay magkakaroon ng access sa iyong hindi pa napirmahang kahilingan sa transaksyon at lagda, address ng iyong account, at iyong nais na wika." }, + "transactionSettings": { + "message": "Mga setting ng transaksyon" + }, "transactionSubmitted": { "message": "Isinumite ang transaksyon nang may bayarin sa gas na $1 sa $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Mag-transfer Mula Kay/Sa" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Nagkakaproblema kami sa pagkonekta ng iyong Ledger. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "I-review ang gabay sa pagkonekta ng ming hardware wallet at subukan muli.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Kung ikaw ay nasa pinakabagong bersyon ng Firefox, maaari kang makaranas ng isyu na nauugnay sa Firefox dropping U2F support. Alamin kung paano aayusin ang isyung ito $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "dito", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Nagkaproblema kami sa pagkonekta sa iyong $1, subukang suriin ang $2 at subukan ulit.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "arrow pataas" }, + "update": { + "message": "I-update" + }, "updatedWithDate": { "message": "Na-update noong $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Nasa kasalukuyang listahan ng mga network na ang URL." }, + "use4ByteResolution": { + "message": "I-decode ang mga smart contract" + }, + "use4ByteResolutionDescription": { + "message": "Upang mapabuti ang karanasan ng user, kino-customize namin ang tab ng mga aktibidad gamit ang mga mensahe ayon sa mga smart contract kung saan ka nakikipag-ugnayan. Gumagamit ang MetaMask ng serbisyong tinatawag na 4byte.directory para i-decode ang datos at ipakita sa iyo ang bersyon ng smart contact na mas madaling basahin. Tumutulong ito na bawasan ang pagkakataon na aprubahan mo ang mga malisyosong aksyon sa smart contract, ngunit maaaring magresulta sa pagbabahagi ng iyong IP address." + }, "useMultiAccountBalanceChecker": { "message": "Mga kahilingan sa balanse ng batch account" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Kumuha ng mas mabilis ng update ng balanse sa pamamagitan ng pagba-batch ng mga kahilingan sa balanse. Nagpapahintulot ito sa amin na makuha nang sama-sama ang iyong balanse ng account, para makakuha ka ng mas mabilis na mga update para sa inaprubahang kasanayan. Kapag nakasara ang feature na ito, posibleng hindi maiugnay ng mga third party ang iyong mga account sa isa't isa." + }, "useNftDetection": { "message": "Awtomatikong tuklasin ang mga NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Magpakita ng babala para sa mga phishing domain na nagta-target sa mga user ng Ethereum" }, + "useSiteSuggestion": { + "message": "Gamitin ang mungkahi ng site" + }, "useTokenDetectionPrivacyDesc": { "message": "Awtomatikong ipinapakita ang mga token na ipinadala sa iyong account na nakapaloob sa komunikasyon ng mga server ng third party para makuha ang mga larawan ng token. Ang mga server na iyon ay magkakaroon ng access sa iyong IP address." }, @@ -4170,7 +5256,7 @@ "message": "Username" }, "verifyContractDetails": { - "message": "I-verify ang mga detalye ng kontrata" + "message": "I-verify ang mga detalye ng third-party" }, "verifyThisTokenDecimalOn": { "message": "Ang token decimal ay maaaring matagpuan sa $1", @@ -4184,12 +5270,18 @@ "message": "I-verify ang token na ito sa $1 at siguruhin na ito ang token na gusto mong i-trade.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Bersyon" + }, "view": { "message": "Tingnan" }, "viewAllDetails": { "message": "Tingnan ang lahat ng detalye" }, + "viewAllQuotes": { + "message": "tingnan ang lahat ng quote" + }, "viewContact": { "message": "Tingnan ang Contact" }, @@ -4213,9 +5305,18 @@ "message": "Tingnan ang $1 sa Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Tingnan sa explorer" + }, "viewOnOpensea": { "message": "Tingnan sa Opensea" }, + "viewPortfolioDashboard": { + "message": "Tingnan ang Dashboard ng Portfolio" + }, + "viewinCustodianApp": { + "message": "Tingnan sa app custodian" + }, "viewinExplorer": { "message": "Tingnan ang $1 sa Explorer", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Gusto mo bang idagdag ang network na ito?" }, + "wantsToAddThisAsset": { + "message": "Gustong idagdag ng $1 ang asset na ito sa iyong wallet", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Babala" }, "warningTooltipText": { - "message": "$1 Maaaring gastusin ng kontrata ang iyong buong balanse ng token nang walang karagdagang abiso o pahintulot. Protektahan ang iyong sarili sa pamamagitan ng pag-customize ng mas mababang limitasyon sa paggastos.", + "message": "$1 Maaaring gastusin ng third party ang iyong buong balanse ng token nang walang karagdagang abiso o pahintulot. Protektahan ang iyong sarili sa pamamagitan ng pag-customize ng mas mababang limitasyon sa paggastos.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Nilalagdaan mo ang" }, + "yourAccounts": { + "message": "Mga account mo" + }, "yourFundsMayBeAtRisk": { "message": "Maaaring nasa panganib ang iyong pondo" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index b6ad12c28..6230fe974 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Hakkında" }, + "accept": { + "message": "Kabul ediyorum" + }, "acceptTermsOfUse": { "message": "$1 bölümünü okudum ve kabul ediyorum", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Takma ad ekle" }, + "addAccount": { + "message": "Hesap ekleyin" + }, "addAcquiredTokens": { "message": "MetaMask kullanarak elde ettiğiniz tokenleri ekleyin" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Popüler ağlar listesinden ekle veya manuel olarak bir ağ ekle. Yalnızca güvendiğin varlıklarla etkileşim kur." }, + "addHardwareWallet": { + "message": "Donanım cüzdanı ekleyin" + }, + "addIPFSGateway": { + "message": "Tercih ettiğiniz IPFS ağ geçidini ekleyin" + }, "addMemo": { "message": "Not ekleyin" }, @@ -244,6 +256,21 @@ "message": "Bu ağ bağlantısı üçüncü taraflara dayalıdır. Bu bağlantı daha az güvenli olabilir veya üçüncü tarafların aktiviteleri takip etmesine olanak sağlayabilir. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Yeni token ekleyin" + }, + "addNft": { + "message": "NFT ekleyin" + }, + "addNfts": { + "message": "NFT ekleyin" + }, + "addSnapAccountModalDescription": { + "message": "MetaMask Snap'leri ile hesabınızı güvende tutma seçeneklerini keşfedin" + }, + "addSuggestedNFTs": { + "message": "Önerilen NFT'leri ekleyin" + }, "addSuggestedTokens": { "message": "Önerilen Tokenleri ekle" }, @@ -254,6 +281,9 @@ "message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Ağ Ekleniyor" + }, "address": { "message": "Adres" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Maks. öncelik ücreti (başka bir deyişle \"madenci bahşişi\") doğrudan madencilere gider ve işleminizin öncelikli olarak gerçekleştirilmesini teşvik eder." }, + "agreeTermsOfUse": { + "message": "MetaMask'in $1 bölümünü kabul ediyorum", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Kasası" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Uyarılar" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Zaten tüm saklayıcı kurum hesaplarını bağladınız ya da MetaMask Kurumsal'a bağlayabileceğiniz hesap yok." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Bağlayabilecek hesap yok" + }, "allOfYour": { "message": "Sahip olduğunuz tüm $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Tüm $1 NFT'leriniz", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Bu harici uzantının şunu yapmasına izin ver:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Bu sitenin şunu yapmasına izin ver:" }, + "allowThisSnapTo": { + "message": "Bu snap için şuna izin verin:" + }, "allowWithdrawAndSpend": { "message": "$1 için şu tutara kadar para çekme ve harcama izni ver:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Tutar" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "Tarayıcında bir Ethereum Cüzdanı", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Sahip olduğun tüm $1 için erişim ve transfer izni verilsin mi?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Tüm $1 NFT'lerinizin erişimine ve transferine izin verilsin mi?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Onayla" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Üçüncü bir tarafın, siz bu erişimi iptal edene kadar başka bildirim yapılmaksızın aşağıdaki NFT'lere erişim sağlamasına ve onları transfer edebilmesine izin verir." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Üçüncü bir tarafın, siz bu erişimi geri çekene kadar başkaca bildiri olmaksızın tüm $1 NFT'lerinize erişim sağlamasına ve onları transfer edebilmesine izin verir.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "$1 için erişim ve transferine izin ver?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Varlıkları doğrudan bir ağdan diğerine göndermeye çalışırsanız bu durum kalıcı varlık kaybına neden olabilir. Bir köprü kullandığınızdan emin olun." }, + "attemptToCancelSwap": { + "message": "~$1 için swap'ı iptal etme girişimi", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Blockzincire bağlanmaya çalışılıyor." }, @@ -411,6 +473,9 @@ "average": { "message": "Ortalama" }, + "awaitingApproval": { + "message": "Onay bekliyor..." + }, "back": { "message": "Geri" }, @@ -454,6 +519,9 @@ "message": "Bu bir beta sürümdür. Lütfen hataları bildirin $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "MetaMask Kurumsal Beta Sürümü" + }, "betaMetamaskVersion": { "message": "MetaMask Beta Sürümü" }, @@ -488,9 +556,45 @@ "message": "Hesabı şurada görüntüleyin: $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Bu talebi onaylarsanız dolandırıcılıkla ünlü üçüncü bir taraf tüm varlıklarınızı çalabilir." + }, + "blockaidDescriptionBlurFarming": { + "message": "Bu talebi onaylarsanız birisi Blur üzerinde yer alan varlıklarınızı çalabilir." + }, + "blockaidDescriptionFailed": { + "message": "Bu talep bir hatadan dolayı güvenlik sağlayıcısı tarafından doğrulanmadı. Dikkatli bir şekilde ilerleyin." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Kötü niyetli bir alanla etkileşimde bulunuyorsunuz. Bu talebi onaylarsanız varlıklarınızı kaybedebilirsiniz." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Bu talebi onaylarsanız varlıklarınızı kaybedebilirsiniz." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Bu talebi onaylarsanız birisi OpenSea üzerinde yer alan varlıklarınızı çalabilir." + }, + "blockaidDescriptionTransferFarming": { + "message": "Bu talebi onaylarsanız dolandırıcılıkla ünlü üçüncü bir taraf tüm varlıklarınızı çalar." + }, + "blockaidTitleDeceptive": { + "message": "Bu aldatıcı bir talep" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Talep güvenli olmayabilir" + }, + "blockaidTitleSuspicious": { + "message": "Bu şüpheli bir talep" + }, "blockies": { "message": "Bloklar" }, + "bridge": { + "message": "Köprü" + }, "browserNotSupported": { "message": "Tarayıcınız desteklenmiyor..." }, @@ -510,6 +614,10 @@ "message": "$1 satın al", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Daha fazla $1 satın al", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Şimdi Satın Al" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Bu işlem hesabın anlık verilerini sıfırlar ve cüzdanınızdaki aktivite sekmesinden verileri siler. Sadece geçerli hesap ve ağ etkilenecektir. Bakiyeleriniz ve gelen işlemleriniz değişmeyecektir." }, + "click": { + "message": "Daha fazla bilgi için" + }, "clickToConnectLedgerViaWebHID": { "message": "WebHID üzerinden Kayıt Defterinizi bağlamak için tıklayın", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Şu anda bu snap'i yapılandırmak için MetaMask'tan ayrılıyorsunuz." + }, + "configureSnapPopupInstallDescription": { + "message": "Şu anda bu snap'i yüklemek için MetaMask'tan ayrılıyorsunuz." + }, + "configureSnapPopupInstallTitle": { + "message": "Snap'i yükle" + }, + "configureSnapPopupLink": { + "message": "Devam etmek için bu bağlantıya tıklayın:" + }, + "configureSnapPopupTitle": { + "message": "Snap'i yapılandır" + }, "confirm": { "message": "Onayla" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Hesabı bağla ya da yeni hesap oluştur" }, + "connectCustodialAccountMenu": { + "message": "Saklayıcı Kurum Hesabı Bağla" + }, + "connectCustodialAccountMsg": { + "message": "Token eklemek veya bir tokeni yenilemek için lütfen bağlamak istediğiniz saklayıcı kurumu seçin." + }, + "connectCustodialAccountTitle": { + "message": "Saklayıcı Kurum Hesapları" + }, "connectManually": { "message": "Mevcut siteye manuel olarak bağlan" }, + "connectSnap": { + "message": "Şuna bağlanın: $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "$1 uygulamasına bağlan", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Linea Goerli test ağına bağlanılıyor" }, + "connectingToLineaMainnet": { + "message": "Linea Ana Ağına bağlanılıyor" + }, "connectingToMainnet": { "message": "Ethereum Mainnet ağına bağlanıyor" }, "connectingToSepolia": { "message": "Sepolia test ağına bağlanılıyor" }, + "connectionFailed": { + "message": "Bağlantı başarısız oldu" + }, + "connectionFailedDescription": { + "message": "$1 getirme başarısız oldu, ağınızı kontrol edip tekrar deneyin.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Bağlantı talebi" + }, "contactUs": { "message": "Bize ulaşın" }, @@ -708,7 +860,7 @@ "message": "Sözleşme kurulumu" }, "contractDescription": { - "message": "Kendinizi dolandırıcılara karşı korumak için bir dakikanızı ayırarak sözleşme bilgilerini doğrulayın." + "message": "Kendinizi dolandırıcılara karşı korumak için bir dakikanızı ayırarak üçüncü taraf bilgilerini doğrulayın." }, "contractInteraction": { "message": "Sözleşme Etkileşimi" @@ -717,16 +869,16 @@ "message": "NFT sözleşmesi" }, "contractRequestingAccess": { - "message": "Sözleşme erişim talep ediyor" + "message": "Üçüncü taraf erişim talep ediyor" }, "contractRequestingSignature": { - "message": "Sözleşme imza gerektiriyor" + "message": "Üçüncü taraf imza talep ediyor" }, "contractRequestingSpendingCap": { - "message": "Sözleşmede harcama üst limiti talep ediliyor" + "message": "Üçüncü taraf harcama üst limiti talep ediyor" }, "contractTitle": { - "message": "Sözleşme ayrıntıları" + "message": "Üçüncü taraf bilgileri" }, "contractToken": { "message": "Token sözleşmesi" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Piyasa gas tahmini grafiği" }, + "custodian": { + "message": "Saklayıcı Kurum" + }, + "custodianAccount": { + "message": "Saklayıcı kurum hesabı" + }, + "custodianAccountAddedDesc": { + "message": "Artık saklayıcı kurum hesaplarınızı MetaMask Kurumsal'da kullanabilirsiniz." + }, + "custodianAccountAddedTitle": { + "message": "Seçili saklayıcı kurum hesapları ekledi." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Hesaplarınızı tekrar MMI'ya bağlamak için lütfen $1 bölümüne gidin ve kullanıcı arayüzünde 'MMI'ya bağla' düğmesine tıklayın." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Artık saklayıcı kurumlardaki hesaplarınızı MetaMask Kurumsal'da kullanabilirsiniz." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Saklayıcı kurum tokeniniz yenilendi" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Bu işlem aşağıdaki adres için saklayıcı kurum tokenini değiştirir:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Saklayıcı kurum tokenini değiştir" + }, + "custodyApiUrl": { + "message": "$1 API URL adresi" + }, + "custodyDeeplinkDescription": { + "message": "İşlemi $1 uygulamasında onaylayın. Gerekli tüm saklayıcı kurum onayları gerçekleştikten sonra işlem tamamlanacaktır. Durum için $1 kontrolünüzü yapın." + }, + "custodyRefreshTokenModalDescription": { + "message": "Hesaplarınızı tekrar MMI'ya bağlamak için lütfen $1 bölümüne gidin ve kullanıcı arayüzünde 'MMI'ya bağla' düğmesine tıklayın." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Saklayıcı kurum MetaMask Kurumsal uzantısını doğrulayan bir token düzenleyerek hesaplarınızı bağlayabilmenize olanak sağlar." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Güvenlik nedenleriyle belirli bir dönemden sonra bu tokenin süresi dolar. Bunun için MMI'ya tekrar bağlamanız gerekir." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Bunu neden görüyorum?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Saklayıcı kurum oturumu sona erdi" + }, + "custodySessionExpired": { + "message": "Saklayıcı kurum oturumu sona erdi." + }, + "custodyWrongChain": { + "message": "Bu hesap $1 ile kullanılmak için ayarlanmamış" + }, "custom": { "message": "Gelişmiş" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "müşteri hizmetleri" }, + "dappRequestedSpendingCap": { + "message": "Site tarafından talep edilen harcama üst limiti" + }, "dappSuggested": { "message": "Site önerisi" }, @@ -851,6 +1060,12 @@ "message": "$1 bu fiyatı önerdi.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Önerilen site" + }, + "dappSuggestedHighShortLabel": { + "message": "Site (yüksek)" + }, "dappSuggestedShortLabel": { "message": "Site" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Sil" }, + "deleteContact": { + "message": "Kişiyi sil" + }, "deleteNetwork": { "message": "Ağı Sil?" }, + "deleteNetworkIntro": { + "message": "Bu ağı silerseniz bu ağdaki varlıklarınızı görüntülemek için bu ağı tekrar eklemeniz gerekecek" + }, + "deleteNetworkTitle": { + "message": "$1 ağını sil?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Para Yatır" }, @@ -917,6 +1142,10 @@ "description": { "message": "Açıklama" }, + "descriptionFromSnap": { + "message": "$1 açıklaması", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Bu hata aralıklı olarak meydana gelebilir, bu yüzden uzantıyı yeniden başlatmayı veya MetaMask Masaüstünü devre dışı bırakmayı deneyin." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Gizli Kurtarma İfadesi yedekleme hatırlatma uyarısını yoksay" }, + "displayNftMedia": { + "message": "NFT medyasını göster" + }, + "displayNftMediaDescription": { + "message": "NFT medyasının ve verilerin gösterilmesi IP adresinizin OpenSea'ye veya diğer üçüncü taraflara aktarılmasına neden olur. Bu durum, saldırganların IP adresinizi Ethereum adresinizle ilişkilendirmesini sağlayabilir. NFT otomatik algılama bu ayara dayalıdır ve bu ayar kapatıldığında kullanılamaz olur." + }, "domain": { "message": "Alan" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Otomatik algılamayı etkinleştir" }, + "enableForAllNetworks": { + "message": "Tüm ağlar için etkinleştir" + }, "enableFromSettings": { "message": " Ayarlardan etkinleştir." }, + "enableSmartSwaps": { + "message": "Akıllı swap'ları etkinleştirin" + }, + "enableSnap": { + "message": "Etkinleştir" + }, "enableToken": { "message": "şunu etkinleştir: $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Etkinleştirildi" + }, "encryptionPublicKeyNotice": { "message": "$1 genel şifreleme anahtarınızı istiyor. Bunu onayladığınızda bu site sizin için şifrelenmiş mesajlar oluşturabilecektir.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Gelişmiş token algılama şu anda $1 üzerinden kullanılabilir. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask, \"https://metamask.eth\" gibi ENS alanlarını tarayıcınızın adres çubuğunda görmenizi sağlar. Şöyle çalışır:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Normal tarayıcılar genellikle ENS veya IPFS adreslerini kullanmaz, ancak MetaMask bu konuda yardımcı olur. Bu özelliği kullandığınızda IP adresiniz IPFS üçüncü taraf hizmetleri ile paylaşılabilir." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask, ENS adına bağlı kodu bulmak için Ethereum'un ENS sözleşmesi ile kontrol gerçekleştirir." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Kod IPFS'ye bağlı ise içeriği IPFS ağından alır." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Ardından, genellikle bir web sitesi veya benzeri olan içeriği görebilirsiniz." + }, + "ensDomainsSettingTitle": { + "message": "ENS alanlarını adres çubuğunda göster" + }, "ensIllegalCharacter": { "message": "ENS için uygun olmayan karakter." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Bir sayı girin" }, + "enterCustodianToken": { + "message": "$1 tokeninizi girin veya yeni bir token ekleyin" + }, "enterMaxSpendLimit": { "message": "Maks. harcama limiti gir" }, + "enterOptionalPassword": { + "message": "İsteğe bağlı parolayı girin" + }, "enterPassword": { "message": "Parolanızı girin" }, "enterPasswordContinue": { "message": "Devam etmek için parola girin" }, + "enterTokenNameOrAddress": { + "message": "Token adı girin veya adresi yapıştırın" + }, + "enterYourPassword": { + "message": "Parolanızı girin" + }, "errorCode": { "message": "Kod: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Yığın:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Özel ağa bağlanırken hata oluştu." + }, + "errorWithSnap": { + "message": "$1 ile hata oluştu", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Ana gaz tahmini hizmeti olarak sunulan yedek gaz fiyatı şu anda kullanılamıyor." }, + "ethereumProviderAccess": { + "message": "Ethereum sağlayıcısına $1 erişimi ver", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Ethereum genel adresi" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Deneysel" }, + "exploreMetaMaskSnaps": { + "message": "MetaMask Snap'lerini keşfedin" + }, "exportPrivateKey": { "message": "Özel anahtarı dışa aktar" }, + "extendWalletWithSnaps": { + "message": "Cüzdan deneyimini genişletin." + }, "externalExtension": { "message": "Harici uzantı" }, @@ -1302,6 +1596,9 @@ "message": "Dosya içe aktarma çalışmıyor mu? Buraya tıklayın!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Bırakılan dosya çok büyük." + }, "flaskWelcomeUninstall": { "message": "bu uzantıyı kaldırmalısın", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Genel" }, + "getStarted": { + "message": "Başlarken" + }, + "globalTitle": { + "message": "Genel menü" + }, + "globalTourDescription": { + "message": "Portföyünüze, bağlı sitelere, ayarlara ve daha fazlasına bakın" + }, "goBack": { "message": "Geri git" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Donanım cüzdanı bağla" }, + "hardwareWalletsInfo": { + "message": "Donanım cüzdanı entegrasyonları, IP adresinizi ve etkileşimde bulunduğunuz akıllı sözleşme adreslerini görebilen harici sunucular için API aramalarını kullanır." + }, "hardwareWalletsMsg": { "message": "MetaMask ile kullanmak istediğiniz donanım cüzdanını seçin." }, @@ -1541,11 +1850,34 @@ "message": "ancak dolandırıcılar talep edilebilir.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Özel Anahtarınız $1 sağlar", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "cüzdanınıza ve paranıza tam erişim.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "kilitli daireyi göstermek için tutun" + }, + "holdToRevealPrivateKey": { + "message": "Özel Anahtarı göstermek için tutun" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Özel anahtarınızı güvende tutun" + }, "holdToRevealSRP": { - "message": "GKİ bilgisinin gösterilmesi için tut" + "message": "GKİ'yi göstermek için tutun" }, "holdToRevealSRPTitle": { - "message": "GKİ bilgini güvende tut" + "message": "GKİ'nizi güvende tutun" + }, + "holdToRevealUnlockedLabel": { + "message": "kilidi açılmış daireyi göstermek için tutun" + }, + "id": { + "message": "Kimlik" }, "ignoreAll": { "message": "Tümünü yoksay" @@ -1563,6 +1895,21 @@ "importAccountError": { "message": "Hesap içe aktarılırken hata oluştu." }, + "importAccountErrorIsSRP": { + "message": "Bir Gizli Kurtarma İfadesi (veya hatırlatıcı ipucu) girdiniz. Hesabı buraya aktarmak için 64 karakter uzunluğunda on altılık bir dizi olan özel bir anahtar girmelisiniz." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Bu geçerli bir özel anahtar değil. On altılık bir dizi girdiniz ancak bu dizi 64 karakter uzunluğunda olmalıdır." + }, + "importAccountErrorNotHexadecimal": { + "message": "Bu geçerli bir özel anahtar değil. 64 karakter uzunluğunda on altılık bir dizi girmelisiniz." + }, + "importAccountJsonLoading1": { + "message": "Bu JSON dosyasının birkaç dakika içinde içe aktarılmasını bekleyin ve MetaMask'i dondurun." + }, + "importAccountJsonLoading2": { + "message": "Özür dileriz, bunu gelecekte daha hızlı hale getireceğiz." + }, "importAccountMsg": { "message": "İçe aktarılan hesaplar ilk olarak oluşturduğunuz MetaMask hesabı Gizli Kurtarma ifadenizle ilişkilendirilmez. İçe aktarılan hesaplar hakkında daha fazla bilgi edinin" }, @@ -1615,18 +1962,29 @@ "message": "İlk işleminiz ağ tarafından onaylanmıştır. Geri gitmek için Tamam düğmesine tıklayın." }, "inputLogicEmptyState": { - "message": "Sadece şu anda ya da gelecekte sözleşme harcaması konusunda rahat olduğunuz bir sayı girin. Harcama üst limitini daha sonra dilediğiniz zaman artırabilirsiniz." + "message": "Sadece şu anda ya da gelecekte üçüncü taraf harcaması konusunda rahat olduğunuz bir sayı girin. Harcama üst limitini daha sonra dilediğiniz zaman artırabilirsiniz." }, "inputLogicEqualOrSmallerNumber": { - "message": "Bu işlem, sözleşmenin mevcut bakiyenizden $1 harcamasına izin verir.", + "message": "Bu işlem, üçüncü tarafın mevcut bakiyenizden $1 harcamasına izin verir.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Bu işlem, üst limite ulaşana kadar ya da siz harcama üst limitini iptal edene kadar sözleşmenin tüm token bakiyenizi harcamasına izin verir. Amacınız bu değilse daha düşük bir harcama üst limiti ayarlamayı deneyin." + "message": "Bu işlem, üst limite ulaşana kadar ya da siz harcama üst limitini iptal edene kadar üçüncü tarafın tüm token bakiyenizi harcamasına izin verir. Amacınız bu değilse daha düşük bir harcama üst limiti ayarlamayı deneyin." + }, + "insightsFromSnap": { + "message": "$1 kaynaklı içgörüler", + "description": "$1 represents the name of the snap" }, "install": { "message": "Yükle" }, + "installOrigin": { + "message": "Kökeni yükle" + }, + "installedOn": { + "message": "$1 üzerinde yüklendi", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Bakiye yetersiz." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Giriş geçersiz! Gizli Kurtarma İfadesi büyük/küçük harf duyarlıdır." }, + "ipfsGateway": { + "message": "IPFS ağ geçidi" + }, + "ipfsGatewayDescription": { + "message": "MetaMask, IPFS'de depolanan NFT'lerinizin görüntülerini göstermek, tarayıcınızın adres çubuğuna girilen ENS adresleri ile ilgili bilgileri göstermek ve farklı token'lerin simgelerini almak için üçüncü taraf hizmetleri kullanır. Siz bunları kullanırken IP adresiniz bu hizmetlerle paylaşılabilir." + }, + "ipfsToggleModalDescriptionOne": { + "message": "IPFS'de depolanan NFT'lerinizin görüntülerini göstermek, tarayıcınızın adres çubuğuna girilen ENS adresleri ile ilgili bilgileri göstermek ve farklı token'lerin simgelerini almak için üçüncü taraf hizmetleri kullanırız. Siz bunları kullanırken IP adresiniz bu hizmetlerle paylaşılabilir." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Onayla seçeneği seçildiğinde IPFS çözünürlüğü açılır. Bunu Dilediğiniz zaman $1 alanında kapatabilirsiniz.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Ayarlar > Güvenlik ve gizlilik" + }, "jazzAndBlockies": { "message": "Jazzicons ve Blockies, bir bakışta bir hesabı tanımlamana yardımcı olan iki farklı benzersiz simge stilidir." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Son satış" }, + "layer1Fees": { + "message": "Aşama 1 ücretleri" + }, "learnCancelSpeeedup": { "message": "Nasıl $1 yapacağınızı öğrenin", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Gaz hakkında $1 istiyor musun?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Daha Fazla Bilgi" + }, "learnMoreUpperCase": { "message": "Daha fazla bilgi" }, @@ -1765,16 +2145,16 @@ "message": "Onayla düğmesine tıklamadan önce:" }, "ledgerConnectionInstructionStepFour": { - "message": "Kayıt Defteri cihazınızda \"akıllı sözleşme verileri\" ya da \"kör imzalama\" özelliğini etkinleştirin" + "message": "Kayıt Defteri cihazınızda \"akıllı sözleşme verilerini\" ya da \"kör imzalama\" özelliğini etkinleştirin." }, "ledgerConnectionInstructionStepOne": { - "message": "Ayarlar > Gelişmiş kısmının altında Ledger Live'i Kullan seçeneğini etkinleştirin" + "message": "Ayarlar > Gelişmiş kısmının altında Ledger Live'i Kullan seçeneğini etkinleştirin." }, "ledgerConnectionInstructionStepThree": { - "message": "Kayıt Defteri cihazınızı bağlayın ve Ethereum uygulamasını seçin" + "message": "Kayıt Defterinizin bağlı olduğundan emin olun ve Ethereum uygulamasını seçin." }, "ledgerConnectionInstructionStepTwo": { - "message": "Ledger Live Uygulamasını açın ve kilidini açın" + "message": "Ledger Live Uygulamasını açıp kilidini açın." }, "ledgerConnectionPreferenceDescription": { "message": "Kayıt Defterinizi MetaMask'a nasıl bağladığınızı özelleştirin. $1 önerilir ancak başka seçenekler de mevcuttur. Buradan daha fazlasını okuyun: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Linea Goerli test ağı" }, + "lineaMainnet": { + "message": "Linea Ana Ağı" + }, "link": { "message": "Bağlantı" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Kilitle" }, + "lockMetaMask": { + "message": "MetaMask'i kilitle" + }, + "lockTimeInvalid": { + "message": "Kilit süresi 0 ile 10080 arasında olmalıdır" + }, "logo": { "message": "$1 logosu", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Bağlantı durumu düğmesi ziyaret ettiğiniz web sitesinin şu anda seçilen hesabınıza bağlı olup olmadığını gösterir." }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Kurumsal Sürümü" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Takas İşlemleri bakımda. Lütfen daha sonra tekrar kontrol edin." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Metrikler" }, + "mismatchAccount": { + "message": "Seçili hesap ($1) imza atmaya çalışan hesaptan ($2) farklı" + }, "mismatchedChainLinkText": { "message": "ağ bilgilerini doğrula", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Sunulan para birimi sembolü bu zincir kimliği için beklediğimiz sembolle uyumlu değil." }, + "mismatchedRpcChainId": { + "message": "Özel ağ tarafından geri dönen Zincir kimliği gönderilen zincir kimliği ile eşleşmiyor." + }, "mismatchedRpcUrl": { "message": "Kayıtlarımıza göre, sunulan RPC URL adresi değeri bu zincir kimliğinin bilinen bir sağlayıcısı ile uyumlu değil." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Buradan talep et" }, + "mmiAddToken": { + "message": "$1 alanındaki sayfa MetaMask Kurumsaldaki aşağıdaki saklayıcı kurum tokenine yetki vermek istiyor" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Kurumsal dünya çapında tasarlanmış ve yapılmıştır." + }, + "more": { + "message": "daha fazla" + }, "moreComingSoon": { - "message": "Daha fazlası çok yakında..." + "message": "Daha fazla sağlayıcı çok yakında" + }, + "multipleSnapConnectionWarning": { + "message": "$1, $2 snap'lerine bağlanmak istiyor. Bu web sitesine güveniyorsanız ilerleyin.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "En az bir token seçilmeli." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Ağ meşgul. Gas fiyatları yüksektir ve tahminler daha az doğrudur." }, + "networkMenu": { + "message": "Ağ Menüsü" + }, + "networkMenuHeading": { + "message": "Ağ seç" + }, "networkName": { "message": "Ağ adı" }, @@ -2033,6 +2450,10 @@ "message": "Son 72 saate göre gaz ücretleri $1.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "$1 ağına bağlanamıyoruz", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Ağ URL adresi" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT'ler" }, + "nftsPreviouslyOwned": { + "message": "Önceden Sahip Olunan" + }, "nickname": { "message": "Takma ad" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Dönüşüm oranı mevcut değil" }, + "noNFTs": { + "message": "Henüz NFT yok" + }, + "noNetworksFound": { + "message": "Belirtilen arama sorgusu için herhangi bir ağ bulunamadı" + }, "noSnaps": { - "message": "Hiç Snap yüklü değil" + "message": "Yüklü snap'iniz yok." }, "noThanksVariant2": { "message": "Hayır, teşekkürler." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Bu hesap doğru mu? Cüzdanınızdaki mevcut seçili hesaptan farklı" }, + "notEnoughBalance": { + "message": "Bakiye yetersiz" + }, "notEnoughGas": { "message": "Yeterli gaz yok" }, + "note": { + "message": "Not" + }, + "notePlaceholder": { + "message": "Onaylayan taraf saklayıcı kurumda işlemi onaylarken bu notu görecektir." + }, + "notificationTransactionFailedMessage": { + "message": "$1 işlemi başarısız oldu! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "İşlem başarısız oldu! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Başarısız işlem", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "$1 işlemi onaylandı!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Onaylanan işlem", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "$1 üzerinde görüntüle", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Bildirimler" + }, "notifications10ActionText": { "message": "Ayarlarda ziyaret et", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Ethereum Birleşmesi başladı!" }, + "notifications18ActionText": { + "message": "Güvenlik uyarılarını etkinleştir" + }, + "notifications18DescriptionOne": { + "message": "Kötü amaçlı bir talep almış olabileceğinizde üçüncü taraflardan uyarı alın.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Talepleri onaylamadan önce her zaman kendiniz gerekli özeni gösterdiğinizden emin olun.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea bu özelliğin ilk sağlayıcısıdır. Daha fazla sağlayıcı çok yakında!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Güvenlik uyarıları ile güvende kalın" + }, + "notifications19ActionText": { + "message": "NFT otomatik algılamayı etkinleştir" + }, + "notifications19DescriptionOne": { + "message": "Başlayabilmenizin iki yolu:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Şu anda sadece ERC-721'i destekliyoruz.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "NFT'lerinizi manuel olarak ekleyin veya Ayarlar > Deneysel alanından otomatik NFT algılama özelliğini açın.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "NFT'lerinizi eskiden hiç olmadığı gibi görün" + }, "notifications1Description": { "message": "MetaMask Mobil kullanıcıları artık mobil cüzdanları içinde token takas edebilirler. Mobil uygulamayı edinmek ve takas yapmaya başlamak için QR kodunu tarayın.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Mobilde takas burada!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Daha fazla bilgi", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Firefox'un en son sürümündeyseniz Firefox'un U2F desteğini bırakması ile ilgili sorun yaşıyor olabilirsiniz.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Bağlantı Sorunları Yaşayan Ledger ve Firefox Kullanıcıları", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Deneyin" + }, + "notifications21Description": { + "message": "Kullanımı daha kolay ve daha hızlı olması için MetaMask uzantısındaki Swap'ları güncelledik.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "İşte karşınızda yeni ve yenilenmiş Swap'lar!" + }, + "notifications22ActionText": { + "message": "Anladım" + }, + "notifications22Description": { + "message": "💡 Bulmak için genel menüye veya hesap menüsüne tıklayın!" + }, + "notifications22Title": { + "message": "Hesap bilgilerinizi veya blok gezgini URL adresini mi arıyorsunuz?" + }, + "notifications23ActionText": { + "message": "Güvenlik uyarılarını etkinleştir" + }, + "notifications23DescriptionOne": { + "message": "Blockaid tarafından desteklenen güvenlik uyarıları ile gizliliğinizi korumaya devam ederken bilinen dolandırıcılıklardan uzak durun." + }, + "notifications23DescriptionThree": { + "message": "OpenSea'den güvenlik uyarılarını etkinleştirdiyseniz sizi bu özelliğe taşıdık." + }, + "notifications23DescriptionTwo": { + "message": "Talepleri onaylamadan önce her zaman kendiniz gerekli özeni gösterin." + }, + "notifications23Title": { + "message": "Güvenlik uyarıları ile güvende kalın" + }, "notifications3ActionText": { "message": "Daha fazla bilgi", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Sadece güvendiğiniz sitelere bağlayın." }, "openFullScreenForLedgerWebHid": { - "message": "Kayıt defterinizi WebHID üzerinden bağlamak için MetaMask'i tam ekran açın.", + "message": "Kayıt defterinizi bağlamak için tam ekrana gidin.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Blok gezgininde aç" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockad (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "İşlem Başarısız Oldu" + }, "optional": { "message": "İsteğe bağlı" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Parolalar Aynı Değil" }, + "pasteJWTToken": { + "message": "Tokeninizi buraya yapıştırın veya bırakın:" + }, "pastePrivateKey": { "message": "Özel anahtar dizinizi buraya yapıştırın:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "İnternete erişim sağla.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Snap'in internete erişim sağlamasına izin verin. Üçüncü taraf sunucuları ile hem veri göndermek hem de veri almak için kullanılabilir.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "$1 snap'e bağlan.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Web sitesinin veya snap'in $1 ile etkileşimde bulunmasına izin verin.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Periyodik eylemleri planla ve gerçekleştir.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Snap'in sabit saat, tarih veya aralıklarda periyodik olarak çalışan eylemleri gerçekleştirmesine izin verin. Zamana duyarlı etkileşim ya da bildirimleri tetiklemek için kullanılabilir.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "MetaMask'te iletişim kutusu pencerelerini göster.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Snap'in bir eylemi onaylamak ya da reddetmek için özel metin, giriş alanı ve düğmelerle MetaMask açılır pencerelerini göstermesine izin verin.\nBir snap için ör. uyarı, onay ve talep edilen akış oluşturmak için kullanılabilir.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Adrese, hesap bakiyesine, aktiviteye bakın ve işlemleri başlatın", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Ethereum sağlayıcısına ulaş.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Blok zincirinden veri okuyabilmesi ve mesaj ile işlemleri önerebilmesi için snap'in doğrudan MetaMask ile iletişim kurmasına izin verin.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Bu snap için eşsiz rastgele anahtarları türet.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Snap'in, ifşa etmeden, bu snap'e özel rastgele anahtarlar türetmesine izin verin. Bu anahtarlar MetaMask hesap veya hesaplarınızdan ayrıdır ve özel anahtarlarınızla ya da Gizli Kurtarma İfadenizle bağlantılı değildir. Diğer snap'ler bu bilgilere erişim sağlayamaz.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Yaşam döngüsü kancalarını kullanın.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Bu yaşam döngüsü sırasında belirli zamanlarda kod çalıştırmak için snap'in yaşam döngüsü kancalarını kullanmasına izin verin.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Süresiz çalıştır.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Snap'in örneğin büyük miktarda veri işlerken belirsiz süreyle çalışmasına izin verin.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Ethereum hesaplarını ekle ve kontrol et", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "$1 ($2) altındaki hesaplarınızı ve varlıklarınızı kontrol edin.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Snap'in, ifşa etmeden, Gizli Kurtarma İfadenize göre BIP-32 anahtar çiftleri türetmesine izin verin. Bu işlem $1 üzerinde tüm hesaplara ve varlıklara tam erişim verir.\nSnap, anahtarları yönetme yetkisi ile Ethereum (EVM'ler) ötesinde çeşitli blok zinciri protokollerini destekleyebilir.", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "\"$1\" hesaplarını ve varlıklarını kontrol et.", + "message": "$1 hesaplarınızı ve varlıklarınızı kontrol edin.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Snap'in, ifşa etmeden, Gizli Kurtarma İfadenize göre BIP-44 anahtar çiftleri türetmesine izin verin. Bu işlem $1 üzerinde tüm hesaplara ve varlıklara tam erişim verir.\nSnap, anahtarları yönetme yetkisi ile Ethereum (EVM'ler) ötesinde çeşitli blok zinciri protokollerini destekleyebilir.", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "$1 hesaplarınızı ve varlıklarınızı kontrol edin.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Verilerini cihazında sakla ve yönet.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Snap'in şifreleme ile güvenli bir şekilde veri depolamasına, güncellemesine ve almasına izin verin. Diğer snap'ler bu bilgilere erişim sağlayamaz.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Bildirimleri göster.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Snap'in MetaMask dahilinde bildirim göstermesine izin verin. Eyleme geçirilebilir ya da zamana duyarlı bilgiler için bir snap tarafından kısa bir bildirim metni tetiklenebilir.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "$1 bu snaple doğrudan iletişim kurabilsin.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "$1 için snap'e mesaj gönderme ve snap'ten yanıt alma izni verin.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "İşlem ayrıntılarını al ve görüntüle.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Snap'in işlemlerin şifresini çözmesine ve MetaMask Kullanıcı Arayüzü dahilinde ayrıntıları göstermesine izin verin. Kimlik avından korunma ve güvenlik çözümleri için kullanılabilir.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "İşlem öneren web sitelerinin kökenlerini gör", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Snap'in işlem öneren web sitelerinin asıl adresini (URI) görmesine izin verin. Kimlik avından korunma ve güvenlik çözümleri için kullanılabilir.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Bilinmeyen izin: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "$1 ($2) için genel anahtarınızı görüntüleyin.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Snap'in $1 için genel anahtarları (ve adresleri) görüntülemesine izin verin. Bu eylem, herhangi bir şekilde hesapların ya da varlıkların kontrolünü vermez.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "$1 için herkese açık anahtarınızı görüntüleyin.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "WebAssembly desteği.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Snap'in WebAssembly aracılığıyla düşük seviye gerçekleştirme ortamlarına erişim sağlamasına izin verin.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "İzinler" }, + "permissionsTitle": { + "message": "İzinler" + }, + "permissionsTourDescription": { + "message": "Burada bağlı hesaplarınızı bulun ve izinleri yönetin" + }, "personalAddressDetected": { "message": "Kişisel adres algılandı. Token sözleşme adresini girin." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Portföy" }, + "portfolioDashboard": { + "message": "Portföy Gösterge Paneli" + }, "preferredLedgerConnectionType": { "message": "Tercih Edilen Kayıt Defteri bağlantı türü", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Özel Anahtar", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "$1 için özel anahtar", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Uyarı: Bu anahtarı kimse ile paylaşmayın. Özel anahtarlarınıza sahip herkes hesaplarınızıdaki tüm varlığınızı çalabilir." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Kuyruğa alındı" }, + "quoteRate": { + "message": "Kota oranı" + }, "reAddAccounts": { "message": "diğer hesapları yeniden ekle" }, @@ -2751,7 +3401,7 @@ "message": "Al" }, "recipientAddressPlaceholder": { - "message": "Ara, genel adres (0x) veya ENS" + "message": "Genel adres (0x) veya ENS adı girin" }, "recommendedGasLabel": { "message": "Önerilen" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Bu hesap cüzdanınızdan kaldırılacaktır. Devam etmeden önce içe aktarılmış olan bu hesap için ilk olarak oluşturulan Gizli Kurtarma İfadesine ya da özel anahtara sahip olduğunuzdan lütfen emin olun. Hesap açılır menüsünden hesapları yeniden içe aktarabilir ya da hesap oluşturabilirsiniz. " }, + "removeJWT": { + "message": "Saklayıcı kurum tokenini kaldır" + }, + "removeJWTDescription": { + "message": "Bu tokeni kaldırmak istediğinizden emin misiniz? Bu tokene atanan tüm hesaplar uzantıdan da kaldırılacaktır: " + }, "removeNFT": { "message": "NFT'yi kaldır" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Tercihleri ve hesap adreslerini içeren kullanıcı ayarlarını daha önce yedeklenmiş bir JSON dosyasından geri yükleyebilirsiniz." }, + "resultPageError": { + "message": "Hata" + }, + "resultPageErrorDefaultMessage": { + "message": "İşlem başarısız oldu." + }, + "resultPageSuccess": { + "message": "Başarılı" + }, + "resultPageSuccessDefaultMessage": { + "message": "İşlem başarılı bir şekilde tamamlandı." + }, "retryTransaction": { "message": "İşlemi tekrar dene" }, @@ -2939,16 +3607,27 @@ "message": "Tüm $1 için izin erişim ve transfer izni geri çekilsin mi?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Tüm $1 NFT'lerinize erişim ve transfer izni geri çekilsin mi?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Bu durumda üçüncü bir tarafın başkaca bildiride bulunmaksızın tüm $1 erişim ve transfer izni geri çekilir.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Üçüncü bir tarafın, başkaca bildiri olmaksızın tüm $1 NFT'lerinize erişim sağlama ve onları transfer etme iznini geri çeker.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "İzni geri al" + }, "revokeSpendingCap": { "message": "$1 için harcama üst limitini iptal et", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Bu sözleşme şimdiki ya da gelecekteki tokenlerinizin hiçbirini kullanamayacak." + "message": "Bu üçüncü taraf şimdiki ya da gelecekteki tokenlerinizi artık kullanamayacak." }, "rpcUrl": { "message": "Yeni RPC URL adresi" @@ -2986,9 +3665,28 @@ "security": { "message": "Güvenlik" }, + "securityAlert": { + "message": "$1 ve $2 güvenlik uyarısı" + }, + "securityAlerts": { + "message": "Güvenlik uyarıları" + }, + "securityAlertsDescription1": { + "message": "Bu özellik, işlemlerinizi ve imza taleplerinizi yerel olarak inceleyerek sizi kötü amaçlı aktivitelere karşı uyarır. Verileriniz bu hizmeti sunan üçüncü taraflarla paylaşılmaz. Herhangi bir talebi onaylamadan önce gerekli özeni her zaman kendiniz gösterin. Bu özelliğin tüm kötü amaçlı aktiviteleri algılayacağına dair herhangi bir garanti söz konusu değildir." + }, + "securityAlertsDescription2": { + "message": "Talepleri onaylamadan önce her zaman gerekli özeni gösterdiğinizden emin olun. Tüm kötü niyetli aktivitelerin bu özellik tarafından algılanacağına dair herhangi bir garanti sunulmamaktadır." + }, "securityAndPrivacy": { "message": "Güvenlik ve gizlilik" }, + "securityProviderAdviceBy": { + "message": "$1 güvenlik önerisi", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Ayrıntılara bakın" + }, "seedPhraseConfirm": { "message": "Gizli Kurtarma İfadesini Onayla" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Gizli Kurtarma İfadenizi not edin" }, + "select": { + "message": "Seç" + }, "selectAccounts": { "message": "Bu sitede kullanılacak hesap veya hesapları seçin" }, + "selectAccountsForSnap": { + "message": "Bu snap ile kullanılacak hesabı/hesapları seçin" + }, "selectAll": { "message": "Tümünü seç" }, + "selectAllAccounts": { + "message": "Tüm hesapları seç" + }, "selectAnAccount": { "message": "Hesap seç" }, "selectAnAccountAlreadyConnected": { "message": "Bu hesap zaten MetaMask'e bağlanmış" }, + "selectAnAccountHelp": { + "message": "MetaMask Kurumsal'da kullanılacak saklayıcı kurum hesaplarını seçin." + }, "selectHdPath": { "message": "HD yolunu seç" }, + "selectJWT": { + "message": "Token seç" + }, "selectNFTPrivacyPreference": { "message": "Ayarlarda NFT algılamayı açın" }, @@ -3113,6 +3826,9 @@ "message": "$1 için harcama limiti olmadan onay ver", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Snap hesabı ekle" + }, "settings": { "message": "Ayarlar" }, @@ -3141,9 +3857,21 @@ "message": "Etherscan'in işlemler listesinde gelecek işlemleri göstermesi için bunu seçin", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Bu, Ethereum adresinize ve IP adresinize erişimi olacak olan her bir ağa dayalıdır." + }, + "showMore": { + "message": "Daha fazlasını göster" + }, + "showNft": { + "message": "NFT'yi göster" + }, "showPermissions": { "message": "İzinleri göster" }, + "showPrivateKey": { + "message": "Özel anahtarı göster" + }, "showPrivateKeys": { "message": "Özel Anahtarları Göster" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Gizli Kurtarma İfademi yedekleyene kadar hesaplarımı ve tüm varlıkları kaybedebileceğimi anlıyorum." }, + "smartContracts": { + "message": "Akıllı sözleşmeler" + }, + "smartSwap": { + "message": "Akıllı swap" + }, + "smartSwapsAreHere": { + "message": "Akıllı Swap'lar burada!" + }, + "smartSwapsDescription": { + "message": "MetaMask Swap işlemleri artık çok daha akıllı! Akıllı Swap'ları etkinleştirmek, MetaMask'in aşağıdakilere yardımcı olmak için Swap'ini programlı olarak optimize etmesine olanak tanır:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Akıllı swap için yeterli para yok." + }, + "smartSwapsErrorUnavailable": { + "message": "Akıllı Swap'lar geçici olarak kullanılamıyor." + }, + "smartSwapsSubDescription": { + "message": "* Akıllı Swap'lar, işleminizi birden çok kez özel olarak göndermeye çalışır. Tüm denemeler başarısız olursa Swap işleminin başarılı bir şekilde gerçekleşmesini sağlamak için işlem herkese açık olarak yayınlanacaktır." + }, + "snapConfigure": { + "message": "Yapılandır" + }, + "snapConnectionWarning": { + "message": "$1, $2 adlı snap'e bağlanmak istiyor. Bu web sitesine güveniyorsanız ilerleyin.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Bu içerik $1 kaynaklıdır", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "MetaMask Snap'lerini kullanarak yeni hesabınızı nasıl güvende tutacağınızı seçin." + }, + "snapCreateAccountTitle": { + "message": "Bir $1 hesabı oluştur", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Oluşturan: MetaMask" + }, + "snapDetailAudits": { + "message": "Denetim" + }, + "snapDetailDeveloper": { + "message": "Geliştirici" + }, + "snapDetailLastUpdated": { + "message": "Güncellendi" + }, + "snapDetailManageSnap": { + "message": "Snap'i yönet" + }, + "snapDetailTags": { + "message": "Etiketler" + }, + "snapDetailVersion": { + "message": "Sürüm" + }, + "snapDetailWebsite": { + "message": "Web Sitesi" + }, + "snapDetailsCreateASnapAccount": { + "message": "Bir Snap Hesabı oluştur" + }, + "snapDetailsInstalled": { + "message": "Yüklendi" + }, "snapError": { "message": "Snap Hatası: '$1'. Hata Kodu: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Snap'i yükle" }, + "snapInstallRequest": { + "message": "$1 yüklendiğinde aşağıdaki izinler verilir. Sadece $1 adlı snap'e güveniyorsanız devam edin.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Yükleme tamamlandı" + }, "snapInstallWarningCheck": { "message": "Anladığını doğrulamak için kutucuğu işaretle.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Anladığınızı kontrol etmek için tüm kutuları işaretleyin.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Dikkatle ilerleyin" + }, "snapInstallWarningKeyAccess": { "message": "\"$1\" için $2 anahtar erişimi veriyorsunuz. Bu iptal edilemez ve $2 hesaplarınıza ve varlıklarınıza \"$1\" kontrolü verir. İlerlemeden önce \"$1\" alanına güvendiğinizden emin olun.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "$2 için $1 genel anahtar erişimi ver", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "$1 yüklenemedi.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Yükleme başarısız oldu", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Denetlendi" + }, + "snapResultError": { + "message": "Hata" + }, + "snapResultSuccess": { + "message": "Başarılı" + }, + "snapResultSuccessDescription": { + "message": "$1 kullanıma hazır" + }, "snapUpdate": { "message": "Snap'i güncelle" }, + "snapUpdateAvailable": { + "message": "Güncelleme mevcut" + }, + "snapUpdateErrorDescription": { + "message": "$1 güncellenemedi.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Güncelleme başarısız oldu", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1, $2 için aşağıdaki izinleri verecek şekilde $3 güncellemesi yapmak istiyor. Sadece $2 sürümüne güveniyorsanız devam edin.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Güncelleme tamamlandı" + }, "snaps": { "message": "Snap'ler" }, "snapsInsightLoading": { "message": "İşlem ayrıntıları yükleniyor..." }, + "snapsInvalidUIError": { + "message": "Snap tarafından belirtilen kullanıcı arayüzü geçersiz." + }, "snapsNoInsight": { "message": "Snap herhangi bir ayrıntıya ulaşamadı" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Yüklemek üzere olduğunuz snap'in Consensys $1 içinde tanımlanan bir Üçüncü Taraf Hizmet olduğunu kabul edersiniz. Üçüncü Taraf Hizmetlerini kullanımınız Üçüncü Taraf Hizmet sağlayıcısı tarafından belirtilen ayrı şart ve koşullar tarafından yönetilir. Üçüncü Taraf Hizmetine erişiminizin, güvenmenizin veya kullanımınızın riski size aittir. Consensys, Üçüncü Taraf Hizmetlerini kullanımınızdan kaynaklanan zararlar konusunda tüm sorumluluk ve yükümlülüğü reddeder.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Üçüncü Taraf Hizmetleri ile paylaştığınız tüm bilgiler söz konusu Üçüncü Taraf Hizmetlerinin gizlilik politikalarına göre doğrudan üçüncü taraflar tarafından toplanacaktır. Daha fazla bilgi için lütfen üçüncü tarafların gizlilik politikalarına bakın.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys'un bu üçüncü taraflarla paylaştığınız bilgilere hiçbir erişimi bulunmamaktadır.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Snap'lerini yönet" }, + "snapsTermsOfUse": { + "message": "Kullanım Şartları" + }, "snapsToggle": { "message": "Bir snap yalnızca etkinleştirilmişse çalışır" }, "snapsUIError": { - "message": "Snap tarafından belirtilen Kullanıcı Arayüzü geçersiz.", + "message": "Daha fazla destek için $1 oluşturucuları ile iletişime geçin.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Sadece şu anda ya da gelecekte $1 erişimi konusunda rahat olduğunuz bir sayı girin. Token limitini daha sonra dilediğiniz zaman artırabilirsiniz.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "$1 için harcama üst limiti talebi" + }, "srpInputNumberOfWords": { "message": "$1 sözcükten oluşan bir ifadem var", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3297,7 +4167,10 @@ "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, "srpSecurityQuizGetStarted": { - "message": "Başla" + "message": "Başlarken" + }, + "srpSecurityQuizImgAlt": { + "message": "Ortada anahtar deliği ile bir göz ve üç kayan şifre alanı" }, "srpSecurityQuizIntroduction": { "message": "Gizli Kurtarma İfadenizi görmek için iki soruyu doğru cevaplamanız gerekmektedir" @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "stabil" }, + "stake": { + "message": "Pay" + }, "stateLogError": { "message": "Durum günlükleri alınırken hata." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Bağlanmadı" }, + "statusNotConnectedAccount": { + "message": "Herhangi bir hesap bağlı değil" + }, "step1LatticeWallet": { "message": "Lattice1'inizi bağlayın" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Bu, alacağınız minimum tutardır. Farka bağlı olarak daha fazla alabilirsiniz." }, + "swapAnyway": { + "message": "Yine de swap gerçekleştir" + }, "swapApproval": { "message": "Takas için şunu onayla: $1", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Bu takası tamamlamak için $1 tane daha $2 gerekli", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Hala orada mısınız?" + }, + "swapAreYouStillThereDescription": { + "message": "Devam etmek istediğinizde size en yeni kotaları göstermeye hazırız" + }, "swapBuildQuotePlaceHolderText": { "message": "$1 ile eşleşen token yok", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Donanım cüzdanınızla onaylayın" }, + "swapContinueSwapping": { + "message": "Swap işlemine devam edin" + }, "swapContractDataDisabledErrorDescription": { "message": "Kayıt defterinizdeki Ethereum uygulamasında \"Ayarlar\" öğesine gidin ve sözleşme verilerine izin verin. Ardından takas işleminizi tekrar deneyin." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Limiti düzenle" }, + "swapEditTransactionSettings": { + "message": "İşlem ayarlarını düzenleyin" + }, "swapEnableDescription": { "message": "Bu gereklidir ve MetaMask'e $1 takası yapma izni verir.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Bu takas için şunu yapacaktır: $1", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Bir tutar girin" + }, "swapEstimatedNetworkFees": { "message": "Tahmini ağ ücretleri" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Takas işlemi başarısız oldu" }, + "swapFetchingQuote": { + "message": "Teklif alınıyor" + }, "swapFetchingQuoteNofN": { "message": "$1 / $2 fiyat teklifi alınıyor", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "%$1 MetaMask ücreti dahildir.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "%$1 MetaMask ücreti - $2 dahildir", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Swap işlemleri hakkında daha fazla bilgi edinin" + }, "swapLowSlippageError": { "message": "İşlem başarısız olabilir, maks. fark çok düşük." }, @@ -3626,6 +4533,10 @@ "message": "$1 içinde yeni teklifler sunulacak", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "$1 ile eşleşen token yok", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Bu işlem gerçekleştikten sonra $1 hesabınıza eklenecek.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Teklif ayrıntıları" }, + "swapQuoteNofM": { + "message": "$1 / $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Teklif kaynağı" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Teklif zaman aşımı" }, + "swapQuotesNotAvailableDescription": { + "message": "İşlem boyutunuzu küçültün veya farklı bir token kullanın." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Lütfen tutarı veya fark ayarlarını değiştirmeyi deneyin ve yeniden deneyin." }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "Birden fazla likidite kaynağından alınmış tüm teklifler aşağıdadır." }, + "swapSelectToken": { + "message": "Token seçin" + }, + "swapShowLatestQuotes": { + "message": "En yeni kotaları göster" + }, "swapSlippageNegative": { "message": "Fark en az sıfır olmalıdır" }, + "swapSlippageNegativeDescription": { + "message": "Fark en az sıfır olmalıdır" + }, + "swapSlippageNegativeTitle": { + "message": "Devam etmek için farkı artırın" + }, + "swapSlippageOverLimitDescription": { + "message": "Fark toleransı en fazla %15 olmalıdır. Daha yüksek olması kötü bir oranla sonuçlanır." + }, + "swapSlippageOverLimitTitle": { + "message": "Devam etmek için farkı düşürün" + }, "swapSlippagePercent": { "message": "%$1", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Maks. fark çok düşük ve bu durum işleminizin başarısız olmasına neden olabilir." + }, + "swapSlippageTooLowTitle": { + "message": "İşlemin başarısız olmasını önlemek için farkı artırın" + }, "swapSlippageTooltip": { "message": "Emrinizin verildiği ve onaylandığı zamanlar arasında fiyat farkı oluşursa buna \"fark\" denir. Fark, \"fark toleransı\" ayarınızı aşarsa takas işleminiz otomatik olarak iptal edilir." }, + "swapSlippageVeryHighDescription": { + "message": "Girilen fark çok yüksek kabul ediliyor ve kötü bir oranla sonuçlanabilir" + }, + "swapSlippageVeryHighTitle": { + "message": "Fark çok yüksek" + }, + "swapSlippageZeroDescription": { + "message": "Daha az rekabetçi bir kota ile sonuçlanacak olan az sayıda sıfır fark kotalı sağlayıcı var." + }, + "swapSlippageZeroTitle": { + "message": "Sıfır farklı sağlayıcılar getiriliyor" + }, "swapSource": { "message": "Likidite kaynağı" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "donanım cüzdanınızla onaylamak için" }, + "swapTokenAddedManuallyDescription": { + "message": "Bu token'i $1 ile doğrulayın ve işlem yapmak istediğiniz token olduğundan emin olun.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token manuel olarak eklendi" + }, "swapTokenAvailable": { "message": "$1 hesabınıza eklendi.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "$1 kaynakta doğrulandı.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 sadece 1 kaynakta doğrulandı. İlerlemeden önce $2 üzerinde doğrulamayı deneyin.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Sahte token olma potansiyeli var" + }, "swapTooManyDecimalsError": { "message": "$1, en fazla $2 ondalık basamağına izin verir", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Bu işlemi tamamlamak için yeterli $1 yok", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Yeterli $1 yok", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Aktivitede görüntüle" }, + "switch": { + "message": "Değiştir" + }, "switchEthereumChainConfirmationDescription": { "message": "Bu, MetaMask'te seçili bir ağı önceden eklenmiş bir ağa dönüştürecektir:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Şuna geçiş yaptınız:" }, + "switcherTitle": { + "message": "Ağ değiştirici" + }, + "switcherTourDescription": { + "message": "Ağ değiştirmek veya yeni bir ağ eklemek için simgeye tıklayın" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Ağ değiştirmek bekleyen tüm onayları iptal eder" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Hizmet şartları" }, + "termsOfUse": { + "message": "kullanım şartları" + }, + "termsOfUseAgreeText": { + "message": " MetaMask ve tüm özelliklerini kullanımım için geçerli olan Kullanım Şartları bölümünü kabul ediyorum" + }, + "termsOfUseFooterText": { + "message": "Tüm bölümleri okumak için lütfen kaydırın" + }, + "termsOfUseTitle": { + "message": "Kullanım Şartları bölümümüz güncellendi" + }, "testNetworks": { "message": "Test ağları" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Unutulmaması gerekenler:" }, + "thirdPartySoftware": { + "message": "Üçüncü taraf yazılım uyarısı", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "bu koleksiyon" + }, + "thisServiceIsExperimental": { + "message": "Hizmet deneyseldir. Bu özelliği etkinleştirerek OpenSea'nin $1 bölümünü kabul edersiniz.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Zaman" }, @@ -3863,11 +4867,44 @@ "message": "Alıcı: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Kimlik avı saldırıları bakımından risk altındasınız. eth_sign özelliğini kapatarak kendinizi koruyun." + }, "toggleEthSignDescriptionField": { - "message": "eth_sign taleplerini kullanarak merkezi olmayan bir uygulamanın imzanızı talep etmesine izin vermek için bunu açın. eth_sign, gelişigüzel bir hash imzalamanıza olanak sağlayan açık uçlu bir imza yöntemidir ve bu da tehlikeli bir kimlik hırsızlığı riski oluşturur. eth_sign taleplerini sadece imzaladığınız şeyi okuyabiliyorsanız ve talebin çıkış yerine güveniyorsanız imzalayın." + "message": "Bu ayarı etkinleştirirseniz okunabilir olmayan imza talepleri alabilirsiniz. Anlamadığınız bir mesajı imzaladığınızda paranızı ve NFT'lerinizi vermeyi kabul ediyor olabilirsiniz." }, "toggleEthSignField": { - "message": "eth_sign taleplerini değiştirin" + "message": "Eth_sign talepleri" + }, + "toggleEthSignModalBannerBoldText": { + "message": " dolandırılıyor olabilirsiniz" + }, + "toggleEthSignModalBannerText": { + "message": "Bu ayarı açmanız istendi ise" + }, + "toggleEthSignModalCheckBox": { + "message": "eth_sign taleplerini etkinleştirirsem tüm paramı ve NFT'lerimi kaybedebileceğimi anlıyorum. " + }, + "toggleEthSignModalDescription": { + "message": "eth_sign taleplerine izin vermeniz sizi kimlik avı saldırılarına karşı hassas hale getirebilir. Her zaman URL adresini inceleyin ve kod içeren mesajları imzalarken dikkat edin." + }, + "toggleEthSignModalFormError": { + "message": "Metin yanlış" + }, + "toggleEthSignModalFormLabel": { + "message": "Devam etmek için \"Sadece anladığım şeyleri imzalıyorum\" girin" + }, + "toggleEthSignModalFormValidation": { + "message": "Sadece anladığım şeyleri imzalıyorum" + }, + "toggleEthSignModalTitle": { + "message": "Kullanım riski size aittir" + }, + "toggleEthSignOff": { + "message": "Kapalı (Önerilir)" + }, + "toggleEthSignOn": { + "message": "Açık (Önerilmez)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Token sembolü" }, + "tokens": { + "message": "Tokenler" + }, "tokensFoundTitle": { "message": "$1 yeni token bulundu", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Anladım" }, + "tooltipSatusConnected": { + "message": "bağlı" + }, + "tooltipSatusNotConnected": { + "message": "bağlı değil" + }, "total": { "message": "Toplam" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "İşlem bir hatayla karşılaştı." }, + "transactionFailed": { + "message": "İşlem Başarısız Oldu" + }, "transactionFee": { "message": "İşlem ücreti" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Toplam gaz ücreti" }, + "transactionNote": { + "message": "İşlem notu" + }, "transactionResubmitted": { "message": "İşlem, $2 itibaeiyle $1 olarak artırılan tahmini gaz ücreti ile yeniden gönderildi" }, "transactionSecurityCheck": { - "message": "İşlem güvenlik sağlayıcılarını etkinleştir" + "message": "Güvenlik uyarılarını etkinleştir" }, "transactionSecurityCheckDescription": { "message": "İmzalanmamış işleme dahil olan riskleri ve onları imzalamadan önce imza taleplerini algılamak ve göstermek için üçüncü taraf API'lerini kullanırız. Bu hizmetler imzalanmamış işlem ve imza taleplerinize, hesap adresinize ve tercih ettiğiniz dile erişim sağlar." }, + "transactionSettings": { + "message": "İşlem ayarları" + }, "transactionSubmitted": { "message": "İşlem $2 itibariyle tahmini $1 gaz ücreti ile gönderildi." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Transfer kaynağı:" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Ledger'ınıza bağlanırken sorun yaşıyoruz. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Donanım cüzdanı bağlantı kılavuzumuzu inceleyip tekrar deneyin.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Firefox'un en son sürümündeyseniz Firefox'un U2F desteğini bırakması ile ilgili sorun yaşıyor olabilirsiniz. Bu sorunun nasıl düzeltileceğini $1 öğrenin.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "buradan", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "$1 ile bağlantı kurmada sorun yaşıyoruz, $2 kısmını inceleyip tekrar deneyin.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "yukarı ok" }, + "update": { + "message": "Güncelle" + }, "updatedWithDate": { "message": "$1 güncellendi" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Bu URL şu anda $1 ağı tarafından kullanılıyor." }, + "use4ByteResolution": { + "message": "Akıllı sözleşmelerin şifresini çöz" + }, + "use4ByteResolutionDescription": { + "message": "Kullanıcı deneyiminizi iyileştirmek amacıyla aktivite sekmesini etkileşimde bulunduğunuz akıllı sözleşmelere bağlı mesajlarla kişiselleştiririz. MetaMask, verileri çözmek ve size okunması daha kolay olan bir akıllı sözleşme sürümü göstermek için 4byte.directory adlı bir hizmet kullanır. Böylece kötü amaçlı akıllı sözleşme eylemlerini onaylama ihtimalinizi düşürmeye yardımcı olur ancak IP adresinizin paylaşılmasına neden olabilir." + }, "useMultiAccountBalanceChecker": { "message": "Toplu hesap bakiyesi talepleri" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Hesap bakiyesi taleplerini toplu ödeme olarak gerçekleştirerek daha hızlı bakiye güncellemeleri alın. Bu, hesap bakiyelerinizi bir arada almanızı sağlar, böylece gelişmiş bir deneyim için daha hızlı güncellemeler alacaksınız. Bu özellik kapalı olduğunda üçüncü tarafların sizin hesaplarınızı birbirleriyle ilişkilendirme olasılığı daha düşük olur." + }, "useNftDetection": { "message": "NFT'leri otomatik algıla" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Ethereum kullanıcılarını hedefleyen kimlik avı alanları için bir uyarı görüntüler" }, + "useSiteSuggestion": { + "message": "Sitenin önerisini kullanın" + }, "useTokenDetectionPrivacyDesc": { "message": "Hesabına gönderilen token'ların otomatik olarak görüntülenmesi, token'ın görüntülerini almak için üçüncü taraf sunucularla iletişimi içerir. Bu servislerin IP adresine erişimi olacaktır." }, @@ -4170,7 +5256,7 @@ "message": "Kullanıcı adı" }, "verifyContractDetails": { - "message": "Sözleşme bilgilerini doğrula" + "message": "Üçüncü taraf bilgilerini doğrula" }, "verifyThisTokenDecimalOn": { "message": "Token ondalık değeri şurada bulunabilir: $1", @@ -4184,12 +5270,18 @@ "message": "Bu tokeni $1 ile doğrulayın ve işlem yapmak istediğiniz tokenin bu olduğundan emin olun.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Sürüm" + }, "view": { "message": "Görüntüle" }, "viewAllDetails": { "message": "Tüm bilgileri görüntüle" }, + "viewAllQuotes": { + "message": "tüm teklifleri görüntüle" + }, "viewContact": { "message": "Kişiyi görüntüle" }, @@ -4213,9 +5305,18 @@ "message": "Etherscan'de $1 görüntüle", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Explorer'da görüntüleyin" + }, "viewOnOpensea": { "message": "Opensea'de görüntüle" }, + "viewPortfolioDashboard": { + "message": "Portföy Kontrol Panelini görüntüle" + }, + "viewinCustodianApp": { + "message": "Saklayıcı kurum uygulamasında görüntüle" + }, "viewinExplorer": { "message": "Explorer'da $1 görüntüle", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Bu ağı eklemek istiyor musunuz?" }, + "wantsToAddThisAsset": { + "message": "$1 bu varlığı cüzdanınıza eklemek istiyor", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Uyarı" }, "warningTooltipText": { - "message": "$1 Sözleşme, başkaca bildiri ya da rıza olmaksızın tüm token bakiyenizi harcayabilir. Düşük bir harcama limitini özelleştirerek kendinizi koruyun.", + "message": "$1 Üçüncü taraf, başkaca bildiri ya da rıza olmaksızın tüm token bakiyenizi harcayabilir. Düşük bir harcama limitini özelleştirerek kendinizi koruyun.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "İmzalıyorsunuz" }, + "yourAccounts": { + "message": "Hesaplarınız" + }, "yourFundsMayBeAtRisk": { "message": "Paranız tehlikede olabilir" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index ea52fc82b..0641006cc 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -106,6 +106,9 @@ "about": { "message": "Giới thiệu" }, + "accept": { + "message": "Chấp nhận" + }, "acceptTermsOfUse": { "message": "Tôi đã đọc và đồng ý với $1", "description": "$1 is the `terms` message" @@ -171,6 +174,9 @@ "addANickname": { "message": "Thêm tên riêng" }, + "addAccount": { + "message": "Thêm tài khoản" + }, "addAcquiredTokens": { "message": "Thêm token mà bạn đã mua bằng MetaMask" }, @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "Thêm từ danh sách các mạng phổ biến hoặc thêm mạng theo cách thủ công. Chỉ tương tác với các đối tượng mà bạn tin tưởng." }, + "addHardwareWallet": { + "message": "Thêm ví cứng" + }, + "addIPFSGateway": { + "message": "Thêm cổng IPFS ưu thích của bạn" + }, "addMemo": { "message": "Thêm bản ghi nhớ" }, @@ -244,6 +256,21 @@ "message": "Kết nối mạng này dựa vào các bên thứ ba. Kết nối này có thể kém tin cậy hơn hoặc cho phép các bên thứ ba theo dõi hoạt động. $1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "Thêm token mới" + }, + "addNft": { + "message": "Thêm NFT" + }, + "addNfts": { + "message": "Thêm NFT" + }, + "addSnapAccountModalDescription": { + "message": "Khám phá các tùy chọn để đảm bảo an toàn cho tài khoản của bạn với MetaMask Snap" + }, + "addSuggestedNFTs": { + "message": "Thêm NFT được đề xuất" + }, "addSuggestedTokens": { "message": "Thêm token được đề xuất" }, @@ -254,6 +281,9 @@ "message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "Thêm mạng" + }, "address": { "message": "Địa chỉ" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "Phí ưu tiên (hay còn được gọi là \"phí khích lệ thợ đào\") được chuyển trực tiếp cho các thợ đào và khuyến khích họ ưu tiên giao dịch của bạn." }, + "agreeTermsOfUse": { + "message": "Tôi đồng ý với $1 của MetaMask", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Vault" }, @@ -302,10 +336,20 @@ "alerts": { "message": "Cảnh báo" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "Bạn đã kết nối tất cả các tài khoản lưu ký của mình hoặc không có bất kỳ tài khoản nào để kết nối với MetaMask Institutional." + }, + "allCustodianAccountsConnectedTitle": { + "message": "Không có tài khoản để kết nối" + }, "allOfYour": { "message": "Tất cả $1 của bạn", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "Tất cả NFT của bạn từ $1", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "Cho phép tiện ích bên ngoài này:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "Cho phép trang web này:" }, + "allowThisSnapTo": { + "message": "Cho phép snap này:" + }, "allowWithdrawAndSpend": { "message": "Cho phép $1 rút và chi tiêu tối đa số tiền sau đây:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "Số tiền" }, + "apiUrl": { + "message": "URL API" + }, "appDescription": { "message": "Ví Ethereum trên trình duyệt của bạn", "description": "The description of the application" @@ -350,6 +400,10 @@ "message": "Cấp quyền truy cập vào và chuyển tất cả $1 của bạn?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "Cho phép truy cập và chuyển tất cả NFT của bạn từ $1?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "Phê duyệt" }, @@ -360,6 +414,10 @@ "approveTokenDescription": { "message": "Điều này cho phép bên thứ ba được quyền truy cập và chuyển các NFT sau đây mà không cần thông báo thêm cho đến khi bạn thu hồi quyền truy cập của họ." }, + "approveTokenDescriptionWithoutSymbol": { + "message": "Hành động này sẽ cho phép bên thứ ba truy cập và chuyển tất cả NFT của bạn từ $1 mà không cần thông báo thêm cho đến khi bạn thu hồi quyền truy cập của họ.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { "message": "Cho phép truy cập và chuyển $1 của bạn?", "description": "$1 is the symbol of the token for which the user is granting approval" @@ -386,6 +444,10 @@ "attemptSendingAssets": { "message": "Nếu bạn cố gắng gửi tài sản trực tiếp từ mạng này sang mạng khác, bạn có thể bị mất tài sản vĩnh viễn. Hãy nhớ sử dụng cầu nối." }, + "attemptToCancelSwap": { + "message": "Cố gắng hủy hoán đổi với giá ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Đang cố gắng kết nối với chuỗi khối." }, @@ -411,6 +473,9 @@ "average": { "message": "Trung bình" }, + "awaitingApproval": { + "message": "Đang chờ duyệt..." + }, "back": { "message": "Quay lại" }, @@ -454,6 +519,9 @@ "message": "Đây là một phiên bản beta. Vui lòng báo cáo lỗi $1", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "Phiên bản MetaMask Institutional Beta" + }, "betaMetamaskVersion": { "message": "Phiên Bản MetaMask Beta" }, @@ -488,9 +556,45 @@ "message": "Xem tài khoản tại $1", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "Nếu bạn chấp thuận yêu cầu này, một bên thứ ba nổi tiếng là lừa đảo có thể lấy hết tài sản của bạn." + }, + "blockaidDescriptionBlurFarming": { + "message": "Nếu bạn chấp thuận yêu cầu này, người khác có thể đánh cắp tài sản của bạn được niêm yết trên Blur." + }, + "blockaidDescriptionFailed": { + "message": "Do có lỗi, yêu cầu này đã không được nhà cung cấp dịch vụ bảo mật xác minh. Hãy thực hiện cẩn thận." + }, + "blockaidDescriptionMaliciousDomain": { + "message": "Bạn đang tương tác với một tên miền độc hại. Nếu bạn chấp thuận yêu cầu này, bạn có thể mất tài sản của mình." + }, + "blockaidDescriptionMightLoseAssets": { + "message": "Nếu bạn chấp thuận yêu cầu này, bạn có thể mất tài sản của mình." + }, + "blockaidDescriptionSeaportFarming": { + "message": "Nếu bạn chấp thuận yêu cầu này, người khác có thể đánh cắp tài sản của bạn được niêm yết trên OpenSea." + }, + "blockaidDescriptionTransferFarming": { + "message": "Nếu bạn chấp thuận yêu cầu này, một bên thứ ba nổi tiếng là lừa đảo sẽ lấy hết tài sản của bạn." + }, + "blockaidTitleDeceptive": { + "message": "Đây là một yêu cầu lừa đảo" + }, + "blockaidTitleMayNotBeSafe": { + "message": "Yêu cầu có thể không an toàn" + }, + "blockaidTitleSuspicious": { + "message": "Đây là một yêu cầu đáng ngờ" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "Cầu nối" + }, "browserNotSupported": { "message": "Trình duyệt của bạn không được hỗ trợ..." }, @@ -510,6 +614,10 @@ "message": "Mua $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "Mua thêm $1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "Mua ngay" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "Thao tác này sẽ đặt lại số chỉ dùng một lần của tài khoản và xóa dữ liệu khỏi thẻ hoạt động trong ví của bạn. Chỉ có tài khoản và mạng hiện tại bị ảnh hưởng. Số dư và các giao dịch đến của bạn sẽ không thay đổi." }, + "click": { + "message": "Nhấn" + }, "clickToConnectLedgerViaWebHID": { "message": "Nhấn vào đây để kết nối với thiết bị Ledger của bạn qua WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "Bây giờ bạn đang rời MetaMask để định cấu hình snap này." + }, + "configureSnapPopupInstallDescription": { + "message": "Bây giờ bạn đang rời MetaMask để cài đặt snap này." + }, + "configureSnapPopupInstallTitle": { + "message": "Cài đặt snap" + }, + "configureSnapPopupLink": { + "message": "Nhấn vào liên kết này để tiếp tục:" + }, + "configureSnapPopupTitle": { + "message": "Định cấu hình snap" + }, "confirm": { "message": "Xác nhận" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "Kết nối tài khoản hoặc tạo tài khoản mới" }, + "connectCustodialAccountMenu": { + "message": "Kết nối tài khoản lưu ký" + }, + "connectCustodialAccountMsg": { + "message": "Vui lòng chọn lưu ký mà bạn muốn kết nối để thêm hoặc làm mới token." + }, + "connectCustodialAccountTitle": { + "message": "Tài khoản lưu ký" + }, "connectManually": { "message": "Kết nối thủ công với trang web hiện tại" }, + "connectSnap": { + "message": "Kết nối $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Kết nối với $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -676,12 +815,25 @@ "connectingToLineaGoerli": { "message": "Đang kết nối với mạng thử nghiệm Linea Goerli" }, + "connectingToLineaMainnet": { + "message": "Đang kết nối với mạng chính thức của Linea" + }, "connectingToMainnet": { "message": "Đang kết nối với mạng chính thức của Ethereum" }, "connectingToSepolia": { "message": "Đang kết nối với mạng thử nghiệm Sepolia" }, + "connectionFailed": { + "message": "Kết nối thất bại" + }, + "connectionFailedDescription": { + "message": "Tìm nạp $1 thất bại, hãy kiểm tra mạng của bạn và thử lại.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Yêu cầu kết nối" + }, "contactUs": { "message": "Liên hệ với chúng tôi" }, @@ -708,7 +860,7 @@ "message": "Triển khai hợp đồng" }, "contractDescription": { - "message": "Để bảo vệ chính mình khỏi những kẻ lừa đảo, hãy dành chút thời gian để xác minh chi tiết hợp đồng." + "message": "Để bảo vệ chính mình khỏi những kẻ lừa đảo, hãy dành chút thời gian để xác minh thông tin bên thứ ba." }, "contractInteraction": { "message": "Tương tác với hợp đồng" @@ -717,16 +869,16 @@ "message": "Hợp đồng NFT" }, "contractRequestingAccess": { - "message": "Hợp đồng yêu cầu quyền truy cập" + "message": "Bên thứ ba yêu cầu quyền truy cập" }, "contractRequestingSignature": { - "message": "Hợp đồng yêu cầu chữ ký" + "message": "Bên thứ ba yêu cầu chữ ký" }, "contractRequestingSpendingCap": { - "message": "Hợp đồng yêu cầu hạn mức chi tiêu" + "message": "Bên thứ ba yêu cầu hạn mức chi tiêu" }, "contractTitle": { - "message": "Chi tiết hợp đồng" + "message": "Thông tin bên thứ ba" }, "contractToken": { "message": "Hợp đồng token" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "Đồ thị ước tính phí gas theo thị trường" }, + "custodian": { + "message": "Lưu ký" + }, + "custodianAccount": { + "message": "Tài khoản lưu ký" + }, + "custodianAccountAddedDesc": { + "message": "Giờ đây bạn có thể sử dụng tài khoản lưu ký của mình trong MetaMask Institutional." + }, + "custodianAccountAddedTitle": { + "message": "Các tài khoản lưu ký được chọn đã được thêm vào." + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "Vui lòng truy cập $1 và nhấn nút 'Kết nối với MMI' trong giao diện người dùng để kết nối lại tài khoản của bạn với MMI." + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "Bây giờ bạn có thể sử dụng tài khoản lưu ký của mình trong MetaMask Institutional." + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "Token lưu ký của bạn đã được làm mới" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "Điều này sẽ thay thế token lưu ký cho địa chỉ sau:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "Thay thế token lưu ký" + }, + "custodyApiUrl": { + "message": "URL API $1" + }, + "custodyDeeplinkDescription": { + "message": "Phê duyệt giao dịch trong ứng dụng $1. Sau khi tất cả các phê duyệt lưu ký bắt buộc đã được thực hiện, giao dịch sẽ hoàn tất. Kiểm tra ứng dụng $1 của bạn để biết trạng thái." + }, + "custodyRefreshTokenModalDescription": { + "message": "Vui lòng truy cập $1 và nhấn nút 'Kết nối với MMI' trong giao diện người dùng để kết nối lại tài khoản của bạn với MMI." + }, + "custodyRefreshTokenModalDescription1": { + "message": "Lưu ký của bạn sẽ phát hành token để xác thực tiện ích MetaMask Institutional, cho phép bạn kết nối các tài khoản của mình." + }, + "custodyRefreshTokenModalDescription2": { + "message": "Vì lý do bảo mật, token này sẽ hết hạn sau một khoảng thời gian nhất định. Điều này yêu cầu bạn kết nối lại với MMI." + }, + "custodyRefreshTokenModalSubtitle": { + "message": "Tại sao tôi lại thấy cái này?" + }, + "custodyRefreshTokenModalTitle": { + "message": "Phiên lưu ký của bạn đã hết hạn" + }, + "custodySessionExpired": { + "message": "Phiên lưu ký đã hết hạn." + }, + "custodyWrongChain": { + "message": "Tài khoản này không được thiết lập để sử dụng với $1" + }, "custom": { "message": "Nâng cao" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "hỗ trợ khách hàng" }, + "dappRequestedSpendingCap": { + "message": "Trang web yêu cầu hạn mức chi tiêu" + }, "dappSuggested": { "message": "Trang web gợi ý" }, @@ -851,6 +1060,12 @@ "message": "$1 đã gợi ý mức giá này.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Trang web gợi ý" + }, + "dappSuggestedHighShortLabel": { + "message": "Trang web (cao)" + }, "dappSuggestedShortLabel": { "message": "Trang web" }, @@ -902,9 +1117,19 @@ "delete": { "message": "Xóa" }, + "deleteContact": { + "message": "Xóa địa chỉ liên hệ" + }, "deleteNetwork": { "message": "Xóa mạng?" }, + "deleteNetworkIntro": { + "message": "Nếu xóa mạng này, bạn sẽ cần thêm lại mạng này để xem các tài sản của mình trong mạng này" + }, + "deleteNetworkTitle": { + "message": "Xóa mạng $1?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "Nạp" }, @@ -917,6 +1142,10 @@ "description": { "message": "Mô tả" }, + "descriptionFromSnap": { + "message": "Mô tả từ $1", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "Lỗi này có thể không xảy ra liên tục, vì vậy hãy thử khởi động lại tiện ích mở rộng hoặc tắt MetaMask Máy tính để bàn." }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "Tắt lời nhắc sao lưu Cụm mật khẩu khôi phục bí mật" }, + "displayNftMedia": { + "message": "Hiển thị phương tiện NFT" + }, + "displayNftMediaDescription": { + "message": "Hiển thị dữ liệu và phương tiện NFT sẽ hiển thị địa chỉ IP của bạn với OpenSea hoặc các bên thứ ba khác. Điều này có thể cho phép kẻ tấn công liên kết địa chỉ IP của bạn với địa chỉ Ethereum của bạn. Tính năng tự động phát hiện NFT dựa trên cài đặt này và sẽ không khả dụng khi cài đặt này bị tắt." + }, "domain": { "message": "Tên miền" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " Bật tự động phát hiện" }, + "enableForAllNetworks": { + "message": "Bật cho tất cả các mạng" + }, "enableFromSettings": { "message": " Bật lên trong Cài Đặt." }, + "enableSmartSwaps": { + "message": "Kích hoạt hoán đổi thông minh" + }, + "enableSnap": { + "message": "Bật" + }, "enableToken": { "message": "bật $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Đã bật" + }, "encryptionPublicKeyNotice": { "message": "$1 muốn biết khóa mã hóa công khai của bạn. Bằng việc đồng ý, trang web này sẽ có thể gửi thông báo được mã hóa cho bạn.", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "Tính năng phát hiện token nâng cao hiện có sẵn trên $1. $2" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask cho phép bạn xem các tên miền ENS như \"https://metamask.eth\" ngay trên thanh địa chỉ của trình duyệt. Sau đây là cách hoạt động:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "Các trình duyệt thông thường thường không xử lý địa chỉ ENS hoặc IPFS, nhưng MetaMask sẽ hỗ trợ xử lý. Việc sử dụng tính năng này có thể chia sẻ địa chỉ IP của bạn với các dịch vụ IPFS bên thứ ba." + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask sẽ kiểm tra hợp đồng ENS của Ethereum để tìm mã được kết nối với tên ENS." + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "Nếu mã được liên kết với IPFS, nó sẽ lấy nội dung từ mạng IPFS." + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "Sau đó, bạn có thể xem nội dung, thường là một trang web hoặc một cái gì đó tương tự." + }, + "ensDomainsSettingTitle": { + "message": "Hiển thị tên miền ENS trong thanh địa chỉ" + }, "ensIllegalCharacter": { "message": "Ký tự không hợp lệ đối với ENS." }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "Nhập số" }, + "enterCustodianToken": { + "message": "Nhập token $1 của bạn hoặc thêm token mới" + }, "enterMaxSpendLimit": { "message": "Nhập giới hạn chi tiêu tối đa" }, + "enterOptionalPassword": { + "message": "Nhập mật khẩu tùy chọn" + }, "enterPassword": { "message": "Nhập mật khẩu" }, "enterPasswordContinue": { "message": "Nhập mật khẩu để tiếp tục" }, + "enterTokenNameOrAddress": { + "message": "Nhập tên token hoặc dán địa chỉ" + }, + "enterYourPassword": { + "message": "Nhập mật khẩu của bạn" + }, "errorCode": { "message": "Mã: $1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "Cụm:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "Gặp lỗi khi kết nối với mạng tùy chỉnh." + }, + "errorWithSnap": { + "message": "Lỗi với $1", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "Giá gas dự phòng được cung cấp vì dịch vụ ước tính giá gas chính hiện không hoạt động." }, + "ethereumProviderAccess": { + "message": "Cấp quyền truy cập $1 cho nhà cung cấp Ethereum", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "Địa chỉ công khai trên Ethereum" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "Thử nghiệm" }, + "exploreMetaMaskSnaps": { + "message": "Khám phá MetaMask Snap" + }, "exportPrivateKey": { "message": "Xuất khóa riêng tư" }, + "extendWalletWithSnaps": { + "message": "Mở rộng trải nghiệm sử dụng ví." + }, "externalExtension": { "message": "Tiện ích bên ngoài" }, @@ -1302,6 +1596,9 @@ "message": "Tính năng nhập tập tin không hoạt động? Nhấn vào đây!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "Kích thước tập tin thả vào quá lớn." + }, "flaskWelcomeUninstall": { "message": "bạn nên gỡ cài đặt tiện ích mở rộng này", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "Chung" }, + "getStarted": { + "message": "Bắt đầu" + }, + "globalTitle": { + "message": "Trình đơn toàn cầu" + }, + "globalTourDescription": { + "message": "Xem danh mục đầu tư, trang web đã kết nối, cài đặt, v.v... của bạn" + }, "goBack": { "message": "Quay Lại" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "Kết nối với một ví cứng" }, + "hardwareWalletsInfo": { + "message": "Các tích hợp của ví cứng sử dụng lệnh gọi API đến máy chủ bên ngoài, máy chủ này có thể xem địa chỉ IP của bạn và địa chỉ hợp đồng thông minh mà bạn tương tác." + }, "hardwareWalletsMsg": { "message": "Chọn một ví cứng mà bạn muốn sử dụng với MetaMask." }, @@ -1541,11 +1850,34 @@ "message": "nhưng những kẻ lừa đảo qua mạng thì có.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "Khóa riêng tư của bạn cung cấp $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "toàn quyền truy cập vào ví và tiền của bạn.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "giữ để hiển thị vòng tròn bị khóa" + }, + "holdToRevealPrivateKey": { + "message": "Giữ để hiển thị Khóa riêng tư" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Đảm bảo an toàn cho khóa riêng tư của bạn" + }, "holdToRevealSRP": { - "message": "Giữ để hiển thị SRP" + "message": "Giữ để hiển thị Cụm từ khôi phục bí mật" }, "holdToRevealSRPTitle": { - "message": "Đảm bảo an toàn cho SRP của bạn" + "message": "Đảm bảo an toàn cho Cụm từ khôi phục bí mật của bạn" + }, + "holdToRevealUnlockedLabel": { + "message": "giữ để hiển thị vòng tròn được mở khóa" + }, + "id": { + "message": "Id" }, "ignoreAll": { "message": "Bỏ qua tất cả" @@ -1563,8 +1895,23 @@ "importAccountError": { "message": "Lỗi khi nhập tài khoản." }, + "importAccountErrorIsSRP": { + "message": "Bạn đã nhập Cụm từ khôi phục bí mật (hoặc thông tin gợi nhớ). Để nhập tài khoản tại đây, bạn phải nhập khóa riêng tư, là một chuỗi thập lục phân có độ dài 64." + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "Khóa riêng tư này không hợp lệ. Bạn đã nhập một chuỗi thập lục phân, nhưng phải dài 64 ký tự." + }, + "importAccountErrorNotHexadecimal": { + "message": "Khóa riêng tư này không hợp lệ. Bạn phải nhập một chuỗi thập lục phân có độ dài 64." + }, + "importAccountJsonLoading1": { + "message": "Dự kiến quá trình nhập JSON này sẽ mất vài phút và đóng băng MetaMask." + }, + "importAccountJsonLoading2": { + "message": "Chân thành xin lỗi bạn, chúng tôi sẽ cải thiện để quá trình này nhanh hơn trong tương lai." + }, "importAccountMsg": { - "message": "Tài khoản đã nhập sẽ không được liên kết với Cụm mật khẩu khôi phục bí mật cho tài khoản MetaMask đã tạo ban đầu của bạn. Tìm hiểu thêm về các tài khoản đã nhập" + "message": "Tài khoản đã nhập sẽ không được liên kết với Cụm từ khôi phục bí mật MetaMask của bạn. Tìm hiểu thêm về tài khoản đã nhập" }, "importMyWallet": { "message": "Nhập ví của tôi" @@ -1615,18 +1962,29 @@ "message": "Mạng đã xác nhận giao dịch ban đầu của bạn. Nhấn OK để quay lại." }, "inputLogicEmptyState": { - "message": "Chỉ nhập số mà bạn cảm thấy thoải mái với mức chi tiêu ở hiện tại hoặc trong tương lai của hợp đồng. Bạn luôn có thể tăng hạn mức chi tiêu sau này." + "message": "Chỉ nhập số mà bạn cảm thấy thoải mái đối với hạn mức chi tiêu ở hiện tại hoặc trong tương lai của bên thứ ba. Bạn luôn có thể tăng hạn mức chi tiêu sau này." }, "inputLogicEqualOrSmallerNumber": { - "message": "Điều này cho phép hợp đồng chi tiêu $1 từ số dư hiện tại của bạn.", + "message": "Điều này cho phép bên thứ ba chi tiêu $1 từ số dư hiện tại của bạn.", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "Điều này cho phép hợp đồng chi tiêu tất cả số dư token của bạn cho đến khi đạt hạn mức hoặc bạn hủy hạn mức chi tiêu. Nếu không có ý định này, hãy cân nhắc đặt hạn mức chi tiêu thấp hơn." + "message": "Điều này cho phép bên thứ ba chi tiêu tất cả số dư token của bạn cho đến khi đạt hạn mức hoặc bạn hủy hạn mức chi tiêu. Nếu không có ý định này, hãy cân nhắc đặt hạn mức chi tiêu thấp hơn." + }, + "insightsFromSnap": { + "message": "Thông tin chi tiết từ $1", + "description": "$1 represents the name of the snap" }, "install": { "message": "Cài đặt" }, + "installOrigin": { + "message": "Cài đặt nguồn gốc" + }, + "installedOn": { + "message": "Đã cài đặt vào $1", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "Không đủ số dư." }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "Nội dung nhập không hợp lệ! Cụm từ khôi phục bí mật phân biệt chữ hoa và chữ thường." }, + "ipfsGateway": { + "message": "Cổng IPFS" + }, + "ipfsGatewayDescription": { + "message": "MetaMask sử dụng các dịch vụ của bên thứ ba để hiển thị hình ảnh của các NFT của bạn được lưu trữ trên IPFS, hiển thị thông tin liên quan đến địa chỉ ENS được nhập trong thanh địa chỉ của trình duyệt và tìm nạp biểu tượng cho các token khác nhau. Địa chỉ IP của bạn có thể được hiển thị với các dịch vụ này khi bạn đang sử dụng chúng." + }, + "ipfsToggleModalDescriptionOne": { + "message": "Chúng tôi sử dụng các dịch vụ của bên thứ ba để hiển thị hình ảnh của các NFT của bạn được lưu trữ trên IPFS, hiển thị thông tin liên quan đến địa chỉ ENS được nhập trong thanh địa chỉ của trình duyệt và tìm nạp biểu tượng cho các token khác nhau. Địa chỉ IP của bạn có thể được hiển thị với các dịch vụ này khi bạn đang sử dụng chúng." + }, + "ipfsToggleModalDescriptionTwo": { + "message": "Việc chọn Xác nhận sẽ bật phân giải IPFS. Bạn có thể tắt nó trong $1 bất cứ lúc nào.", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "Cài đặt > Bảo mật và quyền riêng tư" + }, "jazzAndBlockies": { "message": "Jazzicons và Blockies là hai kiểu biểu tượng độc nhất khác nhau giúp bạn nhận ra tài khoản trong nháy mắt." }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "Đã bán gần nhất" }, + "layer1Fees": { + "message": "Phí Lớp 1" + }, "learnCancelSpeeedup": { "message": "Tìm hiểu cách $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "Muốn $1 về gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "Tìm hiểu thêm" + }, "learnMoreUpperCase": { "message": "Tìm hiểu thêm" }, @@ -1765,16 +2145,16 @@ "message": "Trước khi nhấn xác nhận:" }, "ledgerConnectionInstructionStepFour": { - "message": "Bật \"dữ liệu hợp đồng thông minh\" hoặc \"ký mù\" trên thiết bị Ledger của bạn" + "message": "Bật \"dữ liệu hợp đồng thông minh\" hoặc \"ký mù\" trên thiết bị Ledger của bạn." }, "ledgerConnectionInstructionStepOne": { - "message": "Bật Sử Dụng Ledger Live trong Cài Đặt > Nâng Cao" + "message": "Bật Sử Dụng Ledger Live trong Cài Đặt > Nâng Cao." }, "ledgerConnectionInstructionStepThree": { - "message": "Cắm thiết bị Ledger và chọn ứng dụng Ethereum" + "message": "Nhớ cắm thiết bị Ledger và chọn ứng dụng Ethereum." }, "ledgerConnectionInstructionStepTwo": { - "message": "Mở và mở khóa Ứng Dụng Ledger Live" + "message": "Mở và mở khóa Ứng Dụng Ledger Live." }, "ledgerConnectionPreferenceDescription": { "message": "Tùy chỉnh cách thức kết nối Ledger với MetaMask. Nên dùng $1, nhưng cũng có sẵn các tùy chọn khác. Đọc thêm tại đây: $2", @@ -1815,6 +2195,9 @@ "lineaGoerli": { "message": "Mạng thử nghiệm Linea Goerli" }, + "lineaMainnet": { + "message": "Mạng chính thức của Linea" + }, "link": { "message": "Liên kết" }, @@ -1839,6 +2222,12 @@ "lock": { "message": "Khóa" }, + "lockMetaMask": { + "message": "Khóa MetaMask" + }, + "lockTimeInvalid": { + "message": "Thời gian khóa phải là một số từ 0 đến 10080" + }, "logo": { "message": "Logo $1", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "Nút trạng thái kết nối sẽ hiển thị nếu trang web mà bạn đang truy cập được kết nối với tài khoản bạn đang chọn." }, + "metamaskInstitutionalVersion": { + "message": "Phiên bản MetaMask Institutional" + }, "metamaskSwapsOfflineDescription": { "message": "Tính năng Hoán đổi trên MetaMask đang được bảo trì. Vui lòng kiểm tra lại sau." }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "Chỉ số" }, + "mismatchAccount": { + "message": "Tài khoản bạn đã chọn ($1) khác với tài khoản sử dụng để ký ($2)" + }, "mismatchedChainLinkText": { "message": "xác minh thông tin về mạng", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "Ký hiệu đơn vị tiền tệ đã gửi không khớp với những gì chúng tôi mong đợi cho ID chuỗi này." }, + "mismatchedRpcChainId": { + "message": "Mã chuỗi do mạng tùy chỉnh trả về không khớp với mã chuỗi đã gửi." + }, "mismatchedRpcUrl": { "message": "Theo hồ sơ của chúng tôi, giá trị RPC URL đã gửi không khớp với một nhà cung cấp đã biết cho ID chuỗi này." }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "Yêu cầu tại đây" }, + "mmiAddToken": { + "message": "Trang tại $1 muốn ủy quyền token lưu ký sau trong MetaMask Institutional" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional được thiết kế và xây dựng trên khắp thế giới." + }, + "more": { + "message": "thêm" + }, "moreComingSoon": { - "message": "Sắp có thêm..." + "message": "Sắp có thêm nhiều nhà cung cấp khác" + }, + "multipleSnapConnectionWarning": { + "message": "$1 muốn kết nối với $2 snap. Chỉ tiến hành nếu bạn tin tưởng trang web này.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "Phải chọn ít nhất 1 token." @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "Mạng đang bận. Giá gas cao và ước tính kém chính xác hơn." }, + "networkMenu": { + "message": "Trình đơn mạng" + }, + "networkMenuHeading": { + "message": "Chọn mạng" + }, "networkName": { "message": "Tên mạng" }, @@ -2033,6 +2450,10 @@ "message": "Phí gas tương đối $1 so với 72 giờ qua.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "Chúng tôi không thể kết nối với $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "URL mạng" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "NFT" }, + "nftsPreviouslyOwned": { + "message": "Sở hữu trước đây" + }, "nickname": { "message": "Tên riêng" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "Không có sẵn tỷ lệ quy đổi nào" }, + "noNFTs": { + "message": "Chưa có NFT" + }, + "noNetworksFound": { + "message": "Không tìm thấy mạng nào cho truy vấn tìm kiếm" + }, "noSnaps": { - "message": "Chưa cài đặt Snap nào" + "message": "Bạn chưa cài đặt bất kỳ Snap nào." }, "noThanksVariant2": { "message": "Không, cảm ơn." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "Tài khoản này có chính xác không? Tài khoản này khác với tài khoản bạn đang chọn trong ví của mình" }, + "notEnoughBalance": { + "message": "Không đủ số dư" + }, "notEnoughGas": { "message": "Không đủ gas" }, + "note": { + "message": "Ghi chú" + }, + "notePlaceholder": { + "message": "Người phê duyệt sẽ nhìn thấy ghi chú này khi phê duyệt giao dịch tại lưu ký." + }, + "notificationTransactionFailedMessage": { + "message": "Giao dịch $1 không thành công! $2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "Giao dịch không thành công! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "Giao dịch không thành công", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "Giao dịch $1 đã được xác nhận!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "Đã xác nhận giao dịch", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "Xem trên $1", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "Thông báo" + }, "notifications10ActionText": { "message": "Xem trong phần Cài đặt", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "Hợp nhất Ethereum đã được triển khai!" }, + "notifications18ActionText": { + "message": "Bật cảnh báo bảo mật" + }, + "notifications18DescriptionOne": { + "message": "Nhận cảnh báo từ bên thứ ba khi bạn có thể đã nhận được một yêu cầu độc hại.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "Nhớ luôn tự thẩm định trước khi phê duyệt bất kỳ yêu cầu nào.", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea là nhà cung cấp đầu tiên cho tính năng này. Sắp có thêm nhiều nhà cung cấp khác!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "Đảm bảo an toàn với cảnh báo bảo mật" + }, + "notifications19ActionText": { + "message": "Bật tự động phát hiện NFT" + }, + "notifications19DescriptionOne": { + "message": "Hai cách để bạn có thể bắt đầu:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "Hiện chúng tôi chỉ hỗ trợ ERC-721.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "Thêm NFT của bạn theo cách thủ công hoặc bật tính năng tự động phát hiện NFT trong phần Cài đặt > Thử nghiệm.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "Xem NFT của bạn theo cách mới mẻ nhất" + }, "notifications1Description": { "message": "Giờ đây, người dùng MetaMask trên điện thoại di động có thể hoán đổi token trong ví di động của họ. Quét mã QR để tải ứng dụng di động và bắt đầu hoán đổi.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "Tính năng hoán đổi trên điện thoại di động đã sẵn sàng!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "Tìm hiểu thêm", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "Nếu đang sử dụng phiên bản Firefox mới nhất, bạn có thể gặp sự cố liên quan đến việc Firefox không còn hỗ trợ U2F.", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Người dùng Ledger và Firefox gặp sự cố kết nối", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "Thử ngay" + }, + "notifications21Description": { + "message": "Chúng tôi đã cập nhật Hoán đổi trong tiện ích MetaMask để sử dụng dễ dàng và nhanh hơn.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Giới thiệu Hoán đổi mới và được làm mới!" + }, + "notifications22ActionText": { + "message": "Đã hiểu" + }, + "notifications22Description": { + "message": "💡 Chỉ cần nhấn vào trình đơn chung hoặc trình đơn tài khoản để tìm chúng!" + }, + "notifications22Title": { + "message": "Muốn tìm thông tin tài khoản của bạn hoặc URL của trình khám phá khối?" + }, + "notifications23ActionText": { + "message": "Bật cảnh báo bảo mật" + }, + "notifications23DescriptionOne": { + "message": "Tránh xa các hành vi gian lận đã biết trong khi vẫn bảo vệ quyền riêng tư của bạn với các cảnh báo bảo mật do Blockaid cung cấp." + }, + "notifications23DescriptionThree": { + "message": "Nếu bạn đã bật cảnh báo bảo mật từ OpenSea, chúng tôi đã chuyển bạn sang tính năng này." + }, + "notifications23DescriptionTwo": { + "message": "Luôn tự thẩm định trước khi phê duyệt các yêu cầu." + }, + "notifications23Title": { + "message": "Đảm bảo an toàn với cảnh báo bảo mật" + }, "notifications3ActionText": { "message": "Đọc thêm", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2486,18 +3034,21 @@ "message": "Chỉ kết nối với các trang web mà bạn tin tưởng." }, "openFullScreenForLedgerWebHid": { - "message": "Mở MetaMask ở chế độ toàn màn hình để kết nối thiết bị Ledger của bạn qua WebHID.", + "message": "Bật toàn màn hình để kết nối với thiết bị Ledger của bạn.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "Mở trên trình khám phá khối" }, "openSea": { - "message": "OpenSea (Beta)" + "message": "OpenSea + Blockaid (Beta)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "Thao tác thất bại" + }, "optional": { "message": "Không bắt buộc" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "Mật khẩu không khớp" }, + "pasteJWTToken": { + "message": "Dán hoặc thả token của bạn tại đây:" + }, "pastePrivateKey": { "message": "Dán chuỗi khóa riêng tư của bạn vào đây:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "Truy cập Internet.", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "Cho phép Snap truy cập Internet. Điều này có thể được sử dụng để gửi và nhận dữ liệu với máy chủ của bên thứ ba.", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "Kết nối với Snap $1.", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "Cho phép trang web hoặc Snap tương tác với $1.", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Lên lịch và thực hiện các hành động theo định kỳ.", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "Cho phép Snap thực hiện các hành động định kỳ vào thời gian, ngày hoặc khoảng thời gian cố định. Điều này có thể được sử dụng để kích hoạt các tương tác hoặc thông báo nhạy cảm với thời gian.", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "Hiển thị cửa sổ hộp thoại trong MetaMask.", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "Cho phép Snap hiển thị cửa sổ bật lên MetaMask cùng với văn bản tùy chỉnh, trường nhập và nút để chấp nhận hoặc từ chối một hành động.\nCó thể được sử dụng để tạo cảnh báo, xác nhận và quy trình đồng ý tham gia cho một Snap.", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "Xem địa chỉ, số dư tài khoản, hoạt động và bắt đầu giao dịch", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "Truy cập nhà cung cấp Ethereum.", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "Cho phép Snap giao tiếp trực tiếp với MetaMask để đọc dữ liệu từ chuỗi khối và đề xuất các tin nhắn và giao dịch.", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "Lấy các khóa tùy ý duy nhất cho snap này.", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "Cho phép Snap lấy các khóa tùy ý duy nhất cho Snap này mà không để lộ. Các khóa này tách biệt với tài khoản MetaMask của bạn và không liên quan đến khóa riêng tư hoặc Cụm từ khôi phục bí mật của bạn. Các Snap khác không thể truy cập thông tin này.", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "Sử dụng hook vòng đời.", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "Cho phép snap sử dụng hook vòng đời để chạy mã vào những thời điểm cụ thể trong vòng đời của nó.", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "Chạy không giới hạn.", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "Ví dụ: cho phép Snap chạy vô thời hạn trong quá trình xử lý lượng lớn dữ liệu.", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "Thêm và kiểm soát các tài khoản Ethereum", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "Kiểm soát các tài khoản và tài sản của bạn ở $1 ($2).", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "Cho phép Snap lấy các cặp khóa BIP-32 dựa trên Cụm từ khôi phục bí mật của bạn mà không để lộ. Điều này sẽ cấp toàn quyền truy cập vào tất cả các tài khoản và tài sản trên $1.\nVới khả năng quản lý khóa, Snap có thể hỗ trợ nhiều giao thức chuỗi khối ngoài Ethereum (EVM).", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "Kiểm soát các tài khoản và tài sản \"$1\" của bạn.", + "message": "Kiểm soát các tài khoản và tài sản $1 của bạn.", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "Cho phép Snap lấy các cặp khóa BIP-44 dựa trên Cụm từ khôi phục bí mật của bạn mà không để lộ. Điều này sẽ cấp toàn quyền truy cập vào tất cả các tài khoản và tài sản trên $1.\nVới khả năng quản lý khóa, Snap có thể hỗ trợ nhiều giao thức chuỗi khối ngoài Ethereum (EVM).", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "Kiểm soát các tài khoản và tài sản $1 của bạn.", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "Lưu trữ và quản lý dữ liệu trong thiết bị.", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "Cho phép Snap lưu trữ, cập nhật và truy xuất dữ liệu một cách an toàn bằng mã hóa. Các Snap khác không thể truy cập thông tin này.", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "Hiển thị thông báo.", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "Cho phép Snap hiển thị thông báo trong MetaMask. Một văn bản thông báo ngắn có thể được kích hoạt bằng Snap đối với thông tin có thể thao tác hoặc nhạy cảm với thời gian.", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "Cho phép $1 giao tiếp trực tiếp với snap này.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "Cho phép $1 gửi tin nhắn đến Snap và nhận phản hồi từ Snap.", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "Tìm nạp và hiển thị thông tin chi tiết về giao dịch.", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "Cho phép Snap giải mã các giao dịch và hiển thị thông tin chi tiết trong giao diện người dùng MetaMask. Điều này có thể được sử dụng cho các giải pháp bảo mật và chống lừa đảo.", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "Xem nguồn gốc của các trang web đề xuất giao dịch", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "Cho phép Snap để xem nguồn gốc (URI) của các trang web đề xuất giao dịch. Điều này có thể được sử dụng cho các giải pháp bảo mật và chống lừa đảo.", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "Quyền không xác định: $1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "Xem khóa công khai của bạn cho $1 ($2).", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "Cho phép Snap xem khóa công khai (và địa chỉ) của bạn đối với $1. Điều này sẽ không cấp bất kỳ quyền kiểm soát tài khoản hoặc tài sản nào.", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "Xem khóa công khai của bạn cho $1.", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "Hỗ trợ dành cho WebAssembly.", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "Cho phép Snap truy cập vào các môi trường thực thi cấp thấp thông qua WebAssembly.", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "Quyền" }, + "permissionsTitle": { + "message": "Quyền" + }, + "permissionsTourDescription": { + "message": "Tìm tài khoản đã kết nối của bạn và quản lý quyền tại đây" + }, "personalAddressDetected": { "message": "Đã tìm thấy địa chỉ cá nhân. Nhập địa chỉ hợp đồng token." }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "Danh mục đầu tư" }, + "portfolioDashboard": { + "message": "Trang tổng quan Danh mục đầu tư" + }, "preferredLedgerConnectionType": { "message": "Dạng kết nối Ledger ưu tiên", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "Khóa riêng tư", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "Khóa riêng tư cho $1", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "Cảnh báo: Tuyệt đối không để lộ mã khóa này. Bất kỳ ai có mã khóa riêng tư của bạn cũng có thể đánh cắp tài sản được lưu giữ trong tài khoản của bạn." }, @@ -2738,6 +3385,9 @@ "queued": { "message": "Đã đưa vào hàng đợi" }, + "quoteRate": { + "message": "Tỷ giá báo giá" + }, "reAddAccounts": { "message": "thêm lại bất kỳ tài khoản nào khác" }, @@ -2751,7 +3401,7 @@ "message": "Nhận" }, "recipientAddressPlaceholder": { - "message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS" + "message": "Nhập địa chỉ công khai (0x) hoặc tên ENS" }, "recommendedGasLabel": { "message": "Được đề xuất" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "Tài khoản này sẽ được xóa khỏi ví của bạn. Hãy đảm bảo rằng bạn có Cụm mật khẩu khôi phục bí mật ban đầu hoặc khóa riêng tư cho tài khoản được nhập trước khi tiếp tục. Bạn có thể nhập hoặc tạo lại tài khoản từ trình đơn tài khoản thả xuống. " }, + "removeJWT": { + "message": "Xóa token lưu ký" + }, + "removeJWTDescription": { + "message": "Bạn có chắc chắn muốn xóa token này không? Tất cả các tài khoản được chỉ định cho token này cũng sẽ bị xóa khỏi tiện ích: " + }, "removeNFT": { "message": "Xóa NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "Bạn có thể khôi phục cài đặt người dùng chứa các tùy chọn và địa chỉ tài khoản từ tập tin JSON đã sao lưu trước đó." }, + "resultPageError": { + "message": "Lỗi" + }, + "resultPageErrorDefaultMessage": { + "message": "Thao tác thất bại." + }, + "resultPageSuccess": { + "message": "Thành công" + }, + "resultPageSuccessDefaultMessage": { + "message": "Đã hoàn thành thao tác thành công." + }, "retryTransaction": { "message": "Thử lại giao dịch" }, @@ -2939,16 +3607,27 @@ "message": "Thu hồi quyền truy cập và chuyển tất cả $1 của bạn?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "Thu hồi quyền truy cập và chuyển tất cả NFT của bạn từ $1?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "Hành động này sẽ thu hồi quyền cho phép bên thứ ba truy cập và chuyển tất cả $1 của bạn mà không cần thông báo thêm.", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "Hành động này sẽ thu hồi quyền cho phép bên thứ ba truy cập và chuyển tất cả NFT của bạn từ $1 mà không cần thông báo thêm.", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "Thu hồi quyền" + }, "revokeSpendingCap": { "message": "Thu hồi hạn mức chi tiêu cho $1 của bạn", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "Hợp đồng này sẽ không thể chi tiêu thêm bất kỳ token hiện tại hoặc tương lai nào của bạn." + "message": "Bên thứ ba này sẽ không thể chi tiêu thêm bất kỳ token hiện tại hoặc tương lai nào của bạn." }, "rpcUrl": { "message": "URL RPC mới" @@ -2986,9 +3665,28 @@ "security": { "message": "Bảo mật" }, + "securityAlert": { + "message": "Cảnh báo bảo mật từ $1 và $2" + }, + "securityAlerts": { + "message": "Cảnh báo bảo mật" + }, + "securityAlertsDescription1": { + "message": "Tính năng này sẽ cảnh báo cho bạn khi có hoạt động độc hại bằng cách xem xét cục bộ các giao dịch và yêu cầu chữ ký của bạn. Dữ liệu của bạn không được chia sẻ với các bên thứ ba cung cấp dịch vụ này. Luôn tự thẩm định trước khi phê duyệt bất kỳ yêu cầu nào. Không có gì đảm bảo rằng tính năng này sẽ phát hiện được tất cả các hoạt động độc hại." + }, + "securityAlertsDescription2": { + "message": "Nhớ luôn tự thẩm định trước khi phê duyệt bất kỳ yêu cầu nào. Không có gì đảm bảo tính năng này sẽ phát hiện được tất cả các hành vi độc hại." + }, "securityAndPrivacy": { "message": "Bảo mật và quyền riêng tư" }, + "securityProviderAdviceBy": { + "message": "Lời khuyên bảo mật bởi $1", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "Xem chi tiết" + }, "seedPhraseConfirm": { "message": "Xác nhận Cụm Mật Khẩu Khôi Phục Bí Mật" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "Viết ra Cụm Mật Khẩu Khôi Phục Bí Mật của bạn" }, + "select": { + "message": "Chọn" + }, "selectAccounts": { "message": "Chọn (các) tài khoản để sử dụng trên trang web này" }, + "selectAccountsForSnap": { + "message": "Chọn (các) tài khoản để sử dụng với snap này" + }, "selectAll": { "message": "Chọn tất cả" }, + "selectAllAccounts": { + "message": "Chọn tất cả tài khoản" + }, "selectAnAccount": { "message": "Chọn một tài khoản" }, "selectAnAccountAlreadyConnected": { "message": "Tài khoản này đã được kết nối với MetaMask" }, + "selectAnAccountHelp": { + "message": "Chọn tài khoản lưu ký để sử dụng trong MetaMask Institutional." + }, "selectHdPath": { "message": "Chọn đường dẫn HD" }, + "selectJWT": { + "message": "Chọn token" + }, "selectNFTPrivacyPreference": { "message": "Bật phát hiện NFT trong phần Cài Đặt" }, @@ -3113,6 +3826,9 @@ "message": "Phê duyệt $1 không có giới hạn chi tiêu", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "Thêm tài khoản snap" + }, "settings": { "message": "Cài đặt" }, @@ -3141,9 +3857,21 @@ "message": "Chọn tùy chọn này nếu bạn muốn dùng Etherscan để hiển thị các giao dịch đến trong danh sách giao dịch", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "Điều này phụ thuộc vào từng mạng sẽ có quyền truy cập vào địa chỉ Ethereum và địa chỉ IP của bạn." + }, + "showMore": { + "message": "Hiển thị thêm" + }, + "showNft": { + "message": "Hiển thị NFT" + }, "showPermissions": { "message": "Hiển thị quyền" }, + "showPrivateKey": { + "message": "Hiển thị khóa riêng tư" + }, "showPrivateKeys": { "message": "Hiện khóa riêng tư" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "Tôi hiểu rằng nếu chưa sao lưu Cụm Mật Khẩu Khôi Phục Bí Mật của mình, tôi có thể bị mất tài khoản và toàn bộ tài sản bên trong." }, + "smartContracts": { + "message": "Hợp đồng thông minh" + }, + "smartSwap": { + "message": "Hoán đổi thông minh" + }, + "smartSwapsAreHere": { + "message": "Hoán đổi thông minh đã ra mắt!" + }, + "smartSwapsDescription": { + "message": "Tính năng Hoán đổi của MetaMask nay đã thông minh hơn rất nhiều! Kích hoạt Hoán đổi thông minh sẽ cho phép MetaMask tối ưu quy trình Hoán đổi để giúp bạn:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Không có đủ tiền để thực hiện hoán đổi thông minh." + }, + "smartSwapsErrorUnavailable": { + "message": "Hoán đổi thông minh tạm thời không khả dụng." + }, + "smartSwapsSubDescription": { + "message": "* Hoán đổi thông minh sẽ cố gắng gửi giao dịch của bạn nhiều lần một cách riêng tư. Nếu tất cả các lần thử đều không thành công, giao dịch sẽ được phát công khai để đảm bảo Hoán đổi của bạn được thực hiện thành công." + }, + "snapConfigure": { + "message": "Định cấu hình" + }, + "snapConnectionWarning": { + "message": "$1 muốn kết nối với $2. Chỉ tiếp tục nếu bạn tin tưởng trang web này.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "Nội dung này đến từ $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "Chọn cách bảo mật tài khoản mới của bạn bằng MetaMask Snap." + }, + "snapCreateAccountTitle": { + "message": "Tạo tài khoản $1", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "Bởi MetaMask" + }, + "snapDetailAudits": { + "message": "Kiểm tra" + }, + "snapDetailDeveloper": { + "message": "Nhà phát triển" + }, + "snapDetailLastUpdated": { + "message": "Đã cập nhật" + }, + "snapDetailManageSnap": { + "message": "Quản lý snap" + }, + "snapDetailTags": { + "message": "Thẻ" + }, + "snapDetailVersion": { + "message": "Phiên bản" + }, + "snapDetailWebsite": { + "message": "Trang web" + }, + "snapDetailsCreateASnapAccount": { + "message": "Tạo tài khoản Snap" + }, + "snapDetailsInstalled": { + "message": "Đã cài đặt" + }, "snapError": { "message": "Lỗi Snap: '$1'. Mã lỗi: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "Cài đặt Snap" }, + "snapInstallRequest": { + "message": "Cài đặt $1 để được cấp các quyền sau. Chỉ tiếp tục nếu bạn tin tưởng $1.", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "Cài đặt hoàn tất" + }, "snapInstallWarningCheck": { "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào ô.", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào tất cả các ô.", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "Hãy tiến hành thận trọng" + }, "snapInstallWarningKeyAccess": { "message": "Bạn đang cấp quyền truy cập khóa $2 cho Snap \"$1\". Hành động này không thể hủy bỏ và sẽ cấp quyền kiểm soát tài khoản và tài sản $2 của bạn cho \"$1\". Đảm bảo bạn tin tưởng \"$1\" trước khi tiếp tục.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "Cấp quyền truy cập $1 cho khóa công khai $2", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "Không thể cài đặt $1.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Cài đặt thất bại", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "Đã kiểm tra" + }, + "snapResultError": { + "message": "Lỗi" + }, + "snapResultSuccess": { + "message": "Thành công" + }, + "snapResultSuccessDescription": { + "message": "$1 đã sẵn sàng để sử dụng" + }, "snapUpdate": { "message": "Cập nhật Snap" }, + "snapUpdateAvailable": { + "message": "Có cập nhật mới" + }, + "snapUpdateErrorDescription": { + "message": "Không thể cập nhật $1.", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "Cập nhật thất bại", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 muốn cập nhật $2 lên $3 để được cấp các quyền sau. Chỉ tiếp tục nếu bạn tin tưởng $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Cập nhật hoàn tất" + }, "snaps": { "message": "Snap" }, "snapsInsightLoading": { "message": "Đang tải thông tin chi tiết về giao dịch..." }, + "snapsInvalidUIError": { + "message": "Giao diện người dùng được chỉ định bởi snap không hợp lệ." + }, "snapsNoInsight": { "message": "Snap không trả về bất kỳ thông tin chi tiết nào" }, + "snapsPrivacyWarningFirstMessage": { + "message": "Bạn thừa nhận rằng snap mà bạn sắp cài đặt là Dịch vụ bên thứ ba như được định nghĩa trong $1 của Consensys. Việc bạn sử dụng Dịch vụ bên thứ ba sẽ phải tuân theo các điều khoản và điều kiện riêng do nhà cung cấp Dịch vụ bên thứ ba quy định. Bạn tự chịu rủi ro khi truy cập, tin tưởng hoặc sử dụng Dịch vụ bên thứ ba. Consensys từ chối mọi trách nhiệm và trách nhiệm pháp lý đối với bất kỳ tổn thất nào khi bạn sử dụng Dịch vụ bên thứ ba.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Mọi thông tin mà bạn chia sẻ với Dịch vụ bên thứ ba sẽ được Dịch vụ bên thứ ba đó thu thập trực tiếp theo chính sách quyền riêng tư của họ. Vui lòng tham khảo chính sách quyền riêng tư của họ để biết thêm thông tin.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys không có quyền truy cập vào thông tin mà bạn chia sẻ với các bên thứ ba này.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Quản lý Snap" }, + "snapsTermsOfUse": { + "message": "Điều khoản sử dụng" + }, "snapsToggle": { "message": "Snap chỉ hoạt động khi đã bật" }, "snapsUIError": { - "message": "Giao diện người dùng được chỉ định bởi snap không hợp lệ.", + "message": "Liên hệ với những người tạo ra $1 để được hỗ trợ thêm.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "Chỉ nhập số mà bạn cảm thấy thoải mái khi truy cập $1 ở hiện tại hoặc trong tương lai. Bạn luôn có thể tăng giới hạn token sau này.", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "Yêu cầu hạn mức chi tiêu cho $1 của bạn" + }, "srpInputNumberOfWords": { "message": "Tôi có một cụm từ gồm $1 từ", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,6 +4169,9 @@ "srpSecurityQuizGetStarted": { "message": "Bắt đầu" }, + "srpSecurityQuizImgAlt": { + "message": "Một con mắt có lỗ khóa ở giữa và ba trường mật khẩu nổi" + }, "srpSecurityQuizIntroduction": { "message": "Để hiển thị Cụm từ khôi phục bí mật, bạn cần trả lời đúng hai câu hỏi" }, @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "ổn định" }, + "stake": { + "message": "Nắm giữ và khóa" + }, "stateLogError": { "message": "Lỗi khi truy xuất nhật ký trạng thái." }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "Chưa kết nối" }, + "statusNotConnectedAccount": { + "message": "Không có tài khoản nào được kết nối" + }, "step1LatticeWallet": { "message": "Kết nối với Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "Đây là số tiền tối thiểu mà bạn sẽ nhận được. Bạn sẽ nhận được nhiều hơn tùy thuộc vào mức trượt giá." }, + "swapAnyway": { + "message": "Vẫn hoán đổi" + }, "swapApproval": { "message": "Phê duyệt $1 cho các giao dịch hoán đổi", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "Bạn cần $1 $2 nữa để hoàn tất giao dịch hoán đổi này", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Bạn vẫn còn ở đó chứ?" + }, + "swapAreYouStillThereDescription": { + "message": "Chúng tôi sẵn sàng cho bạn xem báo giá mới nhất khi bạn muốn tiếp tục" + }, "swapBuildQuotePlaceHolderText": { "message": "Không có token nào khớp với $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "Xác nhận ví cứng của bạn" }, + "swapContinueSwapping": { + "message": "Tiếp tục hoán đổi" + }, "swapContractDataDisabledErrorDescription": { "message": "Trong ứng dụng Ethereum trên Ledger của bạn, hãy chuyển đến phần \"Cài đặt\" và cho phép dữ liệu hợp đồng. Sau đó, thử hoán đổi lại." }, @@ -3544,6 +4435,9 @@ "swapEditLimit": { "message": "Chỉnh sửa giới hạn" }, + "swapEditTransactionSettings": { + "message": "Chỉnh sửa cài đặt giao dịch" + }, "swapEnableDescription": { "message": "Thao tác này là bắt buộc và cấp cho MetaMask quyền hoán đổi $1 của bạn.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -3552,6 +4446,9 @@ "message": "Điều này sẽ $1 để hoán đổi", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Nhập số tiền" + }, "swapEstimatedNetworkFees": { "message": "Phí mạng ước tính" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "Hoán đổi không thành công" }, + "swapFetchingQuote": { + "message": "Tìm nạp báo giá" + }, "swapFetchingQuoteNofN": { "message": "Tìm nạp báo giá $1/$2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "Bao gồm $1% phí của MetaMask.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "Bao gồm $1% phí của MetaMask – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "Tìm hiểu thêm về Hoán đổi" + }, "swapLowSlippageError": { "message": "Giao dịch có thể không thành công, mức trượt giá tối đa quá thấp." }, @@ -3626,6 +4533,10 @@ "message": "Báo giá mới sẽ có sau $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "Không có token nào phù hợp với $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "$1 của bạn sẽ được thêm vào tài khoản sau khi xử lý xong giao dịch.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "Chi tiết báo giá" }, + "swapQuoteNofM": { + "message": "$1/$2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Nguồn báo giá" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Hết thời gian chờ báo giá" }, + "swapQuotesNotAvailableDescription": { + "message": "Giảm quy mô giao dịch của bạn hoặc sử dụng một token khác." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Hãy thử điều chỉnh số tiền hoặc tùy chọn cài đặt mức trượt giá và thử lại." }, @@ -3693,21 +4611,57 @@ "message": "Chọn một báo giá" }, "swapSelectAToken": { - "message": "Chọn một token" + "message": "Chọn token" }, "swapSelectQuotePopoverDescription": { "message": "Dưới đây là tất cả các báo giá thu thập từ nhiều nguồn thanh khoản." }, + "swapSelectToken": { + "message": "Chọn token" + }, + "swapShowLatestQuotes": { + "message": "Hiển thị báo giá mới nhất" + }, "swapSlippageNegative": { "message": "Mức trượt giá phải lớn hơn hoặc bằng 0" }, + "swapSlippageNegativeDescription": { + "message": "Mức trượt giá phải lớn hơn hoặc bằng 0" + }, + "swapSlippageNegativeTitle": { + "message": "Tăng mức trượt giá để tiếp tục" + }, + "swapSlippageOverLimitDescription": { + "message": "Giới hạn trượt giá phải từ 15% trở xuống. Mức trượt giá cao hơn sẽ dẫn đến tỷ giá không sinh lời." + }, + "swapSlippageOverLimitTitle": { + "message": "Giảm mức trượt giá để tiếp tục" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Mức trượt giá tối đa quá thấp có thể khiến giao dịch của bạn không thành công." + }, + "swapSlippageTooLowTitle": { + "message": "Tăng mức trượt giá để tránh giao dịch thất bại" + }, "swapSlippageTooltip": { "message": "Khi giá giữa thời điểm đặt lệnh và thời điểm xác nhận lệnh thay đổi, hiện tượng này được gọi là \"trượt giá\". Giao dịch hoán đổi của bạn sẽ tự động hủy nếu mức trượt giá vượt quá \"mức trượt giá cho phép\" đã đặt." }, + "swapSlippageVeryHighDescription": { + "message": "Mức trượt giá đã nhập được xem là quá cao và có thể dẫn đến tỷ giá không sinh lời" + }, + "swapSlippageVeryHighTitle": { + "message": "Mức trượt giá quá cao" + }, + "swapSlippageZeroDescription": { + "message": "Có ít nhà cung cấp báo giá có mức trượt giá bằng 0 hơn sẽ dẫn đến báo giá kém cạnh tranh hơn." + }, + "swapSlippageZeroTitle": { + "message": "Tìm nguồn nhà cung cấp có mức trượt giá bằng 0" + }, "swapSource": { "message": "Nguồn thanh khoản" }, @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "để xác nhận ví cứng của bạn" }, + "swapTokenAddedManuallyDescription": { + "message": "Xác minh token này trên $1 và đảm bảo đây là token mà bạn muốn giao dịch.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Đã thêm token theo cách thủ công" + }, "swapTokenAvailable": { "message": "Đã thêm $1 vào tài khoản của bạn.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "Đã xác minh trên $1 nguồn.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 chỉ được xác minh trên 1 nguồn. Hãy xem xét xác minh nó trên $2 trước khi tiếp tục.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Token có dấu hiệu giả" + }, "swapTooManyDecimalsError": { "message": "$1 cho phép tối đa $2 số thập phân", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "Không đủ $1 để hoàn thành giao dịch này", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Không đủ $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "Xem hoạt động" }, + "switch": { + "message": "Chuyển" + }, "switchEthereumChainConfirmationDescription": { "message": "Thao tác này sẽ chuyển mạng được chọn trong MetaMask sang một mạng đã thêm trước đó:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "Bạn đã chuyển sang" }, + "switcherTitle": { + "message": "Trình chuyển mạng" + }, + "switcherTourDescription": { + "message": "Nhấn vào biểu tượng để chuyển mạng hoặc thêm mạng mới" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "Khi bạn chuyển mạng, mọi xác nhận đang chờ xử lý sẽ bị hủy" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "Điều khoản dịch vụ" }, + "termsOfUse": { + "message": "điều khoản sử dụng" + }, + "termsOfUseAgreeText": { + "message": " Tôi đồng ý với Điều khoản sử dụng được áp dụng cho việc tôi sử dụng MetaMask và tất cả các tính năng của MetaMask" + }, + "termsOfUseFooterText": { + "message": "Vui lòng cuộn để đọc tất cả các phần" + }, + "termsOfUseTitle": { + "message": "Điều khoản sử dụng của chúng tôi đã được cập nhật" + }, "testNetworks": { "message": "Mạng thử nghiệm" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "Những điều cần lưu ý:" }, + "thirdPartySoftware": { + "message": "Thông báo phần mềm của bên thứ ba", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "bộ sưu tập này" + }, + "thisServiceIsExperimental": { + "message": "Đây là dịch vụ thử nghiệm. Bằng cách bật tính năng này, bạn đồng ý với $1 của OpenSea.", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "Thời gian" }, @@ -3863,11 +4867,44 @@ "message": "Đến: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "Bạn có nguy cơ bị tấn công lừa đảo. Tự bảo vệ mình bằng cách tắt eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Bật tính năng này để cho phép các dapp yêu cầu chữ ký của bạn bằng các yêu cầu eth_sign. eth_sign là một phương thức ký mở rộng cho phép bạn ký một hàm băm tùy ý, điều này khiến nó trở thành một rủi ro lừa đảo nguy hiểm. Chỉ ký các yêu cầu eth_sign nếu bạn có thể đọc được những gì bạn đang ký và tin tưởng vào nguồn gốc của yêu cầu." + "message": "Nếu bật chế độ cài đặt này, bạn có thể nhận các yêu cầu chữ ký mà bạn không đọc được. Khi ký vào một tin nhắn mà bạn không hiểu, bạn có thể đang đồng ý cho đi tiền và NFT của mình." }, "toggleEthSignField": { - "message": "Bật/tắt yêu cầu eth_sign" + "message": "Yêu cầu eth_sign" + }, + "toggleEthSignModalBannerBoldText": { + "message": " bạn có thể bị lừa đảo" + }, + "toggleEthSignModalBannerText": { + "message": "Nếu bạn được yêu cầu bật cài đặt này," + }, + "toggleEthSignModalCheckBox": { + "message": "Tôi hiểu rằng tôi có thể mất toàn bộ tiền và NFT của mình nếu tôi kích hoạt yêu cầu eth_sign. " + }, + "toggleEthSignModalDescription": { + "message": "Việc cho phép yêu cầu eth_sign có thể khiến bạn dễ bị tấn công lừa đảo. Nhớ luôn xem lại URL và cẩn thận khi ký các tin nhắn có chứa mã." + }, + "toggleEthSignModalFormError": { + "message": "Văn bản không đúng" + }, + "toggleEthSignModalFormLabel": { + "message": "Nhập “Tôi chỉ ký những gì tôi hiểu” để tiếp tục" + }, + "toggleEthSignModalFormValidation": { + "message": "Tôi chỉ ký những gì tôi hiểu" + }, + "toggleEthSignModalTitle": { + "message": "Bạn tự chịu rủi ro khi sử dụng" + }, + "toggleEthSignOff": { + "message": "TẮT (Khuyến khích)" + }, + "toggleEthSignOn": { + "message": "BẬT (Không khuyến khích)" }, "token": { "message": "Token" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "Ký hiệu token" }, + "tokens": { + "message": "Token" + }, "tokensFoundTitle": { "message": "Đã tìm thấy $1 token mới", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "Tôi đã hiểu" }, + "tooltipSatusConnected": { + "message": "đã kết nối" + }, + "tooltipSatusNotConnected": { + "message": "chưa kết nối" + }, "total": { "message": "Tổng" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "Giao dịch đã gặp lỗi." }, + "transactionFailed": { + "message": "Giao dịch không thành công" + }, "transactionFee": { "message": "Phí giao dịch" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "Tổng phí gas" }, + "transactionNote": { + "message": "Ghi chú giao dịch" + }, "transactionResubmitted": { "message": "Đã gửi lại giao dịch với mức phí gas ước tính tăng lên $1 lúc $2" }, "transactionSecurityCheck": { - "message": "Kích hoạt nhà cung cấp bảo mật giao dịch" + "message": "Bật cảnh báo bảo mật" }, "transactionSecurityCheckDescription": { "message": "Chúng tôi sử dụng API của bên thứ ba để phát hiện và hiển thị những rủi ro liên quan đến các yêu cầu chữ ký và giao dịch chưa ký trước khi bạn ký. Những dịch vụ này sẽ có quyền truy cập vào các yêu cầu chữ ký và giao dịch chưa ký, địa chỉ tài khoản và ngôn ngữ yêu thích của bạn." }, + "transactionSettings": { + "message": "Cài đặt giao dịch" + }, "transactionSubmitted": { "message": "Đã gửi giao dịch với mức phí gas ước tính $1 lúc $2." }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "Chuyển từ" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "Chúng tôi đang gặp sự cố khi kết nối với Ledger của bạn. $1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "Xem lại hướng dẫn kết nối ví cứng của chúng tôi và thử lại.", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "Nếu đang sử dụng phiên bản Firefox mới nhất, bạn có thể gặp sự cố liên quan đến việc Firefox không còn hỗ trợ U2F. Tìm hiểu cách khắc phục sự cố này $1.", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "tại đây", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "Chúng tôi đã gặp phải sự cố khi kết nối với $1 của bạn, hãy thử xem lại $2 và thử lại.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "mũi tên lên" }, + "update": { + "message": "Cập nhật" + }, "updatedWithDate": { "message": "Đã cập nhật $1" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "Mạng $1 hiện đang sử dụng URL này." }, + "use4ByteResolution": { + "message": "Giải mã hợp đồng thông minh" + }, + "use4ByteResolutionDescription": { + "message": "Để cải thiện trải nghiệm người dùng, chúng tôi sẽ tùy chỉnh thẻ hoạt động bằng các thông báo dựa trên các hợp đồng thông minh mà bạn tương tác. MetaMask sử dụng một dịch vụ có tên là 4byte.directory để giải mã dữ liệu và cho bạn thấy một phiên bản hợp đồng thông minh dễ đọc hơn. Điều này giúp giảm nguy cơ phê duyệt các hành động hợp đồng thông minh độc hại, nhưng có thể khiến địa chỉ IP của bạn bị chia sẻ." + }, "useMultiAccountBalanceChecker": { "message": "Xử lý hàng loạt yêu cầu số dư tài khoản" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "Nhận thông tin cập nhật số dư nhanh hơn bằng cách gộp các yêu cầu số dư tài khoản. Điều này cho phép chúng tôi tìm nạp các số dư tài khoản của bạn cùng với nhau, qua đó bạn sẽ nhận được thông tin cập nhật nhanh hơn nhằm cải thiện trải nghiệm. Khi tắt tính năng này, các bên thứ ba có ít khả năng sẽ liên kết các tài khoản của bạn với nhau hơn." + }, "useNftDetection": { "message": "Tự động phát hiện NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "Hiển thị cảnh báo đối với các tên miền lừa đảo nhắm đến người dùng Ethereum" }, + "useSiteSuggestion": { + "message": "Sử dụng gợi ý của trang web" + }, "useTokenDetectionPrivacyDesc": { "message": "Tự động hiển thị các token được gửi vào tài khoản của bạn có liên quan đến hoạt động trao đổi thông tin với các máy chủ bên thứ ba để tìm nạp hình ảnh của token. Các máy chủ đó sẽ có quyền truy cập vào địa chỉ IP của bạn." }, @@ -4170,7 +5256,7 @@ "message": "Tên người dùng" }, "verifyContractDetails": { - "message": "Xác minh chi tiết hợp đồng" + "message": "Xác minh thông tin bên thứ ba" }, "verifyThisTokenDecimalOn": { "message": "Không tìm thấy số thập phân của token trên $1", @@ -4184,12 +5270,18 @@ "message": "Hãy xác minh token này trên $1 và đảm bảo đây là token bạn muốn giao dịch.", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "Phiên bản" + }, "view": { "message": "Xem" }, "viewAllDetails": { "message": "Xem toàn bộ chi tiết" }, + "viewAllQuotes": { + "message": "xem tất cả báo giá" + }, "viewContact": { "message": "Xem địa chỉ liên hệ" }, @@ -4213,9 +5305,18 @@ "message": "Xem $1 trên Etherscan", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "Xem trên trình khám phá" + }, "viewOnOpensea": { "message": "Xem trên Opensea" }, + "viewPortfolioDashboard": { + "message": "Xem Trang tổng quan Danh mục đầu tư" + }, + "viewinCustodianApp": { + "message": "Xem trong ứng dụng lưu ký" + }, "viewinExplorer": { "message": "Xem $1 trong trình khám phá", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "Bạn muốn thêm mạng này?" }, + "wantsToAddThisAsset": { + "message": "$1 muốn thêm tài sản này vào ví của bạn", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "Cảnh báo" }, "warningTooltipText": { - "message": "$1 Hợp đồng có thể chi tiêu toàn bộ số dư token của bạn mà không cần thông báo hoặc chấp thuận. Hãy tự bảo vệ chính mình bằng cách chỉnh hạn mức chi tiêu thấp hơn.", + "message": "$1 Bên thứ ba có thể chi tiêu toàn bộ số dư token của bạn mà không cần thông báo hoặc đồng ý thêm. Hãy tự bảo vệ chính mình bằng cách điều chỉnh hạn mức chi tiêu thấp hơn.", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "Bạn đang ký" }, + "yourAccounts": { + "message": "Tài khoản của bạn" + }, "yourFundsMayBeAtRisk": { "message": "Tiền của bạn có thể gặp rủi ro" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 2aade1fdf..37411fc22 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -30,7 +30,7 @@ "message": "错误" }, "QRHardwareUnknownWalletQRCode": { - "message": "非法二维码,请扫描硬件钱包的同步二维码。" + "message": "二维码无效。请扫描硬件钱包的同步二维码。" }, "QRHardwareWalletImporterTitle": { "message": "扫描二维码" @@ -106,6 +106,9 @@ "about": { "message": "关于" }, + "accept": { + "message": "接受" + }, "acceptTermsOfUse": { "message": "我已阅读并同意 $1", "description": "$1 is the `terms` message" @@ -171,8 +174,11 @@ "addANickname": { "message": "添加昵称" }, + "addAccount": { + "message": "添加账户" + }, "addAcquiredTokens": { - "message": "添加您使用 MetaMask 获得的代币" + "message": "添加您通过MetaMask获得的代币" }, "addAlias": { "message": "添加别名" @@ -201,7 +207,7 @@ "description": "Link text for the 'addEthereumChainConfirmationRisksLearnMore' translation key" }, "addEthereumChainConfirmationTitle": { - "message": "允许此网站增加一个网络?" + "message": "允许此网站添加一个网络到MetaMask上?" }, "addEthereumChainWarningModalHeader": { "message": "仅当您确定可以信任此 RPC 提供商时才能添加它。$1", @@ -231,6 +237,12 @@ "addFromAListOfPopularNetworks": { "message": "从热门网络列表中选择网络来添加,或手动添加网络。仅可与您信任的实体互动。" }, + "addHardwareWallet": { + "message": "添加硬件钱包" + }, + "addIPFSGateway": { + "message": "添加您首选的IPFS网关" + }, "addMemo": { "message": "添加备忘录" }, @@ -244,6 +256,21 @@ "message": "此网络连接依赖于第三方。此连接可能不太可靠,或使第三方可进行活动跟踪。$1", "description": "$1 is Learn more link" }, + "addNewToken": { + "message": "添加新代币" + }, + "addNft": { + "message": "添加 NFT" + }, + "addNfts": { + "message": "添加 NFT" + }, + "addSnapAccountModalDescription": { + "message": "了解使用MetaMask Snap来保护账户安全的选项" + }, + "addSuggestedNFTs": { + "message": "添加推荐的 NFT" + }, "addSuggestedTokens": { "message": "添加推荐代币" }, @@ -254,6 +281,9 @@ "message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addingCustomNetwork": { + "message": "正在添加网络" + }, "address": { "message": "地址" }, @@ -281,6 +311,10 @@ "advancedPriorityFeeToolTip": { "message": "优先费(又称“矿工费”)直接向矿工支付,激励他们优先处理您的交易。" }, + "agreeTermsOfUse": { + "message": "我同意MetaMask的$1", + "description": "$1 is the `terms` link" + }, "airgapVault": { "message": "AirGap Vault" }, @@ -291,7 +325,7 @@ "message": "浏览网站时选择的账户未连接" }, "alertSettingsUnconnectedAccountDescription": { - "message": "当您浏览已连接的 web3 网站时,此警报会显示在弹出窗口中,但当前选择的账户未连接。" + "message": "当您在浏览已连接的Web3网站,但当前所选择的账户没有连接时,此提醒会在弹出的窗口中显示。" }, "alertSettingsWeb3ShimUsage": { "message": "当网站尝试使用已经删除的 window.web3 API 时" @@ -302,10 +336,20 @@ "alerts": { "message": "提醒" }, + "allCustodianAccountsConnectedSubtitle": { + "message": "您或者已连接所有托管账户,或者没有任何账户可连接到 MetaMask Institutional。" + }, + "allCustodianAccountsConnectedTitle": { + "message": "没有可连接的账户" + }, "allOfYour": { "message": "您的所有$1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allYourNFTsOf": { + "message": "您所有在$1的NFT", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "allowExternalExtensionTo": { "message": "允许此外部扩展程序:" }, @@ -316,6 +360,9 @@ "allowThisSiteTo": { "message": "允许此网站:" }, + "allowThisSnapTo": { + "message": "允许此 Snap:" + }, "allowWithdrawAndSpend": { "message": "允许 $1 提取和消费最高以下金额:", "description": "The url of the site that requested permission to 'withdraw and spend'" @@ -323,6 +370,9 @@ "amount": { "message": "数额" }, + "apiUrl": { + "message": "API URL" + }, "appDescription": { "message": "浏览器中的以太坊钱包", "description": "The description of the application" @@ -347,9 +397,13 @@ "message": "批准消费限额" }, "approveAllTokensTitle": { - "message": "是否允许访问您的所有$1?", + "message": "是否允许访问和转移您所有的$1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, + "approveAllTokensTitleWithoutSymbol": { + "message": "是否允许访问和转移您所有在$1的NFT?", + "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveButtonText": { "message": "批准" }, @@ -360,8 +414,12 @@ "approveTokenDescription": { "message": "这允许第三方访问和转移以下 NFT,而无需另行通知,直到您撤销其访问权限。" }, + "approveTokenDescriptionWithoutSymbol": { + "message": "这会允许第三方访问和转移您所有在$1的NFT,而无需另行通知,直到您撤销其访问权限。", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "approveTokenTitle": { - "message": "允许访问和转移您的 $1 吗?", + "message": "是否允许访问和转移您的 $1 ?", "description": "$1 is the symbol of the token for which the user is granting approval" }, "approved": { @@ -384,7 +442,11 @@ "message": "资产选项" }, "attemptSendingAssets": { - "message": "如果您试图将资产从一个网络直接发送到另一个网络,这可能会导致永久的资产损失。必须使用桥来进行。" + "message": "如果您试图将资产从一个网络直接发送到另一个网络,这可能会导致永久的资产损失。请务必使用跨链桥进行操作。" + }, + "attemptToCancelSwap": { + "message": "尝试取消兑换 ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" }, "attemptingConnect": { "message": "正在尝试连接到区块链。" @@ -409,7 +471,10 @@ "message": "设置 MetaMask 将被锁定前的空闲时间(单位:分钟)。" }, "average": { - "message": "中等" + "message": "平均值" + }, + "awaitingApproval": { + "message": "等待批准......" }, "back": { "message": "返回" @@ -454,6 +519,9 @@ "message": "此为测试版。请$1报告错误", "description": "$1 represents the word 'here' in a hyperlink" }, + "betaMetamaskInstitutionalVersion": { + "message": "MetaMask Institutional测试版本" + }, "betaMetamaskVersion": { "message": "MetaMask 测试版本" }, @@ -475,7 +543,7 @@ "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Asset in Explorer" }, "blockExplorerSwapAction": { - "message": "交换", + "message": "兑换", "description": "This is used with viewOnEtherscan e.g View Swap on Etherscan" }, "blockExplorerUrl": { @@ -488,9 +556,45 @@ "message": "在 $1 查看账户", "description": "$1 replaced by URL for custom block explorer" }, + "blockaid": { + "message": "Blockaid" + }, + "blockaidDescriptionApproveFarming": { + "message": "如果您批准此请求,以诈骗闻名的第三方可能会拿走您的所有资产。" + }, + "blockaidDescriptionBlurFarming": { + "message": "如果您批准此请求,则有人可以窃取您列于Blur上的资产。" + }, + "blockaidDescriptionFailed": { + "message": "由于出现错误,安全提供程序无法验证此请求。请谨慎操作。" + }, + "blockaidDescriptionMaliciousDomain": { + "message": "您正在与恶意网域交互。如果您批准此请求,您可能会失去您的资产。" + }, + "blockaidDescriptionMightLoseAssets": { + "message": "如果您批准此请求,您可能会失去您的资产。" + }, + "blockaidDescriptionSeaportFarming": { + "message": "如果您批准此请求,则有人可以窃取您列于OpenSea上的资产。" + }, + "blockaidDescriptionTransferFarming": { + "message": "如果您批准此请求,以诈骗闻名的第三方将会拿走您的所有资产。" + }, + "blockaidTitleDeceptive": { + "message": "此请求属欺骗性质" + }, + "blockaidTitleMayNotBeSafe": { + "message": "请求可能不安全" + }, + "blockaidTitleSuspicious": { + "message": "此请求很可疑" + }, "blockies": { "message": "Blockies" }, + "bridge": { + "message": "桥" + }, "browserNotSupported": { "message": "您的浏览器不受支持……" }, @@ -510,6 +614,10 @@ "message": "购买$1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, + "buyMoreAsset": { + "message": "购买更多$1", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, "buyNow": { "message": "立即购买" }, @@ -577,6 +685,9 @@ "clearActivityDescription": { "message": "这将会重置账户的nonce,并删除钱包中的活动选项卡数据。只有当前账户和网络会受到影响。您的余额和传入的交易不会改变。" }, + "click": { + "message": "点击" + }, "clickToConnectLedgerViaWebHID": { "message": "点击这里以通过 WebHID 连接您的 Ledger", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -590,6 +701,21 @@ "coingecko": { "message": "CoinGecko" }, + "configureSnapPopupDescription": { + "message": "您现在要离开MetaMask来配置此snap。" + }, + "configureSnapPopupInstallDescription": { + "message": "您现在要离开MetaMask来安装此snap。" + }, + "configureSnapPopupInstallTitle": { + "message": "安装snap" + }, + "configureSnapPopupLink": { + "message": "请点击此链接以继续:" + }, + "configureSnapPopupTitle": { + "message": "配置snap" + }, "confirm": { "message": "确认" }, @@ -617,9 +743,22 @@ "connectAccountOrCreate": { "message": "连接账户或创建新账户" }, + "connectCustodialAccountMenu": { + "message": "连接托管账户" + }, + "connectCustodialAccountMsg": { + "message": "请选择您想要连接的托管账户,以添加或刷新代币。" + }, + "connectCustodialAccountTitle": { + "message": "托管账户" + }, "connectManually": { "message": "手动连接到当前站点" }, + "connectSnap": { + "message": "连接$1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "连接到 $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -674,7 +813,10 @@ "message": "正在连接 Goerli 测试网络" }, "connectingToLineaGoerli": { - "message": "正在连接Linea测试网络" + "message": "正在连接 Linea Goerli 测试网络" + }, + "connectingToLineaMainnet": { + "message": "正在连接到 Linea 主网" }, "connectingToMainnet": { "message": "正在连接到以太坊主网" @@ -682,6 +824,16 @@ "connectingToSepolia": { "message": "正在连接Sepolia测试网络" }, + "connectionFailed": { + "message": "连接失败" + }, + "connectionFailedDescription": { + "message": "获取$1失败,请检查您的网络,然后重试。", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "连接请求" + }, "contactUs": { "message": "联系我们" }, @@ -708,7 +860,7 @@ "message": "合约部署" }, "contractDescription": { - "message": "为了保护自己免受骗子欺诈,请花点时间核查合约细节。" + "message": "为保护自己免受欺诈,请花点时间验证第三方详情。" }, "contractInteraction": { "message": "合约交互" @@ -717,22 +869,22 @@ "message": "NFT 合约" }, "contractRequestingAccess": { - "message": "请求访问的合约" + "message": "第三方请求访问" }, "contractRequestingSignature": { - "message": "需要签名的合约" + "message": "第三方请求签名" }, "contractRequestingSpendingCap": { - "message": "合约请求支出上限" + "message": "第三方请求支出上限" }, "contractTitle": { - "message": "合约细节" + "message": "第三方详情" }, "contractToken": { "message": "代币合约" }, "convertTokenToNFTDescription": { - "message": "我们检测到该资产是NFT。Metamask现在完全原生支持NFT。您想将它从您的代币列表中删除并将它添加为NFT吗?" + "message": "我们检测到该资产是NFT。MetaMask现在完全原生支持NFT。您想将它从您的代币列表中删除并将它添加为NFT吗?" }, "convertTokenToNFTExistDescription": { "message": "我们检测到该资产已作为NFT添加。是否要将其从代币列表中删除?" @@ -813,6 +965,60 @@ "curveMediumGasEstimate": { "message": "市场价燃料估算图" }, + "custodian": { + "message": "托管人" + }, + "custodianAccount": { + "message": "托管账户" + }, + "custodianAccountAddedDesc": { + "message": "您现在可以在MetaMask Institutional使用您的托管账户。" + }, + "custodianAccountAddedTitle": { + "message": "已添加所选托管账户。" + }, + "custodianReplaceRefreshTokenChangedFailed": { + "message": "请转到 $1,点击其用户界面内的“连接到 MMI”按钮,再次将您的账户连接到 MMI。" + }, + "custodianReplaceRefreshTokenChangedSubtitle": { + "message": "您现在可以在 MetaMask Institutional 使用您的托管账户。" + }, + "custodianReplaceRefreshTokenChangedTitle": { + "message": "您的托管代币已刷新" + }, + "custodianReplaceRefreshTokenSubtitle": { + "message": "这将替换以下地址的托管代币:" + }, + "custodianReplaceRefreshTokenTitle": { + "message": "替换托管代币" + }, + "custodyApiUrl": { + "message": "$1 API URL" + }, + "custodyDeeplinkDescription": { + "message": "在 $1 应用程序中批准交易。一旦执行了所有所需的托管批准,交易即完成。在您的 $1 应用程序中查看状态。" + }, + "custodyRefreshTokenModalDescription": { + "message": "请转到$1,然后点击用户界面中的“连接到MMI”按钮,将您的账户再次连接到MMI。" + }, + "custodyRefreshTokenModalDescription1": { + "message": "您的托管人会发出一个令牌来验证MetaMask Institutional扩展,使您可以连接您的账户。" + }, + "custodyRefreshTokenModalDescription2": { + "message": "由于安全原因,此令牌会在一段时间后过期。这使您需要重新连接到MMI。" + }, + "custodyRefreshTokenModalSubtitle": { + "message": "我为什么会看到这个?" + }, + "custodyRefreshTokenModalTitle": { + "message": "您的托管人会话已过期" + }, + "custodySessionExpired": { + "message": "托管会话已过期。" + }, + "custodyWrongChain": { + "message": "此账户未设置为与 $1 一起使用" + }, "custom": { "message": "高级" }, @@ -844,6 +1050,9 @@ "customerSupport": { "message": "客户支持" }, + "dappRequestedSpendingCap": { + "message": "网站请求的支出上限" + }, "dappSuggested": { "message": "建议的网站" }, @@ -851,6 +1060,12 @@ "message": "$1 建议了这个价格。", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "建议的网站" + }, + "dappSuggestedHighShortLabel": { + "message": "网站(高)" + }, "dappSuggestedShortLabel": { "message": "网站" }, @@ -902,9 +1117,19 @@ "delete": { "message": "删除" }, + "deleteContact": { + "message": "删除联系人" + }, "deleteNetwork": { "message": "删除网络?" }, + "deleteNetworkIntro": { + "message": "如果您删除此网络,则需要再次添加此网络才能查看您在其中的资产" + }, + "deleteNetworkTitle": { + "message": "要删除$1网络吗?", + "description": "$1 represents the name of the network" + }, "deposit": { "message": "存入" }, @@ -917,6 +1142,10 @@ "description": { "message": "描述" }, + "descriptionFromSnap": { + "message": "来自 $1 的描述", + "description": "$1 represents the name of the snap" + }, "desktopConnectionCriticalErrorDescription": { "message": "此错误可能是间歇性的,因此,请尝试重新启动此扩展程序或禁用MetaMask桌面应用程序。" }, @@ -1047,6 +1276,12 @@ "dismissReminderField": { "message": "关闭账户助记词备份提醒" }, + "displayNftMedia": { + "message": "显示NFT媒体" + }, + "displayNftMediaDescription": { + "message": "显示NFT媒体和数据时,OpenSea或其他第三方会获悉您的IP地址。这可能会使攻击者能够将您的IP地址和您的以太坊地址关联起来。NFT自动检测依赖于此设置,关闭后将不可用。" + }, "domain": { "message": "域" }, @@ -1169,13 +1404,25 @@ "enableAutoDetect": { "message": " 启用自动检测" }, + "enableForAllNetworks": { + "message": "为所有网络启用" + }, "enableFromSettings": { "message": " 从设置中启用它。" }, + "enableSmartSwaps": { + "message": "启用智能兑换" + }, + "enableSnap": { + "message": "启用" + }, "enableToken": { "message": "启用 $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "已启用" + }, "encryptionPublicKeyNotice": { "message": "$1 想要您的加密公钥。同意后,该网站将可以向您发送加密消息。", "description": "$1 is the web3 site name" @@ -1190,6 +1437,24 @@ "enhancedTokenDetectionAlertMessage": { "message": "$1. $2目前提供增强型代币检测" }, + "ensDomainsSettingDescriptionIntro": { + "message": "MetaMask让您可以直接在浏览器地址栏中看到ENS域(例如“https://metamask.eth”)。其工作原理如下:" + }, + "ensDomainsSettingDescriptionOutro": { + "message": "普通浏览器通常不处理ENS或IPFS地址,但MetaMask会帮助解决此问题。使用此功能,您的IP地址可能会与IPFS第三方服务共享。" + }, + "ensDomainsSettingDescriptionPoint1": { + "message": "MetaMask会检查以太坊的ENS合约,以查找与ENS名称相关的代码。" + }, + "ensDomainsSettingDescriptionPoint2": { + "message": "如果代码链接到IPFS,将会从IPFS网络获取内容。" + }, + "ensDomainsSettingDescriptionPoint3": { + "message": "然后,您可以看到内容,通常是网站或类似的内容。" + }, + "ensDomainsSettingTitle": { + "message": "在地址栏中显示ENS域" + }, "ensIllegalCharacter": { "message": "ENS 的非法字符。" }, @@ -1208,15 +1473,27 @@ "enterANumber": { "message": "输入一个数字" }, + "enterCustodianToken": { + "message": "输入您的 $1 代币或添加新代币" + }, "enterMaxSpendLimit": { "message": "输入最大消费限额" }, + "enterOptionalPassword": { + "message": "输入可选密码" + }, "enterPassword": { "message": "输入密码" }, "enterPasswordContinue": { "message": "输入密码继续" }, + "enterTokenNameOrAddress": { + "message": "输入代币名称或粘贴地址" + }, + "enterYourPassword": { + "message": "输入您的密码" + }, "errorCode": { "message": "代码:$1", "description": "Displayed error code for debugging purposes. $1 is the error code" @@ -1249,9 +1526,20 @@ "message": "栈:", "description": "Title for error stack, which is displayed for debugging purposes" }, + "errorWhileConnectingToRPC": { + "message": "连接到自定义网络时出错。" + }, + "errorWithSnap": { + "message": "$1出错", + "description": "$1 represents the name of the snap" + }, "ethGasPriceFetchWarning": { "message": "由于目前主要的燃料估算服务不可用,因此提供了备用燃料价格。" }, + "ethereumProviderAccess": { + "message": "授予以太坊提供商对 $1 的访问权限", + "description": "The parameter is the name of the requesting origin" + }, "ethereumPublicAddress": { "message": "以太坊公共地址" }, @@ -1270,9 +1558,15 @@ "experimental": { "message": "试验" }, + "exploreMetaMaskSnaps": { + "message": "探索MetaMask Snap" + }, "exportPrivateKey": { "message": "导出私钥" }, + "extendWalletWithSnaps": { + "message": "扩展钱包体验。" + }, "externalExtension": { "message": "外部扩展程序" }, @@ -1302,6 +1596,9 @@ "message": "文件导入失败?点击这里!", "description": "Helps user import their account from a JSON file" }, + "fileTooBig": { + "message": "拖放的文件太大。" + }, "flaskWelcomeUninstall": { "message": "您应该卸载此扩展程序", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1445,6 +1742,15 @@ "general": { "message": "常规" }, + "getStarted": { + "message": "开始进行" + }, + "globalTitle": { + "message": "全局菜单" + }, + "globalTourDescription": { + "message": "查看您的投资组合、已连接的网站、设置等等" + }, "goBack": { "message": "返回" }, @@ -1476,6 +1782,9 @@ "hardwareWallets": { "message": "连接硬件钱包" }, + "hardwareWalletsInfo": { + "message": "硬件钱包集成使用API调用外部服务器,这些外部服务器可以看到您的IP地址和您与之交互的智能合约地址。" + }, "hardwareWalletsMsg": { "message": "选择希望用于 MetaMask 的硬件钱包。" }, @@ -1541,11 +1850,34 @@ "message": "但网络钓鱼者可能会。", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealContentPrivateKey1": { + "message": "您的私钥提供 $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "对您的钱包和资金的完整访问权限。", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { + "message": "按住以显示圆圈锁定" + }, + "holdToRevealPrivateKey": { + "message": "按住以显示私钥" + }, + "holdToRevealPrivateKeyTitle": { + "message": "保护好您的私钥" + }, "holdToRevealSRP": { - "message": "按住以显示 助记词" + "message": "按住以显示 SRP" }, "holdToRevealSRPTitle": { - "message": "保护您的 助记词 安全" + "message": "保护好您的 SRP" + }, + "holdToRevealUnlockedLabel": { + "message": "按住以显示圆圈解锁" + }, + "id": { + "message": "ID" }, "ignoreAll": { "message": "忽略所有" @@ -1563,6 +1895,21 @@ "importAccountError": { "message": "导入账户时出错。" }, + "importAccountErrorIsSRP": { + "message": "您输入了助记词(或助记符)。要在此处导入账户,必须输入私钥,它是长度为 64 个字符的十六进制字符串。" + }, + "importAccountErrorNotAValidPrivateKey": { + "message": "这不是有效的私钥。您已输入十六进制字符串,但长度必须为 64 个字符。" + }, + "importAccountErrorNotHexadecimal": { + "message": "这不是有效的私钥。必须输入长度为 64 个字符的十六进制字符串。" + }, + "importAccountJsonLoading1": { + "message": "预计此 JSON 导入需要几分钟时间并暂停 MetaMask。" + }, + "importAccountJsonLoading2": { + "message": "很抱歉,在未来我们将会加快此流程。" + }, "importAccountMsg": { "message": "导入的账户将不会与最初创建的 MetaMask 账户助记词相关联。了解更多有关导入账户的信息" }, @@ -1615,18 +1962,29 @@ "message": "您的初始交易已被网络确认。请点击“确定”返回。" }, "inputLogicEmptyState": { - "message": "仅输入一个您觉得比较恰当的现在或将来合约支出的数字。以后您可以随时增加支出上限。" + "message": "仅需输入一个您觉得比较恰当的现在或将来第三方支出的数字。以后您可以随时提高支出上限。" }, "inputLogicEqualOrSmallerNumber": { - "message": "此操作允许合约从您的当前余额中支出 $1。", + "message": "此操作允许第三方从您的当前余额中支出 $1。", "description": "$1 is the current token balance in the account and the name of the current token" }, "inputLogicHigherNumber": { - "message": "此操作允许合约支出您所有的代币余额,直到达到上限或您撤销支出上限为止。如果不是有意为之,请考虑设置较低的支出上限。" + "message": "此操作允许第三方支出您所有的代币余额,直到达到上限或您撤销支出上限为止。如果不是有意为之,请考虑设置较低的支出上限。" + }, + "insightsFromSnap": { + "message": "来自$1的见解", + "description": "$1 represents the name of the snap" }, "install": { "message": "安装" }, + "installOrigin": { + "message": "安装源" + }, + "installedOn": { + "message": "已在 $1 上安装", + "description": "$1 is the date when the snap has been installed" + }, "insufficientBalance": { "message": "余额不足。" }, @@ -1707,6 +2065,22 @@ "invalidSeedPhraseCaseSensitive": { "message": "输入无效!助记词须区分大小写。" }, + "ipfsGateway": { + "message": "IPFS网关" + }, + "ipfsGatewayDescription": { + "message": "MetaMask使用第三方服务来显示您存储在IPFS上的NFT图像,显示与您输入到浏览器地址栏中的ENS地址相关信息,并获取不同代币的图标。当您使用这些服务时,这些服务可能会获悉您的IP地址。" + }, + "ipfsToggleModalDescriptionOne": { + "message": "我们使用第三方服务来显示存储在 IPFS 中的 NFT 图像,显示与在浏览器地址栏中输入的 ENS 地址相关的信息,并获取不同代币的图标。当您使用这些服务时,您的 IP 地址可能会被其获悉。" + }, + "ipfsToggleModalDescriptionTwo": { + "message": "选择“确认”将开启 IPFS 解析。您可以在 $1 中随时将其关闭。", + "description": "$1 is the method to turn off ipfs" + }, + "ipfsToggleModalSettings": { + "message": "设置 > 安全和隐私" + }, "jazzAndBlockies": { "message": "哈希头像是帮助您一眼识别账户的独特图标,有 Jazzicons 和 Blockies 两种不同风格。" }, @@ -1738,6 +2112,9 @@ "lastSold": { "message": "最后售出" }, + "layer1Fees": { + "message": "1 层费用" + }, "learnCancelSpeeedup": { "message": "学习如何 $1", "description": "$1 is link to cancel or speed up transactions" @@ -1749,6 +2126,9 @@ "message": "想了解有关燃料费的 $1?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreKeystone": { + "message": "了解更多" + }, "learnMoreUpperCase": { "message": "了解更多" }, @@ -1765,16 +2145,16 @@ "message": "点击确认前:" }, "ledgerConnectionInstructionStepFour": { - "message": "在您的 Ledger 设备上启用“智能合约数据”或“盲签”" + "message": "在您的 Ledger 设备上启用“智能合约数据”或“盲签”。" }, "ledgerConnectionInstructionStepOne": { "message": "在“设置 > 高级”下启用使用 Ledger Live" }, "ledgerConnectionInstructionStepThree": { - "message": "在您的 Ledger 设备中插入并选择 Ethereum 应用程序" + "message": "请确保您的 Ledger 设备已插入并选择 Ethereum 应用程序。" }, "ledgerConnectionInstructionStepTwo": { - "message": "打开和解锁 Ledger Live 应用程序" + "message": "打开并解锁 Ledger Live 应用程序。" }, "ledgerConnectionPreferenceDescription": { "message": "自定义连接您的 Ledger 到 MetaMask 的方式。建议使用 $1,但也可使用其他选项。请在这里阅读更多信息:$2", @@ -1813,7 +2193,10 @@ "message": "您想导入这些代币吗?" }, "lineaGoerli": { - "message": "Linea测试网络" + "message": "Linea Goerli 测试网络" + }, + "lineaMainnet": { + "message": "Linea 主网" }, "link": { "message": "链接" @@ -1839,6 +2222,12 @@ "lock": { "message": "注销" }, + "lockMetaMask": { + "message": "锁定 MetaMask" + }, + "lockTimeInvalid": { + "message": "锁定时间必须是 0 到 10080 之间的数字" + }, "logo": { "message": "$1标志", "description": "$1 is the name of the ticker" @@ -1906,6 +2295,9 @@ "metaMaskConnectStatusParagraphTwo": { "message": "连接状态按钮显示所访问的网站是否与您当前选择的账户连接。" }, + "metamaskInstitutionalVersion": { + "message": "MetaMask Institutional版本" + }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps 正在进行维护。请稍后再查看。" }, @@ -1915,6 +2307,9 @@ "metrics": { "message": "指标" }, + "mismatchAccount": { + "message": "您选中的账户($1)与尝试登录的账户($2)不同" + }, "mismatchedChainLinkText": { "message": "验证网络信息", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -1929,6 +2324,9 @@ "mismatchedNetworkSymbol": { "message": "所提交的货币符号与我们对此链ID的预期不匹配。" }, + "mismatchedRpcChainId": { + "message": "自定义网络返回的链ID与提交的链ID不匹配。" + }, "mismatchedRpcUrl": { "message": "根据我们的记录,所提交的RPC URL值与此链ID的已知提供者不匹配。" }, @@ -1938,8 +2336,21 @@ "missingSettingRequest": { "message": "在这里请求" }, + "mmiAddToken": { + "message": "$1的页面想在MetaMask Institutional中授权以下托管代币" + }, + "mmiBuiltAroundTheWorld": { + "message": "MetaMask Institutional面向全球各地设计并建立。" + }, + "more": { + "message": "更多" + }, "moreComingSoon": { - "message": "更多即将到来……" + "message": "即将有更多提供商加入" + }, + "multipleSnapConnectionWarning": { + "message": "$1想连接$2个snap。只有在您信任此网站的情况下才能继续。", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." }, "mustSelectOne": { "message": "至少选择1种代币。" @@ -1983,6 +2394,12 @@ "networkIsBusy": { "message": "网络繁忙。燃料价格较高,估值较不准确。" }, + "networkMenu": { + "message": "网络菜单" + }, + "networkMenuHeading": { + "message": "选择网络" + }, "networkName": { "message": "网络名称" }, @@ -2033,6 +2450,10 @@ "message": "相对过去72小时,燃料费用为 $1。", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "我们无法连接到 $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "网络 URL" }, @@ -2123,6 +2544,9 @@ "nfts": { "message": "收藏品" }, + "nftsPreviouslyOwned": { + "message": "先前拥有的" + }, "nickname": { "message": "昵称" }, @@ -2138,8 +2562,14 @@ "noConversionRateAvailable": { "message": "无可用汇率" }, + "noNFTs": { + "message": "尚无 NFT" + }, + "noNetworksFound": { + "message": "未找到符合搜索查询条件的网络" + }, "noSnaps": { - "message": "没有安装Snap" + "message": "您没有安装Snap。" }, "noThanksVariant2": { "message": "不,谢谢." @@ -2171,9 +2601,45 @@ "notCurrentAccount": { "message": "这是正确的账户吗?这与您钱包中当前选择的账户不同" }, + "notEnoughBalance": { + "message": "余额不足" + }, "notEnoughGas": { "message": "燃料不足" }, + "note": { + "message": "单据" + }, + "notePlaceholder": { + "message": "审批人在托管人处审批交易时会看到此单据。" + }, + "notificationTransactionFailedMessage": { + "message": "交易 $1 失败!$2", + "description": "Content of the browser notification that appears when a transaction fails" + }, + "notificationTransactionFailedMessageMMI": { + "message": "交易失败! $1", + "description": "Content of the browser notification that appears when a transaction fails in MMI" + }, + "notificationTransactionFailedTitle": { + "message": "失败交易", + "description": "Title of the browser notification that appears when a transaction fails" + }, + "notificationTransactionSuccessMessage": { + "message": "交易 $1 已确认!", + "description": "Content of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessTitle": { + "message": "已确认交易", + "description": "Title of the browser notification that appears when a transaction is confirmed" + }, + "notificationTransactionSuccessView": { + "message": "在 $1 上查看", + "description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL" + }, + "notifications": { + "message": "通知" + }, "notifications10ActionText": { "message": "在设置中访问", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2232,6 +2698,42 @@ "notifications15Title": { "message": "以太坊合并来了!" }, + "notifications18ActionText": { + "message": "启用安全警报" + }, + "notifications18DescriptionOne": { + "message": "当您收到可能是恶意的请求时,第三方会向您发出警报。", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionThree": { + "message": "在批准任何请求之前,均必须自行作出审慎调查。", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18DescriptionTwo": { + "message": "OpenSea是这项功能的第一家提供商。即将有更多提供商加入!", + "description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature." + }, + "notifications18Title": { + "message": "使用安全警报以确保安全" + }, + "notifications19ActionText": { + "message": "启用NFT自动检测" + }, + "notifications19DescriptionOne": { + "message": "有两种开始方式:", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionThree": { + "message": "我们目前只支持ERC-721。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19DescriptionTwo": { + "message": "手动添加NFT,或在“设置” > “实验”中开启“NFT自动检测”。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications19Title": { + "message": "以前所未有的方式查看您的NFT" + }, "notifications1Description": { "message": "MetaMask Mobile 用户现在可以在他们的移动钱包中交换代币。扫描二维码以获取移动应用程序并开始交换。", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." @@ -2240,6 +2742,52 @@ "message": "可以在移动设备上交换了!", "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, + "notifications20ActionText": { + "message": "了解更多", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a ledger page to resolve the U2F connection issue." + }, + "notifications20Description": { + "message": "如果您使用的是最新版本的 Firefox,您可能会遇到 Firefox 放弃 U2F 支持的相关问题。", + "description": "Description of a notification in the 'See What's New' popup. Describes the U2F support being dropped by firefox and that it affects ledger users." + }, + "notifications20Title": { + "message": "Ledger 和 Firefox 用户遇到连接问题", + "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." + }, + "notifications21ActionText": { + "message": "试试看" + }, + "notifications21Description": { + "message": "我们已更新 MetaMask 扩展中的 Swaps,使其使用起来更加轻松快捷。", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "全新推出已更新的 Swaps!" + }, + "notifications22ActionText": { + "message": "明白了" + }, + "notifications22Description": { + "message": "💡 只需点击全局菜单或账户菜单即可找到!" + }, + "notifications22Title": { + "message": "正在查找您的账户详细信息或区块资源管理器URL吗?" + }, + "notifications23ActionText": { + "message": "启用安全警报" + }, + "notifications23DescriptionOne": { + "message": "通过由 Blockaid 提供支持的安全警报,避开已知的诈骗,同时仍会保护您的隐私。" + }, + "notifications23DescriptionThree": { + "message": "如果您从 OpenSea 启用了安全警报,则我们已将您转移到此功能。" + }, + "notifications23DescriptionTwo": { + "message": "在批准请求之前,请务必自行作出审慎调查。" + }, + "notifications23Title": { + "message": "使用安全警报以确保安全" + }, "notifications3ActionText": { "message": "了解更多", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2273,7 +2821,7 @@ "description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update." }, "notifications6DescriptionThree": { - "message": "在 MetaMask 中与您的 Ledger 账户交互时,将打开一个新选项卡,并且将要求您打开 Ledger Live 应用程序。应用程序打开后,您将被要求允许 WebSocket 连接到您的 MetaMask 账户。就是这样!", + "message": "在 Metamask 中与您的 Ledger 账户交互时,将打开一个新选项卡,并且将要求您打开 Ledger Live 应用程序。应用程序打开后,您将被要求允许 WebSocket 连接到您的 MetaMask 账户。就是这样!", "description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update." }, "notifications6DescriptionTwo": { @@ -2301,7 +2849,7 @@ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page." }, "notifications8DescriptionOne": { - "message": "从 MetaMask v10.4.0 开始,您不再需要 Ledger Live 即可将您的 Ledger 设备连接到 Metamask。", + "message": "从 MetaMask v10.4.0 开始,您不再需要 Ledger Live 即可将您的 Ledger 设备连接到 MetaMask。", "description": "Description of a notification in the 'See What's New' popup. Describes changes for how Ledger Live is no longer needed to connect the device." }, "notifications8DescriptionTwo": { @@ -2486,18 +3034,21 @@ "message": "只连接您信任的网站。" }, "openFullScreenForLedgerWebHid": { - "message": "全屏打开 MetaMask 以通过 WebHID 连接您的 ledger。", + "message": "全屏打开以连接您的 Ledger。", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." }, "openInBlockExplorer": { "message": "在区块浏览器上打开" }, "openSea": { - "message": "OpenSea(测试版)" + "message": "OpenSea + Blockaid(测试版)" }, "openSeaNew": { "message": "OpenSea" }, + "operationFailed": { + "message": "操作失败" + }, "optional": { "message": "可选" }, @@ -2557,6 +3108,9 @@ "passwordsDontMatch": { "message": "密码不匹配" }, + "pasteJWTToken": { + "message": "在此处粘贴或拖放代币:" + }, "pastePrivateKey": { "message": "请粘贴您的私钥:", "description": "For importing an account from a private key" @@ -2594,18 +3148,34 @@ "message": "访问互联网。", "description": "The description of the `endowment:network-access` permission." }, + "permission_accessNetworkDescription": { + "message": "允许snap访问互联网。这可用于通过第三方服务器发送和接收数据。", + "description": "An extended description of the `endowment:network-access` permission." + }, "permission_accessSnap": { "message": "连接到$1 Snap。", "description": "The description for the `wallet_snap` permission. $1 is the name of the snap." }, + "permission_accessSnapDescription": { + "message": "允许网站或snap与$1交互。", + "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "规划并执行定期操作。", "description": "The description for the `snap_cronjob` permission" }, + "permission_cronjobDescription": { + "message": "允许snap执行按固定时间、日期或间隔定期运行的操作。这可用于触发对时间敏感的交互或通知。", + "description": "An extended description for the `snap_cronjob` permission" + }, "permission_dialog": { "message": "在 MetaMask 中显示对话框窗口。", "description": "The description for the `snap_dialog` permission" }, + "permission_dialogDescription": { + "message": "允许snap显示带有以下内容的MetaMask弹出窗口:自定义文本、输入字段以及用于批准或拒绝操作的按钮。\n这可用于创建snap的警报、确认和流程加入选择等。", + "description": "An extended description for the `snap_dialog` permission" + }, "permission_ethereumAccounts": { "message": "查看您允许的账户的地址(必填)", "description": "The description for the `eth_accounts` permission" @@ -2614,22 +3184,54 @@ "message": "访问以太坊提供商。", "description": "The description for the `endowment:ethereum-provider` permission" }, + "permission_ethereumProviderDescription": { + "message": "允许snap直接与MetaMask通信,使其可以从区块链中读取数据,并提出消息和交易建议。", + "description": "An extended description for the `endowment:ethereum-provider` permission" + }, "permission_getEntropy": { "message": "生成特定于此snap的随机密钥。", "description": "The description for the `snap_getEntropy` permission" }, + "permission_getEntropyDescription": { + "message": "允许snap派生其独有的任意密钥,而不公开密钥。这些密钥与您的MetaMask账户是分开的,与您的私钥或助记词无关。其他snap无法访问此信息。", + "description": "An extended description for the `snap_getEntropy` permission" + }, + "permission_lifecycleHooks": { + "message": "使用生命周期挂钩。", + "description": "The description for the `endowment:lifecycle-hooks` permission" + }, + "permission_lifecycleHooksDescription": { + "message": "允许snap使用生命周期挂钩在其生命周期的特定时间运行代码。", + "description": "An extended description for the `endowment:lifecycle-hooks` permission" + }, "permission_longRunning": { "message": "无限期运行。", "description": "The description for the `endowment:long-running` permission" }, + "permission_longRunningDescription": { + "message": "允许snap无限期运行,例如在处理大量数据时。", + "description": "An extended description for the `endowment:long-running` permission" + }, + "permission_manageAccounts": { + "message": "添加并控制以太坊账户", + "description": "The description for `snap_manageAccounts` permission" + }, "permission_manageBip32Keys": { "message": "在$1($2)下控制您的账户和资产。", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_manageBip32KeysDescription": { + "message": "允许snap根据您的助记词派生BIP-32密钥对,而不将其公开。这会授予对在$1上的所有账户和资产的完全访问权限。\nsnap具有管理密钥的功能,因此可以支持以太坊(EVM)之外的各种区块链协议。", + "description": "An extended description for the `snap_getBip32Entropy` permission. $1 is a derivation path (name)" + }, "permission_manageBip44Keys": { - "message": "控制您的“$1”账户和资产。", + "message": "控制您的 $1 账户和资产。", "description": "The description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g. 'Filecoin'." }, + "permission_manageBip44KeysDescription": { + "message": "允许snap根据您的助记词派生BIP-44密钥对,而不将其公开。这会授予对在$1上的所有账户和资产的完全访问权限。\nsnap具有管理密钥的功能,因此可以支持以太坊(EVM)之外的各种区块链协议。", + "description": "An extended description for the `snap_getBip44Entropy` permission. $1 is the name of a protocol, e.g., 'Filecoin'." + }, "permission_manageNamedBip32Keys": { "message": "控制您的$1账户和资产。", "description": "The description for the `snap_getBip32Entropy` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'. $2 is the plain derivation path, e.g. 'm/44'/0'/0''." @@ -2638,22 +3240,42 @@ "message": "在您的设备上存储和管理其数据。", "description": "The description for the `snap_manageState` permission" }, + "permission_manageStateDescription": { + "message": "允许snap使用加密安全地存储、更新和检索数据。其他snap无法访问此信息。", + "description": "An extended description for the `snap_manageState` permission" + }, "permission_notifications": { "message": "显示通知。", "description": "The description for the `snap_notify` permission" }, + "permission_notificationsDescription": { + "message": "允许snap在MetaMask中显示通知。snap可以触发简短的通知文本,以提供可操作或对时间敏感的信息。", + "description": "An extended description for the `snap_notify` permission" + }, "permission_rpc": { "message": "允许 $1 直接与此 Snap 通信。", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." }, + "permission_rpcDescription": { + "message": "允许$1向snap发送消息以及接收来自snap的响应。", + "description": "An extended description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites'." + }, "permission_transactionInsight": { "message": "获取并显示交易洞察。", "description": "The description for the `endowment:transaction-insight` permission" }, + "permission_transactionInsightDescription": { + "message": "允许snap对交易进行解码,并在MetaMask UI中显示见解。这可用于反网络钓鱼和安全解决方案。", + "description": "An extended description for the `endowment:transaction-insight` permission" + }, "permission_transactionInsightOrigin": { "message": "查看建议交易的网站来源", "description": "The description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" }, + "permission_transactionInsightOriginDescription": { + "message": "允许snap查看建议交易的网站的来源(URI)。这可用于反网络钓鱼和安全解决方案。", + "description": "An extended description for the `transactionOrigin` caveat, to be used with the `endowment:transaction-insight` permission" + }, "permission_unknown": { "message": "未知权限:$1", "description": "$1 is the name of a requested permission that is not recognized." @@ -2662,13 +3284,31 @@ "message": "查看您的$1 ($2)公钥。", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'." }, + "permission_viewBip32PublicKeysDescription": { + "message": "允许snap查看$1的公钥(和地址)。这并不会授予对账户或资产的任何控制权。", + "description": "An extended description for the `snap_getBip32PublicKey` permission. $1 is a derivation path (name)" + }, "permission_viewNamedBip32PublicKeys": { "message": "查看您的$1公钥。", "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, + "permission_webAssembly": { + "message": "支持WebAssembly。", + "description": "The description of the `endowment:webassembly` permission." + }, + "permission_webAssemblyDescription": { + "message": "允许snap通过WebAssembly访问低级执行环境。", + "description": "An extended description of the `endowment:webassembly` permission." + }, "permissions": { "message": "权限" }, + "permissionsTitle": { + "message": "权限" + }, + "permissionsTourDescription": { + "message": "在此处查看您的已连接账户并管理权限" + }, "personalAddressDetected": { "message": "检测到个人地址。请输入代币合约地址。" }, @@ -2685,6 +3325,9 @@ "portfolio": { "message": "投资组合" }, + "portfolioDashboard": { + "message": "投资组合控制面板" + }, "preferredLedgerConnectionType": { "message": "首选 Ledger 连接类型", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" @@ -2717,6 +3360,10 @@ "message": "私钥", "description": "select this type of file to use to import an account" }, + "privateKeyCopyWarning": { + "message": "$1 的私钥", + "description": "$1 represents the account name" + }, "privateKeyWarning": { "message": "警告:切勿泄露此密钥。任何拥有您私钥的人都可以窃取您账户中持有的任何资产。" }, @@ -2738,6 +3385,9 @@ "queued": { "message": "队列中" }, + "quoteRate": { + "message": "报价" + }, "reAddAccounts": { "message": "重新添加任何其他账户" }, @@ -2751,7 +3401,7 @@ "message": "接收" }, "recipientAddressPlaceholder": { - "message": "搜索、公共地址 (0x) 或 ENS" + "message": "输入公共地址 (0x) 或 ENS 名称" }, "recommendedGasLabel": { "message": "建议" @@ -2816,6 +3466,12 @@ "removeAccountDescription": { "message": "该账户将从您的钱包中删除。在继续操作前,确认您已拥有该导入账户的原始账户助记词或私钥。您可以通过账户下拉菜单再次导入或创建账户。" }, + "removeJWT": { + "message": "删除托管代币" + }, + "removeJWTDescription": { + "message": "您确定要删除此代币吗?分配给此代币的所有账户也将从扩展中删除: " + }, "removeNFT": { "message": "删除 NFT" }, @@ -2892,6 +3548,18 @@ "restoreUserDataDescription": { "message": "您可以使用以前备份的JSON文件来恢复包含首选项和账户地址的用户设置。" }, + "resultPageError": { + "message": "错误" + }, + "resultPageErrorDefaultMessage": { + "message": "操作失败。" + }, + "resultPageSuccess": { + "message": "成功" + }, + "resultPageSuccessDefaultMessage": { + "message": "操作已成功完成。" + }, "retryTransaction": { "message": "重试交易" }, @@ -2939,16 +3607,27 @@ "message": "撤销访问和转移您的所有 $1 的权限?", "description": "$1 is the symbol of the token for which the user is revoking approval" }, + "revokeAllTokensTitleWithoutSymbol": { + "message": "要撤销访问和转移您所有在$1的NFT的权限吗?", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, "revokeApproveForAllDescription": { "message": "这将撤销第三方访问和转移您的所有 $1 的权限,而无需另行通知。", "description": "$1 is either a string or link of a given token symbol or name" }, + "revokeApproveForAllDescriptionWithoutSymbol": { + "message": "这会撤销第三方在无需另行通知的情况下访问和转移您所有在$1的NFT的权限。", + "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" + }, + "revokePermission": { + "message": "撤销权限" + }, "revokeSpendingCap": { "message": "撤销 $1 的支出上限", "description": "$1 is a token symbol" }, "revokeSpendingCapTooltipText": { - "message": "本合约将无法再使用您当前或未来的任何代币。" + "message": "第三方将无法再使用您当前或未来的任何代币。" }, "rpcUrl": { "message": "新的 RPC URL" @@ -2986,9 +3665,28 @@ "security": { "message": "安全" }, + "securityAlert": { + "message": "来自 $1 和 $2 的安全警报" + }, + "securityAlerts": { + "message": "安全警告" + }, + "securityAlertsDescription1": { + "message": "此功能通过在本地检查您的交易和签名请求来向您发出恶意活动警报。您的数据不会与提供此服务的第三方共享。在批准任何请求之前,请务必自行作出审慎调查。无法保证此功能可以检测到所有恶意活动。" + }, + "securityAlertsDescription2": { + "message": "在批准任何请求之前,请务必自行谨慎调查。无法保证此功能可以检测到所有恶意活动。" + }, "securityAndPrivacy": { "message": "安全和隐私" }, + "securityProviderAdviceBy": { + "message": "$1的安全建议", + "description": "The security provider that is providing data" + }, + "seeDetails": { + "message": "查看详情" + }, "seedPhraseConfirm": { "message": "确认助记词" }, @@ -3043,21 +3741,36 @@ "seedPhraseWriteDownHeader": { "message": "写下您的助记词" }, + "select": { + "message": "选择" + }, "selectAccounts": { "message": "选择要在此网站上使用的账户" }, + "selectAccountsForSnap": { + "message": "选择使用此 Snap 的账户" + }, "selectAll": { "message": "全部选择" }, + "selectAllAccounts": { + "message": "选择所有账户" + }, "selectAnAccount": { "message": "选择一个账户" }, "selectAnAccountAlreadyConnected": { "message": "此账户已连接到 MetaMask" }, + "selectAnAccountHelp": { + "message": "选择要在 MetaMask Institutional 使用的托管账户。" + }, "selectHdPath": { "message": "选择 HD 路径" }, + "selectJWT": { + "message": "选择代币" + }, "selectNFTPrivacyPreference": { "message": "在设置中打开 NFT 检测" }, @@ -3113,6 +3826,9 @@ "message": "批准$1,且无消费限制", "description": "The token symbol that is being approved" }, + "settingAddSnapAccount": { + "message": "添加snap账户" + }, "settings": { "message": "设置" }, @@ -3141,9 +3857,21 @@ "message": "选择此项以使用 Etherscan 在交易列表中显示传入的交易", "description": "$1 is the link to etherscan url and $2 is the link to the privacy policy of consensys APIs" }, + "showIncomingTransactionsInformation": { + "message": "这依赖于每个可以访问您的以太坊地址和IP地址的网络。" + }, + "showMore": { + "message": "展开" + }, + "showNft": { + "message": "显示 NFT" + }, "showPermissions": { "message": "显示权限" }, + "showPrivateKey": { + "message": "显示私钥" + }, "showPrivateKeys": { "message": "显示私钥" }, @@ -3186,10 +3914,79 @@ "skipAccountSecurityDetails": { "message": "我明白,在我备份我的账户助记词之前,我可能会丢失我的账户及其所有资产。" }, + "smartContracts": { + "message": "智能合约" + }, + "smartSwap": { + "message": "智能兑换" + }, + "smartSwapsAreHere": { + "message": "智能兑换已推出!" + }, + "smartSwapsDescription": { + "message": "MetaMask Swaps 变得更加智能!启用智能兑换使得 MetaMask 在编程方面让您的兑换体验更加优化,有助于:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "没有足够的资金进行智能兑换。" + }, + "smartSwapsErrorUnavailable": { + "message": "智能兑换暂时不可用。" + }, + "smartSwapsSubDescription": { + "message": "* 智能兑换将多次尝试以私密方式提交您的交易。如果所有尝试都失败,交易将会公开广播,以确保您的兑换成功进行。" + }, + "snapConfigure": { + "message": "配置" + }, + "snapConnectionWarning": { + "message": "$1想连接$2。只有在您信任此网站的情况下才能继续。", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." + }, "snapContent": { "message": "此内容来自$1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, + "snapCreateAccountSubtitle": { + "message": "选择如何使用MetaMask Snap来保护您的新账户。" + }, + "snapCreateAccountTitle": { + "message": "创建 $1 账户", + "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + }, + "snapCreateAccountTitle2": { + "message": "snap", + "description": "$1 of the snapCreateAccountTitle" + }, + "snapCreatedByMetaMask": { + "message": "由MetaMask创建" + }, + "snapDetailAudits": { + "message": "审核" + }, + "snapDetailDeveloper": { + "message": "开发人员" + }, + "snapDetailLastUpdated": { + "message": "已更新" + }, + "snapDetailManageSnap": { + "message": "管理snap" + }, + "snapDetailTags": { + "message": "标签" + }, + "snapDetailVersion": { + "message": "版本" + }, + "snapDetailWebsite": { + "message": "网站" + }, + "snapDetailsCreateASnapAccount": { + "message": "创建Snap账户" + }, + "snapDetailsInstalled": { + "message": "已安装" + }, "snapError": { "message": "Snap错误:'$1'。错误代码:'$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." @@ -3197,6 +3994,13 @@ "snapInstall": { "message": "安装Snap" }, + "snapInstallRequest": { + "message": "安装$1,将向其授予以下权限。只有在您信任$1的情况下才能继续。", + "description": "$1 is the snap name." + }, + "snapInstallSuccess": { + "message": "安装完成" + }, "snapInstallWarningCheck": { "message": "请勾选选项框以确认您理解。", "description": "Warning message used in popup displayed on snap install. $1 is the snap name." @@ -3205,30 +4009,93 @@ "message": "请勾选所有方框以确认您理解。", "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, + "snapInstallWarningHeading": { + "message": "请谨慎行事" + }, "snapInstallWarningKeyAccess": { "message": "您正在向snap \"$1\"授予$2的密钥访问权限。此操作不可撤销,并会向\"$1\"授予对您的$2账户和资产的控制权。在继续之前,请确保您信任\"$1\"。", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallWarningPublicKeyAccess": { + "message": "授予 $2 对 $1 的公钥访问权限", + "description": "The first parameter is the name of the snap and the second one is the protocol" + }, + "snapInstallationErrorDescription": { + "message": "无法安装$1。", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "安装失败", + "description": "Error title used when snap installation fails." + }, + "snapIsAudited": { + "message": "已审核" + }, + "snapResultError": { + "message": "错误" + }, + "snapResultSuccess": { + "message": "成功" + }, + "snapResultSuccessDescription": { + "message": "$1已可以使用" + }, "snapUpdate": { "message": "更新Snap" }, + "snapUpdateAvailable": { + "message": "有更新" + }, + "snapUpdateErrorDescription": { + "message": "无法更新$1。", + "description": "Error description used when snap update fails. $1 is the snap name." + }, + "snapUpdateErrorTitle": { + "message": "更新失败", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1希望将$2更新为$3,更新后将获得下列权限。只有在您信任$2的情况下才可继续。", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "更新完成" + }, "snaps": { "message": "Snap" }, "snapsInsightLoading": { "message": "正在加载交易洞察……" }, + "snapsInvalidUIError": { + "message": "Snap指定的用户界面无效。" + }, "snapsNoInsight": { "message": "Snap没有返回任何洞察" }, + "snapsPrivacyWarningFirstMessage": { + "message": "您确认您即将安装的snap是Consensys $1中定义的第三方服务。您对第三方服务的使用,受第三方服务提供商规定的单独条款和条件约束。您访问、依赖或使用第三方服务的风险,由您自己承担。对于您因使用第三方服务而造成的任何损失,Consensys概不承担任何责任。", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "您与第三方服务分享的任何信息,将由这些第三方服务根据其隐私政策直接收集。请参阅其隐私政策以了解更多信息。", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys无法访问您与这些第三方分享的信息。", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "管理您的Snap" }, + "snapsTermsOfUse": { + "message": "使用条款" + }, "snapsToggle": { "message": "Snap仅在启用后才会运行" }, "snapsUIError": { - "message": "Snap指定的用户界面无效。", + "message": "联系 $1 的创建者以获得进一步支持。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, "someNetworksMayPoseSecurity": { @@ -3284,6 +4151,9 @@ "message": "仅输入一个您觉得比较恰当的现在或将来存取 $1 的数字。您稍后可以随时增加代币限额。", "description": "$1 is origin of the site requesting the token limit" }, + "spendingCapRequest": { + "message": "$1的支出上限请求" + }, "srpInputNumberOfWords": { "message": "我有一个包含$1个单词的助记词", "description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)." @@ -3299,17 +4169,20 @@ "srpSecurityQuizGetStarted": { "message": "开始" }, + "srpSecurityQuizImgAlt": { + "message": "一只眼睛,中央有一个钥匙孔,还有三个浮动密码字段" + }, "srpSecurityQuizIntroduction": { "message": "要查看助记词,您需要答对两个问题" }, "srpSecurityQuizQuestionOneQuestion": { - "message": "如果您丢失了助记词,MetaMask......" + "message": "如果您丢失了助记词,MetaMask..." }, "srpSecurityQuizQuestionOneRightAnswer": { "message": "无法帮助您" }, "srpSecurityQuizQuestionOneRightAnswerDescription": { - "message": "将它写下来、刻在金属上,或保存在多个秘密位置,这样您就不会丢失它。如果丢失了,它就会永远消失。" + "message": "将它写下来、刻在金属上,或保存在多个秘密位置,这样您就不会丢失。如果丢失了,它就会永远消失。" }, "srpSecurityQuizQuestionOneRightAnswerTitle": { "message": "答对了!没有人能够帮您找回您的助记词" @@ -3324,7 +4197,7 @@ "message": "答错了!没有人能够帮您找回您的助记词" }, "srpSecurityQuizQuestionTwoQuestion": { - "message": "如果有人(即使是技术支持人员)查问您的助记词......" + "message": "如果有人(即使是技术支持人员)查问您的助记词..." }, "srpSecurityQuizQuestionTwoRightAnswer": { "message": "就是在对您进行诈骗" @@ -3365,6 +4238,9 @@ "stableLowercase": { "message": "稳定" }, + "stake": { + "message": "质押" + }, "stateLogError": { "message": "检索状态日志时出错。" }, @@ -3383,6 +4259,9 @@ "statusNotConnected": { "message": "未连接" }, + "statusNotConnectedAccount": { + "message": "未连接任何账户" + }, "step1LatticeWallet": { "message": "关联您的 Lattice1" }, @@ -3511,6 +4390,9 @@ "swapAmountReceivedInfo": { "message": "这是您将收到的最低金额。根据滑点值,您可能会收到更多。" }, + "swapAnyway": { + "message": "仍然兑换" + }, "swapApproval": { "message": "批准 $1 进行交换", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -3519,6 +4401,12 @@ "message": "您还需要 $1 的 $2 来完成这笔交换", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "您还在吗?" + }, + "swapAreYouStillThereDescription": { + "message": "如果您想继续,我们准备好为您显示最新报价" + }, "swapBuildQuotePlaceHolderText": { "message": "没有与 $1 匹配的代币", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -3526,6 +4414,9 @@ "swapConfirmWithHwWallet": { "message": "使用您的硬件钱包确认" }, + "swapContinueSwapping": { + "message": "继续兑换" + }, "swapContractDataDisabledErrorDescription": { "message": "在您的 Ledger 的 Etherum 应用程序中,转到“设置”并允许合约数据。然后再次尝试交换。" }, @@ -3544,14 +4435,20 @@ "swapEditLimit": { "message": "编辑限制" }, + "swapEditTransactionSettings": { + "message": "编辑交易设置" + }, "swapEnableDescription": { - "message": "这是必须的,并授予 MetaMask 权限交换您的 $1。", + "message": "这是必须的,并且允许 MetaMask 兑换您的 $1。", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." }, "swapEnableTokenForSwapping": { "message": "这将 $1 进行交换", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "输入金额" + }, "swapEstimatedNetworkFees": { "message": "预估网络费用" }, @@ -3565,6 +4462,9 @@ "swapFailedErrorTitle": { "message": "交换失败" }, + "swapFetchingQuote": { + "message": "正在获取报价" + }, "swapFetchingQuoteNofN": { "message": "获取$2的$1报价", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -3605,6 +4505,13 @@ "message": "包括 $1% 的 MetaMask 费用。", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapIncludesMetaMaskFeeViewAllQuotes": { + "message": "包含$1%的MetaMask费用 – $2", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes." + }, + "swapLearnMore": { + "message": "了解有关 Swaps 的更多信息" + }, "swapLowSlippageError": { "message": "交易可能会失败,最大滑点过低。" }, @@ -3626,6 +4533,10 @@ "message": "$1 后更新报价", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "没有与 $1 匹配的代币", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "处理完此交易后,您的 $1 将被添加到您的账户中。", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -3653,6 +4564,10 @@ "swapQuoteDetails": { "message": "报价详情" }, + "swapQuoteNofM": { + "message": "$1/$2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "报价来源" }, @@ -3662,6 +4577,9 @@ "swapQuotesExpiredErrorTitle": { "message": "报价超时" }, + "swapQuotesNotAvailableDescription": { + "message": "缩小您的交易规模或者使用不同的代币。" + }, "swapQuotesNotAvailableErrorDescription": { "message": "尝试调整金额或滑点设置,然后重试。" }, @@ -3698,16 +4616,52 @@ "swapSelectQuotePopoverDescription": { "message": "以下是从多个流动性来源收集到的所有报价。" }, + "swapSelectToken": { + "message": "选择代币" + }, + "swapShowLatestQuotes": { + "message": "显示最新报价" + }, "swapSlippageNegative": { "message": "滑点必须大于或等于0" }, + "swapSlippageNegativeDescription": { + "message": "滑点必须大于或等于 0" + }, + "swapSlippageNegativeTitle": { + "message": "提高滑点以继续" + }, + "swapSlippageOverLimitDescription": { + "message": "滑点容差必须小于等于 15%。任何更高的比例将导致不良费率。" + }, + "swapSlippageOverLimitTitle": { + "message": "降低滑点以继续" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "最大滑点太低,可能导致交易失败。" + }, + "swapSlippageTooLowTitle": { + "message": "提高滑点以避免交易失败" + }, "swapSlippageTooltip": { "message": "如果价格于下单到订单确认期间发生变化,这被称为“滑点”。如果滑点超过“最大滑点”设置,您的兑换将会自动取消。" }, + "swapSlippageVeryHighDescription": { + "message": "输入的滑点被认为非常高,可能导致不良费率" + }, + "swapSlippageVeryHighTitle": { + "message": "非常高的滑点" + }, + "swapSlippageZeroDescription": { + "message": "零滑点报价供应商较少,这将导致报价竞争力下降。" + }, + "swapSlippageZeroTitle": { + "message": "寻找零滑点供应商" + }, "swapSource": { "message": "流动性来源" }, @@ -3724,7 +4678,7 @@ "message": "交换自" }, "swapSwapSwitch": { - "message": "在代币之间切换" + "message": "切换代币顺序" }, "swapSwapTo": { "message": "交换为" @@ -3732,6 +4686,13 @@ "swapToConfirmWithHwWallet": { "message": "使用您的硬件钱包确认" }, + "swapTokenAddedManuallyDescription": { + "message": "在 $1 上验证此代币,并确保这是您想要交易的代币。", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "已手动添加代币" + }, "swapTokenAvailable": { "message": "您的 $1 已添加到您的账户。", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -3758,6 +4719,13 @@ "message": "在 $1 个来源上进行了验证。", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 仅在 1 个源上进行了验证。在继续之前,考虑在 $2 上进行验证。", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "可能伪造的代币" + }, "swapTooManyDecimalsError": { "message": "$1 允许最多 $2 个小数位", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -3795,9 +4763,16 @@ "message": "没有足够的 $1 来完成此交易", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "$1 不足", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "在活动中查看" }, + "switch": { + "message": "切换" + }, "switchEthereumChainConfirmationDescription": { "message": "这将切换 MetaMask 中选定的网络到以前添加的网络:" }, @@ -3820,6 +4795,12 @@ "switchedTo": { "message": "您已切换到" }, + "switcherTitle": { + "message": "网络切换工具" + }, + "switcherTourDescription": { + "message": "点击此图标以切换网络或添加新网络" + }, "switchingNetworksCancelsPendingConfirmations": { "message": "切换网络将取消所有待处理的确认" }, @@ -3838,6 +4819,18 @@ "termsOfService": { "message": "服务条款" }, + "termsOfUse": { + "message": "使用条款" + }, + "termsOfUseAgreeText": { + "message": "我同意适用于我使用MetaMask及其所有功能的使用条款" + }, + "termsOfUseFooterText": { + "message": "请滚动以阅读所有章节" + }, + "termsOfUseTitle": { + "message": "我们的使用条款已更新" + }, "testNetworks": { "message": "测试网络" }, @@ -3850,6 +4843,17 @@ "thingsToKeep": { "message": "注意事项:" }, + "thirdPartySoftware": { + "message": "第三方软件通告", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, + "thisCollection": { + "message": "这个收藏品" + }, + "thisServiceIsExperimental": { + "message": "此服务是实验性质的。启用此功能,即表示您同意OpenSea的$1。", + "description": "$1 is link to open sea terms of use" + }, "time": { "message": "时间" }, @@ -3863,11 +4867,44 @@ "message": "至:$1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "您面临网络钓鱼攻击的风险。通过关闭 eth_sign 保护自己。" + }, "toggleEthSignDescriptionField": { - "message": "启用此选项会允许dapps使用eth_sign请求您的签名。eth_sign是一种开放式签名方法,允许您对任意散列进行签名,因而使其成为危险的网络钓鱼风险。只有在您能够阅读所签内容并信任请求的来源时,才能对eth_sign请求进行签名。" + "message": "如果启用此设置,您可能会收到不可读的签名请求。通过签署一条您不理解的信息,您可能同意放弃您的资金和 NFT。" }, "toggleEthSignField": { - "message": "切换eth_sign请求" + "message": "Eth_sign 请求" + }, + "toggleEthSignModalBannerBoldText": { + "message": " 您可能遭受了诈骗" + }, + "toggleEthSignModalBannerText": { + "message": "如果要求您打开此设置," + }, + "toggleEthSignModalCheckBox": { + "message": "我明白,如果我启用 eth_sign 请求,我可能失去所有资金和 NFT。 " + }, + "toggleEthSignModalDescription": { + "message": "允许 eth_sign 请求可能会使您易于受到网络钓鱼攻击。始终检查 URL,并且在签署包含代码的消息时保持谨慎。" + }, + "toggleEthSignModalFormError": { + "message": "文本不正确" + }, + "toggleEthSignModalFormLabel": { + "message": "输入“我只签署我理解的内容”以继续" + }, + "toggleEthSignModalFormValidation": { + "message": "我只签署我理解的内容" + }, + "toggleEthSignModalTitle": { + "message": "使用风险自负" + }, + "toggleEthSignOff": { + "message": "关闭(推荐)" + }, + "toggleEthSignOn": { + "message": "开启(不推荐)" }, "token": { "message": "代币" @@ -3911,6 +4948,9 @@ "tokenSymbol": { "message": "代币符号" }, + "tokens": { + "message": "代币" + }, "tokensFoundTitle": { "message": "发现$1新代币", "description": "$1 is the number of new tokens detected" @@ -3918,6 +4958,12 @@ "tooltipApproveButton": { "message": "我理解" }, + "tooltipSatusConnected": { + "message": "已连接" + }, + "tooltipSatusNotConnected": { + "message": "未连接" + }, "total": { "message": "共计" }, @@ -3990,6 +5036,9 @@ "transactionErrored": { "message": "交易出现错误。" }, + "transactionFailed": { + "message": "交易失败" + }, "transactionFee": { "message": "交易费" }, @@ -4014,15 +5063,21 @@ "transactionHistoryTotalGasFee": { "message": "燃料费总额" }, + "transactionNote": { + "message": "交易单据" + }, "transactionResubmitted": { "message": "已在 $2 重新提交交易,燃料费预计升至 $1" }, "transactionSecurityCheck": { - "message": "启用交易安全提供程序" + "message": "启用安全警报" }, "transactionSecurityCheckDescription": { "message": "我们使用第三方 API 来检测和显示未签名交易和签名请求中涉及的风险,然后您再进行签名。这些服务将访问您的未签名交易和签名请求、您的账户地址以及首选语言。" }, + "transactionSettings": { + "message": "交易设置" + }, "transactionSubmitted": { "message": "已在 $2 提交交易,燃料费预计为 $1。" }, @@ -4038,6 +5093,22 @@ "transferFrom": { "message": "转移自" }, + "troubleConnectingToLedgerU2FOnFirefox": { + "message": "我们在连接您的 Ledger 时遇到问题。$1", + "description": "$1 is a link to the wallet connection guide;" + }, + "troubleConnectingToLedgerU2FOnFirefox2": { + "message": "查看我们的硬件钱包连接指南并重试。", + "description": "$1 of the ledger wallet connection guide" + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution": { + "message": "如果您使用的是最新版本的 Firefox,您可能会遇到 Firefox 放弃 U2F 支持的相关问题。了解如何修复此问题 $1。", + "description": "It is a link to the ledger website for the workaround." + }, + "troubleConnectingToLedgerU2FOnFirefoxLedgerSolution2": { + "message": "此处", + "description": "Second part of the error message; It is a link to the ledger website for the workaround." + }, "troubleConnectingToWallet": { "message": "我们在连接您的 $1 时遇到问题,尝试检查 $2 并重试。", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" @@ -4124,6 +5195,9 @@ "upArrow": { "message": "向上箭头" }, + "update": { + "message": "更新" + }, "updatedWithDate": { "message": "已于 $1 更新" }, @@ -4133,9 +5207,18 @@ "urlExistsErrorMsg": { "message": "此 URL 目前已被 $1 网络使用。" }, + "use4ByteResolution": { + "message": "对智能合约进行解码" + }, + "use4ByteResolutionDescription": { + "message": "为了改善用户体验,我们根据与您交互的智能合约消息,自定义活动选项卡。MetaMask 使用名为 4byte.directory 的服务来对数据进行解码,并向您显示更方便阅读的智能合约版本。这有助于减少您批准恶意智能合约操作的机会,但可能导致您的 IP 地址被共享。" + }, "useMultiAccountBalanceChecker": { "message": "账户余额分批请求" }, + "useMultiAccountBalanceCheckerSettingDescription": { + "message": "通过批处理账户余额请求,可更快获得余额更新。此操作让我们可以全面获取您的账户余额信息,以便您更快地获得更新,从而获得更佳体验。关闭此功能后,第三方将您的账户相互关联的概率会较低。" + }, "useNftDetection": { "message": "自动检测NFT" }, @@ -4160,6 +5243,9 @@ "usePhishingDetectionDescription": { "message": "显示针对 Ethereum 用户的网络钓鱼域名警告" }, + "useSiteSuggestion": { + "message": "使用网站建议" + }, "useTokenDetectionPrivacyDesc": { "message": "要自动显示发送到您账户的代币,需要与第三方服务器通信以获取代币的图像。这些服务器将拥有您的IP地址的访问权限。" }, @@ -4170,7 +5256,7 @@ "message": "用户名" }, "verifyContractDetails": { - "message": "验证合约详情" + "message": "验证第三方详情" }, "verifyThisTokenDecimalOn": { "message": "代币小数可以在 $1 上找到", @@ -4184,12 +5270,18 @@ "message": "在 $1 上验证此代币,并确保这是您想要交易的代币。", "description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" }, + "version": { + "message": "版本" + }, "view": { "message": "查看" }, "viewAllDetails": { "message": "查看所有详情" }, + "viewAllQuotes": { + "message": "查看所有报价" + }, "viewContact": { "message": "查看联系人" }, @@ -4213,9 +5305,18 @@ "message": "在 Etherscan 上查看 $1", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" }, + "viewOnExplorer": { + "message": "在Explorer上查看 " + }, "viewOnOpensea": { "message": "在 Opensea 上查看" }, + "viewPortfolioDashboard": { + "message": "查看投资组合控制面板" + }, + "viewinCustodianApp": { + "message": "在托管应用程序中查看" + }, "viewinExplorer": { "message": "在 Explorer 中查看 $1", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -4249,11 +5350,15 @@ "wantToAddThisNetwork": { "message": "想要添加此网络吗?" }, + "wantsToAddThisAsset": { + "message": "$1 想将此资产添加到您的钱包", + "description": "$1 is the name of the website that wants to add an asset to your wallet" + }, "warning": { "message": "警告" }, "warningTooltipText": { - "message": "$1 合约可能会花费您的全部代币余额,无需进一步通知或同意。请自定义较低的支出上限以保护自己。", + "message": "$1 第三方可能会支出您的全部代币余额,无需进一步通知或同意。请自定义较低的支出上限以保护自己。", "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" }, "weak": { @@ -4320,6 +5425,9 @@ "youSign": { "message": "您正在签名" }, + "yourAccounts": { + "message": "您的账户" + }, "yourFundsMayBeAtRisk": { "message": "您的资金可能面临风险" }, From e6cd4525067ab9861ccfdb13b5557f988c39bcc5 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Tue, 22 Aug 2023 15:06:18 +0100 Subject: [PATCH 080/102] fix: Remove sentry warning on `undefined` `TokenListController` (#20547) * fix sentry warning * correct test * switch to using spyOn method --- app/scripts/migrations/088.test.ts | 16 ++++++++++------ app/scripts/migrations/088.ts | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/scripts/migrations/088.test.ts b/app/scripts/migrations/088.test.ts index ca672f982..e60446109 100644 --- a/app/scripts/migrations/088.test.ts +++ b/app/scripts/migrations/088.test.ts @@ -724,7 +724,9 @@ describe('migration #88', () => { expect(newStorage.data).toStrictEqual(oldData); }); - it('captures an exception if it has no TokenListController property', async () => { + it('logs a warning if it has no TokenListController property', async () => { + const mockWarnFn = jest.spyOn(console, 'warn'); + const oldData = { TokensController: {}, NftController: { @@ -760,13 +762,15 @@ describe('migration #88', () => { }; await migrate(oldStorage); - expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); - expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + expect(mockWarnFn).toHaveBeenCalledTimes(1); + expect(mockWarnFn).toHaveBeenCalledWith( new Error(`typeof state.TokenListController is undefined`), ); }); - it('captures an exception if the TokenListController property is not an object', async () => { + it('logs a warning if the TokenListController property is not an object', async () => { + const mockWarnFn = jest.spyOn(console, 'warn'); + const oldData = { TokensController: {}, NftController: { @@ -803,8 +807,8 @@ describe('migration #88', () => { }; await migrate(oldStorage); - expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); - expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + expect(mockWarnFn).toHaveBeenCalledTimes(1); + expect(mockWarnFn).toHaveBeenCalledWith( new Error(`typeof state.TokenListController is boolean`), ); }); diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts index 5ede1b0fa..031a1184e 100644 --- a/app/scripts/migrations/088.ts +++ b/app/scripts/migrations/088.ts @@ -164,7 +164,7 @@ function migrateData(state: Record): void { ); } } else { - global.sentry?.captureException?.( + console.warn( new Error( `typeof state.TokenListController is ${typeof state.TokenListController}`, ), From 787fc13f193df5a1fd176cc9a9b5720abb3869b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Tue, 22 Aug 2023 17:50:33 +0200 Subject: [PATCH 081/102] Fixed bug that was causing to not show the correct account name and instead was displaying the default "Account x" one (#20555) --- app/scripts/controllers/mmi-controller.js | 37 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index f5fcc339c..bfeca6a24 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -292,9 +292,9 @@ export default class MMIController extends EventEmitter { })), ); - newAccounts.forEach( - async () => await this.keyringController.addNewAccount(keyring), - ); + for (let i = 0; i < newAccounts.length; i++) { + await this.keyringController.addNewAccount(keyring); + } const allAccounts = await this.keyringController.getAccounts(); @@ -303,12 +303,33 @@ export default class MMIController extends EventEmitter { ...new Set(oldAccounts.concat(allAccounts.map((a) => a.toLowerCase()))), ]; + // Create a Set of lowercased addresses from oldAccounts for efficient existence checks + const oldAccountsSet = new Set( + oldAccounts.map((address) => address.toLowerCase()), + ); + + // Create a map of lowercased addresses to names from newAccounts for efficient lookups + const accountNameMap = newAccounts.reduce((acc, item) => { + // For each account in newAccounts, add an entry to the map with the lowercased address as the key and the name as the value + acc[item.toLowerCase()] = accounts[item].name; + return acc; + }, {}); + + // Iterate over all accounts allAccounts.forEach((address) => { - if (!oldAccounts.includes(address.toLowerCase())) { - const label = newAccounts - .filter((item) => item.toLowerCase() === address) - .map((item) => accounts[item].name)[0]; - this.preferencesController.setAccountLabel(address, label); + // Convert the address to lowercase for consistent comparisons + const lowercasedAddress = address.toLowerCase(); + + // If the address is not in oldAccounts + if (!oldAccountsSet.has(lowercasedAddress)) { + // Look up the label in the map + const label = accountNameMap[lowercasedAddress]; + + // If the label is defined + if (label) { + // Set the label for the address + this.preferencesController.setAccountLabel(address, label); + } } }); From b8525566f2e0e4497594aaf8c7252c51144907d9 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 22 Aug 2023 17:41:52 -0230 Subject: [PATCH 082/102] Enable legacy HD paths for trezor users (#19552) * Update eth-trezor-keyring to v1.1.0 * Revert "Revert "feature: Add legacy derivation path to Trezor (#19443)" (#19451)" This reverts commit b5ef94b9f060e67dfca440df737ae7a9c6a1ed51. * Fix trezor import * Update lavamoat policies * Remove accidentally committed code * Fix type in previous commit --- app/scripts/metamask-controller.js | 2 +- development/sentry-upload-artifacts.sh | 2 +- lavamoat/browserify/beta/policy.json | 13 +++++++++++++ lavamoat/browserify/desktop/policy.json | 13 +++++++++++++ lavamoat/browserify/flask/policy.json | 13 +++++++++++++ lavamoat/browserify/main/policy.json | 13 +++++++++++++ lavamoat/browserify/mmi/policy.json | 13 +++++++++++++ package.json | 2 +- ui/pages/create-account/connect-hardware/index.js | 1 + yarn.lock | 11 ++++++----- 10 files changed, 75 insertions(+), 8 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 183de1fb0..3e4a67eed 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -18,7 +18,7 @@ import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager' import { errorCodes as rpcErrorCodes, EthereumRpcError } from 'eth-rpc-errors'; import { Mutex } from 'await-semaphore'; import log from 'loglevel'; -import TrezorKeyring from '@metamask/eth-trezor-keyring'; +import { TrezorKeyring } from '@metamask/eth-trezor-keyring'; import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring'; import LatticeKeyring from 'eth-lattice-keyring'; import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring'; diff --git a/development/sentry-upload-artifacts.sh b/development/sentry-upload-artifacts.sh index e70989123..9d2fd32b4 100755 --- a/development/sentry-upload-artifacts.sh +++ b/development/sentry-upload-artifacts.sh @@ -31,7 +31,7 @@ function upload_sourcemaps { local release="${1}"; shift local dist_directory="${1}"; shift - sentry-cli releases files "${release}" upload-sourcemaps "${dist_directory}"/chrome/*.js "${dist_directory}"/sourcemaps/ --rewrite --url-prefix '/metamask' + sentry-cli releases files "${release}" upload-sourcemaps "${dist_directory}"/chrome/*.js "${dist_directory}"/sourcemaps/ --rewrite --url-prefix 'metamask' } function main { diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 12cd90e66..533907a83 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1276,6 +1276,7 @@ "packages": { "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/utils": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, @@ -1283,6 +1284,18 @@ "browserify>events": true } }, + "@metamask/eth-trezor-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 56edb5e32..26fa23a20 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1404,6 +1404,7 @@ "packages": { "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/utils": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, @@ -1411,6 +1412,18 @@ "browserify>events": true } }, + "@metamask/eth-trezor-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index d56789b6a..19f0b9b7e 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1404,6 +1404,7 @@ "packages": { "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/utils": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, @@ -1411,6 +1412,18 @@ "browserify>events": true } }, + "@metamask/eth-trezor-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 1a6c48c6d..0eaa5467f 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1276,6 +1276,7 @@ "packages": { "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/utils": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, @@ -1283,6 +1284,18 @@ "browserify>events": true } }, + "@metamask/eth-trezor-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 45b960760..21f77e06a 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1417,6 +1417,7 @@ "packages": { "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/utils": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, @@ -1424,6 +1425,18 @@ "browserify>events": true } }, + "@metamask/eth-trezor-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true diff --git a/package.json b/package.json index 680305809..b5650934e 100644 --- a/package.json +++ b/package.json @@ -244,7 +244,7 @@ "@metamask/eth-ledger-bridge-keyring": "^0.15.0", "@metamask/eth-snap-keyring": "^0.1.3", "@metamask/eth-token-tracker": "^4.0.0", - "@metamask/eth-trezor-keyring": "^1.0.0", + "@metamask/eth-trezor-keyring": "^1.1.0", "@metamask/etherscan-link": "^2.2.0", "@metamask/gas-fee-controller": "^6.0.1", "@metamask/jazzicon": "^2.0.0", diff --git a/ui/pages/create-account/connect-hardware/index.js b/ui/pages/create-account/connect-hardware/index.js index e55d16c01..93a6c82c7 100644 --- a/ui/pages/create-account/connect-hardware/index.js +++ b/ui/pages/create-account/connect-hardware/index.js @@ -60,6 +60,7 @@ export const LATTICE_HD_PATHS = [ const TREZOR_TESTNET_PATH = `m/44'/1'/0'/0`; export const TREZOR_HD_PATHS = [ { name: `BIP44 Standard (e.g. MetaMask, Trezor)`, value: BIP44_PATH }, + { name: `Legacy (Ledger / MEW / MyCrypto)`, value: MEW_PATH }, { name: `Trezor Testnets`, value: TREZOR_TESTNET_PATH }, ]; diff --git a/yarn.lock b/yarn.lock index 604271ab0..8afd46255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4182,17 +4182,18 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-trezor-keyring@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/eth-trezor-keyring@npm:1.0.0" +"@metamask/eth-trezor-keyring@npm:^1.1.0": + version: 1.1.0 + resolution: "@metamask/eth-trezor-keyring@npm:1.1.0" dependencies: "@ethereumjs/tx": "npm:^4.0.0" "@ethereumjs/util": "npm:^8.0.0" "@metamask/eth-sig-util": "npm:^5.0.2" + "@metamask/utils": "npm:^4.0.0" "@trezor/connect-plugin-ethereum": "npm:^9.0.1" "@trezor/connect-web": "npm:^9.0.6" hdkey: "npm:0.8.0" - checksum: 421da0ffef37f92d0b16d360acf00317bce32bf1a5471d98cf30b7536df38f111cb894006210997c8c1aa6da2e1b291e22571451cd771a8d2f084da111a2d038 + checksum: eb1ac827d07a6c2d7b0f1f291691b13b1b65db85f4bced5e2af9f5bdf9117a9b725673b069ad67fd57b7c525cbb53755f8b3877a1f2c72e7812b2d8e127a52a9 languageName: node linkType: hard @@ -24214,7 +24215,7 @@ __metadata: "@metamask/eth-ledger-bridge-keyring": "npm:^0.15.0" "@metamask/eth-snap-keyring": "npm:^0.1.3" "@metamask/eth-token-tracker": "npm:^4.0.0" - "@metamask/eth-trezor-keyring": "npm:^1.0.0" + "@metamask/eth-trezor-keyring": "npm:^1.1.0" "@metamask/etherscan-link": "npm:^2.2.0" "@metamask/forwarder": "npm:^1.1.0" "@metamask/gas-fee-controller": "npm:^6.0.1" From d3d30fd373b7deedce0c02bf36ed1270a3c237df Mon Sep 17 00:00:00 2001 From: Howard Braham Date: Tue, 22 Aug 2023 22:13:13 -0700 Subject: [PATCH 083/102] fix(settings): fixed two IPFS gateway issues (#19700) * fix(settings): fixed two IPFS gateway issues - adds back in two bugfixes that were originally in #19283 - fixes #16871 - fixes #18140 - achieves 100% code coverage for /ui/pages/settings/security-tab - removes the npm package `valid-url`, which has not been updated in 10 years * changes after #20172 was merged * improved URL validation (specifically spaces) * better Jest coverage * response to legobeat review * fixing lint and Jest --- .../handlers/add-ethereum-chain.js | 36 +++-- app/scripts/lib/util.test.js | 52 ++++++- app/scripts/lib/util.ts | 67 ++++++--- jest.config.js | 5 +- package.json | 1 - test/data/mock-state.json | 2 + ui/pages/keychains/reveal-seed.js | 2 +- .../networks-form/networks-form.js | 97 ++++++------- .../__snapshots__/security-tab.test.js.snap | 89 ++++++------ .../security-tab/security-tab.component.js | 137 +++++++++--------- .../security-tab/security-tab.test.js | 112 +++++++++++++- yarn.lock | 8 - 12 files changed, 381 insertions(+), 227 deletions(-) diff --git a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js index 7c0c5af57..f7c2d586c 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js @@ -1,16 +1,16 @@ -import { ethErrors, errorCodes } from 'eth-rpc-errors'; -import validUrl from 'valid-url'; -import { omit } from 'lodash'; import { ApprovalType } from '@metamask/controller-utils'; +import { errorCodes, ethErrors } from 'eth-rpc-errors'; +import { omit } from 'lodash'; import { MESSAGE_TYPE, UNKNOWN_TICKER_SYMBOL, } from '../../../../../shared/constants/app'; +import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; import { isPrefixedFormattedHexString, isSafeChainId, } from '../../../../../shared/modules/network.utils'; -import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; +import { getValidUrl } from '../../util'; const addEthereumChain = { methodNames: [MESSAGE_TYPE.ADD_ETHEREUM_CHAIN], @@ -83,27 +83,25 @@ async function addEthereumChainHandler( ); } - const isLocalhost = (strUrl) => { - try { - const url = new URL(strUrl); - return url.hostname === 'localhost' || url.hostname === '127.0.0.1'; - } catch (error) { - return false; - } - }; + function isLocalhostOrHttps(urlString) { + const url = getValidUrl(urlString); + + return ( + url !== null && + (url.hostname === 'localhost' || + url.hostname === '127.0.0.1' || + url.protocol === 'https:') + ); + } const firstValidRPCUrl = Array.isArray(rpcUrls) - ? rpcUrls.find( - (rpcUrl) => isLocalhost(rpcUrl) || validUrl.isHttpsUri(rpcUrl), - ) + ? rpcUrls.find((rpcUrl) => isLocalhostOrHttps(rpcUrl)) : null; const firstValidBlockExplorerUrl = blockExplorerUrls !== null && Array.isArray(blockExplorerUrls) - ? blockExplorerUrls.find( - (blockExplorerUrl) => - isLocalhost(blockExplorerUrl) || - validUrl.isHttpsUri(blockExplorerUrl), + ? blockExplorerUrls.find((blockExplorerUrl) => + isLocalhostOrHttps(blockExplorerUrl), ) : null; diff --git a/app/scripts/lib/util.test.js b/app/scripts/lib/util.test.js index c93e9f8e0..3b70d9be7 100644 --- a/app/scripts/lib/util.test.js +++ b/app/scripts/lib/util.test.js @@ -1,24 +1,27 @@ -import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; import { - ENVIRONMENT_TYPE_POPUP, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_BACKGROUND, - PLATFORM_FIREFOX, - PLATFORM_OPERA, + ENVIRONMENT_TYPE_FULLSCREEN, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_POPUP, PLATFORM_CHROME, PLATFORM_EDGE, + PLATFORM_FIREFOX, + PLATFORM_OPERA, } from '../../../shared/constants/app'; import { + TransactionEnvelopeType, TransactionStatus, TransactionType, - TransactionEnvelopeType, } from '../../../shared/constants/transaction'; +import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; import { + addUrlProtocolPrefix, deferredPromise, + formatTxMetaForRpcResult, getEnvironmentType, getPlatform, - formatTxMetaForRpcResult, + getValidUrl, + isWebUrl, } from './util'; describe('app utils', () => { @@ -73,6 +76,39 @@ describe('app utils', () => { }); }); + describe('URL utils', () => { + it('should test addUrlProtocolPrefix', () => { + expect(addUrlProtocolPrefix('http://example.com')).toStrictEqual( + 'http://example.com', + ); + expect(addUrlProtocolPrefix('https://example.com')).toStrictEqual( + 'https://example.com', + ); + expect(addUrlProtocolPrefix('example.com')).toStrictEqual( + 'https://example.com', + ); + expect(addUrlProtocolPrefix('exa mple.com')).toStrictEqual(null); + }); + + it('should test isWebUrl', () => { + expect(isWebUrl('http://example.com')).toStrictEqual(true); + expect(isWebUrl('https://example.com')).toStrictEqual(true); + expect(isWebUrl('https://exa mple.com')).toStrictEqual(false); + expect(isWebUrl('')).toStrictEqual(false); + }); + + it('should test getValidUrl', () => { + expect(getValidUrl('http://example.com').toString()).toStrictEqual( + 'http://example.com/', + ); + expect(getValidUrl('https://example.com').toString()).toStrictEqual( + 'https://example.com/', + ); + expect(getValidUrl('https://exa%20mple.com')).toStrictEqual(null); + expect(getValidUrl('')).toStrictEqual(null); + }); + }); + describe('isPrefixedFormattedHexString', () => { it('should return true for valid hex strings', () => { expect(isPrefixedFormattedHexString('0x1')).toStrictEqual(true); diff --git a/app/scripts/lib/util.ts b/app/scripts/lib/util.ts index dc21a9608..a6d159bf4 100644 --- a/app/scripts/lib/util.ts +++ b/app/scripts/lib/util.ts @@ -1,24 +1,24 @@ +import urlLib from 'url'; +import { AccessList } from '@ethereumjs/tx'; import BN from 'bn.js'; import { memoize } from 'lodash'; -import { AccessList } from '@ethereumjs/tx'; -import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network'; - import { - ENVIRONMENT_TYPE_POPUP, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_BACKGROUND, - PLATFORM_FIREFOX, - PLATFORM_OPERA, + ENVIRONMENT_TYPE_FULLSCREEN, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_POPUP, + PLATFORM_BRAVE, PLATFORM_CHROME, PLATFORM_EDGE, - PLATFORM_BRAVE, + PLATFORM_FIREFOX, + PLATFORM_OPERA, } from '../../../shared/constants/app'; -import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; +import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network'; import { TransactionEnvelopeType, TransactionMeta, } from '../../../shared/constants/transaction'; +import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; /** * @see {@link getEnvironmentType} @@ -143,13 +143,13 @@ function checkAlarmExists(alarmList: { name: string }[], alarmName: string) { } export { - getPlatform, - getEnvironmentType, - hexToBn, BnMultiplyByFraction, addHexPrefix, - getChainType, checkAlarmExists, + getChainType, + getEnvironmentType, + getPlatform, + hexToBn, }; // Taken from https://stackoverflow.com/a/1349426/3696652 @@ -235,10 +235,43 @@ export function previousValueComparator( } export function addUrlProtocolPrefix(urlString: string) { - if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) { - return `https://${urlString}`; + let trimmed = urlString.trim(); + + if (trimmed.length && !urlLib.parse(trimmed).protocol) { + trimmed = `https://${trimmed}`; } - return urlString; + + if (getValidUrl(trimmed) !== null) { + return trimmed; + } + + return null; +} + +export function getValidUrl(urlString: string): URL | null { + try { + const url = new URL(urlString); + + if (url.hostname.length === 0 || url.pathname.length === 0) { + return null; + } + + if (url.hostname !== decodeURIComponent(url.hostname)) { + return null; // will happen if there's a %, a space, or other invalid character in the hostname + } + + return url; + } catch (error) { + return null; + } +} + +export function isWebUrl(urlString: string): boolean { + const url = getValidUrl(urlString); + + return ( + url !== null && (url.protocol === 'https:' || url.protocol === 'http:') + ); } interface FormattedTransactionMeta { diff --git a/jest.config.js b/jest.config.js index c95ec4ce8..1b68e475a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,7 +8,7 @@ module.exports = { '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts', '/app/scripts/controllers/transactions/IncomingTransactionHelper.ts', '/app/scripts/flask/**/*.js', - '/app/scripts/lib/**/*.js', + '/app/scripts/lib/**/*.(js|ts)', '/app/scripts/lib/createRPCMethodTrackingMiddleware.js', '/app/scripts/migrations/*.js', '/app/scripts/migrations/*.ts', @@ -48,8 +48,7 @@ module.exports = { '/app/scripts/controllers/sign.test.ts', '/app/scripts/controllers/decrypt-message.test.ts', '/app/scripts/flask/**/*.test.js', - '/app/scripts/lib/**/*.test.js', - '/app/scripts/lib/**/*.test.ts', + '/app/scripts/lib/**/*.test.(js|ts)', '/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js', '/app/scripts/migrations/*.test.(js|ts)', '/app/scripts/platforms/*.test.js', diff --git a/package.json b/package.json index b5650934e..d5f482941 100644 --- a/package.json +++ b/package.json @@ -362,7 +362,6 @@ "single-call-balance-checker-abi": "^1.0.0", "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", - "valid-url": "^1.0.9", "web3-stream-provider": "^4.0.0", "zxcvbn": "^4.4.2" }, diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 69db1fc05..ae58d5dba 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -340,6 +340,8 @@ "unapprovedTypedMessagesCount": 0, "useTokenDetection": true, "useCurrencyRateCheck": true, + "useNftDetection": true, + "openSeaEnabled": true, "advancedGasFee": { "maxBaseFee": "75", "priorityFee": "2" diff --git a/ui/pages/keychains/reveal-seed.js b/ui/pages/keychains/reveal-seed.js index 3dec64888..c2f0125fb 100644 --- a/ui/pages/keychains/reveal-seed.js +++ b/ui/pages/keychains/reveal-seed.js @@ -129,7 +129,7 @@ const RevealSeedPage = () => { const renderPasswordPromptContent = () => { return ( -
handleSubmit(event)}> + { return prefixedChainId; }; -const isValidWhenAppended = (url) => { - const appendedRpc = `http://${url}`; - return validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/u); -}; - const NetworksForm = ({ addNewNetwork, restrictHeight, @@ -208,23 +203,20 @@ const NetworksForm = ({ const validateBlockExplorerURL = useCallback( (url) => { - if (!validUrl.isWebUri(url) && url !== '') { - let errorKey; - let errorMessage; - - if (isValidWhenAppended(url)) { - errorKey = 'urlErrorMsg'; - errorMessage = t('urlErrorMsg'); - } else { - errorKey = 'invalidBlockExplorerURL'; - errorMessage = t('invalidBlockExplorerURL'); + if (url.length > 0 && !isWebUrl(url)) { + if (isWebUrl(`https://${url}`)) { + return { + key: 'urlErrorMsg', + msg: t('urlErrorMsg'), + }; } return { - key: errorKey, - msg: errorMessage, + key: 'invalidBlockExplorerURL', + msg: t('invalidBlockExplorerURL'), }; } + return null; }, [t], @@ -407,7 +399,6 @@ const NetworksForm = ({ const validateRPCUrl = useCallback( (url) => { - const isValidUrl = validUrl.isWebUri(url); const [ { rpcUrl: matchingRPCUrl = null, @@ -417,20 +408,16 @@ const NetworksForm = ({ ] = networksToRender.filter((e) => e.rpcUrl === url); const { rpcUrl: selectedNetworkRpcUrl } = selectedNetwork; - if (!isValidUrl && url !== '') { - let errorKey; - let errorMessage; - if (isValidWhenAppended(url)) { - errorKey = 'urlErrorMsg'; - errorMessage = t('urlErrorMsg'); - } else { - errorKey = 'invalidRPC'; - errorMessage = t('invalidRPC'); + if (url.length > 0 && !isWebUrl(url)) { + if (isWebUrl(`https://${url}`)) { + return { + key: 'urlErrorMsg', + msg: t('urlErrorMsg'), + }; } - return { - key: errorKey, - msg: errorMessage, + key: 'invalidRPC', + msg: t('invalidRPC'), }; } else if (matchingRPCUrl && matchingRPCUrl !== selectedNetworkRpcUrl) { return { diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 85b5b4e77..f4b8627f9 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -448,6 +448,7 @@ exports[`Security Tab should match snapshot 1`] = `
-