diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 248522c29d0..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,372 +0,0 @@ -const path = require('path'); - -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - // latest is best, because it's backwards compatible and we have linted everything - ecmaVersion: 'latest', - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - plugins: [ - 'import', - '@typescript-eslint', - 'react-hooks', - 'jest', - 'chai-friendly', - 'react', - 'local-rules', - ], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - 'plugin:import/typescript', - 'plugin:react-hooks/recommended', - ], - settings: { - react: { - version: 'detect', - }, - 'import/ignore': ['node_modules', '\\.(coffee|scss|css|less|hbs|svg|json)$'], - 'import/resolver': { - node: { - paths: [path.resolve(__dirname, 'eslint-rules')], - }, - }, - }, - env: { - jest: true, - 'jest/globals': true, - }, - ignorePatterns: [ - '**/lib/*', - '**/libDev/*', - '**/dist/*', - '**/coverage/*', - '**/build/*', - '**/build-electron/*', - '**/node_modules/*', - '**/public/*', - 'packages/suite-data/files/*', - 'packages/protobuf/scripts/protobuf-patches/*', - 'packages/address-validator', - 'packages/connect-examples', - 'ci/', - 'eslint-local-rules/*', - ], - rules: { - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/prefer-ts-expect-error': 'error', - // I believe type is enforced by callers. - '@typescript-eslint/explicit-function-return-type': 'off', - // Enforce arrow functions only is afaik not possible. But this helps. - 'func-style': [ - 'error', - 'declaration', - { - allowArrowFunctions: true, - }, - ], - // Fix for TypeScript. - 'react/jsx-filename-extension': [ - 'error', - { - extensions: ['.tsx'], - }, - ], - 'import/order': [ - 1, - { - groups: [['builtin', 'external'], 'internal', ['sibling', 'parent']], - pathGroups: [ - { - pattern: 'react*', - group: 'external', - position: 'before', - }, - { pattern: '@trezor/**', group: 'internal' }, // Translates to /packages/** */ - { pattern: '@suite-native/**', group: 'internal' }, - { pattern: '@suite-common/**', group: 'internal' }, - { pattern: 'src/**', group: 'internal', position: 'after' }, - ], - pathGroupsExcludedImportTypes: ['internal', 'react'], - 'newlines-between': 'always', - }, - ], - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: [ - '**/*fixtures*/**', - '**/*.test.{tsx,ts,js}', - '**/blockchain-link/tests/**', - '**/blockchain-link/webpack/**', - '**/suite-desktop-core/**', - '**/*e2e/**', - '**/suite/src/support/tests/**', - '**/suite-data/**', - '**/*.stories.*', - '**/*webpack.config*', - '**/webpack/**', - ], - includeTypes: true, - }, - ], - // This promotes using default case, which is not always correct (explicit is better than implicit) - 'default-case': 'off', - // Does not work with TypeScript export type. - 'import/prefer-default-export': 'off', - 'import/no-named-as-default': 'off', // default export is forbidden anyway - 'no-nested-ternary': 'error', - // Does not work with Babel react-native to react-native-web - 'import/no-unresolved': 'off', - 'import/extensions': 'off', - // Could be useful, but it's very very very slow - 'import/no-cycle': 'off', - 'import/no-anonymous-default-export': [ - 'error', - { - allowArray: true, - allowLiteral: true, - allowObject: true, - }, - ], - // We have typescript. - 'react/prop-types': 'off', - // It's fine. - 'react/no-multi-comp': 'off', - 'react/no-unescaped-entities': 'off', - 'react/jsx-curly-brace-presence': ['warn', { props: 'never', children: 'never' }], - // This is fine. - 'class-methods-use-this': 'off', - 'lines-between-class-members': 'off', - // We use it for immer. It should be checked by readonly anyway. - 'no-param-reassign': 'off', - // Irrelevant. - 'no-plusplus': 'off', - 'no-return-assign': 'off', - 'consistent-return': 'off', - 'no-console': ['error', { allow: ['warn', 'error'] }], - // TSC checks it. - 'no-undef': 'off', - 'react/jsx-no-undef': 'off', - 'react/react-in-jsx-scope': 'off', - // React Hooks. - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'error', - // Reconsider, maybe enable later: - '@typescript-eslint/explicit-member-accessibility': 'off', - '@typescript-eslint/no-explicit-any': 'off', - 'react/destructuring-assignment': 'off', - 'func-names': 'off', - 'react/require-default-props': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - // We use this syntax - '@typescript-eslint/triple-slash-reference': 'off', - // new rules (eslint 6) temporary disabled until components-v2 and ts-ignore resolve - 'react/jsx-props-no-spreading': 'off', - '@typescript-eslint/ban-ts-ignore': 'off', - // We need empty functions for mocking modules for react-native - '@typescript-eslint/no-empty-function': 'off', - 'no-useless-constructor': 'off', - '@typescript-eslint/no-useless-constructor': 'error', - // valid case of class method overloads in typescript - 'no-dupe-class-members': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - // Missing return type on function - '@typescript-eslint/explicit-module-boundary-types': 'off', - // note you must disable the base rule as it can report incorrect errors - 'no-use-before-define': 'off', - '@typescript-eslint/no-use-before-define': ['error'], - 'require-await': ['error'], - 'react/display-name': 'off', - 'react/jsx-key': 'warn', - 'react/prefer-stateless-function': 'off', // we don't use classes at all - 'react/no-deprecated': 'off', // checked by TS - 'react/no-direct-mutation-state': 'off', // we don't use classes at all - 'react/require-render-return': 'off', // we don't use classes at all - 'react/no-is-mounted': 'off', // we don't use classes at all - 'react/jsx-indent': 'off', // we use prettier - 'prefer-destructuring': [ - 'error', - { - VariableDeclarator: { - array: false, - object: true, - }, - AssignmentExpression: { - array: false, - object: false, - }, - }, - { - enforceForRenamedProperties: false, - }, - ], - - // Node.js - // These rules are specific to JavaScript running on Node.js. - 'handle-callback-err': 'error', // enforces error handling in callbacks (off by default) (on by default in the node environment) - 'no-mixed-requires': 'error', // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) - 'no-new-require': 'error', // disallow use of new operator with the require function (off by default) (on by default in the node environment) - 'no-path-concat': 'error', // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) - 'no-process-exit': 'off', // disallow process.exit() (on by default in the node environment) - 'no-restricted-modules': 'error', // restrict usage of specified node modules (off by default) - 'no-sync': 'off', // disallow use of synchronous methods (off by default) - 'eol-last': 'error', - 'import/no-default-export': 'error', - - // Variables - // These rules have to do with variable declarations. - 'no-label-var': 'error', // disallow labels that share a name with a variable - 'no-shadow': 'off', // @typescript-eslint/no-shadow will be used instead - '@typescript-eslint/no-shadow': [ - 'error', - { builtinGlobals: true, allow: ['_', 'error', 'resolve', 'reject', 'fetch'] }, - ], // disallow declaration of variables already declared in the outer scope - 'no-shadow-restricted-names': 'error', // disallow shadowing of names such as arguments - 'no-undefined': 'off', // disallow use of undefined variable (off by default) - 'no-undef-init': 'error', // disallow use of undefined when initializing variables - 'no-unused-vars': 'off', - 'no-unused-expressions': 0, - 'prefer-const': 'off', - 'chai-friendly/no-unused-expressions': 2, - '@typescript-eslint/no-unused-vars': [ - 'error', - { vars: 'all', args: 'none', ignoreRestSiblings: true, varsIgnorePattern: '^_' }, - ], - '@typescript-eslint/no-restricted-imports': [ - 'error', - { - paths: [{ name: '.' }, { name: '..' }, { name: '../..' }], - patterns: ['@trezor/*/lib', '@trezor/*/lib/**'], - }, - ], - 'no-restricted-syntax': [ - 'error', - { - message: - "Please don't use createAsyncThunk. Use createThunk from @suite-common/redux-utils instead.", - selector: "CallExpression[callee.name='createAsyncThunk']", - }, - { - message: - 'Please don\'t use getState directly. Always use strongly typed selector, because geState is typed as "any" and it\'s dangerous to use it directly.', - selector: - 'MemberExpression[property.type="Identifier"]:matches([object.callee.name="getState"])', - }, - { - message: - 'Do not assign "getState" directly. Always use strongly typed selector, because geState is typed as "any" and it\'s dangerous to use it directly.', - selector: - "VariableDeclarator[init.type='CallExpression']:matches([init.callee.name='getState'])", - }, - { - message: - 'Please don\'t use "state" directly because it\'s typed as "any". Always use it only as parameter for strongly typed selector function.', - selector: - "CallExpression[callee.name='useSelector'] MemberExpression[object.name='state']:matches([property.type='Identifier'])", - }, - ], - 'object-shorthand': [ - 'error', - 'always', - { - ignoreConstructors: false, - avoidQuotes: true, - }, - ], - 'constructor-super': 'error', - 'no-duplicate-imports': 'off', - // disallow renaming import, export, and destructured assignments to the same name - // https://eslint.org/docs/rules/no-useless-rename - 'no-useless-rename': [ - 'error', - { - ignoreDestructuring: false, - ignoreImport: false, - ignoreExport: false, - }, - ], - 'prefer-numeric-literals': 'error', - 'padding-line-between-statements': [ - 'error', - { blankLine: 'always', prev: '*', next: 'return' }, - ], - 'local-rules/no-override-ds-component': ['error', { packageName: '@trezor/components' }], - 'local-rules/no-override-ds-component': [ - 'error', - { packageName: '@trezor/product-components' }, - ], - }, - overrides: [ - { - files: ['**/*.js'], - rules: { - // JS files are usually configs or scripts where require is OK - '@typescript-eslint/no-var-requires': 'off', - 'no-console': 'off', - }, - }, - { - // we are using explicit blacklist because this will enforce new rules in newly created packages - files: [ - 'packages/analytics/**/*', - 'packages/blockchain-link/**/*', - 'packages/components/**/*', - 'packages/product-components/**/*', - 'packages/connect/**/*', - 'packages/connect-common/**/*', - 'packages/connect-explorer/**/*', - 'packages/connect-web/**/*', - 'packages/connect-popup/**/*', - 'packages/connect-iframe/**/*', - 'packages/connect-examples/**/*', - 'packages/connect-plugin-ethereum/**/*', - 'packages/connect-plugin-stellar/**/*', - 'packages/request-manager/**/*', - 'packages/suite/**/*', - 'packages/suite-build/**/*', - 'packages/suite-data/**/*', - 'packages/suite-desktop-api/**/*', - 'packages/suite-storage/**/*', - 'packages/suite-web/**/*', - 'packages/transport/**/*', - 'packages/utxo-lib/**/*', - 'scripts/**/*', - 'docs/**/*', - ], - rules: { - '@typescript-eslint/no-shadow': 'off', - 'import/no-default-export': 'off', - 'import/order': 'off', - '@typescript-eslint/no-unused-vars': 'off', - 'no-console': 'off', - 'react/jsx-no-undef': 'off', - 'no-catch-shadow': 'off', - '@typescript-eslint/no-restricted-imports': 'off', - 'no-restricted-syntax': 'off', - }, - }, - { - files: ['suite-native/**/*'], - rules: { - '@typescript-eslint/no-var-requires': 'off', - 'global-require': 'off', - }, - }, - // tests - { - files: ['**/*.test.*', '**/__tests__/**/*'], - rules: { - 'import/no-extraneous-dependencies': 'off', - 'import/no-unresolved': 'off', - 'import/no-default-export': 'off', - }, - }, - ], -}; diff --git a/.github/workflows/build-desktop-apps.yml b/.github/workflows/build-desktop-apps.yml index 2b5d7013bb1..2660e959946 100644 --- a/.github/workflows/build-desktop-apps.yml +++ b/.github/workflows/build-desktop-apps.yml @@ -48,10 +48,6 @@ jobs: with: node-version-file: ".nvmrc" - - name: Install missing Python deps (to build bcrypto lib in Node) - if: matrix.os == 'macos-14' - run: brew install python-setuptools - - name: Install deps and build libs run: | yarn install --immutable diff --git a/.github/workflows/build-node-bridge.yml b/.github/workflows/build-node-bridge.yml index f936e657975..87af318f8fb 100644 --- a/.github/workflows/build-node-bridge.yml +++ b/.github/workflows/build-node-bridge.yml @@ -8,12 +8,11 @@ on: push: branches: - develop - pull_request: - types: [labeled] workflow_dispatch: jobs: node-bridge-bin-js: + if: github.repository == 'trezor/trezor-suite' name: Build node-bridge bin.js file runs-on: ubuntu-latest steps: diff --git a/.github/workflows/build-suite-native-preview.yml b/.github/workflows/build-suite-native-preview.yml new file mode 100644 index 00000000000..dca504d7a21 --- /dev/null +++ b/.github/workflows/build-suite-native-preview.yml @@ -0,0 +1,81 @@ +name: "[Build] suite-native preview" + +on: + push: + # push develop(default) branch is necessary for this action to update its fingerprint database + branches: [develop] + pull_request: + types: [opened, synchronize, labeled] + paths: + - "suite-native/**" + - "suite-common/**" + - "packages/react-native-usb/**" + - "packages/transport-native/**" + - "yarn.lock" + - ".github/workflows/build-suite-native-preview.yml" + # list of paths is not complete, but it's always possible to dispatch manually using 'build-mobile' label + workflow_dispatch: + # manual dispatch will not add any comment to PR, use label 'build-mobile' if PR exists + +jobs: + update: + if: ( github.event.action != 'labeled' || contains(github.event.pull_request.labels.*.name, 'build-mobile')) && (github.repository == 'trezor/trezor-suite' || github.repository == 'trezor/trezor-suite-private') + name: EAS Update + runs-on: ubuntu-latest + concurrency: fingerprint-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + EXPO_PUBLIC_ENVIRONMENT: preview + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + COMMIT_HASH: ${{ github.sha }} + permissions: + contents: read + pull-requests: write + actions: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: yarn + - name: Setup EAS + uses: expo/expo-github-action@v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN_DEVELOP }} + - name: Install libs + run: yarn workspaces focus @suite-native/app + + - name: Check runtimeVersion builds + run: | + cd suite-native/app + # Read runtimeVersion from dynamic app.json + RUNTIME_VERSION=$(npx expo config --json | jq -r '.runtimeVersion') + echo "Current runtimeVersion is $RUNTIME_VERSION" + # Check if there is a build with the same runtimeVersion on EAS already + EXISTING_BUILD=$(eas build:list --status=finished --build-profile=preview --channel=preview --limit=1 --non-interactive --json --runtime-version=$RUNTIME_VERSION) + if [ "$EXISTING_BUILD" = "[]" ]; then + echo "No build with runtimeVersion $RUNTIME_VERSION found" + echo "RUNTIME_BUILD_EXISTS=false" >> $GITHUB_ENV + else + echo "Found build with runtimeVersion $RUNTIME_VERSION" + echo "$EXISTING_BUILD" + echo "RUNTIME_BUILD_EXISTS=true" >> $GITHUB_ENV + fi + + - name: Create preview builds if fingerprint changed + # Only create preview builds if there is no build with the same runtimeVersion on EAS already + if: env.RUNTIME_BUILD_EXISTS == 'false' + uses: expo/expo-github-action/preview-build@main + with: + command: eas build --profile preview --platform all + working-directory: suite-native/app + + - name: Build message system config + run: yarn message-system-sign-config + + - name: Create preview update + uses: expo/expo-github-action/preview@v8 + with: + command: eas update --auto --branch ${{ env.BRANCH_NAME }} + working-directory: suite-native/app diff --git a/.github/workflows/build-suite-web.yml b/.github/workflows/build-suite-web.yml index d3f89a79679..e5822696110 100644 --- a/.github/workflows/build-suite-web.yml +++ b/.github/workflows/build-suite-web.yml @@ -65,7 +65,7 @@ jobs: run: | yarn message-system-sign-config yarn workspace @trezor/suite-data build:lib - yarn workspace @trezor/connect-iframe build:lib + NODE_ENV=production yarn workspace @trezor/connect-iframe build:lib yarn workspace @trezor/connect-web build yarn workspace @trezor/suite-web build # this step should upload build result to s3 bucket dev.suite.sldev.cz using awscli diff --git a/.github/workflows/release-suite-desktop-web-staging.yml b/.github/workflows/release-suite-desktop-web-staging.yml index ea7139b32bb..abdd85f67f7 100644 --- a/.github/workflows/release-suite-desktop-web-staging.yml +++ b/.github/workflows/release-suite-desktop-web-staging.yml @@ -179,7 +179,7 @@ jobs: run: | yarn message-system-sign-config yarn workspace @trezor/suite-data build:lib - yarn workspace @trezor/connect-iframe build:lib + NODE_ENV=production yarn workspace @trezor/connect-iframe build:lib yarn workspace @trezor/connect-web build yarn workspace @trezor/suite-web build @@ -191,7 +191,7 @@ jobs: create-test-releases: if: github.repository == 'trezor/trezor-suite-release' && github.event.inputs.createTestRelease == 'true' - name: Create test versions for autoupdate sudite-desktop + name: Create test versions for autoupdate suite-desktop environment: suite-production needs: - suite-desktop diff --git a/.github/workflows/template-connect-test-params.yml b/.github/workflows/template-connect-test-params.yml index c95e9eec5ae..c8c3c25ea4f 100644 --- a/.github/workflows/template-connect-test-params.yml +++ b/.github/workflows/template-connect-test-params.yml @@ -2,14 +2,14 @@ name: "[Template] connect unit" on: workflow_call: inputs: - methods: - description: "List of methods to include in tests (example: applySettings,applyFlags,getFeatures)" - type: "string" - required: false testPattern: - description: "Test pattern to use (example: `init` or `methods`)" + description: "Test pattern to use to match for test files (example: `init` or `methods`)" type: "string" required: true + includeFilter: + description: "List of methods to include in tests (example: applySettings,applyFlags,getFeatures)" + type: "string" + required: false testsFirmware: description: "Firmware version for the tests (example: 2-latest, 2.2.0, 2-main)" type: "string" @@ -19,16 +19,6 @@ on: description: "Firmware model for the tests (example: T3T1)" type: "string" required: false - nodeEnvironment: - description: "Should the test run on nodejs environment, it runs by default." - type: "boolean" - required: false - default: true - webEnvironment: - description: "Should the test run on web environment, it runs by default." - type: "boolean" - required: false - default: true testDescription: description: "A description to make test title more descriptive (example: T3T1-latest)" type: "string" @@ -39,12 +29,25 @@ on: type: "boolean" required: false default: false + cache_tx: + description: "Cache transactions" + type: "string" + required: false + default: false + transport: + description: "Transport to use (example: bridge, node-bridge)" + type: "string" + required: false + default: "Bridge" + testEnv: + description: "Environment to test (example: node, web)" + type: "string" + required: true jobs: - node: - name: "node-${{ inputs.testDescription }}" + test: + name: "${{ inputs.testDescription }}" runs-on: ubuntu-latest - if: ${{ inputs.nodeEnvironment }} steps: - uses: actions/checkout@v4 with: @@ -54,54 +57,43 @@ jobs: with: node-version-file: ".nvmrc" cache: yarn - # todo: ideally do not install everything. possibly only devDependencies could be enough for testing (if there was not for building libs)? - - run: sed -i "/\"node\"/d" package.json - - run: yarn install - # nightly test - run without cached txs - - if: ${{ github.event_name == 'schedule' }} - run: echo "ADDITIONAL_ARGS=-c" >> "$GITHUB_ENV" - - if: ${{ inputs.testFirmwareModel }} - run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -m ${{ inputs.testFirmwareModel }}" >> "$GITHUB_ENV" - - if: ${{ inputs.methods }} - run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -i ${{ inputs.methods }}" >> "$GITHUB_ENV" - - if: ${{ inputs.testRandomizedOrder }} - run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -r" >> "$GITHUB_ENV" - - run: './docker/docker-connect-test.sh node -p "${{ inputs.testPattern }}" -f "${{ inputs.testsFirmware }}" $ADDITIONAL_ARGS' - web: - name: "web-${{ inputs.testDescription }}" - runs-on: ubuntu-latest - if: ${{ inputs.webEnvironment }} - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - cache: yarn - - run: | + - if: ${{ inputs.testEnv == 'web' }} + run: | echo -e "\nenableScripts: false" >> .yarnrc.yml # Install dependencies only for @trezor/connect package - - run: yarn workspaces focus @trezor/connect - - name: Retrieve build connect-web + - if: ${{ inputs.testEnv == 'web' }} + run: yarn workspaces focus @trezor/connect + - if: ${{ inputs.testEnv == 'web' }} + name: Retrieve build connect-web uses: actions/download-artifact@v4 with: name: build-artifact-connect-web path: packages/connect-web/build - - name: Retrieve build connect-iframe + - if: ${{ inputs.testEnv == 'web' }} + name: Retrieve build connect-iframe uses: actions/download-artifact@v4 with: name: build-artifact-connect-iframe path: packages/connect-iframe/build - - run: cd packages/connect-iframe && tree . - - name: "Echo download path" + - if: ${{ inputs.testEnv == 'web' }} + run: cd packages/connect-iframe && tree . + - if: ${{ inputs.testEnv == 'web' }} + name: "Echo download path" run: echo ${{steps.download.outputs.download-path}} - - if: ${{ github.event_name == 'schedule' }} + + # todo: ideally do not install everything. possibly only devDependencies could be enough for testing (if there was not for building libs)? + - if: ${{ inputs.testEnv == 'node' }} + run: sed -i "/\"node\"/d" package.json + - if: ${{ inputs.testEnv == 'node' }} + run: yarn install + + - if: ${{ inputs.cache_tx == 'true' }} run: echo "ADDITIONAL_ARGS=-c" >> "$GITHUB_ENV" - if: ${{ inputs.testFirmwareModel }} run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -m ${{ inputs.testFirmwareModel }}" >> "$GITHUB_ENV" - - if: ${{ inputs.methods }} - run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -i ${{ inputs.methods }}" >> "$GITHUB_ENV" - - run: './docker/docker-connect-test.sh web -p "${{ inputs.testPattern }}" -f "${{ inputs.testsFirmware }}" $ADDITIONAL_ARGS' + - if: ${{ inputs.includeFilter }} + run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -i ${{ inputs.includeFilter }}" >> "$GITHUB_ENV" + - if: ${{ inputs.testRandomizedOrder }} + run: echo "ADDITIONAL_ARGS=$ADDITIONAL_ARGS -r" >> "$GITHUB_ENV" + - run: './docker/docker-connect-test.sh ${{ inputs.testEnv }} -p "${{ inputs.testPattern }}" -f "${{ inputs.testsFirmware }}" $ADDITIONAL_ARGS' diff --git a/.github/workflows/test-connect.yml b/.github/workflows/test-connect.yml index 30462d810ad..001be06a531 100644 --- a/.github/workflows/test-connect.yml +++ b/.github/workflows/test-connect.yml @@ -69,99 +69,92 @@ jobs: outputs: dailyMatrix: ${{ steps.set-matrix-daily.outputs.dailyMatrix }} otherDevicesMatrix: ${{ steps.set-matrix-other-devices.outputs.otherDevicesMatrix }} - legacyFirmwareMatrix: ${{ steps.set-matrix-legacy-firmware.outputs.legacyFirmwareMatrix }} - canaryFirmwareMatrix: ${{ steps.set-matrix-canary-firmware.outputs.canaryFirmwareMatrix }} + allFwsMatrix: ${{ steps.set-matrix-all-firmwares.outputs.allFwsMatrix }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set daily matrix id: set-matrix-daily - run: echo "dailyMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js daily)" >> $GITHUB_OUTPUT + run: echo "dailyMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js --model=T2T1 --firmware=2-latest --env=all --groups=api,api-flaky --cache_tx=true --transport=Bridge)" >> $GITHUB_OUTPUT - - name: Set legacy devices matrix - id: set-matrix-legacy-firmware - run: echo "legacyFirmwareMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js legacyFirmware)" >> $GITHUB_OUTPUT - - - name: Set canary devices matrix - id: set-matrix-canary-firmware - run: echo "canaryFirmwareMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js canaryFirmware)" >> $GITHUB_OUTPUT + - name: Set all firmwares matrix + id: set-matrix-all-firmwares + run: echo "allFwsMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js --model=T2T1 --firmware=all --env=all --groups=all --cache_tx=false --transport=Bridge)" >> $GITHUB_OUTPUT - name: Set other devices matrix id: set-matrix-other-devices - run: echo "otherDevicesMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js otherDevices)" >> $GITHUB_OUTPUT + run: echo "otherDevicesMatrix=$(node ./scripts/ci/connect-test-matrix-generator.js --model=all --firmware=2-main --env=node --groups=api --cache_tx=true --transport=Bridge)" >> $GITHUB_OUTPUT - connect-PR: + PR-check: needs: [build, set-matrix] - name: PR-${{ matrix.name }} + name: PR-check ${{ matrix.key }} + if: github.repository == 'trezor/trezor-suite' uses: ./.github/workflows/template-connect-test-params.yml with: - testPattern: ${{ matrix.pattern }} - methods: ${{ matrix.methods }} + testPattern: ${{ matrix.groups.pattern }} + includeFilter: ${{ matrix.groups.includeFilter }} testsFirmware: ${{ matrix.firmware }} - testDescription: ${{ matrix.name }} + testDescription: ${{ matrix.env }}-${{ matrix.groups.pattern }}-${{ matrix.groups.name }} + cache_tx: ${{ matrix.cache_tx }} + transport: ${{ matrix.transport }} + testEnv: ${{ matrix.env }} + testFirmwareModel: ${{ matrix.model }} strategy: fail-fast: false matrix: ${{ fromJson(needs.set-matrix.outputs.dailyMatrix) }} - connect-randomized-order: + randomized: needs: [build, set-matrix] - if: github.event_name == 'schedule' && github.repository == 'trezor/trezor-suite' - name: randomized-${{ matrix.name }} + name: randomized ${{ matrix.key }} + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'trezor/trezor-suite' uses: ./.github/workflows/template-connect-test-params.yml with: - testPattern: ${{ matrix.pattern }} - methods: ${{ matrix.methods }} + testPattern: ${{ matrix.groups.pattern }} + includeFilter: ${{ matrix.groups.includeFilter }} testsFirmware: ${{ matrix.firmware }} - testDescription: ${{ matrix.name }}-${{ matrix.firmware }} + testDescription: ${{ matrix.env }}-${{ matrix.groups.pattern }}-${{ matrix.groups.name }} + cache_tx: ${{ matrix.cache_tx }} + transport: ${{ matrix.transport }} + testEnv: ${{ matrix.env }} + testFirmwareModel: ${{ matrix.model }} testRandomizedOrder: true - webEnvironment: false - nodeEnvironment: true strategy: fail-fast: false matrix: ${{ fromJson(needs.set-matrix.outputs.dailyMatrix) }} - connect-legacy-firmware: - needs: [build, set-matrix] - if: github.event_name == 'schedule' && github.repository == 'trezor/trezor-suite' - name: legacy-${{ matrix.name }} - uses: ./.github/workflows/template-connect-test-params.yml - with: - testPattern: ${{ matrix.pattern }} - methods: ${{ matrix.methods }} - testsFirmware: ${{ matrix.firmware }} - testDescription: ${{ matrix.name }}-${{ matrix.firmware }} - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.set-matrix.outputs.legacyFirmwareMatrix) }} - - connect-canary-firmware: + all-fws: needs: [build, set-matrix] - if: github.event_name == 'schedule' && github.repository == 'trezor/trezor-suite' - name: canary-${{ matrix.name }} + name: all-fws ${{ matrix.key }} ${{ matrix.firmware }} + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'trezor/trezor-suite' uses: ./.github/workflows/template-connect-test-params.yml with: - testPattern: ${{ matrix.pattern }} - methods: ${{ matrix.methods }} + testPattern: ${{ matrix.groups.pattern }} + includeFilter: ${{ matrix.groups.includeFilter }} testsFirmware: ${{ matrix.firmware }} - testDescription: ${{ matrix.name }}-${{ matrix.firmware }} + testDescription: ${{ matrix.firmware }}-${{ matrix.groups.pattern }}-${{ matrix.groups.name }}-${{ matrix.env }} + cache_tx: ${{ matrix.cache_tx }} + transport: ${{ matrix.transport }} + testEnv: ${{ matrix.env }} + testFirmwareModel: ${{ matrix.model }} strategy: fail-fast: false - matrix: ${{ fromJson(needs.set-matrix.outputs.canaryFirmwareMatrix) }} + matrix: ${{ fromJson(needs.set-matrix.outputs.allFwsMatrix) }} - connect-other-devices: + all-models-api: needs: [build, set-matrix] - if: github.event_name == 'schedule' && github.repository == 'trezor/trezor-suite' - name: other-devices-${{ matrix.name }}-${{ matrix.model }} + name: all-models-api ${{ matrix.key }} + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'trezor/trezor-suite' uses: ./.github/workflows/template-connect-test-params.yml with: - testPattern: ${{ matrix.pattern }} - methods: ${{ matrix.methods }} + testPattern: ${{ matrix.groups.pattern }} + includeFilter: ${{ matrix.groups.includeFilter }} testsFirmware: ${{ matrix.firmware }} + testDescription: ${{ matrix.model }}-${{ matrix.firmware }} + cache_tx: ${{ matrix.cache_tx }} + transport: ${{ matrix.transport }} + testEnv: ${{ matrix.env }} testFirmwareModel: ${{ matrix.model }} - nodeEnvironment: true - webEnvironment: false - testDescription: ${{ matrix.name }}-${{ matrix.firmware }}-${{ matrix.model }} strategy: fail-fast: false matrix: ${{ fromJson(needs.set-matrix.outputs.otherDevicesMatrix) }} diff --git a/.github/workflows/test-suite-native-e2e-android.yml b/.github/workflows/test-suite-native-e2e-android.yml index 3f55ace16ac..debaa53d2c5 100644 --- a/.github/workflows/test-suite-native-e2e-android.yml +++ b/.github/workflows/test-suite-native-e2e-android.yml @@ -158,11 +158,11 @@ jobs: force-avd-creation: true avd-name: ${{ steps.device.outputs.AVD_NAME }} emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -grpc 8554 - script: yarn test:e2e android.emu.release --headless --take-screenshots failing --record-videos failing --retries 2 + script: yarn test:e2e android.emu.release --headless --take-screenshots failing --record-videos failing - name: "Store failed test screenshot artifacts" if: ${{failure()}} uses: actions/upload-artifact@v4 with: - name: failed-android-tests-screenshots + name: failed-android-tests path: suite-native/app/artifacts diff --git a/.github/workflows/test-suite-web-e2e.yml b/.github/workflows/test-suite-web-e2e.yml index 1bbc508ce08..f67047b39e1 100644 --- a/.github/workflows/test-suite-web-e2e.yml +++ b/.github/workflows/test-suite-web-e2e.yml @@ -22,6 +22,7 @@ on: - ".github/workflows/release*" - ".github/workflows/template*" - ".github/actions/release*/**" + workflow_dispatch: env: DEV_SERVER_URL: "https://dev.suite.sldev.cz" @@ -65,7 +66,7 @@ jobs: run: | yarn message-system-sign-config yarn workspace @trezor/suite-data build:lib - yarn workspace @trezor/connect-iframe build:lib + NODE_ENV=production yarn workspace @trezor/connect-iframe build:lib yarn workspace @trezor/connect-web build yarn workspace @trezor/suite-web build # this step should upload build result to s3 bucket dev.suite.sldev.cz using awscli diff --git a/.gitignore b/.gitignore index 9f221c73a26..47a92d5492d 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,7 @@ local.properties !**/suite-native/app/.env.debug !**/suite-native/app/.env.develop +!**/suite-native/app/.env.preview !**/suite-native/app/.env.production !**/suite-native/app/.env.staging @@ -158,6 +159,3 @@ packages/suite-data/files/translations/master.json # cypress download folder packages/suite-web/e2e/downloads - -# python virtualenv, some Linux users may need it for workaround -.venv diff --git a/.prettierignore b/.prettierignore index 4327d37037e..d2a9c2b4c04 100644 --- a/.prettierignore +++ b/.prettierignore @@ -160,6 +160,4 @@ packages/suite-data/files/translations/master.json # lottie animations packages/suite-data/files/videos/lottie/*.json -# js files which look like ts files -packages/protobuf/scripts/** -/.nx/cache \ No newline at end of file +/.nx/cache diff --git a/README.md b/README.md index cc8571dd104..7e4240471d8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Development is not possible on Windows. This can be circumvented by tools such a ### Getting started - `git clone git@github.com:trezor/trezor-suite.git` +- `cd trezor-suite` - `git submodule update --init --recursive` - `git lfs install` // Set up Git LFS for your user account. You only need to run this once per user account. - `git lfs pull` @@ -28,8 +29,6 @@ Development is not possible on Windows. This can be circumvented by tools such a - `yarn` - `yarn build:libs` -For troubleshooting of common issues, [see guide](https://docs.trezor.io/trezor-suite/misc/suite-setup-troubleshooting.html). - It's recommended to enable `git config --global submodule.recurse true` so you don't need to run `git submodule update --init --recursive` every time when submodules are updated. > You don't need a Trezor device to get into the app, you can use emulator. There is a [Trezor User Env](https://github.com/trezor/trezor-user-env) to help you set it up and run emulator for any Trezor model 🎉 diff --git a/docker/docker-compose.connect-popup-ci.yml b/docker/docker-compose.connect-popup-ci.yml index ba56b58495c..49e81098ee8 100644 --- a/docker/docker-compose.connect-popup-ci.yml +++ b/docker/docker-compose.connect-popup-ci.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.connect-popup-test.yml b/docker/docker-compose.connect-popup-test.yml index 37dbbfbf477..65d20fbb18e 100644 --- a/docker/docker-compose.connect-popup-test.yml +++ b/docker/docker-compose.connect-popup-test.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - DISPLAY=$DISPLAY - QT_X11_NO_MITSHM=1 diff --git a/docker/docker-compose.connect-test.yml b/docker/docker-compose.connect-test.yml index 1f356dd38a2..6f6295dac52 100644 --- a/docker/docker-compose.connect-test.yml +++ b/docker/docker-compose.connect-test.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.connect-webextension-test.yml b/docker/docker-compose.connect-webextension-test.yml index 79ee3393bf4..fe8dd07812d 100644 --- a/docker/docker-compose.connect-webextension-test.yml +++ b/docker/docker-compose.connect-webextension-test.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.suite-ci.yml b/docker/docker-compose.suite-ci.yml index 630a554a1be..a8a759ad41e 100644 --- a/docker/docker-compose.suite-ci.yml +++ b/docker/docker-compose.suite-ci.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.suite-desktop-ci.yml b/docker/docker-compose.suite-desktop-ci.yml index 69196cf66b7..a463ef434cc 100644 --- a/docker/docker-compose.suite-desktop-ci.yml +++ b/docker/docker-compose.suite-desktop-ci.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.suite-dev.yml b/docker/docker-compose.suite-dev.yml index 6390b28d3af..6bc20a5f44b 100644 --- a/docker/docker-compose.suite-dev.yml +++ b/docker/docker-compose.suite-dev.yml @@ -4,7 +4,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - DISPLAY=$DISPLAY - QT_X11_NO_MITSHM=1 diff --git a/docker/docker-compose.suite-native-ci.yml b/docker/docker-compose.suite-native-ci.yml index b09cfedf752..7c5e97aa093 100644 --- a/docker/docker-compose.suite-native-ci.yml +++ b/docker/docker-compose.suite-native-ci.yml @@ -3,7 +3,7 @@ services: trezor-user-env-unix: network_mode: "host" container_name: trezor-user-env.unix - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docker/docker-compose.suite-test.yml b/docker/docker-compose.suite-test.yml index e113aa1dd55..367d3d5c1ac 100644 --- a/docker/docker-compose.suite-test.yml +++ b/docker/docker-compose.suite-test.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - DISPLAY=$DISPLAY - QT_X11_NO_MITSHM=1 diff --git a/docker/docker-compose.transport-test-ci.yml b/docker/docker-compose.transport-test-ci.yml index d755f65637b..a71c779ee62 100644 --- a/docker/docker-compose.transport-test-ci.yml +++ b/docker/docker-compose.transport-test-ci.yml @@ -1,7 +1,7 @@ version: "3.9" services: trezor-user-env-unix: - image: ghcr.io/trezor/trezor-user-env:4ba1d062469911b9be78eff2c0503ac77b06f598 + image: ghcr.io/trezor/trezor-user-env:c637c2f58e799284f6019481fa0de30457bd6b60 environment: - SDL_VIDEODRIVER=dummy - XDG_RUNTIME_DIR=/var/tmp diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index e3da7a61254..a6fe0544ce3 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -21,47 +21,6 @@ - [events](./packages/connect/events.md) - [path](./packages/connect/path.md) - [methods](./packages/connect/methods.md) - - [common parameters](./packages/connect/methods/commonParams.md) - - [getPublicKey](./packages/connect/methods/getPublicKey.md) - - [requestLogin](./packages/connect/methods/requestLogin.md) - - [cipherKeyValue](./packages/connect/methods/cipherKeyValue.md) - - [wipeDevice](./packages/connect/methods/wipeDevice.md) - - [resetDevice](./packages/connect/methods/resetDevice.md) - - [getCoinInfo](./packages/connect/methods/getCoinInfo.md) - - [getAddress](./packages/connect/methods/getAddress.md) - - [getAccountInfo](./packages/connect/methods/getAccountInfo.md) - - [getOwnershipId](./packages/connect/methods/getOwnershipId.md) - - [getOwnershipProof](./packages/connect/methods/getOwnershipProof.md) - - [composeTransaction](./packages/connect/methods/composeTransaction.md) - - [signTransaction](./packages/connect/methods/signTransaction.md) - - [pushTransaction](./packages/connect/methods/pushTransaction.md) - - [signMessage](./packages/connect/methods/signMessage.md) - - [verifyMessage](./packages/connect/methods/verifyMessage.md) - - [authorizeCoinjoin](./packages/connect/methods/authorizeCoinjoin.md) - - [ethereumGetAddress](./packages/connect/methods/ethereumGetAddress.md) - - [ethereumSignTransaction](./packages/connect/methods/ethereumSignTransaction.md) - - [ethereumSignMessage](./packages/connect/methods/ethereumSignMessage.md) - - [ethereumSignTypedData](./packages/connect/methods/ethereumSignTypedData.md) - - [ethereumVerifyMessage](./packages/connect/methods/ethereumVerifyMessage.md) - - [eosGetPublicKey](./packages/connect/methods/eosGetPublicKey.md) - - [eosSignTransaction](./packages/connect/methods/eosSignTransaction.md) - - [nemGetAddress](./packages/connect/methods/nemGetAddress.md) - - [nemSignTransaction](./packages/connect/methods/nemSignTransaction.md) - - [stellarGetAddress](./packages/connect/methods/stellarGetAddress.md) - - [stellarSignTransaction](./packages/connect/methods/stellarSignTransaction.md) - - [cardanoGetPublicKey](./packages/connect/methods/cardanoGetPublicKey.md) - - [cardanoGetAddress](./packages/connect/methods/cardanoGetAddress.md) - - [cardanoSignTransaction](./packages/connect/methods/cardanoSignTransaction.md) - - [rippleGetAddress](./packages/connect/methods/rippleGetAddress.md) - - [rippleSignTransaction](./packages/connect/methods/rippleSignTransaction.md) - - [tezosGetAddress](./packages/connect/methods/tezosGetAddress.md) - - [tezosGetPublicKey](./packages/connect/methods/tezosGetPublicKey.md) - - [tezosSignTransaction](./packages/connect/methods/tezosSignTransaction.md) - - [binanceGetAddress](./packages/connect/methods/binanceGetAddress.md) - - [binanceGetPublicKey](./packages/connect/methods/binanceGetPublicKey.md) - - [binanceSignTransaction](./packages/connect/methods/binanceSignTransaction.md) - - [firmwareUpdate](./packages/connect/methods/firmwareUpdate.md) - - [getFirmwareHash](./packages/connect/methods/getFirmwareHash.md) - [@trezor/suite](./packages/suite/index.md) - [send form](./packages/suite/send.md) - [send form architecture](./packages/suite/send/ARCHITECTURE.md) @@ -88,5 +47,4 @@ - [development on Windows](./misc/development-on-windows.md) - [device naming](./misc/device-naming.md) - [review](./misc/review.md) - - [suite setup troubleshooting](./misc/suite-setup-troubleshooting.md) - [videos](./misc/videos.md) diff --git a/docs/features/localization.md b/docs/features/localization.md index 4ca66a1497a..0f3c3de88ec 100644 --- a/docs/features/localization.md +++ b/docs/features/localization.md @@ -19,6 +19,7 @@ _Do not manually edit language json files in `suite-data/files/translations/` di - `id`: We don't have strict conventions for generating these IDs, although using a prefix `TR_`, or expanded variant `TR_`, where scope is, for example, "ONBOARDING" is really handy. ID must be the same as the object's key. - `defaultMessage`: Used as a source string for translator. It's also a text that is shown in the app as a fallback till someone changes/improves it in Crowdin. - `description`: Optional. Useful for describing the context in which the message occurs, especially if it is not clear from a `defaultMessage` field. +- `dynamic`: Optional. Must be set to true for programmatically constructed keys. Otherwise, the keys will be deleted by the list-duplicates script. Example: diff --git a/docs/misc/development-on-windows.md b/docs/misc/development-on-windows.md index a44e3e95ee9..b4a40d0ab86 100644 --- a/docs/misc/development-on-windows.md +++ b/docs/misc/development-on-windows.md @@ -19,8 +19,6 @@ In WSL: Proceed with the [general readme instructions](../../README.md#trezor-suite-trezorsuite). -See general [setup troubleshooting docs](./suite-setup-troubleshooting.md) if necessary. - ## Connecting USB device On Windows, run `usbipd list`, find the bus id of the Trezor device, e.g. `2-1`. diff --git a/docs/misc/index.md b/docs/misc/index.md index 38a08818b4f..68f032a9cde 100644 --- a/docs/misc/index.md +++ b/docs/misc/index.md @@ -8,6 +8,8 @@ At any time, information stored here might be restructured or moved to a differe location, so as to ensure that the documentation is well structured overall. - [build](./build.md) +- [development on Windows](./development-on-windows.md) - [device naming](./device-naming.md) - [review](./review.md) +- [suite setup troubleshooting](./suite-setup-troubleshooting.md) - [videos](./videos.md) diff --git a/docs/misc/suite-setup-troubleshooting.md b/docs/misc/suite-setup-troubleshooting.md deleted file mode 100644 index f356a1ba72b..00000000000 --- a/docs/misc/suite-setup-troubleshooting.md +++ /dev/null @@ -1,39 +0,0 @@ -# Suite setup troubleshooting - -This document is a guide to overcome common issues with Trezor Suite local dev environment setup. - -The setup happy path is described in the main [Suite README](https://github.com/trezor/trezor-suite). - -### bcrypto fails yarn install - -#### Issue - -On Linux, comamnd `yarn` fails with a similar error: - -``` -bcrypto@npm:5.4.0 couldn't be built successfully (exit code 1, logs can be found here: /tmp/something.log -``` - -And the mentioned logfile contains: `ModuleNotFoundError: No module named 'distutils'` - -#### Solution - -In trezor-suite root directory, run: - -``` -python3 -m venv .venv -source .venv/bin/activate -pip install setuptools -yarn -deactivate -``` - -When you need it next time in future, repeat the same, but **without** the first command (`python3 -m venv .venv`), because virtualenv has already been created. - -#### Explanation - -`bcrypto` is a native module that requires `node-gyp` to build, which in turn requires python `gyp`. -However, `bcrypto` relies on the deprecated Python2 build method, and the built-in `distutils` package, which has been removed in Python 3.12. -Installing `setuptools` via pip solves this, as it also provides legacy `distutils`... -but on Linux Ubuntu you can't pip install globally, as it's managed by apt, and there is no apt package anymore which would solve it. -Though you can install `setuptools` in a virtual environment (a.k.a. venv), and yarn then uses it. diff --git a/docs/tests/e2e-suite-web.md b/docs/tests/e2e-suite-web.md index a086174d848..cbcbcd5f8ac 100644 --- a/docs/tests/e2e-suite-web.md +++ b/docs/tests/e2e-suite-web.md @@ -103,6 +103,25 @@ Steps: ## Notes +### Best practices and established patterns + +#### Page objects + +There should be no direct getting of elements in the test code. We want that and most other logic operating the app to be abstracted to the page objects located in `packages/suite-web/e2e/support/pageObjects`. + +#### Steps + +Give the test a structure by using cy.step() blocks. +Example: + +``` +cy.step('Setup standard wallet with label', () => {...}); +``` + +#### Easily understandable and readable code + +If there is a magic constant or hard to understand block of code, put it under a named constant/function. + ### Image snapshots It is possible to run tests with image snapshots to test for visual regressions. To enable snapshots, use env variable: diff --git a/eslint-local-rules/rules.ts b/eslint-local-rules/rules.ts index c3b0d811265..003fa947b9e 100644 --- a/eslint-local-rules/rules.ts +++ b/eslint-local-rules/rules.ts @@ -1,5 +1,58 @@ import type { Rule } from 'eslint'; +const findNodeWithCalleeInSubTree = (node, calleeName) => { + if (node.type === 'CallExpression' && node.callee.name === calleeName) { + return node; + } + + if ( + 'callee' in node && + typeof node.callee === 'object' && + node.callee !== null && + 'object' in node.callee + ) { + return findNodeWithCalleeInSubTree(node.callee.object, calleeName); + } + + return null; +}; + +const checkNodeForAvoidStyledComponent = (node, context, nodeRef, importedComponents) => { + if (node[nodeRef]?.type === 'CallExpression') { + // We need to recursively search for the styled component in the call tree in case its chained + // + // Example: + // styled(Button).attrs(props => ({ ... {))`...` + // + const nodeWithCallee = findNodeWithCalleeInSubTree(node[nodeRef], 'styled'); + + if (nodeWithCallee === null) { + return; + } + + if ( + nodeWithCallee.callee.name === 'styled' && + nodeWithCallee.arguments[0].type === 'Identifier' + ) { + const componentName = nodeWithCallee.arguments[0].name; + + // Check if component name matches any imported component from the specified packages + for (const [pkgName, components] of importedComponents) { + if (components.has(componentName)) { + context.report({ + node, + messageId: 'avoidStyledComponent', + data: { + packageName: pkgName, + }, + }); + break; + } + } + } + } +}; + export default { 'no-override-ds-component': { meta: { @@ -19,8 +72,10 @@ export default { { type: 'object', properties: { - packageName: { - type: 'string', + packageNames: { + type: 'array', + items: { type: 'string' }, + minItems: 1, }, }, additionalProperties: false, @@ -28,41 +83,38 @@ export default { ], }, create(context) { - const packageName = context.options[0] && context.options[0].packageName; - if (!packageName) { + const packageNames = context.options[0]?.packageNames || []; + if (packageNames.length === 0) { return {}; } - const importedComponents = new Set(); + const importedComponents = new Map>(); // Map to store components per package name return { ImportDeclaration(node) { - if (node.source.value === packageName) { + if (packageNames.includes(node.source.value)) { node.specifiers.forEach(specifier => { if ( specifier.type === 'ImportSpecifier' || specifier.type === 'ImportDefaultSpecifier' ) { - importedComponents.add(specifier.local.name); + if (!importedComponents.has(node.source.value)) { + importedComponents.set(node.source.value, new Set()); + } + importedComponents.get(node.source.value).add(specifier.local.name); } }); } }, + + // This is for case the styled component is assigned to a variable but not evaluated with `...` + VariableDeclarator(node) { + checkNodeForAvoidStyledComponent(node, context, 'init', importedComponents); + }, + + // This for case when the standard styled(Component)`...` is used TaggedTemplateExpression(node) { - if ( - node.tag.type === 'CallExpression' && - node.tag.callee.name === 'styled' && - node.tag.arguments[0].type === 'Identifier' && - importedComponents.has(node.tag.arguments[0].name) - ) { - context.report({ - node, - messageId: 'avoidStyledComponent', - data: { - packageName, - }, - }); - } + checkNodeForAvoidStyledComponent(node, context, 'tag', importedComponents); }, }; }, diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..33fe02284bc --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,18 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/connect-examples/**', // This must be here, connect-examples are not a package + ], + }, + ], + }, + }, +]; diff --git a/nx.json b/nx.json index b0872c528d1..3c0f6271bde 100644 --- a/nx.json +++ b/nx.json @@ -44,11 +44,6 @@ ], "cache": true }, - "lint:js": { - "inputs": ["default", "{workspaceRoot}/.eslintrc.js"], - "outputs": ["{projectRoot}/.eslintcache"], - "cache": true - }, "lint:styles": { "inputs": ["default", "{workspaceRoot}/.stylelintrc"], "outputs": ["{projectRoot}/.stylelintcache"], diff --git a/package.json b/package.json index 0206503c0f3..1805a93dfc6 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,13 @@ "type-check": "yarn nx run-many --target=type-check", "type-check:force": "rimraf -rf -- **/libDev && yarn type-check", "test:unit": "yarn nx run-many --target=test:unit", - "lint:js": "eslint . --cache --cache-strategy content --ignore-path .gitignore", + "lint:js": "eslint . --cache --cache-strategy content --flag unstable_config_lookup_from_file", "lint:styles": "yarn nx run-many --target=lint:styles", "lint": "yarn lint:styles && yarn lint:js", "lint-staged": "npx lint-staged", "lint:shellcheck": "./scripts/shellcheck.sh", "_______ Global Scripts _______": "Shared scripts for running in all workspaces", - "g:eslint": "cd $INIT_CWD && eslint --report-unused-disable-directives --cache --ignore-path ../../.gitignore", + "g:eslint": "cd $INIT_CWD && eslint . --cache --cache-strategy content --flag unstable_config_lookup_from_file", "g:jest": "cd $INIT_CWD && jest", "g:prettier": "cd $INIT_CWD && prettier", "g:rimraf": "cd $INIT_CWD && rimraf", @@ -48,7 +48,6 @@ "nx:build:libs": "yarn nx affected --target=build:lib", "nx:type-check": "yarn nx affected --target=type-check", "nx:test-unit": "yarn nx affected --target=test:unit", - "nx:lint:js": "yarn nx affected --target=lint:js", "nx:lint:styles": "yarn nx affected --target=lint:styles", "nx:format": "yarn nx format:check", "_______ Commands _______": "Useful commands and scripts.", @@ -74,6 +73,7 @@ "native:ios": "yarn workspace @suite-native/app ios", "native:prebuild": "yarn workspace @suite-native/app prebuild", "native:prebuild:clean": "yarn workspace @suite-native/app prebuild:clean", + "native:adhoc": "yarn workspace @suite-native/app build:adhoc", "native:reverse-ports": "yarn workspace @suite-native/app reverse-ports", "_______ Aliases _______": "Aliases for longer commands which we often have to run manually. Names don't have to be pretty or make total sense.", "refs": "yarn update-project-references", @@ -103,10 +103,12 @@ "type-fest": "4.24.0", "bcrypto": "5.4.0", "react": "18.2.0", - "electron": "32.1.2", + "electron": "33.1.0", "@types/node": "20.12.7", "@types/react": "18.2.55", - "bn.js": "5.2.1" + "bn.js": "5.2.1", + "node-gyp": "10.2.0", + "reselect": "5.1.1" }, "devDependencies": { "@babel/cli": "^7.23.9", @@ -120,28 +122,21 @@ "@babel/preset-typescript": "^7.24.7", "@babel/runtime": "^7.23.9", "@suite-common/wallet-types": "workspace:*", + "@trezor/eslint": "workspace:*", "@types/jest": "29.5.12", "@types/node": "20.12.7", "@types/prettier": "^3.0.0", "@types/semver": "^7.5.6", "@types/tar": "^6.1.11", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", "babel-jest": "29.7.0", "depcheck": "^1.4.7", - "eslint": "^8.56.0", - "eslint-plugin-chai-friendly": "^0.7.4", - "eslint-plugin-cypress": "^2.15.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^27.6.3", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-local-rules": "^3.0.2", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint": "^9.13.0", + "globals": "^15.11.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-expo": "^50.0.2", "metro-react-native-babel-preset": "0.77.0", + "node-fetch": "^2.6.4", "nx": "^18.0.3", "patch-package": "8.0.0", "prettier": "^3.3.2", diff --git a/packages/address-validator/eslint.config.mjs b/packages/address-validator/eslint.config.mjs new file mode 100644 index 00000000000..cc9da3ff07b --- /dev/null +++ b/packages/address-validator/eslint.config.mjs @@ -0,0 +1,13 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: [ + // This is a JS only codebase written in JS, so we are not checking it + // Todo: reconsider this decision as most of the ESLint issues are auto-fixable + 'src/**/*.js', + 'tests/**/*.js', + ], + }, +]; diff --git a/packages/address-validator/package.json b/packages/address-validator/package.json index a786e7281d6..481b68c110a 100644 --- a/packages/address-validator/package.json +++ b/packages/address-validator/package.json @@ -9,7 +9,7 @@ "test:unit": "yarn g:jest -c ../../jest.config.base.js" }, "dependencies": { - "base-x": "^3.0.7", + "base-x": "^5.0.0", "bech32": "^2.0.0", "browserify-bignum": "^1.3.0-2", "cbor-js": "^0.1.0", @@ -19,6 +19,7 @@ "lodash": "^4.17.15" }, "devDependencies": { + "@trezor/eslint": "workspace:*", "rimraf": "^6.0.1" } } diff --git a/packages/address-validator/src/crypto/blake256.js b/packages/address-validator/src/crypto/blake256.js index a65a8e3d2f6..39ba7e83e81 100644 --- a/packages/address-validator/src/crypto/blake256.js +++ b/packages/address-validator/src/crypto/blake256.js @@ -27,7 +27,7 @@ Blake256.u256 = [ 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, ]; -Blake256.padding = new Buffer([ +Blake256.padding = Buffer.from([ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -44,7 +44,7 @@ Blake256.prototype._length_carry = function (arr) { }; Blake256.prototype.update = function (data, encoding) { - data = new Buffer(data, encoding); + data = Buffer.from(data, encoding); var block = this._block; var offset = 0; @@ -62,8 +62,8 @@ Blake256.prototype.update = function (data, encoding) { return this; }; -var zo = new Buffer([0x01]); -var oo = new Buffer([0x81]); +var zo = Buffer.from([0x01]); +var oo = Buffer.from([0x81]); function rot(x, n) { return ((x << (32 - n)) | (x >>> n)) >>> 0; @@ -91,7 +91,7 @@ function Blake256() { this._s = [0, 0, 0, 0]; - this._block = new Buffer(64); + this._block = Buffer.alloc(64); this._blockOffset = 0; this._length = [0, 0]; @@ -144,7 +144,7 @@ Blake256.prototype._padding = function () { hi += 1; } - var msglen = new Buffer(8); + var msglen = Buffer.alloc(8); msglen.writeUInt32BE(hi, 0); msglen.writeUInt32BE(lo, 4); @@ -175,7 +175,7 @@ Blake256.prototype._padding = function () { Blake256.prototype.digest = function (encoding) { this._padding(); - var buffer = new Buffer(32); + var buffer = Buffer.alloc(32); for (var i = 0; i < 8; ++i) buffer.writeUInt32BE(this._h[i], i * 4); return buffer.toString(encoding); }; diff --git a/packages/address-validator/src/currencies.js b/packages/address-validator/src/currencies.js index 410371a0dff..3538176897b 100644 --- a/packages/address-validator/src/currencies.js +++ b/packages/address-validator/src/currencies.js @@ -1451,6 +1451,11 @@ var CURRENCIES = [ symbol: 'bnb', validator: ETHValidator, }, + { + name: 'Optimism', + symbol: 'eth', + validator: ETHValidator, + }, { name: 'EOS', symbol: 'eos', diff --git a/packages/address-validator/src/index.d.ts b/packages/address-validator/src/index.d.ts index 8658315aeae..4b3b38c17f8 100644 --- a/packages/address-validator/src/index.d.ts +++ b/packages/address-validator/src/index.d.ts @@ -3,6 +3,7 @@ declare module '@trezor/address-validator' { name: string; symbol: string; } + export type AddressType = | 'address' | 'p2pkh' @@ -11,16 +12,20 @@ declare module '@trezor/address-validator' { | 'p2sh' | 'p2tr' | 'pw-unknown'; + export function validate( address: string, currencyNameOrSymbol?: string, networkType?: string, ): boolean; + export function getAddressType( address: string, currencyNameOrSymbol?: string, networkType?: string, ): AddressType | undefined; + export function getCurrencies(): Currency[]; + export function findCurrency(symbol: string): Currency; } diff --git a/packages/address-validator/src/nano_validator.js b/packages/address-validator/src/nano_validator.js index f57f4e8e89d..c595e7f147d 100644 --- a/packages/address-validator/src/nano_validator.js +++ b/packages/address-validator/src/nano_validator.js @@ -1,6 +1,6 @@ const { addressType } = require('./crypto/utils'); var cryptoUtils = require('./crypto/utils'); -var baseX = require('base-x'); +var baseX = require('base-x').default; var ALLOWED_CHARS = '13456789abcdefghijkmnopqrstuwxyz'; diff --git a/packages/address-validator/src/ripple_validator.js b/packages/address-validator/src/ripple_validator.js index 33dd3f36c9e..ffad6e9a9bb 100644 --- a/packages/address-validator/src/ripple_validator.js +++ b/packages/address-validator/src/ripple_validator.js @@ -1,6 +1,6 @@ const { addressType } = require('./crypto/utils'); var cryptoUtils = require('./crypto/utils'); -var baseX = require('base-x'); +var baseX = require('base-x').default; var ALLOWED_CHARS = 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'; diff --git a/packages/address-validator/src/stellar_validator.js b/packages/address-validator/src/stellar_validator.js index eed4bdc6479..cc788ce3010 100644 --- a/packages/address-validator/src/stellar_validator.js +++ b/packages/address-validator/src/stellar_validator.js @@ -1,5 +1,5 @@ const { addressType } = require('./crypto/utils'); -const baseX = require('base-x'); +const baseX = require('base-x').default; const crc = require('crc'); const cryptoUtils = require('./crypto/utils'); diff --git a/packages/address-validator/tsconfig.json b/packages/address-validator/tsconfig.json index c7ebe855e21..d836e07709c 100644 --- a/packages/address-validator/tsconfig.json +++ b/packages/address-validator/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "libDev" }, - "references": [] + "references": [{ "path": "../eslint" }] } diff --git a/packages/analytics/package.json b/packages/analytics/package.json index a128603115d..f3f739419aa 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/analytics", - "version": "1.2.1", + "version": "1.2.2-beta.1", "license": "See LICENSE.md in repo root", "sideEffects": false, "main": "src/index.ts", @@ -12,7 +12,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", diff --git a/packages/analytics/src/tests/analytics.test.ts b/packages/analytics/src/tests/analytics.test.ts index ff342789000..291a8026d44 100644 --- a/packages/analytics/src/tests/analytics.test.ts +++ b/packages/analytics/src/tests/analytics.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { fixtures } from './fixtures/encodeDataToQueryString'; import { Analytics } from '../analytics'; import { encodeDataToQueryString, getRandomId } from '../utils'; diff --git a/packages/analytics/src/utils.ts b/packages/analytics/src/utils.ts index 5cb59538ee6..031e6b6e043 100644 --- a/packages/analytics/src/utils.ts +++ b/packages/analytics/src/utils.ts @@ -1,7 +1,8 @@ import type { Environment } from '@trezor/env-utils'; -import type { App, Event as AnalyticsEvent } from './types'; import { getWeakRandomId } from '@trezor/utils'; +import type { App, Event as AnalyticsEvent } from './types'; + export const getTrackingRandomId = () => getWeakRandomId(10); export const getRandomId = () => getWeakRandomId(10); diff --git a/packages/connect-deeplink/package.json b/packages/asset-utils/package.json similarity index 55% rename from packages/connect-deeplink/package.json rename to packages/asset-utils/package.json index 0329d95bc88..79425b37bda 100644 --- a/packages/connect-deeplink/package.json +++ b/packages/asset-utils/package.json @@ -1,5 +1,5 @@ { - "name": "@trezor/connect-deeplink", + "name": "@trezor/asset-utils", "version": "1.0.0", "private": true, "license": "See LICENSE.md in repo root", @@ -7,11 +7,6 @@ "main": "src/index", "scripts": { "depcheck": "yarn g:depcheck", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "type-check": "yarn g:tsc --build" - }, - "dependencies": { - "@trezor/connect": "workspace:^", - "@trezor/utils": "workspace:^" } } diff --git a/packages/asset-utils/src/asset-logo-urls.ts b/packages/asset-utils/src/asset-logo-urls.ts new file mode 100644 index 00000000000..dc49d194ceb --- /dev/null +++ b/packages/asset-utils/src/asset-logo-urls.ts @@ -0,0 +1,18 @@ +const ICONS_URL_BASE = 'https://data.trezor.io/suite/icons/coins/'; + +const composeAssetLogoUrl = (fileName: string, quality?: '@2x') => + `${ICONS_URL_BASE}${fileName}${quality === undefined ? '' : quality}.webp`; + +export const getAssetLogoUrl = ({ + coingeckoId, + contractAddress, + quality, +}: { + coingeckoId: string; + contractAddress?: string; + quality?: '@2x'; +}) => + composeAssetLogoUrl( + contractAddress ? `${coingeckoId}--${contractAddress}` : coingeckoId, + quality, + ); diff --git a/packages/asset-utils/src/index.ts b/packages/asset-utils/src/index.ts new file mode 100644 index 00000000000..5465a64c5b4 --- /dev/null +++ b/packages/asset-utils/src/index.ts @@ -0,0 +1 @@ +export { getAssetLogoUrl } from './asset-logo-urls'; diff --git a/packages/asset-utils/tsconfig.json b/packages/asset-utils/tsconfig.json new file mode 100644 index 00000000000..c7ebe855e21 --- /dev/null +++ b/packages/asset-utils/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "libDev" }, + "references": [] +} diff --git a/packages/auth-server/package.json b/packages/auth-server/package.json index cb26b4f10a1..605af0c8452 100644 --- a/packages/auth-server/package.json +++ b/packages/auth-server/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "dev": "yarn g:tsx watch ./src/index.ts", @@ -15,7 +14,7 @@ }, "dependencies": { "cors": "^2.8.5", - "express": "^4.21.0" + "express": "^4.21.1" }, "devDependencies": { "@types/cors": "^2.8.17" diff --git a/packages/blockchain-link-types/package.json b/packages/blockchain-link-types/package.json index 123b2a9839b..6880fad9811 100644 --- a/packages/blockchain-link-types/package.json +++ b/packages/blockchain-link-types/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/blockchain-link-types", - "version": "1.2.1", + "version": "1.2.2-beta.2", "license": "See LICENSE.md in repo root", "sideEffects": false, "main": "src/index.ts", @@ -12,7 +12,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", @@ -20,10 +19,9 @@ "prepublish": "yarn tsx ../../scripts/prepublish.js" }, "dependencies": { - "@solana/web3.js": "^1.95.0", + "@solana/web3.js": "^1.95.4", "@trezor/type-utils": "workspace:*", - "@trezor/utxo-lib": "workspace:*", - "socks-proxy-agent": "6.1.1" + "@trezor/utxo-lib": "workspace:*" }, "devDependencies": { "tsx": "^4.16.3" diff --git a/packages/blockchain-link-types/src/blockbook-api.ts b/packages/blockchain-link-types/src/blockbook-api.ts index 06835e78a8a..2debd21191f 100644 --- a/packages/blockchain-link-types/src/blockbook-api.ts +++ b/packages/blockchain-link-types/src/blockbook-api.ts @@ -33,6 +33,10 @@ export interface EthereumSpecific { gasLimit: number; gasUsed?: number; gasPrice?: string; + l1Fee?: number; + l1FeeScalar?: string; + l1GasPrice?: string; + l1GasUsed?: number; data?: string; parsedData?: EthereumParsedInputData; internalTransfers?: EthereumInternalTransfer[]; @@ -46,14 +50,11 @@ export interface TokenTransfer { from: string; to: string; contract: string; - name: string; + name?: string; symbol?: string; - decimals: number; + decimals: number; // it is optional #14796 value?: string; multiTokenValues?: MultiTokenValue[]; - fingerprint?: string; - policyId?: string; - unit?: string; } export interface Vout { value?: string; @@ -114,6 +115,7 @@ export interface FeeStats { } export interface StakingPool { contract: string; + name: string; pendingBalance: string; pendingDepositedBalance: string; depositedBalance: string; @@ -121,7 +123,6 @@ export interface StakingPool { claimableAmount: string; restakedReward: string; autocompoundBalance: string; - name: string; } export interface ContractInfo { type: string; @@ -175,6 +176,7 @@ export interface Address { contractInfo?: ContractInfo; erc20Contract?: ContractInfo; addressAliases?: { [key: string]: AddressAlias }; + stakingPools?: StakingPool[]; } export interface Utxo { txid: string; @@ -259,6 +261,7 @@ export interface InternalStateColumn { } export interface BlockbookInfo { coin: string; + network: string; host: string; version: string; gitCommit: string; @@ -278,6 +281,7 @@ export interface BlockbookInfo { currentFiatRatesTime?: string; historicalFiatRatesTime?: string; historicalTokenFiatRatesTime?: string; + supportedStakingPools?: string[]; dbSizeFromColumns?: number; dbColumns?: InternalStateColumn[]; about: string; @@ -352,6 +356,7 @@ export interface WsBackendInfo { export interface WsInfoRes { name: string; shortcut: string; + network: string; decimals: number; version: string; bestHeight: number; @@ -372,11 +377,15 @@ export interface WsBlockReq { page?: number; } export interface WsBlockFilterReq { + scriptType: string; blockHash: string; + M?: number; } export interface WsBlockFiltersBatchReq { + scriptType: string; bestKnownBlockHash: string; pageSize?: number; + M?: number; } export interface WsAccountUtxoReq { descriptor: string; @@ -437,7 +446,17 @@ export interface WsFiatRatesTickersListReq { export interface WsMempoolFiltersReq { scriptType: string; fromTimestamp: number; + M?: number; +} +export interface WsRpcCallReq { + from?: string; + to: string; + data: string; +} +export interface WsRpcCallRes { + data: string; } export interface MempoolTxidFilterEntries { entries?: { [key: string]: string }; + usedZeroedKey?: boolean; } diff --git a/packages/blockchain-link-types/src/blockbook.ts b/packages/blockchain-link-types/src/blockbook.ts index 5ea2ab45c13..34973f812e0 100644 --- a/packages/blockchain-link-types/src/blockbook.ts +++ b/packages/blockchain-link-types/src/blockbook.ts @@ -6,6 +6,7 @@ import type { GetFiatRatesForTimestampsParams, GetFiatRatesTickersListParams, EstimateFeeParams, + RpcCallParams, AccountInfoParams, } from './params'; import type { AccountBalanceHistory, FiatRatesBySymbol, TokenStandard } from './common'; @@ -210,6 +211,7 @@ declare function FSend( params: GetFiatRatesForTimestampsParams, ): Promise; declare function FSend(method: 'estimateFee', params: EstimateFeeParams): Promise; +declare function FSend(method: 'rpcCall', params: RpcCallParams): Promise<{ data: string }>; declare function FSend( method: 'subscribeAddresses', params: { addresses: string[] }, diff --git a/packages/blockchain-link-types/src/common.ts b/packages/blockchain-link-types/src/common.ts index 2c7039f2179..75004777e2c 100644 --- a/packages/blockchain-link-types/src/common.ts +++ b/packages/blockchain-link-types/src/common.ts @@ -1,4 +1,4 @@ -import type { SocksProxyAgentOptions } from 'socks-proxy-agent'; +import type tls from 'tls'; import type { Transaction as BlockbookTransaction, VinVout } from './blockbook'; import type { @@ -10,6 +10,25 @@ import type { /* Common types used in both params and responses */ +type AgentOptions = { + timeout?: number | undefined; +}; + +interface BaseSocksProxyAgentOptions { + host?: string | null; + port?: string | number | null; + username?: string | null; + tls?: tls.ConnectionOptions | null; + ipaddress?: string; + type: 4 | 5; + userId?: string; + password?: string; +} + +// todo: connect10 here we are using the old `SocksProxyAgentOptions` from older version of socks-proxy-agent +// but we keep the old API so we do not introduce breaking changes. +interface SocksProxyAgentOptions extends AgentOptions, BaseSocksProxyAgentOptions {} + export interface BlockchainSettings { name: string; worker: string | (() => any); @@ -34,7 +53,7 @@ export interface ServerInfo { consensusBranchId?: number; // zcash current branch id } -export type TokenStandard = 'ERC20' | 'ERC1155' | 'ERC721' | 'SPL'; +export type TokenStandard = 'ERC20' | 'ERC1155' | 'ERC721' | 'SPL' | 'BEP20'; export type TransferType = 'sent' | 'recv' | 'self' | 'unknown'; diff --git a/packages/blockchain-link-types/src/constants/messages.ts b/packages/blockchain-link-types/src/constants/messages.ts index 7b3ba7305d4..e3e82e88f85 100644 --- a/packages/blockchain-link-types/src/constants/messages.ts +++ b/packages/blockchain-link-types/src/constants/messages.ts @@ -14,6 +14,7 @@ export const GET_ACCOUNT_UTXO = 'm_get_account_utxo'; export const GET_TRANSACTION = 'm_get_transaction'; export const GET_TRANSACTION_HEX = 'm_get_transaction_hex'; export const ESTIMATE_FEE = 'm_estimate_fee'; +export const RPC_CALL = 'm_rpc_call'; export const SUBSCRIBE = 'm_subscribe'; export const UNSUBSCRIBE = 'm_unsubscribe'; export const PUSH_TRANSACTION = 'm_push_tx'; diff --git a/packages/blockchain-link-types/src/constants/responses.ts b/packages/blockchain-link-types/src/constants/responses.ts index 313b58f16de..d975f5d6289 100644 --- a/packages/blockchain-link-types/src/constants/responses.ts +++ b/packages/blockchain-link-types/src/constants/responses.ts @@ -13,6 +13,7 @@ export const GET_ACCOUNT_BALANCE_HISTORY = 'r_get_account_balance_history'; export const GET_TRANSACTION = 'r_get_transaction'; export const GET_TRANSACTION_HEX = 'r_get_transaction_hex'; export const ESTIMATE_FEE = 'r_estimate_fee'; +export const RPC_CALL = 'r_rpc_call'; export const SUBSCRIBE = 'r_subscribe'; export const UNSUBSCRIBE = 'r_unsubscribe'; export const PUSH_TRANSACTION = 'r_push_tx'; diff --git a/packages/blockchain-link-types/src/messages.ts b/packages/blockchain-link-types/src/messages.ts index 64d09f7e67a..ae9f7a4f09d 100644 --- a/packages/blockchain-link-types/src/messages.ts +++ b/packages/blockchain-link-types/src/messages.ts @@ -6,6 +6,7 @@ import type { GetFiatRatesForTimestampsParams, GetFiatRatesTickersListParams, EstimateFeeParams, + RpcCallParams, AccountInfoParams, } from './params'; @@ -77,6 +78,11 @@ export interface EstimateFee { payload: EstimateFeeParams; } +export interface RpcCall { + type: typeof MESSAGES.RPC_CALL; + payload: RpcCallParams; +} + export interface Subscribe { type: typeof MESSAGES.SUBSCRIBE; payload: @@ -144,6 +150,7 @@ export type Message = | ChannelMessage | ChannelMessage | ChannelMessage + | ChannelMessage | ChannelMessage | ChannelMessage | ChannelMessage; diff --git a/packages/blockchain-link-types/src/params.ts b/packages/blockchain-link-types/src/params.ts index a5bbf442843..ebb9bd42662 100644 --- a/packages/blockchain-link-types/src/params.ts +++ b/packages/blockchain-link-types/src/params.ts @@ -35,6 +35,12 @@ export interface EstimateFeeParams { }; } +export interface RpcCallParams { + from: string; + to: string; + data: string; +} + export interface AccountInfoParams { descriptor: string; // address or xpub details?: 'basic' | 'tokens' | 'tokenBalances' | 'txids' | 'txs'; // depth, default: 'basic' diff --git a/packages/blockchain-link-types/src/responses.ts b/packages/blockchain-link-types/src/responses.ts index 5a88dbedf0f..aa96ef26489 100644 --- a/packages/blockchain-link-types/src/responses.ts +++ b/packages/blockchain-link-types/src/responses.ts @@ -101,6 +101,13 @@ export interface EstimateFee { }[]; } +export interface RpcCall { + type: typeof RESPONSES.RPC_CALL; + payload: { + data: string; + }; +} + export interface Subscribe { type: typeof RESPONSES.SUBSCRIBE; payload: { subscribed: boolean }; @@ -173,6 +180,7 @@ export type Response = | ChannelMessage | ChannelMessage | ChannelMessage + | ChannelMessage | ChannelMessage | ChannelMessage | ChannelMessage diff --git a/packages/blockchain-link-utils/.eslintrc.js b/packages/blockchain-link-utils/.eslintrc.js deleted file mode 100644 index d5e6c09c159..00000000000 --- a/packages/blockchain-link-utils/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - 'import/no-extraneous-dependencies': ['error', { includeTypes: true }], - }, -}; diff --git a/packages/blockchain-link-utils/eslint.config.mjs b/packages/blockchain-link-utils/eslint.config.mjs new file mode 100644 index 00000000000..eafc52e3d55 --- /dev/null +++ b/packages/blockchain-link-utils/eslint.config.mjs @@ -0,0 +1,15 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + includeTypes: true, + }, + ], + }, + }, +]; diff --git a/packages/blockchain-link-utils/package.json b/packages/blockchain-link-utils/package.json index 113a2fdec92..59a6e5545b7 100644 --- a/packages/blockchain-link-utils/package.json +++ b/packages/blockchain-link-utils/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/blockchain-link-utils", - "version": "1.2.1", + "version": "1.2.2-beta.2", "license": "See LICENSE.md in repo root", "sideEffects": false, "main": "src/index.ts", @@ -12,7 +12,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", @@ -22,12 +21,13 @@ }, "dependencies": { "@mobily/ts-belt": "^3.13.1", - "@solana/web3.js": "^1.95.0", + "@solana/web3.js": "^1.95.4", "@trezor/env-utils": "workspace:*", "@trezor/utils": "workspace:*" }, "devDependencies": { "@trezor/blockchain-link-types": "workspace:*", + "@trezor/eslint": "workspace:*", "@trezor/type-utils": "workspace:*", "tsx": "^4.16.3" }, diff --git a/packages/blockchain-link-utils/src/solana.ts b/packages/blockchain-link-utils/src/solana.ts index 918b212c122..e9682634953 100644 --- a/packages/blockchain-link-utils/src/solana.ts +++ b/packages/blockchain-link-utils/src/solana.ts @@ -242,9 +242,9 @@ export const getTargets = ( ): Transaction['targets'] => effects .filter(effect => { - // for 'self` transaction there is only one effect + // exclude target for 'self` transaction because it is redundant with fee if (txType === 'self') { - return true; + return false; } // ignore all targets for unknown transactions if (txType === 'unknown') { @@ -365,10 +365,12 @@ export const getDetails = ( // include positive effects only on accountAddress for tx types other then sent, otherwise it // leads to foreign address being displayed next to users own address which might lead to confusion - const receivers = effects.filter( - ({ amount, address }) => - amount.isPositive() && (txType !== 'sent' ? address === accountAddress : true), - ); + const receivers = effects + .filter( + ({ amount, address }) => + amount.isPositive() && (txType !== 'sent' ? address === accountAddress : true), + ) + .filter(({ address }) => !(txType === 'self' && address === accountAddress)); const getVin = ({ address, amount }: { address: string; amount?: BigNumber }, i: number) => ({ txid: transaction.transaction.signatures[0].toString(), @@ -409,7 +411,8 @@ export const getAmount = ( return '0'; } if (txType === 'self') { - return accountEffect.amount?.abs().toString(); + // we do not want to show amount because its redundant with fee + return '0'; } return accountEffect.amount.toString(); diff --git a/packages/blockchain-link-utils/src/tests/fixtures/solana.ts b/packages/blockchain-link-utils/src/tests/fixtures/solana.ts index daa33a96501..a00f2e06647 100644 --- a/packages/blockchain-link-utils/src/tests/fixtures/solana.ts +++ b/packages/blockchain-link-utils/src/tests/fixtures/solana.ts @@ -554,15 +554,7 @@ export const fixtures = { txType: 'self', accountAddress: effects.negative.address, }, - expectedOutput: [ - { - n: 0, - addresses: [effects.negative.address], - isAddress: true, - amount: effects.negative.amount.abs().toString(), - isAccountTarget: true, - }, - ], + expectedOutput: [], }, { description: 'should return an array with a target for "sent" transaction type', @@ -641,7 +633,7 @@ export const fixtures = { accountEffect: effects.negative, txType: 'self', }, - expectedOutput: effects.negative.amount.abs().toString(), + expectedOutput: '0', }, { description: 'should return the amount as a string for other transaction types', diff --git a/packages/blockchain-link-utils/tsconfig.json b/packages/blockchain-link-utils/tsconfig.json index 4a916aecceb..384167f6a4b 100644 --- a/packages/blockchain-link-utils/tsconfig.json +++ b/packages/blockchain-link-utils/tsconfig.json @@ -5,6 +5,7 @@ { "path": "../env-utils" }, { "path": "../utils" }, { "path": "../blockchain-link-types" }, + { "path": "../eslint" }, { "path": "../type-utils" } ] } diff --git a/packages/blockchain-link-utils/tsconfig.lib.json b/packages/blockchain-link-utils/tsconfig.lib.json index 0a807b03a87..ff5e7509065 100644 --- a/packages/blockchain-link-utils/tsconfig.lib.json +++ b/packages/blockchain-link-utils/tsconfig.lib.json @@ -14,6 +14,9 @@ { "path": "../blockchain-link-types" }, + { + "path": "../eslint" + }, { "path": "../type-utils" } diff --git a/packages/blockchain-link/.eslintrc.js b/packages/blockchain-link/.eslintrc.js deleted file mode 100644 index ee0d328e90a..00000000000 --- a/packages/blockchain-link/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - rules: { - camelcase: 'off', - 'no-underscore-dangle': 'off', - 'no-console': 'warn', - }, -}; diff --git a/packages/blockchain-link/eslint.config.mjs b/packages/blockchain-link/eslint.config.mjs new file mode 100644 index 00000000000..a30c5e34ea0 --- /dev/null +++ b/packages/blockchain-link/eslint.config.mjs @@ -0,0 +1,24 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + camelcase: 'off', + 'no-underscore-dangle': 'off', + 'no-console': 'warn', + 'import/no-default-export': 'off', + '@typescript-eslint/no-shadow': 'off', // Todo: shall be fixed + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/tests/**', + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/blockchain-link/package.json b/packages/blockchain-link/package.json index 77b72c32733..d43338e6fc1 100644 --- a/packages/blockchain-link/package.json +++ b/packages/blockchain-link/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/blockchain-link", - "version": "2.3.1", + "version": "2.3.2-beta.2", "author": "Trezor ", "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/blockchain-link", "description": "High-level javascript interface for blockchain communication", @@ -56,7 +56,6 @@ "build:workers": "yarn g:rimraf build && yarn build:workers-web && yarn build:workers-module", "build:workers-web": "webpack --config ./webpack/workers.web.js", "build:workers-module": "webpack --config ./webpack/workers.module.js", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest --verbose -c jest.config.unit.js", "test:integration": "yarn g:jest -c jest.config.integration.js", "type-check": "yarn g:tsc --build tsconfig.json", @@ -65,28 +64,29 @@ }, "devDependencies": { "@trezor/e2e-utils": "workspace:*", + "@trezor/eslint": "workspace:*", "@trezor/type-utils": "workspace:*", "fs-extra": "^11.2.0", "html-webpack-plugin": "^5.6.0", "tiny-worker": "^2.3.0", "tsx": "^4.16.3", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", "worker-loader": "^3.0.8" }, "dependencies": { "@solana/buffer-layout": "^4.0.1", - "@solana/web3.js": "^1.95.0", + "@solana/web3.js": "^1.95.4", "@trezor/blockchain-link-types": "workspace:*", "@trezor/blockchain-link-utils": "workspace:*", "@trezor/env-utils": "workspace:*", "@trezor/utils": "workspace:*", "@trezor/utxo-lib": "workspace:*", - "@types/web": "^0.0.162", + "@types/web": "^0.0.174", "events": "^3.3.0", "ripple-lib": "^1.10.1", - "socks-proxy-agent": "6.1.1", + "socks-proxy-agent": "8.0.4", "ws": "^8.18.0" }, "peerDependencies": { diff --git a/packages/blockchain-link/src/index.ts b/packages/blockchain-link/src/index.ts index 407483353c5..3ba26ef9942 100644 --- a/packages/blockchain-link/src/index.ts +++ b/packages/blockchain-link/src/index.ts @@ -50,7 +50,7 @@ const initWorker = async (settings: BlockchainSettings) => { worker.onerror = null; try { worker.terminate(); - } catch (error) { + } catch { // empty } @@ -229,6 +229,13 @@ class BlockchainLink extends TypedEmitter { }); } + rpcCall(payload: MessageTypes.RpcCall['payload']): Promise { + return this.sendMessage({ + type: MESSAGES.RPC_CALL, + payload, + }); + } + /** * Subscribe for live changes in * - blockchain i.e new blocks mined. diff --git a/packages/blockchain-link/src/ui/config.ts b/packages/blockchain-link/src/ui/config.ts index df63223da52..dd35b4a197b 100644 --- a/packages/blockchain-link/src/ui/config.ts +++ b/packages/blockchain-link/src/ui/config.ts @@ -91,7 +91,29 @@ export default [ blockchain: { name: 'BNB Smart Chain', worker: 'js/blockbook-worker.js', - server: ['https://bsc1.trezor.io'], + server: ['https://bsc1.trezor.io', 'https://bsc2.trezor.io'], + debug: true, + }, + data: { + address: '', + accountInfoOptions: { + page: 1, + pageSize: 25, + contractFilter: undefined, + }, + estimateFeeOptions: { + blocks: [1, 2, 10], + }, + txid: '', + tx: '', + subscribe: '', + }, + }, + { + blockchain: { + name: 'Optimism', + worker: 'js/blockbook-worker.js', + server: ['https://op1.trezor.io', 'https://op2.trezor.io'], debug: true, }, data: { diff --git a/packages/blockchain-link/src/utils/socks-proxy-agent.ts b/packages/blockchain-link/src/utils/socks-proxy-agent.ts index 4b4519d5ff5..d28767e5b7a 100644 --- a/packages/blockchain-link/src/utils/socks-proxy-agent.ts +++ b/packages/blockchain-link/src/utils/socks-proxy-agent.ts @@ -1,6 +1,4 @@ // socks-proxy-agent is not supported in browser // use fallback module. see package.json "browser" field -const createSocksProxyAgent = (_opts: string) => ({}); - -export default createSocksProxyAgent; +export const SocksProxyAgent = (_opts: string) => ({}); diff --git a/packages/blockchain-link/src/workers/baseWebsocket.ts b/packages/blockchain-link/src/workers/baseWebsocket.ts index 349dc29026c..0132080c76e 100644 --- a/packages/blockchain-link/src/workers/baseWebsocket.ts +++ b/packages/blockchain-link/src/workers/baseWebsocket.ts @@ -1,8 +1,6 @@ import WebSocket from 'ws'; -import { createDeferred } from '@trezor/utils'; -import { createDeferredManager } from '@trezor/utils'; -import { TypedEmitter } from '@trezor/utils'; +import { createDeferred, createDeferredManager, TypedEmitter } from '@trezor/utils'; import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; interface Subscription { @@ -44,6 +42,7 @@ export abstract class BaseWebsocket extends TypedEmitter; protected abstract ping(): Promise; + protected abstract createWebsocket(): WebSocket; constructor(options: Options) { @@ -81,7 +80,7 @@ export abstract class BaseWebsocket extends TypedEmitter extends TypedEmitter extends TypedEmitter {}); // hack; ws throws uncaughtably when there's no error listener ws.close(); - } catch (error) { + } catch { // empty } }, diff --git a/packages/blockchain-link/src/workers/baseWorker.ts b/packages/blockchain-link/src/workers/baseWorker.ts index e842786caa3..e1ad3184439 100644 --- a/packages/blockchain-link/src/workers/baseWorker.ts +++ b/packages/blockchain-link/src/workers/baseWorker.ts @@ -5,14 +5,16 @@ // and // new BlockchainLink({ worker: () => new BlockchainLinkModule() }); -import SocksProxyAgent from 'socks-proxy-agent'; +import { SocksProxyAgent } from 'socks-proxy-agent'; + import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; -import { WorkerState } from './state'; -import { prioritizeEndpoints } from './utils'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; import type { Response, BlockchainSettings } from '@trezor/blockchain-link-types'; import type { Message } from '@trezor/blockchain-link-types/src/messages'; +import { prioritizeEndpoints } from './utils'; +import { WorkerState } from './state'; + // self is not declared in TS Webworker lib typings declare const self: { postMessage: (...args: any[]) => any }; @@ -31,7 +33,7 @@ export type ContextType = { export abstract class BaseWorker { api: API | undefined; - proxyAgent: ReturnType | undefined; + proxyAgent: SocksProxyAgent | undefined; settings: Partial = {}; state: WorkerState; post: (data: Response) => void; @@ -141,9 +143,16 @@ export abstract class BaseWorker { if (data.type === MESSAGES.HANDSHAKE) { this.settings = data.settings; - this.proxyAgent = data.settings.proxy - ? SocksProxyAgent(data.settings.proxy) - : undefined; + const { proxy } = data.settings; + if (proxy) { + const agentUri = + typeof proxy === 'string' ? proxy : `socks://${proxy.host}:${proxy.port}`; + const socketOptions = + typeof proxy === 'object' ? { timeout: proxy?.timeout } : undefined; + this.proxyAgent = new SocksProxyAgent(agentUri, socketOptions); + } else { + this.proxyAgent = undefined; + } return true; } diff --git a/packages/blockchain-link/src/workers/blockbook/index.ts b/packages/blockchain-link/src/workers/blockbook/index.ts index 58c1b428265..f552b4b6ed7 100644 --- a/packages/blockchain-link/src/workers/blockbook/index.ts +++ b/packages/blockchain-link/src/workers/blockbook/index.ts @@ -1,7 +1,5 @@ import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; -import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; -import { BlockbookAPI } from './websocket'; import * as utils from '@trezor/blockchain-link-utils/src/blockbook'; import type { Response, SubscriptionAccountInfo } from '@trezor/blockchain-link-types'; import type { @@ -12,6 +10,9 @@ import type { } from '@trezor/blockchain-link-types/src/blockbook'; import type * as MessageTypes from '@trezor/blockchain-link-types/src/messages'; +import { BlockbookAPI } from './websocket'; +import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; + type Context = ContextType; type Request = T & Context; @@ -163,6 +164,16 @@ const estimateFee = async (request: Request) => { } as const; }; +const rpcCall = async (request: Request) => { + const api = await request.connect(); + const resp = await api.rpcCall(request.payload); + + return { + type: RESPONSES.RPC_CALL, + payload: resp, + } as const; +}; + const onNewBlock = ({ post }: Context, event: BlockNotification) => { post({ id: -1, @@ -417,6 +428,8 @@ const onRequest = (request: Request) => { return getFiatRatesTickersList(request); case MESSAGES.ESTIMATE_FEE: return estimateFee(request); + case MESSAGES.RPC_CALL: + return rpcCall(request); case MESSAGES.PUSH_TRANSACTION: return pushTransaction(request); case MESSAGES.SUBSCRIBE: diff --git a/packages/blockchain-link/src/workers/blockbook/websocket.ts b/packages/blockchain-link/src/workers/blockbook/websocket.ts index bfa6862946f..77a74f11a30 100644 --- a/packages/blockchain-link/src/workers/blockbook/websocket.ts +++ b/packages/blockchain-link/src/workers/blockbook/websocket.ts @@ -18,10 +18,11 @@ import type { AccountInfoParams, EstimateFeeParams, AccountBalanceHistoryParams, + RpcCallParams, } from '@trezor/blockchain-link-types/src/params'; +import { getSuiteVersion } from '@trezor/env-utils'; import { BaseWebsocket } from '../baseWebsocket'; -import { getSuiteVersion } from '@trezor/env-utils'; interface BlockbookEvents { block: BlockNotification; @@ -126,6 +127,10 @@ export class BlockbookAPI extends BaseWebsocket { return this.send('estimateFee', payload); } + rpcCall(payload: RpcCallParams) { + return this.send('rpcCall', payload); + } + getCurrentFiatRates(payload: GetCurrentFiatRates['payload']) { return this.send('getCurrentFiatRates', payload); } diff --git a/packages/blockchain-link/src/workers/blockfrost/index.ts b/packages/blockchain-link/src/workers/blockfrost/index.ts index 7fa640e7c7e..7b6f2b5ca69 100644 --- a/packages/blockchain-link/src/workers/blockfrost/index.ts +++ b/packages/blockchain-link/src/workers/blockfrost/index.ts @@ -1,7 +1,5 @@ import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; -import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; -import { BlockfrostAPI } from './websocket'; import { transformUtxos, transformAccountInfo, @@ -15,6 +13,9 @@ import type { } from '@trezor/blockchain-link-types/src/blockfrost'; import type * as MessageTypes from '@trezor/blockchain-link-types/src/messages'; +import { BlockfrostAPI } from './websocket'; +import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; + type Context = ContextType; type Request = T & Context; diff --git a/packages/blockchain-link/src/workers/blockfrost/websocket.ts b/packages/blockchain-link/src/workers/blockfrost/websocket.ts index f629692dffc..274477224e1 100644 --- a/packages/blockchain-link/src/workers/blockfrost/websocket.ts +++ b/packages/blockchain-link/src/workers/blockfrost/websocket.ts @@ -10,9 +10,9 @@ import type { EstimateFeeParams, AccountBalanceHistoryParams, } from '@trezor/blockchain-link-types/src/params'; +import { getSuiteVersion } from '@trezor/env-utils'; import { BaseWebsocket } from '../baseWebsocket'; -import { getSuiteVersion } from '@trezor/env-utils'; interface BlockfrostEvents { block: BlockContent; diff --git a/packages/blockchain-link/src/workers/electrum/client/caching.ts b/packages/blockchain-link/src/workers/electrum/client/caching.ts index 5ea93b48f5e..cbe8dd58002 100644 --- a/packages/blockchain-link/src/workers/electrum/client/caching.ts +++ b/packages/blockchain-link/src/workers/electrum/client/caching.ts @@ -1,6 +1,7 @@ -import { ElectrumClient } from './electrum'; import { Status } from '@trezor/blockchain-link-types/src/electrum'; +import { ElectrumClient } from './electrum'; + type Cache = { [descriptor: string]: [Status, any]; }; diff --git a/packages/blockchain-link/src/workers/electrum/client/electrum.ts b/packages/blockchain-link/src/workers/electrum/client/electrum.ts index 4fbd3d50a5d..17733b50ef2 100644 --- a/packages/blockchain-link/src/workers/electrum/client/electrum.ts +++ b/packages/blockchain-link/src/workers/electrum/client/electrum.ts @@ -1,5 +1,6 @@ import { Network, networks } from '@trezor/utxo-lib'; import { ElectrumAPI, BlockHeader, Version } from '@trezor/blockchain-link-types/src/electrum'; + import { JsonRpcClientOptions } from './json-rpc'; import { BatchingJsonRpcClient } from './batching'; import type { ISocket } from '../sockets/interface'; diff --git a/packages/blockchain-link/src/workers/electrum/client/json-rpc.ts b/packages/blockchain-link/src/workers/electrum/client/json-rpc.ts index d63e6554713..1d8add2ad97 100644 --- a/packages/blockchain-link/src/workers/electrum/client/json-rpc.ts +++ b/packages/blockchain-link/src/workers/electrum/client/json-rpc.ts @@ -1,5 +1,7 @@ import { EventEmitter } from 'events'; + import { throwError } from '@trezor/utils'; + import type { ISocket } from '../sockets/interface'; type Callback = (error: any, result?: any) => void; diff --git a/packages/blockchain-link/src/workers/electrum/devrun.ts b/packages/blockchain-link/src/workers/electrum/devrun.ts deleted file mode 100644 index ca15ba0cef0..00000000000 --- a/packages/blockchain-link/src/workers/electrum/devrun.ts +++ /dev/null @@ -1,145 +0,0 @@ -// @ts-nocheck -/// - -import ElectrumWorker from '.'; -import { createSocket } from './sockets'; -import type { Message, Response } from '../../types'; - -const TOR_ADDRESS = '127.0.0.1:9050'; - -const LOCALHOST_CONFIG = 'localhost:50001:t'; -const TCP_CONFIG = 'electrum.corp.sldev.cz:50001:t'; -const TLS_CONFIG = 'bitcoin.aranguren.org:50002:s'; -const IPv6_CONFIG = '[2401:d002:3902:700:d72c:5e22:4e95:389d]:50001:t'; -const TOR_CONFIG = ''; // My personal Umbrel - -const ADDR_REGTEST = 'bcrt1qx4009e2mpuch20p3uwvwmplaxgnjqwjmenrcfu'; -const ADDR_LEGACY = '1BitcoinEaterAddressDontSendf59kuE'; // 393 transactions -const ADDR_SEGWIT = '3AVjhFvVHKhPfFccdFnPTBaqRqWq4EWoU2'; // 2 transactions -const ADDR_SEGWIT_MID = '33QJtiYPkQzYf5BNbCztHWowhco77sg18g'; // 44 transactions -const ADDR_BECH32 = 'bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej'; // almost 1M transactions -const XPUB_ALL_SEED_1 = - 'xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy'; -const XPUB_ALL_SEED_2 = - 'xpub6BiVtCpG9fQQ1EW99bMSYwySbPWvzTFRQZCFgTmV3samLSZAYU7C3f4Je9vkNh7h1GAWi5Fn93BwoGBy9EAXbWTTgTnVKAbthHpxM1fXVRL'; -const YPUB = - 'ypub6XKbB5DSkq8Royg8isNtGktj6bmEfGJXDs83Ad5CZ5tpDV8QofwSWQFTWP2Pv24vNdrPhquehL7vRMvSTj2GpKv6UaTQCBKZALm6RJAmxG6'; -const ZPUB = - 'zpub6rDjzkRoJEhB4Z4j77umtwDFpuqDhNxWy85wZZwF6z7TM1TUw1oCucRsPHjeVNCPa29EXxFbzoPCSSbdcR66KUh9R7oPWD7XQtHuVSy47mk'; -const ZPUB_FIRST = 'bc1qqmu7jr0twysm6n0zd3xz93h3jysre8aaatd6g8'; -const ZPUB_SECOND = 'bc1qrhl9v6nagn9v9ckg4u63vwgzteh5wzdutj2ecd'; -const TX = '353cad24cf028dc32d7be44d9b96acc112dba7705a4f3bba4be077f500cdc416'; -const COINBASE_TX = '485579924ce684df7aa7a9861abb4b2858a8d917aa1df94bf3a234368a250516'; -const OP_RETURN_TX = '8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684'; -const MULTISIG_TX = 'aafbce314cadd619585034c4d949a59569fcf79902d3c35e162d25aa207dfb61'; - -const SCRIPTHASH = '495fa456cdb66064db3dae04d7b2f307a874cb6f731ab4251b7d73308001ebba'; - -const TX_HASH = - '02000000000102f503777e6e86bbdc285fdc781807466ab7187496b9c13d1e11aab6a45c3a31c90000000017160014207b37056ce222c4c7511dbaa9441df907904a73feffffffc1b3e9aa635f20f98a611bee50077aa7d36bde466a0deb547df1efbceecdfec30100000000feffffff02fa990e00000000001600148775fb220c008347029e71b76e3b31a941cf588269601a00000000001976a9140093caafa7c8d442cf37b26eb79e4c4f6a9afae188ac02473044022027a921f91fd8c5a5b198c8851c08a00824f8678768964c7069bcdc498513992802203b2eb659f5b7a77a17db8093e10e0dc4b35bf6315a4f6f4fa0b2e5fd8e6895760121036761cccced49d0500e036b59f69b21f9455bcd055e005405bd6c13b11d40293602473044022036da9dd150c2d3501fa18c1f3d121999c3bd64003137f9eb68330f1833c214f202206de0a66364b5ddb0feb12b87df6f15ec5389d4201bea1f929d7d6e8ea97a2d560121021506315fd256cfad9cf15257eebbf80411a1ab0cc00de3e96ff9df4a8cd1f83608cd0a00'; - -(async () => { - const worker = ElectrumWorker(); - - const resolvers: { [id: number]: (value: any) => void } = {}; - - worker.onmessage = ({ data }: { data: Response }) => { - if (resolvers[data.id]) resolvers[data.id](data); - }; - - let id = 0; - const sendAndWait = (data: Message) => - new Promise((resolve, reject) => { - resolvers[data.id] = resolve; - worker.postMessage(data); - }); - - worker.postMessage({ - type: 'm_handshake', - id: 0, - settings: { - name: 'REGTEST', - worker: 'unknown', - server: [LOCALHOST_CONFIG], - debug: true, - }, - }); - - await sendAndWait({ id: ++id, type: 'm_connect' }); - // await sendAndWait({ id: ++id, type: 'm_push_tx', payload: TX_HASH }); - /* await sendAndWait({ - id: ++id, - // @ts-ignore - type: 'raw', - // @ts-ignore - payload: { - method: 'blockchain.block.headers', - params: [666666, 3], - }, - }); - return; - await sendAndWait({ id: ++id, type: 'm_get_info' }); - - console.time('tx'); - await sendAndWait({ id: ++id, type: 'm_get_account_utxo', payload: ADDR_SEGWIT_MID }); - console.timeEnd('tx'); - return; - - await sendAndWait({ - id: ++id, - type: 'm_subscribe', - payload: { addresses: [ZPUB_FIRST], type: 'addresses' }, - }); - return; - await sendAndWait({ id: ++id, type: 'm_get_block_hash', payload: 666666 }); - - await sendAndWait({ id: ++id, type: 'm_get_transaction', payload: TX }); - return; - - await sendAndWait({ - id: ++id, - // @ts-ignore - type: 'raw', - // @ts-ignore - payload: { - method: 'server.ping', - }, - }); - - return; - */ - /* - await sendAndWait({ - id: ++id, - // @ts-ignore - type: 'raw', - // @ts-ignore - payload: { - method: 'blockchain.scripthash.subscribe', - params: ['ed11f638de44195276094b2f055628db95a1f726a65155debf45f54ffc799acb'], - }, - }); - */ - /* - await sendAndWait({ - id: ++id, - type: 'm_get_account_info', - payload: { descriptor: ADDR_REGTEST, details: 'txs' }, - }); - /* - return; - await sendAndWait({ - id: ++id, - type: 'm_get_account_balance_history', - payload: { - descriptor: ADDR_REGTEST, - from: NaN, - to: NaN, - groupBy: 7200000, - currencies: ['btc'], - }, - }); - return; - await sendAndWait({ id: ++id, type: 'm_estimate_fee', payload: { blocks: [1, 2, 10] } }); - */ -})(); diff --git a/packages/blockchain-link/src/workers/electrum/index.ts b/packages/blockchain-link/src/workers/electrum/index.ts index af8af2c546b..bc732d36a94 100644 --- a/packages/blockchain-link/src/workers/electrum/index.ts +++ b/packages/blockchain-link/src/workers/electrum/index.ts @@ -1,13 +1,14 @@ import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; +import type { Response } from '@trezor/blockchain-link-types'; +import { Message } from '@trezor/blockchain-link-types/src/messages'; + import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; import * as M from './methods'; import * as L from './listeners'; import { createSocket } from './sockets'; import { CachingElectrumClient } from './client/caching'; import type { ElectrumClient } from './client/electrum'; -import type { Response } from '@trezor/blockchain-link-types'; -import { Message } from '@trezor/blockchain-link-types/src/messages'; type BlockListener = ReturnType; type TxListener = ReturnType; @@ -127,14 +128,14 @@ const onRequest = async ( throw new CustomError(`Subscription ${request.payload.type} not implemented`); } // @ts-expect-error this message is used in tests - case 'raw': + case 'raw': { // @ts-expect-error - const { method, params } = request.payload; return client .request(method, ...params) .then((res: any) => ({ type: method, payload: res })); + } default: throw new CustomError('worker_unknown_request', `+${request.type}`); } diff --git a/packages/blockchain-link/src/workers/electrum/listeners/blockListener.ts b/packages/blockchain-link/src/workers/electrum/listeners/blockListener.ts index 94600a25da6..dca0709e606 100644 --- a/packages/blockchain-link/src/workers/electrum/listeners/blockListener.ts +++ b/packages/blockchain-link/src/workers/electrum/listeners/blockListener.ts @@ -1,8 +1,9 @@ import { throwError } from '@trezor/utils'; import { RESPONSES } from '@trezor/blockchain-link-types/src/constants'; +import type { BlockHeader, ElectrumAPI } from '@trezor/blockchain-link-types/src/electrum'; + import { blockheaderToBlockhash } from '../utils'; import type { BaseWorker } from '../../baseWorker'; -import type { BlockHeader, ElectrumAPI } from '@trezor/blockchain-link-types/src/electrum'; export const blockListener = (worker: BaseWorker) => { const { state } = worker; diff --git a/packages/blockchain-link/src/workers/electrum/listeners/txListener.ts b/packages/blockchain-link/src/workers/electrum/listeners/txListener.ts index 7e3f0c6d01b..204694af64b 100644 --- a/packages/blockchain-link/src/workers/electrum/listeners/txListener.ts +++ b/packages/blockchain-link/src/workers/electrum/listeners/txListener.ts @@ -1,8 +1,6 @@ import { throwError } from '@trezor/utils'; import { RESPONSES } from '@trezor/blockchain-link-types/src/constants'; -import { createAddressManager, getTransactions } from '../utils'; import { transformTransaction } from '@trezor/blockchain-link-utils/src/blockbook'; -import type { BaseWorker } from '../../baseWorker'; import type { ElectrumAPI, HistoryTx, @@ -10,6 +8,9 @@ import type { } from '@trezor/blockchain-link-types/src/electrum'; import type { Subscribe, Unsubscribe } from '@trezor/blockchain-link-types/src/messages'; +import type { BaseWorker } from '../../baseWorker'; +import { createAddressManager, getTransactions } from '../utils'; + type Payload = Extract< T['payload'], { type: 'addresses' | 'accounts' } diff --git a/packages/blockchain-link/src/workers/electrum/methods/estimateFee.ts b/packages/blockchain-link/src/workers/electrum/methods/estimateFee.ts index 9be3f4bf75f..5dede3fe56e 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/estimateFee.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/estimateFee.ts @@ -1,7 +1,8 @@ -import { Api, btcToSat } from '../utils'; import type { EstimateFee as Req } from '@trezor/blockchain-link-types/src/messages'; import type { EstimateFee as Res } from '@trezor/blockchain-link-types/src/responses'; +import { Api, btcToSat } from '../utils'; + const estimateFee: Api = (client, payload) => Promise.all( (payload.blocks || []).map(num => diff --git a/packages/blockchain-link/src/workers/electrum/methods/getAccountBalanceHistory.ts b/packages/blockchain-link/src/workers/electrum/methods/getAccountBalanceHistory.ts index a39c57f282e..b6d51ec3dbc 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getAccountBalanceHistory.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getAccountBalanceHistory.ts @@ -1,13 +1,14 @@ import { BigNumber } from '@trezor/utils/src/bigNumber'; import { discovery } from '@trezor/utxo-lib'; import { sumVinVout } from '@trezor/blockchain-link-utils'; -import { Api, tryGetScripthash, getTransactions, discoverAddress, AddressHistory } from '../utils'; import { transformTransaction } from '@trezor/blockchain-link-utils/src/blockbook'; import type { GetAccountBalanceHistory as Req } from '@trezor/blockchain-link-types/src/messages'; import type { GetAccountBalanceHistory as Res } from '@trezor/blockchain-link-types/src/responses'; import type { AccountAddresses, Transaction } from '@trezor/blockchain-link-types/src/common'; import type { HistoryTx } from '@trezor/blockchain-link-types/src/electrum'; +import { Api, tryGetScripthash, getTransactions, discoverAddress, AddressHistory } from '../utils'; + const transformAddress = (addr: AddressHistory) => ({ address: addr.address, path: addr.path, diff --git a/packages/blockchain-link/src/workers/electrum/methods/getAccountInfo.ts b/packages/blockchain-link/src/workers/electrum/methods/getAccountInfo.ts index 381a0724b9f..fd62f7f813f 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getAccountInfo.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getAccountInfo.ts @@ -1,6 +1,5 @@ import { discovery } from '@trezor/utxo-lib'; import { sortTxsFromLatest } from '@trezor/blockchain-link-utils'; -import { Api, tryGetScripthash, discoverAddress, AddressHistory, getTransactions } from '../utils'; import { transformTransaction } from '@trezor/blockchain-link-utils/src/blockbook'; import type { ElectrumAPI } from '@trezor/blockchain-link-types/src/electrum'; import type { GetAccountInfo as Req } from '@trezor/blockchain-link-types/src/messages'; @@ -8,6 +7,8 @@ import type { GetAccountInfo as Res } from '@trezor/blockchain-link-types/src/re import type { VinVout } from '@trezor/blockchain-link-types/src/blockbook'; import type { Address, Transaction } from '@trezor/blockchain-link-types'; +import { Api, tryGetScripthash, discoverAddress, AddressHistory, getTransactions } from '../utils'; + // const PAGE_DEFAULT = 0; const PAGE_SIZE_DEFAULT = 25; diff --git a/packages/blockchain-link/src/workers/electrum/methods/getAccountUtxo.ts b/packages/blockchain-link/src/workers/electrum/methods/getAccountUtxo.ts index cdea012d9a6..73736975d06 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getAccountUtxo.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getAccountUtxo.ts @@ -1,10 +1,11 @@ import { throwError } from '@trezor/utils'; import { discovery } from '@trezor/utxo-lib'; -import { Api, tryGetScripthash, discoverAddress } from '../utils'; import type { GetAccountUtxo as Req } from '@trezor/blockchain-link-types/src/messages'; import type { GetAccountUtxo as Res } from '@trezor/blockchain-link-types/src/responses'; import type { Utxo } from '@trezor/blockchain-link-types/src/electrum'; +import { Api, tryGetScripthash, discoverAddress } from '../utils'; + const transformUtxo = (currentHeight: number, addressInfo: { address?: string; path?: string } = {}) => ({ height, tx_hash, tx_pos, value }: Utxo): Res['payload'][number] => ({ diff --git a/packages/blockchain-link/src/workers/electrum/methods/getBlockHash.ts b/packages/blockchain-link/src/workers/electrum/methods/getBlockHash.ts index c8e2e60879e..de8e55a0047 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getBlockHash.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getBlockHash.ts @@ -1,7 +1,8 @@ -import { Api, blockheaderToBlockhash } from '../utils'; import type { GetBlockHash as Req } from '@trezor/blockchain-link-types/src/messages'; import type { GetBlockHash as Res } from '@trezor/blockchain-link-types/src/responses'; +import { Api, blockheaderToBlockhash } from '../utils'; + const getBlockHash: Api = async (client, payload) => { const blockheader = await client.request('blockchain.block.header', payload); diff --git a/packages/blockchain-link/src/workers/electrum/methods/getInfo.ts b/packages/blockchain-link/src/workers/electrum/methods/getInfo.ts index 88da9bb1a91..7b553cc8d6c 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getInfo.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getInfo.ts @@ -1,8 +1,9 @@ import { throwError } from '@trezor/utils'; -import { Api, blockheaderToBlockhash } from '../utils'; import type { GetInfo as Req } from '@trezor/blockchain-link-types/src/messages'; import type { GetInfo as Res } from '@trezor/blockchain-link-types/src/responses'; +import { Api, blockheaderToBlockhash } from '../utils'; + const getInfo: Api = client => { const { url, diff --git a/packages/blockchain-link/src/workers/electrum/methods/getTransaction.ts b/packages/blockchain-link/src/workers/electrum/methods/getTransaction.ts index 6efc4ee9591..c4a088ee6b4 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/getTransaction.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/getTransaction.ts @@ -1,8 +1,9 @@ -import { Api, getTransactions } from '../utils'; import { transformTransaction } from '@trezor/blockchain-link-utils/src/blockbook'; import type { GetTransaction as Req } from '@trezor/blockchain-link-types/src/messages'; import type { GetTransaction as Res } from '@trezor/blockchain-link-types/src/responses'; +import { Api, getTransactions } from '../utils'; + const getTransaction: Api = async (client, payload) => { const [tx] = await getTransactions(client, [{ tx_hash: payload, height: -1 }]); diff --git a/packages/blockchain-link/src/workers/electrum/methods/pushTransaction.ts b/packages/blockchain-link/src/workers/electrum/methods/pushTransaction.ts index 0ffbd7ef278..00122d50688 100644 --- a/packages/blockchain-link/src/workers/electrum/methods/pushTransaction.ts +++ b/packages/blockchain-link/src/workers/electrum/methods/pushTransaction.ts @@ -1,7 +1,8 @@ -import { Api } from '../utils'; import type { PushTransaction as Req } from '@trezor/blockchain-link-types/src/messages'; import type { PushTransaction as Res } from '@trezor/blockchain-link-types/src/responses'; +import { Api } from '../utils'; + const pushTransaction: Api = async (client, payload) => { const res = await client.request('blockchain.transaction.broadcast', payload); diff --git a/packages/blockchain-link/src/workers/electrum/sockets/base.ts b/packages/blockchain-link/src/workers/electrum/sockets/base.ts index a724c199407..6b5614f0b1c 100644 --- a/packages/blockchain-link/src/workers/electrum/sockets/base.ts +++ b/packages/blockchain-link/src/workers/electrum/sockets/base.ts @@ -1,6 +1,7 @@ import type { Socket as TCPSocket } from 'net'; import type { TLSSocket } from 'tls'; import type { SocksProxyAgent } from 'socks-proxy-agent'; + import type { ISocket, SocketListener } from './interface'; const TIMEOUT = 10000; diff --git a/packages/blockchain-link/src/workers/electrum/sockets/index.ts b/packages/blockchain-link/src/workers/electrum/sockets/index.ts index dfb401af562..888d286708c 100644 --- a/packages/blockchain-link/src/workers/electrum/sockets/index.ts +++ b/packages/blockchain-link/src/workers/electrum/sockets/index.ts @@ -1,5 +1,6 @@ import { parseElectrumUrl } from '@trezor/utils'; import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; + import { TcpSocket } from './tcp'; import { TlsSocket } from './tls'; import { TorSocket } from './tor'; diff --git a/packages/blockchain-link/src/workers/electrum/sockets/tcp.ts b/packages/blockchain-link/src/workers/electrum/sockets/tcp.ts index e183142ed4c..f4ed9bfe994 100644 --- a/packages/blockchain-link/src/workers/electrum/sockets/tcp.ts +++ b/packages/blockchain-link/src/workers/electrum/sockets/tcp.ts @@ -1,4 +1,5 @@ import { Socket as TCPSocket } from 'net'; + import { SocketBase } from './base'; import type { SocketListener } from './interface'; diff --git a/packages/blockchain-link/src/workers/electrum/sockets/tls.ts b/packages/blockchain-link/src/workers/electrum/sockets/tls.ts index 71d09798bd6..16bbe5bab7e 100644 --- a/packages/blockchain-link/src/workers/electrum/sockets/tls.ts +++ b/packages/blockchain-link/src/workers/electrum/sockets/tls.ts @@ -1,4 +1,5 @@ import { TLSSocket } from 'tls'; + import { SocketBase } from './base'; import type { SocketListener } from './interface'; diff --git a/packages/blockchain-link/src/workers/electrum/sockets/tor.ts b/packages/blockchain-link/src/workers/electrum/sockets/tor.ts index a1cc5ce1aa6..902456364cb 100644 --- a/packages/blockchain-link/src/workers/electrum/sockets/tor.ts +++ b/packages/blockchain-link/src/workers/electrum/sockets/tor.ts @@ -1,5 +1,6 @@ -import { SocketBase, SocketConfig } from './base'; import type { SocksProxyAgent } from 'socks-proxy-agent'; + +import { SocketBase, SocketConfig } from './base'; import type { SocketListener } from './interface'; type TorSocketConfig = SocketConfig & { @@ -17,7 +18,7 @@ export class TorSocket extends SocketBase { protected async openSocket(listener: SocketListener) { const { host, port } = this; const socket = await this.proxyAgent - .callback(null as any, { host, port, timeout: this.timeout, secureEndpoint: false }) + .connect(null as any, { host, port, timeout: this.timeout, secureEndpoint: false }) .catch(e => { listener.onError(e); throw e; diff --git a/packages/blockchain-link/src/workers/electrum/utils/addressManager.ts b/packages/blockchain-link/src/workers/electrum/utils/addressManager.ts index d6bfe7495c2..2a950b4bc29 100644 --- a/packages/blockchain-link/src/workers/electrum/utils/addressManager.ts +++ b/packages/blockchain-link/src/workers/electrum/utils/addressManager.ts @@ -1,8 +1,9 @@ import { isNotUndefined, arrayDistinct, objectPartition } from '@trezor/utils'; -import { addressToScripthash } from './transform'; import type { Network } from '@trezor/utxo-lib'; import type { AccountAddresses, SubscriptionAccountInfo } from '@trezor/blockchain-link-types/src'; +import { addressToScripthash } from './transform'; + type AddressMap = { [address: string]: string }; type AccountMap = { [descriptor: string]: AccountAddresses }; diff --git a/packages/blockchain-link/src/workers/electrum/utils/discovery.ts b/packages/blockchain-link/src/workers/electrum/utils/discovery.ts index 4d29a33388c..8a26ab70763 100644 --- a/packages/blockchain-link/src/workers/electrum/utils/discovery.ts +++ b/packages/blockchain-link/src/workers/electrum/utils/discovery.ts @@ -1,6 +1,7 @@ -import { addressToScripthash } from './transform'; import type { ElectrumAPI, HistoryTx } from '@trezor/blockchain-link-types/src/electrum'; +import { addressToScripthash } from './transform'; + export type AddressHistory = { address: string; scripthash: string; diff --git a/packages/blockchain-link/src/workers/electrum/utils/transaction.ts b/packages/blockchain-link/src/workers/electrum/utils/transaction.ts index 6e1feb07d70..d6ac609b584 100644 --- a/packages/blockchain-link/src/workers/electrum/utils/transaction.ts +++ b/packages/blockchain-link/src/workers/electrum/utils/transaction.ts @@ -1,5 +1,4 @@ import { arrayToDictionary, arrayDistinct } from '@trezor/utils'; -import { btcToSat } from './transform'; import type { Transaction as BlockbookTransaction } from '@trezor/blockchain-link-types/src/blockbook'; import type { ElectrumAPI, @@ -10,6 +9,8 @@ import type { HistoryTx, } from '@trezor/blockchain-link-types/src/electrum'; +import { btcToSat } from './transform'; + const transformOpReturn = (hex: string) => { const [, _len, data] = hex.match(/^6a(?:4c)?([0-9a-f]{2})([0-9a-f]*)$/i) ?? []; diff --git a/packages/blockchain-link/src/workers/ripple/index.ts b/packages/blockchain-link/src/workers/ripple/index.ts index 5ee200360e9..94224e1153f 100644 --- a/packages/blockchain-link/src/workers/ripple/index.ts +++ b/packages/blockchain-link/src/workers/ripple/index.ts @@ -1,13 +1,15 @@ import { RippleAPI, APIOptions } from 'ripple-lib'; import { RippleError } from 'ripple-lib/dist/npm/common/errors'; + import { BigNumber } from '@trezor/utils/src/bigNumber'; import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; -import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; import * as utils from '@trezor/blockchain-link-utils/src/ripple'; import type { Response, SubscriptionAccountInfo, AccountInfo } from '@trezor/blockchain-link-types'; import type * as MessageTypes from '@trezor/blockchain-link-types/src/messages'; +import { BaseWorker, CONTEXT, ContextType } from '../baseWorker'; + type Context = ContextType; type Request = T & Context; @@ -139,7 +141,7 @@ const getAccountInfo = async (request: Request) => account.availableBalance = new BigNumber(mempoolInfo.xrpBalance).minus(reserve).toString(); account.misc.sequence = mempoolInfo.sequence; account.history.unconfirmed = mempoolInfo.txs; - } catch (error) { + } catch { // do not throw error for mempool (ledger_index: "current") // mainnet sometimes return "error": "noNetwork", "error_message": "InsufficientNetworkMode", // TODO: investigate diff --git a/packages/blockchain-link/src/workers/solana/fee.ts b/packages/blockchain-link/src/workers/solana/fee.ts index 72c46971abb..dcaa4e6967b 100644 --- a/packages/blockchain-link/src/workers/solana/fee.ts +++ b/packages/blockchain-link/src/workers/solana/fee.ts @@ -1,4 +1,5 @@ import { Connection, Message, PublicKey } from '@solana/web3.js'; + import { BigNumber } from '@trezor/utils/src/bigNumber'; const COMPUTE_BUDGET_PROGRAM_ID = 'ComputeBudget111111111111111111111111111111'; diff --git a/packages/blockchain-link/src/workers/solana/index.ts b/packages/blockchain-link/src/workers/solana/index.ts index 1753f1af26c..52a55969eed 100644 --- a/packages/blockchain-link/src/workers/solana/index.ts +++ b/packages/blockchain-link/src/workers/solana/index.ts @@ -1,3 +1,5 @@ +import { Connection, Message, PublicKey, SendTransactionError } from '@solana/web3.js'; + import type { Response, AccountInfo, @@ -13,20 +15,19 @@ import type { } from '@trezor/blockchain-link-types/src/solana'; import type * as MessageTypes from '@trezor/blockchain-link-types/src/messages'; import { CustomError } from '@trezor/blockchain-link-types/src/constants/errors'; -import { BaseWorker, ContextType, CONTEXT } from '../baseWorker'; import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants'; -import { Connection, Message, PublicKey } from '@solana/web3.js'; import { solanaUtils } from '@trezor/blockchain-link-utils'; import { createLazy } from '@trezor/utils'; - import { transformTokenInfo, TOKEN_PROGRAM_PUBLIC_KEY, } from '@trezor/blockchain-link-utils/src/solana'; +import { getSuiteVersion } from '@trezor/env-utils'; + import { TOKEN_ACCOUNT_LAYOUT } from './tokenUtils'; import { getBaseFee, getPriorityFee } from './fee'; import { confirmTransactionWithResubmit } from './transactionConfirmation'; -import { getSuiteVersion } from '@trezor/env-utils'; +import { BaseWorker, ContextType, CONTEXT } from '../baseWorker'; export type SolanaAPI = Connection; @@ -105,17 +106,35 @@ const pushTransaction = async (request: Request) = const { lastValidBlockHeight } = await api.getLatestBlockhash('finalized'); const txBuffer = Buffer.from(rawTx, 'hex'); - const signature = await api.sendRawTransaction(txBuffer, { - skipPreflight: true, - maxRetries: 0, - }); - await confirmTransactionWithResubmit(api, txBuffer, signature, lastValidBlockHeight); + try { + await api.sendRawTransaction(txBuffer, { + skipPreflight: true, + maxRetries: 0, + }); + + const signature = await api.sendRawTransaction(txBuffer, { + skipPreflight: true, + maxRetries: 0, + }); - return { - type: RESPONSES.PUSH_TRANSACTION, - payload: signature, - } as const; + await confirmTransactionWithResubmit(api, txBuffer, signature, lastValidBlockHeight); + + return { + type: RESPONSES.PUSH_TRANSACTION, + payload: signature, + } as const; + } catch (error) { + if ( + error instanceof SendTransactionError && + error?.transactionError?.message === 'Internal error' + ) { + throw new Error( + 'Please make sure that you submit the transaction within 1 minute after signing.', + ); + } + throw error; + } }; const getAccountInfo = async (request: Request) => { @@ -154,14 +173,21 @@ const getAccountInfo = async (request: Request) => const allAccounts = [payload.descriptor, ...tokenAccounts.value.map(a => a.pubkey.toString())]; - const allTxIds = Array.from( - new Set( - (await Promise.all(allAccounts.map(account => getAllSignatures(api, account)))) - .flat() - .sort((a, b) => b.slot - a.slot) - .map(it => it.signature), - ), - ); + const allTxIds = + details === 'basic' || details === 'txs' || details === 'txids' + ? Array.from( + new Set( + ( + await Promise.all( + allAccounts.map(account => getAllSignatures(api, account)), + ) + ) + .flat() + .sort((a, b) => b.slot - a.slot) + .map(it => it.signature), + ), + ) + : []; const pageNumber = payload.page ? payload.page - 1 : 0; // for the first page of txs, payload.page is undefined, for the second page is 2 diff --git a/packages/blockchain-link/src/workers/solana/transactionConfirmation.ts b/packages/blockchain-link/src/workers/solana/transactionConfirmation.ts index d475248a87c..693136b658e 100644 --- a/packages/blockchain-link/src/workers/solana/transactionConfirmation.ts +++ b/packages/blockchain-link/src/workers/solana/transactionConfirmation.ts @@ -12,7 +12,7 @@ const tryConfirmBySignatureStatus = async ( const getCurrentBlockHeight = async () => { try { return await api.getBlockHeight('finalized'); - } catch (_) { + } catch { return -1; } }; diff --git a/packages/blockchain-link/tests/integration/blockbook.test.ts b/packages/blockchain-link/tests/integration/blockbook.test.ts index 389a80689d7..4dd92f02049 100644 --- a/packages/blockchain-link/tests/integration/blockbook.test.ts +++ b/packages/blockchain-link/tests/integration/blockbook.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import BlockchainLink from '../../src'; import { // blockbookWorkerFactory, diff --git a/packages/blockchain-link/tests/integration/blockfrost.test.ts b/packages/blockchain-link/tests/integration/blockfrost.test.ts index ddccb1f5e96..9f03bd8f1d7 100644 --- a/packages/blockchain-link/tests/integration/blockfrost.test.ts +++ b/packages/blockchain-link/tests/integration/blockfrost.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import BlockchainLink from '../../src'; import { // blockfrostWorkerFactory, diff --git a/packages/blockchain-link/tests/integration/connection.test.ts b/packages/blockchain-link/tests/integration/connection.test.ts index 180767616ba..ef68ff4137b 100644 --- a/packages/blockchain-link/tests/integration/connection.test.ts +++ b/packages/blockchain-link/tests/integration/connection.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import BlockchainLink from '../../src'; import { // rippleWorkerFactory, @@ -131,17 +132,19 @@ backends.forEach((b, i) => { }, 10000); describe('Event listeners', () => { - it('Handle connect event', done => { - blockchain.on('connected', () => done()); - blockchain.connect(); - }); + it('Handle connect event', () => + new Promise(done => { + blockchain.on('connected', () => done()); + blockchain.connect(); + })); - it('Handle disconnect event', done => { - blockchain.on('disconnected', () => done()); - blockchain.connect().then(() => { - blockchain.disconnect(); - }); - }); + it('Handle disconnect event', () => + new Promise(done => { + blockchain.on('disconnected', () => done()); + blockchain.connect().then(() => { + blockchain.disconnect(); + }); + })); }); }); }); diff --git a/packages/blockchain-link/tests/integration/electrum.test.ts b/packages/blockchain-link/tests/integration/electrum.test.ts index d3e927f424d..3781c2fea8f 100644 --- a/packages/blockchain-link/tests/integration/electrum.test.ts +++ b/packages/blockchain-link/tests/integration/electrum.test.ts @@ -1,8 +1,9 @@ -import ElectrumWorker from '../../src/workers/electrum'; import type { Message } from '@trezor/blockchain-link-types/src/messages'; import type { Response } from '@trezor/blockchain-link-types/src/responses'; import { GET_ACCOUNT_INFO, HANDSHAKE } from '@trezor/blockchain-link-types/src/constants/messages'; +import ElectrumWorker from '../../src/workers/electrum'; + const TCP_CONFIG = '127.0.0.1:50001:t'; const NETWORK = 'REGTEST'; diff --git a/packages/blockchain-link/tests/integration/ripple.test.ts b/packages/blockchain-link/tests/integration/ripple.test.ts index 4119cf331bd..c64c807148c 100644 --- a/packages/blockchain-link/tests/integration/ripple.test.ts +++ b/packages/blockchain-link/tests/integration/ripple.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import BlockchainLink from '../../src'; import { // rippleWorkerFactory, diff --git a/packages/blockchain-link/tests/integration/solana.test.ts b/packages/blockchain-link/tests/integration/solana.test.ts index 001a8fe2ef5..2752bc95c03 100644 --- a/packages/blockchain-link/tests/integration/solana.test.ts +++ b/packages/blockchain-link/tests/integration/solana.test.ts @@ -1,4 +1,5 @@ import { AccountInfoParams } from '@trezor/blockchain-link-types'; + import BlockchainLink from '../../src'; import SolanaWorker, { SolanaAPI } from '../../src/workers/solana'; @@ -111,7 +112,7 @@ const fixtures = { ], }; -export const solanaApi = { +const solanaApi = { getGenesisHash: () => '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d', getVersion: () => ({ 'feature-set': 1879391783, 'solana-core': '1.14.22' }), getParsedBlock: () => ({ diff --git a/packages/blockchain-link/tests/integration/worker/index.ts b/packages/blockchain-link/tests/integration/worker/index.ts index acc8ee443c5..aa2f58d11fa 100644 --- a/packages/blockchain-link/tests/integration/worker/index.ts +++ b/packages/blockchain-link/tests/integration/worker/index.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ import TinyWorker from 'tiny-worker'; const BlockbookWorkerModule = require('../../../build/module/blockbook-worker'); @@ -8,7 +7,6 @@ const BlockfrostWorkerModule = require('../../../build/module/blockfrost-worker' export const rippleWorkerFactory = () => { if (typeof Worker === 'undefined') { return new TinyWorker(() => { - // eslint-disable-next-line import/no-extraneous-dependencies require('@trezor/blockchain-link/src/workers/ripple'); }); // return new TinyWorker('./build/module/blockbook-worker.js'); @@ -26,7 +24,6 @@ export const rippleModuleFactory = RippleWorkerModule; export const blockbookWorkerFactory = () => { if (typeof Worker === 'undefined') { return new TinyWorker(() => { - // eslint-disable-next-line import/no-extraneous-dependencies require('@trezor/blockchain-link/src/workers/blockbook'); }); // return new TinyWorker('./build/module/blockbook-worker.js'); @@ -44,7 +41,6 @@ export const blockbookModuleFactory = BlockbookWorkerModule; export const blockfrostWorkerFactory = () => { if (typeof Worker === 'undefined') { return new TinyWorker(() => { - // eslint-disable-next-line import/no-extraneous-dependencies require('@trezor/blockchain-link/src/workers/blockfrost'); }); // return new TinyWorker('./build/module/blockfrost-worker.js'); diff --git a/packages/blockchain-link/tests/unit/connection.test.ts b/packages/blockchain-link/tests/unit/connection.test.ts index 4d7c0223501..86e42eccf9a 100644 --- a/packages/blockchain-link/tests/unit/connection.test.ts +++ b/packages/blockchain-link/tests/unit/connection.test.ts @@ -1,4 +1,6 @@ +/* eslint-disable jest/no-jasmine-globals */ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; @@ -127,27 +129,29 @@ workers.forEach(instance => { expect(server.getFixtures()!.length).toEqual(2); }); - it('Handle connect event', done => { - blockchain.on('connected', done); - blockchain.connect().then(result => { - expect(result).toEqual(true); - }); - }); - - it('Handle disconnect event', done => { - blockchain.on('disconnected', done); - blockchain.connect().then(() => { - // TODO: ripple-lib throws error when disconnect is called immediately - // investigate more, use setTimeout as a workaround - // Error [ERR_UNHANDLED_ERROR]: Unhandled error. (websocket) - // at Connection.RippleAPI.connection.on (../../node_modules/ripple-lib/src/api.ts:133:14) - if (instance.name === 'ripple') { - setTimeout(() => blockchain.disconnect(), 100); - } else { - blockchain.disconnect(); - } - }); - }); + it('Handle connect event', () => + new Promise(done => { + blockchain.on('connected', done); + blockchain.connect().then(result => { + expect(result).toEqual(true); + }); + })); + + it('Handle disconnect event', () => + new Promise(done => { + blockchain.on('disconnected', done); + blockchain.connect().then(() => { + // TODO: ripple-lib throws error when disconnect is called immediately + // investigate more, use setTimeout as a workaround + // Error [ERR_UNHANDLED_ERROR]: Unhandled error. (websocket) + // at Connection.RippleAPI.connection.on (../../node_modules/ripple-lib/src/api.ts:133:14) + if (instance.name === 'ripple') { + setTimeout(() => blockchain.disconnect(), 100); + } else { + blockchain.disconnect(); + } + }); + })); it('Connect (only one endpoint is valid)', async () => { // blockfrost has only one valid endpoint diff --git a/packages/blockchain-link/tests/unit/estimateFee.test.ts b/packages/blockchain-link/tests/unit/estimateFee.test.ts index 57a4b1187fe..db58740a5a5 100644 --- a/packages/blockchain-link/tests/unit/estimateFee.test.ts +++ b/packages/blockchain-link/tests/unit/estimateFee.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/estimateFee'; diff --git a/packages/blockchain-link/tests/unit/getAccountInfo.test.ts b/packages/blockchain-link/tests/unit/getAccountInfo.test.ts index 24c1ec08db9..8712a6464a0 100644 --- a/packages/blockchain-link/tests/unit/getAccountInfo.test.ts +++ b/packages/blockchain-link/tests/unit/getAccountInfo.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/getAccountInfo'; diff --git a/packages/blockchain-link/tests/unit/getAccountUtxo.test.ts b/packages/blockchain-link/tests/unit/getAccountUtxo.test.ts index 9fb0b3ada3d..500c3f31a1c 100644 --- a/packages/blockchain-link/tests/unit/getAccountUtxo.test.ts +++ b/packages/blockchain-link/tests/unit/getAccountUtxo.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/getAccountUtxo'; diff --git a/packages/blockchain-link/tests/unit/getBlockHash.test.ts b/packages/blockchain-link/tests/unit/getBlockHash.test.ts index 347a77615e7..9a63c0873da 100644 --- a/packages/blockchain-link/tests/unit/getBlockHash.test.ts +++ b/packages/blockchain-link/tests/unit/getBlockHash.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/getBlockHash'; diff --git a/packages/blockchain-link/tests/unit/getInfo.test.ts b/packages/blockchain-link/tests/unit/getInfo.test.ts index 53b1a2fd85a..f143f1af43c 100644 --- a/packages/blockchain-link/tests/unit/getInfo.test.ts +++ b/packages/blockchain-link/tests/unit/getInfo.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/getInfo'; diff --git a/packages/blockchain-link/tests/unit/getTransaction.test.ts b/packages/blockchain-link/tests/unit/getTransaction.test.ts index a821639183c..3aacdca842a 100644 --- a/packages/blockchain-link/tests/unit/getTransaction.test.ts +++ b/packages/blockchain-link/tests/unit/getTransaction.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/getTransaction'; diff --git a/packages/blockchain-link/tests/unit/notifications.test.ts b/packages/blockchain-link/tests/unit/notifications.test.ts index b0ecbf1687f..dbab1887fdb 100644 --- a/packages/blockchain-link/tests/unit/notifications.test.ts +++ b/packages/blockchain-link/tests/unit/notifications.test.ts @@ -1,7 +1,7 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; - import fixturesBlockbook from './fixtures/notifications-blockbook'; import fixturesRipple from './fixtures/notifications-ripple'; import fixturesBlockfrost from './fixtures/notifications-blockfrost'; diff --git a/packages/blockchain-link/tests/unit/pushTransaction.test.ts b/packages/blockchain-link/tests/unit/pushTransaction.test.ts index 0b58302d260..c27b934611d 100644 --- a/packages/blockchain-link/tests/unit/pushTransaction.test.ts +++ b/packages/blockchain-link/tests/unit/pushTransaction.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/pushTransaction'; diff --git a/packages/blockchain-link/tests/unit/subscribe.test.ts b/packages/blockchain-link/tests/unit/subscribe.test.ts index 83db8ed478f..66d6051a46d 100644 --- a/packages/blockchain-link/tests/unit/subscribe.test.ts +++ b/packages/blockchain-link/tests/unit/subscribe.test.ts @@ -1,4 +1,5 @@ import { BackendWebsocketServerMock } from '@trezor/e2e-utils'; + import workers from './worker'; import BlockchainLink from '../../src'; import fixtures from './fixtures/subscribe'; diff --git a/packages/blockchain-link/tests/unit/utils.test.ts b/packages/blockchain-link/tests/unit/utils.test.ts index b8e3eba9cfc..a294210a0c1 100644 --- a/packages/blockchain-link/tests/unit/utils.test.ts +++ b/packages/blockchain-link/tests/unit/utils.test.ts @@ -1,5 +1,4 @@ import { prioritizeEndpoints } from '../../src/workers/utils'; - import * as fixtures from './fixtures/utils'; describe('prioritizeEndpoints', () => { diff --git a/packages/blockchain-link/tests/unit/workers.test.ts b/packages/blockchain-link/tests/unit/workers.test.ts index 81959fc6980..ee5d9649f3a 100644 --- a/packages/blockchain-link/tests/unit/workers.test.ts +++ b/packages/blockchain-link/tests/unit/workers.test.ts @@ -1,4 +1,6 @@ +/* eslint-disable jest/no-jasmine-globals */ import TinyWorker from 'tiny-worker'; + import BlockchainLink from '../../src'; class BaseMockWorker { @@ -165,6 +167,7 @@ describe('Worker', () => { return new TinyWorker(() => { self.onmessage = () => { // @ts-expect-error undefined "x" + // eslint-disable-next-line @typescript-eslint/no-unused-vars const r = 1 / x; }; diff --git a/packages/blockchain-link/tsconfig.json b/packages/blockchain-link/tsconfig.json index 8835026e99c..e13bab8d4a8 100644 --- a/packages/blockchain-link/tsconfig.json +++ b/packages/blockchain-link/tsconfig.json @@ -13,6 +13,7 @@ { "path": "../utils" }, { "path": "../utxo-lib" }, { "path": "../e2e-utils" }, + { "path": "../eslint" }, { "path": "../type-utils" } ] } diff --git a/packages/blockchain-link/tsconfig.lib.json b/packages/blockchain-link/tsconfig.lib.json index 54918d49e91..428254ad4b5 100644 --- a/packages/blockchain-link/tsconfig.lib.json +++ b/packages/blockchain-link/tsconfig.lib.json @@ -25,6 +25,9 @@ { "path": "../e2e-utils" }, + { + "path": "../eslint" + }, { "path": "../type-utils" } diff --git a/packages/blockchain-link/webpack/dev.js b/packages/blockchain-link/webpack/dev.js index ff811e42dd4..2d583b3dcb4 100644 --- a/packages/blockchain-link/webpack/dev.js +++ b/packages/blockchain-link/webpack/dev.js @@ -1,5 +1,6 @@ const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); + const { SRC, BUILD, PORT } = require('./constants'); module.exports = { @@ -22,7 +23,6 @@ module.exports = { directory: `${SRC}ui`, }, hot: false, - https: false, port: PORT, }, module: { diff --git a/packages/blockchain-link/webpack/workers.web.js b/packages/blockchain-link/webpack/workers.web.js index bb8b9594a77..4197bfb4ada 100644 --- a/packages/blockchain-link/webpack/workers.web.js +++ b/packages/blockchain-link/webpack/workers.web.js @@ -1,4 +1,5 @@ const webpack = require('webpack'); + const { SRC, BUILD } = require('./constants'); module.exports = { diff --git a/packages/coinjoin/.eslintrc.js b/packages/coinjoin/.eslintrc.js deleted file mode 100644 index adfdb637ac9..00000000000 --- a/packages/coinjoin/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - rules: { - 'no-bitwise': 'off', - 'no-console': 'warn', - }, -}; diff --git a/packages/coinjoin/eslint.config.mjs b/packages/coinjoin/eslint.config.mjs new file mode 100644 index 00000000000..6287357377b --- /dev/null +++ b/packages/coinjoin/eslint.config.mjs @@ -0,0 +1,11 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-bitwise': 'off', + 'no-console': 'warn', + }, + }, +]; diff --git a/packages/coinjoin/package.json b/packages/coinjoin/package.json index dee79661a65..a6ca08ffb54 100644 --- a/packages/coinjoin/package.json +++ b/packages/coinjoin/package.json @@ -16,7 +16,6 @@ "sideEffects": false, "main": "src/index.ts", "scripts": { - "lint:js": "eslint '**/*.{ts,js}'", "test:unit": "yarn g:jest", "test:discovery": "yarn g:tsx ./tests/tools/discovery-test.ts", "test:anonymity": "yarn g:tsx ./tests/tools/anonymity-test.ts", @@ -25,9 +24,11 @@ "type-check": "yarn g:tsc --build" }, "dependencies": { + "@suite-common/suite-constants": "workspace:*", "@trezor/blockchain-link": "workspace:*", "@trezor/blockchain-link-types": "workspace:*", "@trezor/blockchain-link-utils": "workspace:*", + "@trezor/eslint": "workspace:*", "@trezor/utils": "workspace:*", "@trezor/utxo-lib": "workspace:*", "cross-fetch": "^4.0.0", diff --git a/packages/coinjoin/src/backend/CoinjoinBackendClient.ts b/packages/coinjoin/src/backend/CoinjoinBackendClient.ts index 99de08ffa84..740962d3bb5 100644 --- a/packages/coinjoin/src/backend/CoinjoinBackendClient.ts +++ b/packages/coinjoin/src/backend/CoinjoinBackendClient.ts @@ -1,5 +1,10 @@ -import { scheduleAction, arrayShuffle, urlToOnion } from '@trezor/utils'; -import { TypedEmitter } from '@trezor/utils'; +import { + scheduleAction, + arrayShuffle, + urlToOnion, + getWeakRandomInt, + TypedEmitter, +} from '@trezor/utils'; import type { BlockbookAPI } from '@trezor/blockchain-link/src/workers/blockbook/websocket'; import { RequestOptions, resetIdentityCircuit } from '../utils/http'; @@ -38,7 +43,7 @@ export class CoinjoinBackendClient { constructor(settings: CoinjoinBackendClientSettings) { this.logger = settings.logger; - this.blockbookUrls = arrayShuffle(settings.blockbookUrls); + this.blockbookUrls = arrayShuffle(settings.blockbookUrls, { randomInt: getWeakRandomInt }); this.onionDomains = settings.onionDomains ?? {}; this.blockbookRequestId = Math.floor(Math.random() * settings.blockbookUrls.length); this.websockets = new CoinjoinWebsocketController(settings); diff --git a/packages/coinjoin/src/client/CoinjoinRound.ts b/packages/coinjoin/src/client/CoinjoinRound.ts index bb81fb6560d..982858a7693 100644 --- a/packages/coinjoin/src/client/CoinjoinRound.ts +++ b/packages/coinjoin/src/client/CoinjoinRound.ts @@ -1,5 +1,4 @@ -import { TypedEmitter } from '@trezor/utils'; -import { scheduleAction, arrayDistinct, arrayPartition } from '@trezor/utils'; +import { TypedEmitter, scheduleAction, arrayDistinct, arrayPartition } from '@trezor/utils'; import { Network } from '@trezor/utxo-lib'; import { diff --git a/packages/coinjoin/src/client/Status.ts b/packages/coinjoin/src/client/Status.ts index 47ca5535d4c..68ea510ddd9 100644 --- a/packages/coinjoin/src/client/Status.ts +++ b/packages/coinjoin/src/client/Status.ts @@ -240,15 +240,15 @@ export class Status extends TypedEmitter { identity: this.identities[0], attempts: 3, // schedule 3 attempts on start }, - ).then(patchResponse); - - return version - ? ({ - majorVersion: version.BackenMajordVersion, - commitHash: version.CommitHash, - legalDocumentsVersion: version.Ww2LegalDocumentsVersion, - } as CoinjoinClientVersion) - : undefined; + ) + .then(patchResponse) + .catch(() => undefined); + + return { + majorVersion: version?.BackenMajordVersion ?? '0', + commitHash: version?.CommitHash ?? 'deadbeef', + legalDocumentsVersion: version?.Ww2LegalDocumentsVersion ?? '1.0', + } as CoinjoinClientVersion; } async start() { diff --git a/packages/coinjoin/src/client/round/endedRound.ts b/packages/coinjoin/src/client/round/endedRound.ts index 844af2a7324..1d561b9f35f 100644 --- a/packages/coinjoin/src/client/round/endedRound.ts +++ b/packages/coinjoin/src/client/round/endedRound.ts @@ -1,4 +1,4 @@ -import { enumUtils, getRandomNumberInRange } from '@trezor/utils'; +import { enumUtils, getWeakRandomNumberInRange } from '@trezor/utils'; import type { CoinjoinRound, CoinjoinRoundOptions } from '../CoinjoinRound'; import { EndRoundState, WabiSabiProtocolErrorCode } from '../../enums'; @@ -56,7 +56,7 @@ export const ended = (round: CoinjoinRound, { logger, network }: CoinjoinRoundOp // repeated input-registration will tell if they are really banned, // make sure that addresses registered in round are recycled (reset Infinity sentence) const minute = 60 * 1000; - const sentenceEnd = getRandomNumberInRange(5 * minute, 10 * minute); + const sentenceEnd = getWeakRandomNumberInRange(5 * minute, 10 * minute); [...inputs, ...addresses].forEach(vinvout => prison.detain(vinvout, { sentenceEnd, diff --git a/packages/coinjoin/src/client/round/inputRegistration.ts b/packages/coinjoin/src/client/round/inputRegistration.ts index 2e20812a4a3..9bc93b77d5b 100644 --- a/packages/coinjoin/src/client/round/inputRegistration.ts +++ b/packages/coinjoin/src/client/round/inputRegistration.ts @@ -1,4 +1,4 @@ -import { getRandomNumberInRange } from '@trezor/utils'; +import { getWeakRandomNumberInRange } from '@trezor/utils'; import * as coordinator from '../coordinator'; import * as middleware from '../middleware'; @@ -56,7 +56,7 @@ const registerInput = async ( // setup random delay for registration request. we want each input to be registered in different time as different TOR identity // note that this may cause that the input will not be registered if phase change before expected deadline const deadline = round.phaseDeadline - Date.now() - ROUND_SELECTION_REGISTRATION_OFFSET; - const delay = deadline > 0 ? getRandomNumberInRange(0, deadline) : 0; + const delay = deadline > 0 ? getWeakRandomNumberInRange(0, deadline) : 0; logger.info( `Trying to register ~~${input.outpoint}~~ to ~~${round.id}~~ with delay ${delay}ms and deadline ${round.phaseDeadline}`, ); diff --git a/packages/coinjoin/src/client/round/outputRegistration.ts b/packages/coinjoin/src/client/round/outputRegistration.ts index 19d9c5a8153..6c7ac003408 100644 --- a/packages/coinjoin/src/client/round/outputRegistration.ts +++ b/packages/coinjoin/src/client/round/outputRegistration.ts @@ -1,4 +1,4 @@ -import { getWeakRandomId, arrayShuffle } from '@trezor/utils'; +import { getWeakRandomId, arrayShuffle, getWeakRandomInt } from '@trezor/utils'; import * as coordinator from '../coordinator'; import * as middleware from '../middleware'; @@ -160,7 +160,7 @@ export const outputRegistration = async ( const assignedAddresses: AccountAddress[] = []; return Promise.all( - arrayShuffle(outputs).map(output => + arrayShuffle(outputs, { randomInt: getWeakRandomInt }).map(output => registerOutput(round, account, output, assignedAddresses, options), ), ); @@ -170,7 +170,9 @@ export const outputRegistration = async ( round.setSessionPhase(SessionPhase.AwaitingOthersOutputs); // inform coordinator that each registered input is ready to sign await Promise.all( - arrayShuffle(round.inputs).map(input => readyToSign(round, input, options)), + arrayShuffle(round.inputs, { randomInt: getWeakRandomInt }).map(input => + readyToSign(round, input, options), + ), ); logger.info(`Ready to sign ~~${round.id}~~`); } catch (error) { diff --git a/packages/coinjoin/src/client/round/transactionSigning.ts b/packages/coinjoin/src/client/round/transactionSigning.ts index d8a5677696a..9424d587daf 100644 --- a/packages/coinjoin/src/client/round/transactionSigning.ts +++ b/packages/coinjoin/src/client/round/transactionSigning.ts @@ -1,4 +1,4 @@ -import { arrayShuffle } from '@trezor/utils'; +import { arrayShuffle, getWeakRandomInt } from '@trezor/utils'; import * as coordinator from '../coordinator'; import * as middleware from '../middleware'; @@ -234,7 +234,7 @@ export const transactionSigning = async ( round.setSessionPhase(SessionPhase.SendingSignature); await Promise.all( - arrayShuffle(round.inputs).map(input => + arrayShuffle(round.inputs, { randomInt: getWeakRandomInt }).map(input => sendTxSignature(round, resolvedTime, input, options), ), ); diff --git a/packages/coinjoin/src/utils/roundUtils.ts b/packages/coinjoin/src/utils/roundUtils.ts index 93fa5ab67d1..763d294a43b 100644 --- a/packages/coinjoin/src/utils/roundUtils.ts +++ b/packages/coinjoin/src/utils/roundUtils.ts @@ -1,5 +1,5 @@ import { bufferutils, Transaction, Network } from '@trezor/utxo-lib'; -import { getRandomNumberInRange } from '@trezor/utils'; +import { getWeakRandomNumberInRange } from '@trezor/utils'; import { COORDINATOR_FEE_RATE_FALLBACK, @@ -88,7 +88,7 @@ export const scheduleDelay = ( // and at most 1 sec before the calculated max (so there's room for randomness) const min = clamp(minimumDelay, 0, max - 1000); - return getRandomNumberInRange(min, max); + return getWeakRandomNumberInRange(min, max); }; // NOTE: deadlines are not accurate. phase may change earlier diff --git a/packages/coinjoin/tests/client/CoinjoinPrison.test.ts b/packages/coinjoin/tests/client/CoinjoinPrison.test.ts index b0418b2680f..d80699fedc5 100644 --- a/packages/coinjoin/tests/client/CoinjoinPrison.test.ts +++ b/packages/coinjoin/tests/client/CoinjoinPrison.test.ts @@ -1,24 +1,25 @@ import { CoinjoinPrison } from '../../src/client/CoinjoinPrison'; describe('CoinjoinPrison', () => { - it('release inmate when Round is no longer present in Status', done => { - const inmate = { - accountKey: 'account-A', - type: 'input', - sentenceStart: Date.now() - 10000, - } as const; + it('release inmate when Round is no longer present in Status', () => + new Promise(done => { + const inmate = { + accountKey: 'account-A', + type: 'input', + sentenceStart: Date.now() - 10000, + } as const; - const prison = new CoinjoinPrison([ - { ...inmate, id: '00AA', roundId: '00', sentenceEnd: Date.now() + 10000 }, // will be released - round not present) - { ...inmate, id: '00AB', roundId: '00', sentenceEnd: Infinity }, // will NOT be released - sentence Infinity - { ...inmate, id: '01AA', roundId: '01', sentenceEnd: Date.now() + 10000 }, // wil NOT be released - round IS present - { ...inmate, id: '01AB', roundId: '01', sentenceEnd: Date.now() - 1 }, // will be released - sentence is lower - ]); + const prison = new CoinjoinPrison([ + { ...inmate, id: '00AA', roundId: '00', sentenceEnd: Date.now() + 10000 }, // will be released - round not present) + { ...inmate, id: '00AB', roundId: '00', sentenceEnd: Infinity }, // will NOT be released - sentence Infinity + { ...inmate, id: '01AA', roundId: '01', sentenceEnd: Date.now() + 10000 }, // wil NOT be released - round IS present + { ...inmate, id: '01AB', roundId: '01', sentenceEnd: Date.now() - 1 }, // will be released - sentence is lower + ]); - prison.on('change', () => done()); // end test after change event + prison.on('change', () => done()); // end test after change event - prison.release(['01']); // only one round is present in Status + prison.release(['01']); // only one round is present in Status - expect(prison.inmates.map(({ id }) => id)).toEqual(['00AB', '01AA']); - }); + expect(prison.inmates.map(({ id }) => id)).toEqual(['00AB', '01AA']); + })); }); diff --git a/packages/coinjoin/tests/client/CoinjoinRound.test.ts b/packages/coinjoin/tests/client/CoinjoinRound.test.ts index f6a3dc638d4..52d886d76a6 100644 --- a/packages/coinjoin/tests/client/CoinjoinRound.test.ts +++ b/packages/coinjoin/tests/client/CoinjoinRound.test.ts @@ -12,7 +12,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: () => 0, + getWeakRandomNumberInRange: () => 0, }; }); @@ -188,7 +188,7 @@ describe(`CoinjoinRound`, () => { it('onPhaseChange lock cool off resolved', async () => { const delayMock = jest - .spyOn(trezorUtils, 'getRandomNumberInRange') + .spyOn(trezorUtils, 'getWeakRandomNumberInRange') .mockImplementation(() => 800); const constantsMock = jest @@ -396,7 +396,7 @@ describe(`CoinjoinRound`, () => { it('unregisterAccount when round is locked', async () => { const delayMock = jest - .spyOn(trezorUtils, 'getRandomNumberInRange') + .spyOn(trezorUtils, 'getWeakRandomNumberInRange') .mockImplementation(() => 800); const constantsMock = jest @@ -420,6 +420,7 @@ describe(`CoinjoinRound`, () => { round.on('changed', spyChanged); // process but not wait for the result + // eslint-disable-next-line jest/valid-expect-in-promise round.process([]).then(() => { expect(spyEnded).toHaveBeenCalledTimes(1); expect(spyChanged).toHaveBeenCalledTimes(1); diff --git a/packages/coinjoin/tests/client/Status.test.ts b/packages/coinjoin/tests/client/Status.test.ts index 093fc63fbc4..cff39db0b5d 100644 --- a/packages/coinjoin/tests/client/Status.test.ts +++ b/packages/coinjoin/tests/client/Status.test.ts @@ -208,25 +208,26 @@ describe('Status', () => { expect(identities.length).toEqual(1); }); - it('Status start and immediate stop', done => { - status = new Status(server?.requestOptions); - const errorListener = jest.fn(); - status.on('log', errorListener); - const updateListener = jest.fn(); - status.on('update', updateListener); - const requestListener = jest.fn(); - server?.addListener('test-handle-request', requestListener); - - // start but not await - status.start().then(() => { - expect(requestListener).toHaveBeenCalledTimes(0); // aborted, request not sent - expect(updateListener).toHaveBeenCalledTimes(0); // not emitted, listener removed by .stop() - expect(errorListener).toHaveBeenCalledTimes(0); // not emitted, listener removed by .stop() - done(); - }); - // immediate stop - status.stop(); - }); + it('Status start and immediate stop', () => + new Promise(done => { + status = new Status(server?.requestOptions); + const errorListener = jest.fn(); + status.on('log', errorListener); + const updateListener = jest.fn(); + status.on('update', updateListener); + const requestListener = jest.fn(); + server?.addListener('test-handle-request', requestListener); + + // start but not await + status.start().then(() => { + expect(requestListener).toHaveBeenCalledTimes(0); // aborted, request not sent + expect(updateListener).toHaveBeenCalledTimes(0); // not emitted, listener removed by .stop() + expect(errorListener).toHaveBeenCalledTimes(0); // not emitted, listener removed by .stop() + done(); + }); + // immediate stop + status.stop(); + })); it('Status start attempts, keep lifecycle regardless of failed requests', async () => { jest.spyOn(STATUS_TIMEOUT, 'registered', 'get').mockReturnValue(250 as any); diff --git a/packages/coinjoin/tests/client/connectionConfirmation.test.ts b/packages/coinjoin/tests/client/connectionConfirmation.test.ts index 985863ec82e..680d4c4538e 100644 --- a/packages/coinjoin/tests/client/connectionConfirmation.test.ts +++ b/packages/coinjoin/tests/client/connectionConfirmation.test.ts @@ -14,7 +14,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: () => 0, + getWeakRandomNumberInRange: () => 0, }; }); @@ -196,38 +196,39 @@ describe('connectionConfirmation', () => { }); }); - it('connection-confirmation interval aborted, Alice unregistered', done => { - const alice = createInput('account-A', 'A1', { - registrationData: { - AliceId: '01A1-01a1', - }, - realAmountCredentials: {}, - realVsizeCredentials: {}, - }); - - const interval = confirmationInterval( - createCoinjoinRound([alice], { - ...server?.requestOptions, - roundParameters: { - ConnectionConfirmationTimeout: '0d 0h 0m 2s', // intervalDelay = 1 sec - }, - round: { - phaseDeadline: Date.now() + 1000, // phase will end in less than intervalDelay + it('connection-confirmation interval aborted, Alice unregistered', () => + new Promise(done => { + const alice = createInput('account-A', 'A1', { + registrationData: { + AliceId: '01A1-01a1', }, - }), - alice, - server?.requestOptions, - ); + realAmountCredentials: {}, + realVsizeCredentials: {}, + }); - server?.addListener('test-request', ({ url, resolve }) => { - resolve(); - if (url.includes('input-unregistration')) { - done(); - } - }); + const interval = confirmationInterval( + createCoinjoinRound([alice], { + ...server?.requestOptions, + roundParameters: { + ConnectionConfirmationTimeout: '0d 0h 0m 2s', // intervalDelay = 1 sec + }, + round: { + phaseDeadline: Date.now() + 1000, // phase will end in less than intervalDelay + }, + }), + alice, + server?.requestOptions, + ); - interval.abort(); - }); + server?.addListener('test-request', ({ url, resolve }) => { + resolve(); + if (url.includes('input-unregistration')) { + done(); + } + }); + + interval.abort(); + })); it('404 error in coordinator connection-confirmation', async () => { server?.addListener('test-request', ({ url, data, resolve, reject }) => { diff --git a/packages/coinjoin/tests/client/coordinatorRequest.test.ts b/packages/coinjoin/tests/client/coordinatorRequest.test.ts index e7065315a06..6d91ce6adc1 100644 --- a/packages/coinjoin/tests/client/coordinatorRequest.test.ts +++ b/packages/coinjoin/tests/client/coordinatorRequest.test.ts @@ -146,20 +146,21 @@ describe('http', () => { ).rejects.toThrow('Aborted by signal'); }); - it('successful', done => { - coordinatorRequest('input-registration', {}, { baseUrl }).then(resp => { - expect(resp).toMatchObject({ AliceId: expect.any(String) }); - }); - // without baseUrl - coordinatorRequest(`status`, {}, { baseUrl }).then(resp => { - expect(resp.RoundStates.length).toEqual(1); - }); - // without json response - coordinatorRequest('ready-to-sign', {}, { baseUrl }).then(resp => { - expect(resp).toEqual(''); - done(); - }); - }); + it('successful', () => + new Promise(done => { + coordinatorRequest('input-registration', {}, { baseUrl }).then(resp => { + expect(resp).toMatchObject({ AliceId: expect.any(String) }); + }); + // without baseUrl + coordinatorRequest(`status`, {}, { baseUrl }).then(resp => { + expect(resp.RoundStates.length).toEqual(1); + }); + // without json response + coordinatorRequest('ready-to-sign', {}, { baseUrl }).then(resp => { + expect(resp).toEqual(''); + done(); + }); + })); it('with identity', async () => { const requestListener = jest.fn(req => { diff --git a/packages/coinjoin/tests/client/inputRegistration.test.ts b/packages/coinjoin/tests/client/inputRegistration.test.ts index d6afb9f851f..c79c5a23ac6 100644 --- a/packages/coinjoin/tests/client/inputRegistration.test.ts +++ b/packages/coinjoin/tests/client/inputRegistration.test.ts @@ -10,7 +10,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: () => 0, + getWeakRandomNumberInRange: () => 0, }; }); @@ -342,15 +342,16 @@ describe('inputRegistration', () => { expect(spy.mock.calls.length).toBeGreaterThan(0); // 2. wait for confirmation interval to resolve - Promise.all(response.inputs.map(input => input.getConfirmationInterval()?.promise)).then( - res => { - res.forEach(input => { - expect(input?.getConfirmationInterval()).toBeUndefined(); - }); - }, + const promises = Promise.all( + response.inputs.map(input => input.getConfirmationInterval()?.promise), ); // 1. abort confirmation interval response.inputs.forEach(input => input.clearConfirmationInterval()); + + const res = await promises; + res.forEach(input => { + expect(input?.getConfirmationInterval()).toBeUndefined(); + }); }); }); diff --git a/packages/coinjoin/tests/client/outputRegistration.test.ts b/packages/coinjoin/tests/client/outputRegistration.test.ts index 890efd0155d..f927f41d9ca 100644 --- a/packages/coinjoin/tests/client/outputRegistration.test.ts +++ b/packages/coinjoin/tests/client/outputRegistration.test.ts @@ -11,7 +11,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: () => 0, + getWeakRandomNumberInRange: () => 0, }; }); diff --git a/packages/coinjoin/tests/client/transactionSigning.test.ts b/packages/coinjoin/tests/client/transactionSigning.test.ts index 35db099f4f4..66cf63eb7b4 100644 --- a/packages/coinjoin/tests/client/transactionSigning.test.ts +++ b/packages/coinjoin/tests/client/transactionSigning.test.ts @@ -1,5 +1,5 @@ import { networks } from '@trezor/utxo-lib'; -import { getRandomNumberInRange } from '@trezor/utils'; +import { getWeakRandomNumberInRange } from '@trezor/utils'; import { transactionSigning } from '../../src/client/round/transactionSigning'; import { createServer } from '../mocks/server'; @@ -13,7 +13,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: jest.fn(() => 0), + getWeakRandomNumberInRange: jest.fn(() => 0), }; }); @@ -529,7 +529,7 @@ describe('transactionSigning signature delay', () => { ); // signature is sent in range 17-67 sec. (resolve time is less than 50 sec TX_SIGNING_DELAY) - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(17000, 67000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(17000, 67000); expect(response.isSignedSuccessfully()).toBe(true); }); @@ -558,7 +558,7 @@ describe('transactionSigning signature delay', () => { ); // signature is sent in range 0-46.21 sec. (resolve time is greater than 50 sec of TX_SIGNING_DELAY) - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 46210); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 46210); expect(response.isSignedSuccessfully()).toBe(true); }); @@ -588,7 +588,7 @@ describe('transactionSigning signature delay', () => { ); // signature is sent in default range 0-1 sec. - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); expect(response.isSignedSuccessfully()).toBe(true); }); }); diff --git a/packages/coinjoin/tests/tools/benchmark-test.ts b/packages/coinjoin/tests/tools/benchmark-test.ts index bf020c0b6b2..af5a2df5340 100644 --- a/packages/coinjoin/tests/tools/benchmark-test.ts +++ b/packages/coinjoin/tests/tools/benchmark-test.ts @@ -15,7 +15,7 @@ const TIMEOUT = 20000; const [bestKnownHash, batchSizeString = '500', torSocket = ''] = process.argv.slice(2); const batchSize = Number(batchSizeString); const [host, port] = torSocket.split(':'); -const agent = host && port ? new SocksProxyAgent({ host, port }) : undefined; +const agent = host && port ? new SocksProxyAgent(`socks://${host}:${port}`) : undefined; // Copied from request-manager to remove disallowed headers because of Wasabi const stripHeaders = () => { diff --git a/packages/coinjoin/tests/tools/discovery.ts b/packages/coinjoin/tests/tools/discovery.ts index f497a227da4..17cf5a084c2 100644 --- a/packages/coinjoin/tests/tools/discovery.ts +++ b/packages/coinjoin/tests/tools/discovery.ts @@ -1,14 +1,18 @@ /* eslint-disable no-console */ -/* eslint-disable @typescript-eslint/no-var-requires */ + +import { BITCOIN_ONLY_NETWORKS } from '@suite-common/suite-constants'; +import { isArrayMember } from '@trezor/utils'; import { CoinjoinBackend } from '../../src/backend/CoinjoinBackend'; import type { CoinjoinBackendSettings } from '../../src/types'; const { getCoinjoinConfig } = require('../../../suite/src/services/coinjoin/config'); -const supportedNetworks = ['btc', 'test', 'regtest'] as const; -const isSupportedNetwork = (network: string): network is (typeof supportedNetworks)[number] => - supportedNetworks.includes(network as any); +const supportedNetworks = BITCOIN_ONLY_NETWORKS; +type SupportedNetwork = (typeof supportedNetworks)[number]; + +const isSupportedNetwork = (network: string): network is SupportedNetwork => + isArrayMember(network, supportedNetworks); export const getAccountInfoParams = (network: string, descriptor: string) => { if (!network) throw new Error('network arg missing'); diff --git a/packages/coinjoin/tests/utils/roundUtils.test.ts b/packages/coinjoin/tests/utils/roundUtils.test.ts index fbb6597ea29..c28166694fe 100644 --- a/packages/coinjoin/tests/utils/roundUtils.test.ts +++ b/packages/coinjoin/tests/utils/roundUtils.test.ts @@ -1,4 +1,4 @@ -import { getRandomNumberInRange } from '@trezor/utils'; +import { getWeakRandomNumberInRange } from '@trezor/utils'; import { getCommitmentData, @@ -19,7 +19,7 @@ jest.mock('@trezor/utils', () => { return { __esModule: true, ...originalModule, - getRandomNumberInRange: jest.fn(originalModule.getRandomNumberInRange), + getWeakRandomNumberInRange: jest.fn(originalModule.getWeakRandomNumberInRange), }; }); @@ -150,38 +150,38 @@ describe('roundUtils', () => { // default (no min, no max) range 0-10 sec. resultInRange(scheduleDelay(60000), 0, 10000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 10000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 10000); // range 3-10sec. resultInRange(scheduleDelay(20000, 3000), 3000, 10000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(3000, 10000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(3000, 10000); // deadlineOffset < 0, range 0-1 sec. resultInRange(scheduleDelay(1000, 3000), 0, 1000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); // deadline < min, range 9-10 sec. resultInRange(scheduleDelay(60000, 61000), 9000, 10000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(9000, 10000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(9000, 10000); // deadline < min && deadline < max, range 49-50 sec. resultInRange(scheduleDelay(60000, 61000, 62000), 49000, 50000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(49000, 50000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(49000, 50000); // deadline > min && deadline < max, range 3-20 sec. resultInRange(scheduleDelay(30000, 3000, 50000), 3000, 20000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(3000, 20000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(3000, 20000); // min < 0 && deadline < max && deadlineOffset > 0, range 0-2.5 sec. resultInRange(scheduleDelay(12500, -3000, 50000), 0, 2500); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 2500); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 2500); // min < 0 && max < 0 && deadlineOffset > 0, range 0-1 sec. resultInRange(scheduleDelay(12500, -10000, -5000), 0, 1000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); // min < 0 && max < 0 && deadlineOffset < 0, range 0-1 sec. resultInRange(scheduleDelay(7500, -10000, -5000), 0, 1000); - expect(getRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); + expect(getWeakRandomNumberInRange).toHaveBeenLastCalledWith(0, 1000); }); }); diff --git a/packages/coinjoin/tsconfig.json b/packages/coinjoin/tsconfig.json index 5a6ff6da88b..0dc96bcb848 100644 --- a/packages/coinjoin/tsconfig.json +++ b/packages/coinjoin/tsconfig.json @@ -2,9 +2,13 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "libDev" }, "references": [ + { + "path": "../../suite-common/suite-constants" + }, { "path": "../blockchain-link" }, { "path": "../blockchain-link-types" }, { "path": "../blockchain-link-utils" }, + { "path": "../eslint" }, { "path": "../utils" }, { "path": "../utxo-lib" } ] diff --git a/packages/components/.storybook/main.js b/packages/components/.storybook/main.js index 681db99f95d..63df8f0b85d 100644 --- a/packages/components/.storybook/main.js +++ b/packages/components/.storybook/main.js @@ -1,5 +1,13 @@ import { dirname, join } from 'path'; +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value) { + return dirname(require.resolve(join(value, 'package.json'))); +} + module.exports = { stories: ['../src/**/*.stories.*'], logLevel: 'debug', @@ -14,18 +22,12 @@ module.exports = { name: getAbsolutePath('@storybook/react-webpack5'), options: {}, }, - babel: async options => { + babel: options => { options.presets.push('@babel/preset-typescript'); + return options; }, features: { storyStoreV7: false, // Remove this line when storiesOf is not used anymore }, }; -/** - * This function is used to resolve the absolute path of a package. - * It is needed in projects that use Yarn PnP or are set up within a monorepo. - */ -function getAbsolutePath(value) { - return dirname(require.resolve(join(value, 'package.json'))); -} diff --git a/packages/components/.storybook/manager.js b/packages/components/.storybook/manager.js index 17b478d55a6..f6074ea0baa 100644 --- a/packages/components/.storybook/manager.js +++ b/packages/components/.storybook/manager.js @@ -1,6 +1,7 @@ import { addons } from '@storybook/addons'; + import theme from './theme'; addons.setConfig({ - theme: theme, + theme, }); diff --git a/packages/components/.storybook/preview.js b/packages/components/.storybook/preview.jsx similarity index 100% rename from packages/components/.storybook/preview.js rename to packages/components/.storybook/preview.jsx diff --git a/packages/components/.storybook/theme.js b/packages/components/.storybook/theme.js index 3b857ccf19c..19faabd46ba 100644 --- a/packages/components/.storybook/theme.js +++ b/packages/components/.storybook/theme.js @@ -1,5 +1,6 @@ import { create } from '@storybook/theming/create'; +// eslint-disable-next-line import/no-default-export export default create({ base: 'light', fontBase: 'TT Satoshi', diff --git a/packages/components/eslint.config.mjs b/packages/components/eslint.config.mjs new file mode 100644 index 00000000000..367e76114ae --- /dev/null +++ b/packages/components/eslint.config.mjs @@ -0,0 +1,27 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { ignores: ['**/.build-storybook/*'] }, + { + files: ['**/*.stories.tsx'], + rules: { + 'no-console': 'off', + 'import/no-default-export': 'off', + }, + }, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/*.stories.*', + '**/.storybook/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/components/package.json b/packages/components/package.json index cc59a1e6dc6..98b2282d699 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -8,7 +8,6 @@ "sideEffects": false, "scripts": { "lint": "yarn lint:js && yarn lint:styles", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "lint:styles": "npx stylelint './src/**/*{.ts,.tsx}' --cache --config ../../.stylelintrc", "lint-fix": "npx eslint ./src --fix", "type-check": "yarn g:tsc --build tsconfig.json", @@ -23,6 +22,7 @@ "@suite-common/suite-constants": "workspace:*", "@suite-common/suite-utils": "workspace:*", "@suite-common/validators": "workspace:*", + "@trezor/asset-utils": "workspace:*", "@trezor/connect": "workspace:*", "@trezor/dom-utils": "workspace:*", "@trezor/env-utils": "workspace:*", @@ -54,6 +54,8 @@ "@storybook/client-api": "^7.6.13", "@storybook/react": "^7.6.13", "@storybook/react-webpack5": "^7.6.13", + "@storybook/theming": "^7.6.13", + "@trezor/eslint": "workspace:*", "@types/react": "18.2.79", "@types/react-date-range": "^1.4.9", "postcss-styled-syntax": "^0.6.4", diff --git a/packages/components/src/components/AssetLogo/AssetInitials.tsx b/packages/components/src/components/AssetLogo/AssetInitials.tsx index e22a3fede70..23ff3f25efe 100644 --- a/packages/components/src/components/AssetLogo/AssetInitials.tsx +++ b/packages/components/src/components/AssetLogo/AssetInitials.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { ElevationUp, useElevation } from '../ElevationContext/ElevationContext'; + import { borders, Elevation, @@ -7,6 +7,8 @@ import { mapElevationToBorder, spacingsPx, } from '@trezor/theme'; + +import { ElevationUp, useElevation } from '../ElevationContext/ElevationContext'; import { Text } from '../typography/Text/Text'; import { Tooltip } from '../Tooltip/Tooltip'; @@ -27,18 +29,24 @@ const Circle = styled.div<{ $size: number; $elevation: Elevation }>` `; type AssetInitialsProps = { children: string; + withTooltip?: boolean; size: number; }; -const AssetInitialsInner = ({ children, size }: AssetInitialsProps) => { +const AssetInitialsInner = ({ children, size, withTooltip = true }: AssetInitialsProps) => { const { elevation } = useElevation(); + const firstChar = children[0]; return ( - - {children[0]} - + {withTooltip ? ( + + {firstChar} + + ) : ( + {firstChar} + )} ); diff --git a/packages/components/src/components/AssetLogo/AssetLogo.tsx b/packages/components/src/components/AssetLogo/AssetLogo.tsx index b4af7cf5100..c515cedd030 100644 --- a/packages/components/src/components/AssetLogo/AssetLogo.tsx +++ b/packages/components/src/components/AssetLogo/AssetLogo.tsx @@ -1,17 +1,19 @@ +import { useEffect, useState } from 'react'; + +import styled from 'styled-components'; + +import { borders, Elevation, mapElevationToBackground, mapElevationToBorder } from '@trezor/theme'; +import { getAssetLogoUrl } from '@trezor/asset-utils'; + import { FrameProps, FramePropsKeys, pickAndPrepareFrameProps, withFrameProps, } from '../../utils/frameProps'; -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import { borders } from '@trezor/theme'; - import { AssetInitials } from './AssetInitials'; import { TransientProps } from '../../utils/transientProps'; - -const ICONS_URL_BASE = 'https://data.trezor.io/suite/icons/coins/'; +import { ElevationUp, useElevation } from '../ElevationContext/ElevationContext'; export const allowedAssetLogoSizes = [20, 24]; type AssetLogoSize = (typeof allowedAssetLogoSizes)[number]; @@ -24,6 +26,7 @@ export type AssetLogoProps = AllowedFrameProps & { coingeckoId: string; contractAddress?: string; shouldTryToFetch?: boolean; + placeholderWithTooltip?: boolean; placeholder: string; 'data-testid'?: string; }; @@ -36,17 +39,25 @@ const Container = styled.div & { $size: number ${withFrameProps} `; -const Logo = styled.img<{ $size: number; $isVisible: boolean }>( - ({ $size, $isVisible }) => ` - width: ${$size}px; - height: ${$size}px; - border-radius: ${borders.radii.full}; - visibility: ${$isVisible ? 'visible' : 'hidden'}; - `, -); +const Logo = styled.img<{ $size: number; $isVisible: boolean; $elevation: Elevation }>` + width: ${({ $size }) => $size}px; + height: ${({ $size }) => $size}px; + border-radius: ${borders.radii.full}; + visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')}; + box-shadow: inset 0 0 0 1px ${mapElevationToBorder}; + background-color: ${mapElevationToBackground}; +`; + +interface LogoProps extends React.ImgHTMLAttributes { + $size: number; + $isVisible: boolean; +} -const getAssetLogoUrl = (fileName: string, quality?: '@2x') => - `${ICONS_URL_BASE}${fileName}${quality === undefined ? '' : quality}.webp`; +const ElevatedLogo = (props: LogoProps) => { + const { elevation } = useElevation(); + + return ; +}; export const AssetLogo = ({ size, @@ -54,13 +65,15 @@ export const AssetLogo = ({ contractAddress, shouldTryToFetch = true, placeholder, + placeholderWithTooltip = true, 'data-testid': dataTest, ...rest }: AssetLogoProps) => { const [isLoading, setIsLoading] = useState(true); const [isPlaceholder, setIsPlaceholder] = useState(!shouldTryToFetch); - const fileName = contractAddress ? `${coingeckoId}--${contractAddress}` : coingeckoId; - const logoUrl = getAssetLogoUrl(fileName); + + const logoUrl = getAssetLogoUrl({ coingeckoId, contractAddress }); + const logoUrl2x = getAssetLogoUrl({ coingeckoId, contractAddress, quality: '@2x' }); const frameProps = pickAndPrepareFrameProps(rest, allowedAssetLogoFrameProps); @@ -76,19 +89,25 @@ export const AssetLogo = ({ return ( - {isPlaceholder && {placeholder}} + {isPlaceholder && ( + + {placeholder} + + )} {!isPlaceholder && ( - + + + )} ); diff --git a/packages/components/src/components/AutoScalingInput/AutoScalingInput.stories.tsx b/packages/components/src/components/AutoScalingInput/AutoScalingInput.stories.tsx index c6f5a269363..d8cd47f8067 100644 --- a/packages/components/src/components/AutoScalingInput/AutoScalingInput.stories.tsx +++ b/packages/components/src/components/AutoScalingInput/AutoScalingInput.stories.tsx @@ -1,7 +1,9 @@ import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { AutoScalingInput as AutoScalingInputComponent, Props } from './AutoScalingInput'; + import { Meta, StoryObj } from '@storybook/react'; +import { AutoScalingInput as AutoScalingInputComponent, Props } from './AutoScalingInput'; + const meta: Meta = { title: 'Form/AutoScalingInput', component: AutoScalingInputComponent, diff --git a/packages/components/src/components/AutoScalingInput/AutoScalingInput.tsx b/packages/components/src/components/AutoScalingInput/AutoScalingInput.tsx index 72e940cc7f0..402af20f113 100644 --- a/packages/components/src/components/AutoScalingInput/AutoScalingInput.tsx +++ b/packages/components/src/components/AutoScalingInput/AutoScalingInput.tsx @@ -1,4 +1,5 @@ import React, { useRef, useEffect, forwardRef, useState, ChangeEvent, useCallback } from 'react'; + import styled from 'styled-components'; const HiddenInputToMeasurePlaceholderScrollableWidth = styled.input` diff --git a/packages/components/src/components/AutoScalingInput/AutoScalingInputExamples.stories.tsx b/packages/components/src/components/AutoScalingInput/AutoScalingInputExamples.stories.tsx index 480071c9b3c..a4e8f2d90e0 100644 --- a/packages/components/src/components/AutoScalingInput/AutoScalingInputExamples.stories.tsx +++ b/packages/components/src/components/AutoScalingInput/AutoScalingInputExamples.stories.tsx @@ -1,14 +1,14 @@ import styled from 'styled-components'; -import { AutoScalingInput as Input } from './AutoScalingInput'; import { Meta } from '@storybook/react'; +import { AutoScalingInput as Input } from './AutoScalingInput'; + const Wrapper = styled.div` display: flex; flex: 1; flex-direction: column; `; -// eslint-disable-next-line local-rules/no-override-ds-component const BorderAutoScalingInput = styled(Input)` padding: 1px 5px; border-radius: 3px; @@ -16,7 +16,6 @@ const BorderAutoScalingInput = styled(Input)` border-width: 1px; `; -// eslint-disable-next-line local-rules/no-override-ds-component const BorderlessAutoScalingInput = styled(Input)` padding: 1px 5px; border-style: solid; diff --git a/packages/components/src/components/Badge/Badge.stories.tsx b/packages/components/src/components/Badge/Badge.stories.tsx index 91256ca80fa..38b0bb26787 100644 --- a/packages/components/src/components/Badge/Badge.stories.tsx +++ b/packages/components/src/components/Badge/Badge.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Badge as BadgeComponent, BadgeProps, allowedBadgeFrameProps, badgeSizes } from './Badge'; import { getFramePropsStory } from '../../utils/frameProps'; @@ -11,10 +12,24 @@ export default meta; export const Badge: StoryObj = { args: { children: 'Badge label', + isDisabled: false, + variant: 'primary', size: 'tiny', ...getFramePropsStory(allowedBadgeFrameProps).args, }, argTypes: { + isDisabled: { control: 'boolean' }, + variant: { + control: { + type: 'radio', + }, + options: [ + 'primary', + 'tertiary', + 'destructive', + undefined, + ] satisfies BadgeProps['variant'][], + }, size: { control: { type: 'radio', diff --git a/packages/components/src/components/Badge/Badge.tsx b/packages/components/src/components/Badge/Badge.tsx index 66199cbf0ce..856c27264a3 100644 --- a/packages/components/src/components/Badge/Badge.tsx +++ b/packages/components/src/components/Badge/Badge.tsx @@ -1,6 +1,9 @@ import React from 'react'; + import styled, { css, DefaultTheme, useTheme } from 'styled-components'; + import { borders, Color, CSSColor, spacings, spacingsPx, typography } from '@trezor/theme'; + import { focusStyleTransition, getFocusShadowStyle } from '../../utils/utils'; import type { UISize, UIVariant } from '../../config/types'; import { @@ -18,7 +21,7 @@ export type BadgeSize = Extract; export const allowedBadgeFrameProps = ['margin'] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; -type BadgeVariant = Extract; +export type BadgeVariant = Extract; export type BadgeProps = AllowedFrameProps & { size?: BadgeSize; @@ -150,7 +153,7 @@ export const Badge = ({ name={icon} color={ isDisabled - ? 'iconDisabled' + ? theme.iconDisabled : mapVariantToIconColor({ $variant: variant, theme }) } /> diff --git a/packages/components/src/components/Banner/Banner.stories.tsx b/packages/components/src/components/Banner/Banner.stories.tsx index 54565c6bf5a..3349a7f91ef 100644 --- a/packages/components/src/components/Banner/Banner.stories.tsx +++ b/packages/components/src/components/Banner/Banner.stories.tsx @@ -1,7 +1,8 @@ import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + import { Banner as BannerComponent, BannerProps, variables, Row } from '../../index'; import { allowedBannerFrameProps } from './Banner'; -import styled from 'styled-components'; import { getFramePropsStory } from '../../utils/frameProps'; const Wrapper = styled.div` @@ -43,6 +44,8 @@ export const Banner: StoryObj = { variant: 'warning', icon: undefined, rightContent: Click, + color: 'inherit', + spacingX: 'lg', ...getFramePropsStory(allowedBannerFrameProps).args, }, argTypes: { @@ -55,6 +58,18 @@ export const Banner: StoryObj = { type: 'select', }, }, + color: { + options: ['inherit', 'default'], + control: { + type: 'select', + }, + }, + spacingX: { + options: ['xs', 'lg'], + control: { + type: 'select', + }, + }, rightContent: { options: ['nothing', 'button', 'combinedButtons', 'iconButton', 'iconButtons'], mapping: { diff --git a/packages/components/src/components/Banner/Banner.tsx b/packages/components/src/components/Banner/Banner.tsx index b433e331e99..5cf523ed6e7 100755 --- a/packages/components/src/components/Banner/Banner.tsx +++ b/packages/components/src/components/Banner/Banner.tsx @@ -1,9 +1,9 @@ import { ReactNode } from 'react'; -import styled, { css, useTheme } from 'styled-components'; -import { variables } from '../../config'; +import styled, { css, DefaultTheme, useTheme } from 'styled-components'; + import { Elevation, borders, spacingsPx, typography, spacings } from '@trezor/theme'; -import { Row, Column, TransientProps, useElevation, useMediaQuery } from '../..'; + import { FrameProps, FramePropsKeys, @@ -23,9 +23,16 @@ import { } from './utils'; import { Icon, IconName } from '../Icon/Icon'; import { SCREEN_SIZE } from '../../config/variables'; +import { TransientProps } from '../../utils/transientProps'; +import { useMediaQuery } from '../../utils/useMediaQuery'; +import { useElevation } from '../ElevationContext/ElevationContext'; +import { Column, Row } from '../Flex/Flex'; +import { variables } from '../../config'; export const allowedBannerFrameProps = ['margin'] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; +type SpacingX = 'xs' | 'lg'; +type Color = 'inherit' | 'default'; export type BannerProps = AllowedFrameProps & { children: ReactNode; @@ -35,6 +42,8 @@ export type BannerProps = AllowedFrameProps & { icon?: IconName | true; filled?: boolean; 'data-testid'?: string; + spacingX?: SpacingX; + color?: Color; }; type WrapperParams = TransientProps & { @@ -42,8 +51,15 @@ type WrapperParams = TransientProps & { $withIcon?: boolean; $elevation: Elevation; $filled: boolean; + $spacingX: SpacingX; + $color: Color; }; +const colorMap = (theme: DefaultTheme) => ({ + inherit: mapVariantToTextColor, + default: theme.textDefault, +}); + const Wrapper = styled.div` align-items: center; ${({ $filled }) => @@ -54,14 +70,13 @@ const Wrapper = styled.div` ` : ''} - color: ${mapVariantToTextColor}; + color: ${({ $color, theme }) => colorMap(theme)[$color]}; display: flex; ${typography.hint} gap: ${spacingsPx.sm}; - padding: ${spacingsPx.sm} ${spacingsPx.lg}; + padding: ${spacingsPx.sm} ${({ $spacingX }) => spacingsPx[$spacingX]}; ${withFrameProps} - ${variables.SCREEN_QUERY.MOBILE} { align-items: stretch; flex-direction: column; @@ -72,10 +87,12 @@ const Wrapper = styled.div` export const Banner = ({ children, className, + color = 'inherit', variant = DEFAULT_VARIANT, icon, filled = true, rightContent, + spacingX = 'lg', 'data-testid': dataTest, ...rest }: BannerProps) => { @@ -86,6 +103,7 @@ export const Banner = ({ const isMobile = useMediaQuery(`(max-width: ${SCREEN_SIZE.SM})`); + // eslint-disable-next-line @typescript-eslint/no-shadow const ContentComponent = ({ children }: { children: ReactNode }) => { const commonProps = { justifyContent: 'space-between' as const, @@ -110,6 +128,8 @@ export const Banner = ({ className={className} $elevation={elevation} $filled={filled} + $spacingX={spacingX} + $color={color} data-testid={dataTest} {...frameProps} > diff --git a/packages/components/src/components/Banner/BannerContext.ts b/packages/components/src/components/Banner/BannerContext.ts index cddde230cc2..58ba257eded 100644 --- a/packages/components/src/components/Banner/BannerContext.ts +++ b/packages/components/src/components/Banner/BannerContext.ts @@ -1,4 +1,5 @@ import { createContext, useContext } from 'react'; + import { BannerVariant } from './types'; export const BannerContext = createContext<{ diff --git a/packages/components/src/components/Banner/utils.tsx b/packages/components/src/components/Banner/utils.tsx index ded9531ee3d..ea4165229d4 100644 --- a/packages/components/src/components/Banner/utils.tsx +++ b/packages/components/src/components/Banner/utils.tsx @@ -1,6 +1,8 @@ +import { DefaultTheme } from 'styled-components'; + import { Color, CSSColor, Elevation, mapElevationToBackgroundToken } from '@trezor/theme'; + import { BannerVariant } from './types'; -import { DefaultTheme } from 'styled-components'; import { IconName } from '../Icon/Icon'; type MapArgs = { diff --git a/packages/components/src/components/Card/Card.stories.tsx b/packages/components/src/components/Card/Card.stories.tsx index cf82770cc85..46d13292a32 100644 --- a/packages/components/src/components/Card/Card.stories.tsx +++ b/packages/components/src/components/Card/Card.stories.tsx @@ -1,5 +1,7 @@ import { Meta, StoryObj } from '@storybook/react'; -import { Card as CardComponent, allowedCardFrameProps, paddingTypes } from './Card'; + +import { Card as CardComponent, allowedCardFrameProps } from './Card'; +import { paddingTypes, fillTypes } from './types'; import { getFramePropsStory } from '../../utils/frameProps'; const meta: Meta = { @@ -10,18 +12,48 @@ export default meta; export const Card: StoryObj = { args: { - children: 'Some content', + children: ( +

+ Quos delectus veritatis est doloribus dolor. Odit fugit omnis magni ipsam quia rem + aut. Et alias sint non. Consequuntur dignissimos veritatis debitis corporis esse. + Quaerat voluptatem unde aut. Iusto laborum omnis quis amet atque. Sint culpa + delectus non soluta temporibus saepe. Sequi saepe corrupti aliquam ut sit assumenda + aspernatur consequuntur. Ut est ullam iusto facilis voluptatibus. Sit est cum quos. + Quasi deleniti non fugit iste alias consequuntur. Ullam ad ut culpa est reiciendis + molestiae. Reiciendis ab veritatis a totam inventore nihil voluptatem occaecati. + Quisquam atque odit quia nam. Laboriosam rem et ut. Maxime qui voluptatem + voluptatem. +

+ ), label: '', paddingType: 'normal', + fillType: 'default', + isHiglighted: false, ...getFramePropsStory(allowedCardFrameProps).args, }, argTypes: { + onClick: { + options: ['onClick'], + control: { type: 'select' }, + mapping: { onClick: () => {} }, + }, paddingType: { options: paddingTypes, control: { type: 'radio', }, }, + fillType: { + options: fillTypes, + control: { + type: 'radio', + }, + }, + isHiglighted: { + control: { + type: 'boolean', + }, + }, ...getFramePropsStory(allowedCardFrameProps).argTypes, }, }; diff --git a/packages/components/src/components/Card/Card.tsx b/packages/components/src/components/Card/Card.tsx index b67f5b6ba71..aa25ce8c267 100644 --- a/packages/components/src/components/Card/Card.tsx +++ b/packages/components/src/components/Card/Card.tsx @@ -1,7 +1,10 @@ import { forwardRef, HTMLAttributes, ReactNode } from 'react'; + import styled, { css } from 'styled-components'; -import { borders, Elevation, mapElevationToBackground, spacingsPx } from '@trezor/theme'; -import { ElevationContext, useElevation } from '../ElevationContext/ElevationContext'; + +import { borders, Elevation, spacingsPx } from '@trezor/theme'; + +import { ElevationUp, useElevation } from '../ElevationContext/ElevationContext'; import { FrameProps, FramePropsKeys, @@ -10,13 +13,8 @@ import { } from '../../utils/frameProps'; import { TransientProps } from '../../utils/transientProps'; import { AccessibilityProps, withAccessibilityProps } from '../../utils/accessibilityProps'; - -export const paddingTypes = ['small', 'none', 'normal', 'large'] as const; -export type PaddingType = (typeof paddingTypes)[number]; - -type MapArgs = { - $paddingType: PaddingType; -}; +import { PaddingType, FillType } from './types'; +import { mapPaddingTypeToLabelPadding, mapPaddingTypeToPadding, mapFillTypeToCSS } from './utils'; export const allowedCardFrameProps = [ 'margin', @@ -30,30 +28,11 @@ export const allowedCardFrameProps = [ ] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; -const mapPaddingTypeToLabelPadding = ({ $paddingType }: MapArgs): number | string => { - const paddingMap: Record = { - none: `${spacingsPx.xxs} 0`, - small: `${spacingsPx.xxs} ${spacingsPx.sm}`, - normal: `${spacingsPx.xs} ${spacingsPx.lg}`, - large: `${spacingsPx.sm} ${spacingsPx.xl}`, - }; - - return paddingMap[$paddingType]; -}; -const mapPaddingTypeToPadding = ({ $paddingType }: MapArgs): number | string => { - const paddingMap: Record = { - none: 0, - small: spacingsPx.sm, - normal: spacingsPx.lg, - large: spacingsPx.xl, - }; - - return paddingMap[$paddingType]; -}; - -const Container = styled.div>` +const Container = styled.div<{ $fillType: FillType } & TransientProps>` + width: 100%; border-radius: ${borders.radii.md}; - background: ${({ theme }) => theme.backgroundTertiaryDefaultOnElevation0}; + background: ${({ theme, $fillType }) => + $fillType !== 'none' && theme.backgroundTertiaryDefaultOnElevation0}; padding: ${spacingsPx.xxxs}; ${withFrameProps} @@ -68,86 +47,90 @@ const CardContainer = styled.div< { $elevation: Elevation; $paddingType: PaddingType; + $fillType: FillType; $isClickable: boolean; + $isHiglighted: boolean; + $hasLabel: boolean; } & TransientProps >` - display: flex; width: 100%; + display: flex; flex-direction: column; + position: relative; padding: ${mapPaddingTypeToPadding}; - background: ${mapElevationToBackground}; border-radius: ${borders.radii.md}; - - ${({ $isClickable, theme }) => - $isClickable && - ` - &:hover { - box-shadow: ${theme.boxShadowElevated}; - cursor: pointer; - } - `} - - box-shadow: ${({ theme, $elevation }) => $elevation === 1 && theme.boxShadowBase}; - - ${({ onClick, theme }) => - onClick !== undefined - ? css` - &:hover { - cursor: pointer; - - box-shadow: ${() => theme.boxShadowElevated}; - } - ` - : ''} - - /* when theme changes from light to dark */ - transition: background 0.3s, box-shadow 0.2s; - + transition: + background 0.3s, + box-shadow 0.2s, + border-color 0.2s; + cursor: ${({ $isClickable }) => ($isClickable ? 'pointer' : 'default')}; + + ${({ theme, $isHiglighted, $paddingType }) => + $isHiglighted && + css` + overflow: hidden; + padding-left: calc(${spacingsPx.xxs} + ${mapPaddingTypeToPadding({ $paddingType })}); + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: ${spacingsPx.xxs}; + background: ${theme.backgroundSecondaryDefault}; + } + `} + + ${mapFillTypeToCSS} ${withFrameProps} `; type CommonCardProps = AccessibilityProps & { paddingType?: PaddingType; + fillType?: FillType; onMouseEnter?: HTMLAttributes['onMouseEnter']; onMouseLeave?: HTMLAttributes['onMouseLeave']; onClick?: HTMLAttributes['onClick']; children?: ReactNode; className?: string; label?: ReactNode; - forceElevation?: Elevation; + isHiglighted?: boolean; 'data-testid'?: string; }; export type CardPropsWithTransientProps = CommonCardProps & TransientProps; export type CardProps = CommonCardProps & AllowedFrameProps; -const CardComponent = forwardRef< - HTMLDivElement, - CardPropsWithTransientProps & { $paddingType: PaddingType } ->( +const CardComponent = forwardRef( ( { children, - forceElevation, - $paddingType, + paddingType = 'normal', + fillType = 'default', onClick, onMouseEnter, onMouseLeave, className, tabIndex, + label, + isHiglighted = false, 'data-testid': dataTest, ...rest }, ref, ) => { - const { elevation } = useElevation(forceElevation); + const { elevation } = useElevation(); return ( - {children} + {fillType === 'none' ? children : {children}} ); }, @@ -166,6 +149,7 @@ export const Card = forwardRef( ( { paddingType = 'normal', + fillType = 'default', label, onClick, onMouseEnter, @@ -174,6 +158,7 @@ export const Card = forwardRef( tabIndex, children, 'data-testid': dataTest, + isHiglighted, ...rest }, ref, @@ -184,14 +169,17 @@ export const Card = forwardRef( onMouseLeave, className, tabIndex, - $paddingType: paddingType, + paddingType, + fillType, children, + label, + isHiglighted, 'data-testid': dataTest, }; const frameProps = pickAndPrepareFrameProps(rest, allowedCardFrameProps); return label ? ( - + {label} diff --git a/packages/components/src/components/Card/types.tsx b/packages/components/src/components/Card/types.tsx new file mode 100644 index 00000000000..dd37487f9f0 --- /dev/null +++ b/packages/components/src/components/Card/types.tsx @@ -0,0 +1,5 @@ +export const paddingTypes = ['small', 'none', 'normal', 'large'] as const; +export type PaddingType = (typeof paddingTypes)[number]; + +export const fillTypes = ['none', 'default'] as const; +export type FillType = (typeof fillTypes)[number]; diff --git a/packages/components/src/components/Card/utils.tsx b/packages/components/src/components/Card/utils.tsx new file mode 100644 index 00000000000..7ab24439420 --- /dev/null +++ b/packages/components/src/components/Card/utils.tsx @@ -0,0 +1,72 @@ +import { css, DefaultTheme, RuleSet } from 'styled-components'; + +import { + spacingsPx, + Elevation, + mapElevationToBackground, + mapElevationToBorder, + SpacingPxValues, +} from '@trezor/theme'; + +import { PaddingType, FillType } from './types'; + +type PaddingMapArgs = { + $paddingType: PaddingType; +}; + +type FillTypeMapArgs = { + $fillType: FillType; + $elevation: Elevation; + $isClickable: boolean; + $hasLabel: boolean; + theme: DefaultTheme; +}; + +export const mapPaddingTypeToLabelPadding = ({ $paddingType }: PaddingMapArgs): string => { + const paddingMap: Record = { + none: `${spacingsPx.xxs} 0`, + small: `${spacingsPx.xxs} ${spacingsPx.sm}`, + normal: `${spacingsPx.xs} ${spacingsPx.lg}`, + large: `${spacingsPx.sm} ${spacingsPx.xl}`, + }; + + return paddingMap[$paddingType]; +}; + +export const mapPaddingTypeToPadding = ({ $paddingType }: PaddingMapArgs): SpacingPxValues => { + const paddingMap: Record = { + none: '0px', + small: spacingsPx.sm, + normal: spacingsPx.lg, + large: spacingsPx.xl, + }; + + return paddingMap[$paddingType]; +}; + +export const mapFillTypeToCSS = ({ + $fillType, + $elevation, + $isClickable, + $hasLabel, + theme, +}: FillTypeMapArgs): RuleSet => { + const cssMap: Record> = { + default: css` + background: ${mapElevationToBackground({ $elevation, theme })}; + box-shadow: ${$elevation === 1 && !$hasLabel && theme.boxShadowBase}; + + ${$isClickable && + css` + &:hover { + box-shadow: ${$elevation === 1 && theme.boxShadowElevated}; + } + `} + `, + none: css` + border: 1px solid ${mapElevationToBorder({ $elevation, theme })}; + `, + }; + + return cssMap[$fillType]; +}; diff --git a/packages/components/src/components/CollapsibleBox/CollapsibleBox.stories.tsx b/packages/components/src/components/CollapsibleBox/CollapsibleBox.stories.tsx index f782c0bd909..80a051a02d5 100644 --- a/packages/components/src/components/CollapsibleBox/CollapsibleBox.stories.tsx +++ b/packages/components/src/components/CollapsibleBox/CollapsibleBox.stories.tsx @@ -24,6 +24,9 @@ export const CollapsibleBox: StoryObj = {

), hasDivider: true, + fillType: 'default', + paddingType: 'normal', + headingSize: 'large', ...getFramePropsStory(allowedCollapsibleBoxFrameProps).args, }, argTypes: { @@ -34,19 +37,19 @@ export const CollapsibleBox: StoryObj = { hasDivider: { type: 'boolean' }, fillType: { control: { - type: 'select', + type: 'radio', }, options: fillTypes, }, paddingType: { control: { - type: 'select', + type: 'radio', }, options: paddingTypes, }, headingSize: { control: { - type: 'select', + type: 'radio', }, options: headingSizes, }, diff --git a/packages/components/src/components/CollapsibleBox/CollapsibleBox.tsx b/packages/components/src/components/CollapsibleBox/CollapsibleBox.tsx index 90990b206a0..3f6dfaca3db 100644 --- a/packages/components/src/components/CollapsibleBox/CollapsibleBox.tsx +++ b/packages/components/src/components/CollapsibleBox/CollapsibleBox.tsx @@ -1,6 +1,8 @@ import { useState, ReactNode, MouseEvent } from 'react'; + import { motion } from 'framer-motion'; import styled, { css } from 'styled-components'; + import { spacingsPx, borders, @@ -9,9 +11,9 @@ import { mapElevationToBorder, spacings, } from '@trezor/theme'; + import { Icon } from '../Icon/Icon'; import { Row, Column } from '../Flex/Flex'; -import { Paragraph } from '../typography/Paragraph/Paragraph'; import { Text } from '../typography/Text/Text'; import { motionEasing } from '../../config/motion'; import { ElevationUp, useElevation } from './../ElevationContext/ElevationContext'; @@ -83,7 +85,6 @@ export type CollapsibleBoxProps = AllowedFrameProps & { }; const Container = styled.div & ContainerProps>` - flex: 1; width: 100%; border-radius: ${borders.radii.sm}; transition: background 0.3s; @@ -175,6 +176,51 @@ export const CollapsibleBox = ({ setIsOpen(!isOpen); }; + const headerContent = ( + + + + {heading} + + {subHeading && ( + + {subHeading} + + )} + + + + {toggleLabel && ( + + {toggleLabel} + + )} + + {toggleComponent ?? ( + + )} + + + + + ); + return (
- - - - {heading} - - {subHeading && ( - - {subHeading} - - )} - - - - {toggleLabel && ( - - {toggleLabel} - - )} - - {toggleComponent ?? ( - - )} - - - - + {fillType === 'none' ? headerContent : {headerContent}}
- {children} + {fillType === 'none' ? children : {children}}
diff --git a/packages/components/src/components/CollapsibleBox/utils.tsx b/packages/components/src/components/CollapsibleBox/utils.tsx index 6691b9a7b68..4d107dfb60b 100644 --- a/packages/components/src/components/CollapsibleBox/utils.tsx +++ b/packages/components/src/components/CollapsibleBox/utils.tsx @@ -1,4 +1,5 @@ import { spacingsPx, TypographyStyle } from '@trezor/theme'; + import { HeadingSize, PaddingType } from './types'; import { IconSize } from '../Icon/Icon'; diff --git a/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.stories.tsx b/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.stories.tsx index 6cc42af8c57..74c18b1bf77 100644 --- a/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.stories.tsx +++ b/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Icon, iconVariants } from '../Icon/Icon'; import { allowedComponentWithSubIconFrameProps, diff --git a/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.tsx b/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.tsx index 32125d7d71f..632436ff1db 100644 --- a/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.tsx +++ b/packages/components/src/components/ComponentWithSubIcon/ComponentWithSubIcon.tsx @@ -1,4 +1,9 @@ +import { ReactNode } from 'react'; + import styled, { useTheme } from 'styled-components'; + +import { borders, spacingsPx } from '@trezor/theme'; + import { ExclusiveColorOrVariant, getColorForIconVariant, @@ -6,9 +11,7 @@ import { Icon, IconProps, } from '../Icon/Icon'; -import { borders, spacingsPx } from '@trezor/theme'; import { TransientProps } from '../../utils/transientProps'; -import { ReactNode } from 'react'; import { FramePropsKeys, FrameProps, diff --git a/packages/components/src/components/DataAnalytics.tsx b/packages/components/src/components/DataAnalytics.tsx index 665c8c2ee05..b3f6cab5042 100644 --- a/packages/components/src/components/DataAnalytics.tsx +++ b/packages/components/src/components/DataAnalytics.tsx @@ -1,13 +1,15 @@ import { Fragment, useState, ReactNode } from 'react'; +import { FormattedMessage } from 'react-intl'; + import styled from 'styled-components'; -import { FormattedMessage } from 'react-intl'; +import { spacingsPx } from '@trezor/theme'; + import { variables } from '../config'; import { Button } from './buttons/Button/Button'; import { CollapsibleBox } from './CollapsibleBox/CollapsibleBox'; import { Card } from './Card/Card'; import { Switch } from './form/Switch/Switch'; -import { spacingsPx } from '@trezor/theme'; const Wrapper = styled.div` display: flex; @@ -30,7 +32,6 @@ const ButtonWrapper = styled.div` align-self: center; `; -// eslint-disable-next-line local-rules/no-override-ds-component const StyledButton = styled(Button)` min-width: 180px; `; diff --git a/packages/components/src/components/Divider/Divider.stories.tsx b/packages/components/src/components/Divider/Divider.stories.tsx index c93e77fd5e4..41faa6ccc28 100644 --- a/packages/components/src/components/Divider/Divider.stories.tsx +++ b/packages/components/src/components/Divider/Divider.stories.tsx @@ -1,6 +1,7 @@ import { Meta, StoryObj } from '@storybook/react'; -import { Divider as DividerComponent, allowedDividerFrameProps } from './Divider'; import styled from 'styled-components'; + +import { Divider as DividerComponent, allowedDividerFrameProps } from './Divider'; import { getFramePropsStory } from '../../utils/frameProps'; const Container = styled.div` @@ -35,7 +36,7 @@ export const Divider: StoryObj = { type: 'number', }, color: { - type: 'string', + control: 'color', }, ...getFramePropsStory(allowedDividerFrameProps).argTypes, }, diff --git a/packages/components/src/components/Divider/Divider.tsx b/packages/components/src/components/Divider/Divider.tsx index 3df4fcd2552..04b78ec99f3 100644 --- a/packages/components/src/components/Divider/Divider.tsx +++ b/packages/components/src/components/Divider/Divider.tsx @@ -1,5 +1,7 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; + import { Color, Elevation, mapElevationToBorder, spacings } from '@trezor/theme'; + import { useElevation } from '../ElevationContext/ElevationContext'; import { FrameProps, @@ -8,13 +10,16 @@ import { withFrameProps, } from '../../utils/frameProps'; import { TransientProps } from '../../utils/transientProps'; -import { allowedHeadingFrameProps } from '../typography/Heading/Heading'; -export const allowedDividerFrameProps = ['margin'] as const satisfies FramePropsKeys[]; +export const allowedDividerFrameProps = [ + 'margin', + 'width', + 'overflow', +] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; type DividerOrientation = 'horizontal' | 'vertical'; -type DividerProps = AllowedFrameProps & { +export type DividerProps = AllowedFrameProps & { orientation?: DividerOrientation; strokeWidth?: number; color?: Color; @@ -30,15 +35,16 @@ const Line = styled.div< >` ${({ $orientation, $strokeWidth }) => $orientation === 'vertical' - ? ` - height: 100%; - width: ${$strokeWidth}px; - min-width: ${$strokeWidth}px;` - : ` - width: 100%; - height: ${$strokeWidth}px; - min-height: ${$strokeWidth}px; - `} + ? css` + height: 100%; + width: ${$strokeWidth}px; + min-width: ${$strokeWidth}px; + ` + : css` + width: 100%; + height: ${$strokeWidth}px; + min-height: ${$strokeWidth}px; + `} background: ${({ theme, $elevation, $color }) => $color ? theme[$color] : mapElevationToBorder({ theme, $elevation })}; @@ -56,7 +62,7 @@ export const Divider = ({ const frameProps = pickAndPrepareFrameProps( { ...rest, margin: rest.margin ?? { top: spacings.md, bottom: spacings.md } }, - allowedHeadingFrameProps, + allowedDividerFrameProps, ); return ( diff --git a/packages/components/src/components/Dropdown/Dropdown.stories.tsx b/packages/components/src/components/Dropdown/Dropdown.stories.tsx index f5b6becf2ed..97f48f9b6ad 100644 --- a/packages/components/src/components/Dropdown/Dropdown.stories.tsx +++ b/packages/components/src/components/Dropdown/Dropdown.stories.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { Dropdown as DropdownComponent, DropdownProps } from './Dropdown'; const Center = styled.div` diff --git a/packages/components/src/components/Dropdown/Dropdown.tsx b/packages/components/src/components/Dropdown/Dropdown.tsx index 0a18745ba6a..ff0d11c3a8c 100644 --- a/packages/components/src/components/Dropdown/Dropdown.tsx +++ b/packages/components/src/components/Dropdown/Dropdown.tsx @@ -11,8 +11,11 @@ import { useEffect, } from 'react'; import { createPortal } from 'react-dom'; + import styled from 'styled-components'; + import { useOnClickOutside } from '@trezor/react-utils'; + import { Menu, MenuProps, DropdownMenuItemProps } from './Menu'; import { Coords, getAdjustedCoords } from './getAdjustedCoords'; import { IconButton } from '../buttons/IconButton/IconButton'; @@ -124,7 +127,7 @@ export const Dropdown = forwardRef( return; } - const { width, height } = menuRef.current?.getBoundingClientRect(); + const { width, height } = menuRef.current.getBoundingClientRect(); const adjustedCoords = getAdjustedCoords({ coords: coordsToUse, @@ -149,9 +152,9 @@ export const Dropdown = forwardRef( } }, [isToggled, content]); - const setToggled = (isToggled: boolean) => { - if (onToggle) onToggle(isToggled); - setIsToggledState(isToggled); + const setToggled = (isToggled2: boolean) => { + if (onToggle) onToggle(isToggled2); + setIsToggledState(isToggled2); }; useImperativeHandle(ref, () => ({ diff --git a/packages/components/src/components/Dropdown/Menu.tsx b/packages/components/src/components/Dropdown/Menu.tsx index 646fc96df97..cd2c1218a0f 100644 --- a/packages/components/src/components/Dropdown/Menu.tsx +++ b/packages/components/src/components/Dropdown/Menu.tsx @@ -1,5 +1,7 @@ import React, { forwardRef, useEffect, useState } from 'react'; + import styled, { css, keyframes, useTheme } from 'styled-components'; + import { borders, spacings, @@ -9,6 +11,7 @@ import { mapElevationToBackground, nextElevation, } from '@trezor/theme'; + import type { Coords } from './getAdjustedCoords'; import { menuStyle } from './menuStyle'; import { useElevation } from '../ElevationContext/ElevationContext'; @@ -270,11 +273,11 @@ const getNextIndex = nextIndex = getPrevIndex(nextIndex); } } else if (keyboardKey === 'ArrowDown') { - const getNextIndex = (current: number) => (current < lastIndex ? current + 1 : 0); - nextIndex = getNextIndex(nextIndex); + const getNextIndex2 = (current: number) => (current < lastIndex ? current + 1 : 0); + nextIndex = getNextIndex2(nextIndex); // skip disabled items while (flatGroupItems[nextIndex].isDisabled) { - nextIndex = getNextIndex(nextIndex); + nextIndex = getNextIndex2(nextIndex); } } diff --git a/packages/components/src/components/Dropdown/menuStyle.ts b/packages/components/src/components/Dropdown/menuStyle.ts index 773550a5db5..4d9bcf01b61 100644 --- a/packages/components/src/components/Dropdown/menuStyle.ts +++ b/packages/components/src/components/Dropdown/menuStyle.ts @@ -1,4 +1,5 @@ import { css, keyframes } from 'styled-components'; + import { spacingsPx, borders, diff --git a/packages/components/src/components/ElevationContext/ElevationContext.stories.tsx b/packages/components/src/components/ElevationContext/ElevationContext.stories.tsx index 859b816350b..5159c0dea27 100644 --- a/packages/components/src/components/ElevationContext/ElevationContext.stories.tsx +++ b/packages/components/src/components/ElevationContext/ElevationContext.stories.tsx @@ -1,7 +1,18 @@ +import { ReactNode } from 'react'; + import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + +import { + Elevation, + borders, + mapElevationToBackground, + mapElevationToBorder, + spacingsPx, +} from '@trezor/theme'; + import { Card } from '../Card/Card'; import { Modal } from '../modals/Modal/Modal'; -import styled from 'styled-components'; import { Textarea } from '../form/Textarea/Textarea'; import { useElevation, @@ -9,14 +20,6 @@ import { ElevationDown, ElevationUp, } from './ElevationContext'; -import { - Elevation, - borders, - mapElevationToBackground, - mapElevationToBorder, - spacingsPx, -} from '@trezor/theme'; -import { ReactNode } from 'react'; const UiBox = styled.div<{ $elevation: Elevation }>` background-color: ${mapElevationToBackground}; @@ -109,12 +112,12 @@ export const ElevationContext: StoryObj = { - Card and Textarea inside it wrapped in the "extender" component with same - elevation as the Textarea has. + Card and Textarea inside it wrapped in the "extender" component with + same elevation as the Textarea has. diff --git a/packages/components/src/components/ElevationContext/ElevationContext.tsx b/packages/components/src/components/ElevationContext/ElevationContext.tsx index cea2ee7011b..291f5834c68 100644 --- a/packages/components/src/components/ElevationContext/ElevationContext.tsx +++ b/packages/components/src/components/ElevationContext/ElevationContext.tsx @@ -1,7 +1,9 @@ -import { Elevation, nextElevation, prevElevation } from '@trezor/theme'; import { ReactNode, createContext, useContext, useMemo } from 'react'; + import styled from 'styled-components'; +import { Elevation, nextElevation, prevElevation } from '@trezor/theme'; + const DEBUG = false; const ElevationReactContext = createContext<{ diff --git a/packages/components/src/components/Flag/Flag.stories.tsx b/packages/components/src/components/Flag/Flag.stories.tsx index 603200398d0..ee375c2fccf 100644 --- a/packages/components/src/components/Flag/Flag.stories.tsx +++ b/packages/components/src/components/Flag/Flag.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Flag as FlagComponent } from './Flag'; const meta: Meta = { diff --git a/packages/components/src/components/Flag/Flag.tsx b/packages/components/src/components/Flag/Flag.tsx index f0b2e7c4564..393f1000648 100644 --- a/packages/components/src/components/Flag/Flag.tsx +++ b/packages/components/src/components/Flag/Flag.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import { FLAGS } from './flags'; export type FlagType = keyof typeof FLAGS; diff --git a/packages/components/src/components/Flex/Flex.stories.tsx b/packages/components/src/components/Flex/Flex.stories.tsx index 80e4a22729d..fd4cced5f86 100644 --- a/packages/components/src/components/Flex/Flex.stories.tsx +++ b/packages/components/src/components/Flex/Flex.stories.tsx @@ -1,4 +1,8 @@ import { ArgTypes, Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + +import { spacings } from '@trezor/theme'; + import { FlexProps, flexAlignItems, @@ -8,8 +12,6 @@ import { Column as ColumnComponent, allowedFlexFrameProps, } from './Flex'; -import { spacings } from '@trezor/theme'; -import styled from 'styled-components'; import { getFramePropsStory } from '../../utils/frameProps'; const Container = styled.div` @@ -90,9 +92,9 @@ const meta: Meta = { export default meta; export const Row: StoryObj = { - render: args => ( + render: rowArgs => ( - + ), args, @@ -100,9 +102,9 @@ export const Row: StoryObj = { }; export const Column: StoryObj = { - render: args => ( + render: columnArgs => ( - + ), args, diff --git a/packages/components/src/components/Flex/Flex.tsx b/packages/components/src/components/Flex/Flex.tsx index bb5635eee9a..8fec08e74ae 100644 --- a/packages/components/src/components/Flex/Flex.tsx +++ b/packages/components/src/components/Flex/Flex.tsx @@ -1,5 +1,9 @@ -import { Elevation, mapElevationToBorder, SpacingValues } from '@trezor/theme'; +import React from 'react'; + import styled, { css, DefaultTheme } from 'styled-components'; + +import { Elevation, mapElevationToBorder, SpacingValues } from '@trezor/theme'; + import { FrameProps, FramePropsKeys, @@ -8,13 +12,13 @@ import { } from '../../utils/frameProps'; import { makePropsTransient, TransientProps } from '../../utils/transientProps'; import { useElevation } from '../ElevationContext/ElevationContext'; -import React from 'react'; export const allowedFlexFrameProps = [ 'margin', 'width', 'height', 'minHeight', + 'maxWidth', 'overflow', ] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; @@ -112,6 +116,7 @@ type ContainerProps = TransientProps & { $direction: FlexDirection; $flex: Flex; $flexWrap: FlexWrap; + $order?: number; $isReversed: boolean; $hasDivider: boolean; $dividerColor?: string; @@ -127,6 +132,7 @@ const Container = styled.div` gap: ${({ $gap }) => $gap}px; justify-content: ${({ $justifyContent }) => $justifyContent}; align-items: ${({ $alignItems }) => $alignItems}; + ${({ $order }) => (typeof $order !== 'undefined' ? `order: ${$order};` : '')} ${({ $hasDivider, ...props }) => $hasDivider && withDivider(props)} ${withFrameProps} @@ -140,6 +146,7 @@ export type FlexProps = AllowedFrameProps & { direction?: FlexDirection; flex?: Flex; flexWrap?: FlexWrap; + order?: number; isReversed?: boolean; hasDivider?: boolean; /** @deprecated Use only is case of absolute desperation. Prefer keep it according to elevation. */ @@ -147,19 +154,23 @@ export type FlexProps = AllowedFrameProps & { className?: string; onClick?: () => void; 'data-testid'?: string; + as?: string; }; -const Flex = ({ +export const Flex = ({ gap = 0, justifyContent = 'flex-start', alignItems = 'center', children, direction = 'row', flex = 'initial', + // eslint-disable-next-line @typescript-eslint/no-shadow flexWrap = 'nowrap', + order, isReversed = false, className, 'data-testid': dataTestId, + as = 'div', hasDivider = false, dividerColor, onClick, @@ -180,12 +191,14 @@ const Flex = ({ direction, flex, flexWrap, + order, isReversed, hasDivider, dividerColor, elevation, })} onClick={onClick} + as={as} {...frameProps} > {children} diff --git a/packages/components/src/components/GradientOverlay/GradientOverlay.stories.tsx b/packages/components/src/components/GradientOverlay/GradientOverlay.stories.tsx index db8cc9b3416..2d7eda6a8f5 100644 --- a/packages/components/src/components/GradientOverlay/GradientOverlay.stories.tsx +++ b/packages/components/src/components/GradientOverlay/GradientOverlay.stories.tsx @@ -1,9 +1,10 @@ import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + import { GradientOverlay as GradientOverlayComponent, GradientOverlayProps, } from './GradientOverlay'; -import styled from 'styled-components'; import { Card } from '../Card/Card'; import { ElevationContext } from '../ElevationContext/ElevationContext'; diff --git a/packages/components/src/components/GradientOverlay/GradientOverlay.tsx b/packages/components/src/components/GradientOverlay/GradientOverlay.tsx index b399bb68c51..f0d0c8874b5 100644 --- a/packages/components/src/components/GradientOverlay/GradientOverlay.tsx +++ b/packages/components/src/components/GradientOverlay/GradientOverlay.tsx @@ -1,5 +1,7 @@ -import { Elevation, mapElevationToBackground } from '@trezor/theme'; import styled from 'styled-components'; + +import { Elevation, mapElevationToBackground } from '@trezor/theme'; + import { useElevation } from '../ElevationContext/ElevationContext'; export interface GradientOverlayProps { diff --git a/packages/components/src/components/Grid/Grid.stories.tsx b/packages/components/src/components/Grid/Grid.stories.tsx index bc4c60cc523..3f0f37b7d92 100644 --- a/packages/components/src/components/Grid/Grid.stories.tsx +++ b/packages/components/src/components/Grid/Grid.stories.tsx @@ -1,7 +1,9 @@ import { ArgTypes, Meta, StoryObj } from '@storybook/react'; -import { GridProps, Grid as GridComponent, allowedGridFrameProps } from './Grid'; -import { spacings } from '@trezor/theme'; import styled from 'styled-components'; + +import { spacings } from '@trezor/theme'; + +import { GridProps, Grid as GridComponent, allowedGridFrameProps } from './Grid'; import { getFramePropsStory } from '../../utils/frameProps'; const Container = styled.div` @@ -60,9 +62,9 @@ const meta: Meta = { export default meta; export const Grid: StoryObj = { - render: args => ( + render: gridArgs => ( - + ), args, diff --git a/packages/components/src/components/Grid/Grid.tsx b/packages/components/src/components/Grid/Grid.tsx index 26cbf3a1093..27d3a24a606 100644 --- a/packages/components/src/components/Grid/Grid.tsx +++ b/packages/components/src/components/Grid/Grid.tsx @@ -1,5 +1,7 @@ -import { SpacingValues } from '@trezor/theme'; import styled from 'styled-components'; + +import { SpacingValues } from '@trezor/theme'; + import { FrameProps, FramePropsKeys, withFrameProps } from '../../utils/frameProps'; import { makePropsTransient, TransientProps } from '../../utils/transientProps'; diff --git a/packages/components/src/components/HotkeyBadge/HotkeyBadge.stories.tsx b/packages/components/src/components/HotkeyBadge/HotkeyBadge.stories.tsx index 42e43595f90..90e3ad32595 100644 --- a/packages/components/src/components/HotkeyBadge/HotkeyBadge.stories.tsx +++ b/packages/components/src/components/HotkeyBadge/HotkeyBadge.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { HotkeyBadge as HotkeyBadgeComponent, HotkeyBadgeProps } from './HotkeyBadge'; const meta: Meta = { diff --git a/packages/components/src/components/HotkeyBadge/HotkeyBadge.tsx b/packages/components/src/components/HotkeyBadge/HotkeyBadge.tsx index 19ebd53f4a0..cc8553f28a6 100644 --- a/packages/components/src/components/HotkeyBadge/HotkeyBadge.tsx +++ b/packages/components/src/components/HotkeyBadge/HotkeyBadge.tsx @@ -1,3 +1,7 @@ +import { Fragment } from 'react'; + +import styled from 'styled-components'; + import { Elevation, borders, @@ -5,10 +9,9 @@ import { spacingsPx, typography, } from '@trezor/theme'; -import styled from 'styled-components'; + import { ElevationDown, useElevation } from '../ElevationContext/ElevationContext'; import { Keys, keyboardKeys } from './keyboardKeys'; -import { Fragment } from 'react'; export const Container = styled.div<{ $elevation: Elevation; $isActive: boolean }>` display: flex; diff --git a/packages/components/src/components/Icon/Icon.stories.tsx b/packages/components/src/components/Icon/Icon.stories.tsx index 595e387aae2..7c9d2518b41 100644 --- a/packages/components/src/components/Icon/Icon.stories.tsx +++ b/packages/components/src/components/Icon/Icon.stories.tsx @@ -1,4 +1,11 @@ import { Meta, StoryObj } from '@storybook/react'; + +import { IconName, icons } from '@suite-common/icons/src/icons'; +import { + icons as iconsDeprecated, + IconName as IconNameDeprecated, +} from '@suite-common/icons-deprecated'; + import { allowedIconFrameProps, Icon as IconComponent, @@ -6,12 +13,8 @@ import { iconVariants, iconSizes, } from './Icon'; -import { IconName, icons } from '@suite-common/icons'; -import { - icons as iconsDeprecated, - IconName as IconNameDeprecated, -} from '@suite-common/icons-deprecated/src/webComponents'; import { getFramePropsStory } from '../../utils/frameProps'; + const meta: Meta = { title: 'Icons', component: IconComponent, @@ -55,6 +58,9 @@ export const Icon: StoryObj = { type: 'select', }, }, + color: { + control: 'color', + }, size: { options: iconSizes, control: { diff --git a/packages/components/src/components/Icon/Icon.tsx b/packages/components/src/components/Icon/Icon.tsx index 6ce0eb7c68c..62ce1458e3b 100644 --- a/packages/components/src/components/Icon/Icon.tsx +++ b/packages/components/src/components/Icon/Icon.tsx @@ -2,11 +2,12 @@ import { ReactSVG } from 'react-svg'; import { forwardRef, MouseEvent, Ref } from 'react'; import styled, { css, DefaultTheme } from 'styled-components'; + import { icons as iconsDeprecated, IconName as IconNameDeprecated, -} from '@suite-common/icons-deprecated/src/webComponents'; -import { icons, IconName as IconNameNew } from '@suite-common/icons'; +} from '@suite-common/icons-deprecated'; +import { icons, IconName as IconNameNew } from '@suite-common/icons/src/icons'; import { CSSColor, Color } from '@trezor/theme'; import { UIVariant } from '../../config/types'; @@ -21,10 +22,12 @@ import { export const iconVariants = [ 'primary', 'tertiary', + 'default', 'info', 'warning', 'destructive', 'purple', + 'disabled', ] as const; export type IconVariant = Extract | 'purple'; @@ -34,7 +37,7 @@ export type ExclusiveColorOrVariant = | { variant?: undefined; /** @deprecated Use only is case of absolute desperation. Prefer using `variant`. */ - color?: string; + color?: CSSColor; }; export const allowedIconFrameProps = [ @@ -64,16 +67,15 @@ const variantColorMap: Record = { warning: 'iconAlertYellow', destructive: 'iconAlertRed', purple: 'iconAlertPurple', + default: 'iconDefault', + disabled: 'iconDisabled', }; export const getColorForIconVariant = ({ variant, theme, color, -}: Pick & { theme: DefaultTheme }): - | CSSColor - | 'inherit' - | string => { +}: Pick & { theme: DefaultTheme }): CSSColor => { if (color !== undefined) { return color; } @@ -92,7 +94,6 @@ const SvgWrapper = styled.div getColorForIconVariant({ variant: $variant, color: $color, theme })}; @@ -124,19 +125,6 @@ const SVG = styled(ReactSVG)` stroke 0.15s, fill 0.15s; } - - ${({ onClick }) => - onClick && - css` - cursor: pointer; - - &:focus-visible { - svg { - transition: opacity 0.2s; - opacity: 0.5; - } - } - `} ` as typeof ReactSVG; export type IconName = IconNameNew | IconNameDeprecated; @@ -197,7 +185,7 @@ export const Icon = forwardRef( return ( { placeholder="Search icon" value={search} onChange={event => setSearch(event.target.value)} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={theme.legacy.THEME === 'light'} onClear={() => setSearch('')} showClearButton="always" @@ -152,6 +156,9 @@ export const AllIcons: StoryObj = { type: 'select', }, }, + color: { + control: 'color', + }, size: { options: iconSizes, control: { diff --git a/packages/components/src/components/IconCircle/IconCircle.stories.tsx b/packages/components/src/components/IconCircle/IconCircle.stories.tsx new file mode 100644 index 00000000000..d20b670aa83 --- /dev/null +++ b/packages/components/src/components/IconCircle/IconCircle.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import { Meta, StoryObj } from '@storybook/react'; + +import { icons, IconName } from '@suite-common/icons/src/icons'; + +import { + IconCircle as IconCircleComponent, + IconCircleProps, + allowedIconCircleFrameProps, +} from './IconCircle'; +import { iconCircleVariants } from './types'; +import { getFramePropsStory } from '../../utils/frameProps'; + +const meta: Meta = { + title: 'IconCircle', +} as Meta; +export default meta; + +export const IconCircle: StoryObj = { + render: props => , + args: { + variant: 'primary', + name: 'butterfly', + size: 40, + hasBorder: true, + ...getFramePropsStory(allowedIconCircleFrameProps).args, + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: iconCircleVariants, + }, + size: { + control: { + type: 'number', + }, + }, + hasBorder: { + control: { + type: 'boolean', + }, + }, + name: { + control: { + type: 'select', + }, + options: Object.keys(icons) as IconName[], + }, + ...getFramePropsStory(allowedIconCircleFrameProps).argTypes, + }, +}; diff --git a/packages/components/src/components/IconCircle/IconCircle.tsx b/packages/components/src/components/IconCircle/IconCircle.tsx new file mode 100644 index 00000000000..5fe6b78708d --- /dev/null +++ b/packages/components/src/components/IconCircle/IconCircle.tsx @@ -0,0 +1,74 @@ +import styled from 'styled-components'; + +import { ExclusiveColorOrVariant, Icon, IconName, IconSize, getIconSize } from '../Icon/Icon'; +import { TransientProps } from '../../utils/transientProps'; +import { IconCircleExclusiveColorOrVariant, IconCircleVariant, IconCircleColors } from './types'; +import { mapVariantToIconBackground, mapVariantToIconBorderColor } from './utils'; +import { + FrameProps, + FramePropsKeys, + pickAndPrepareFrameProps, + withFrameProps, +} from '../../utils/frameProps'; + +export const allowedIconCircleFrameProps = ['margin'] as const satisfies FramePropsKeys[]; +type AllowedFrameProps = Pick; + +type IconCircleWrapperProps = TransientProps< + IconCircleExclusiveColorOrVariant & AllowedFrameProps +> & { + $size: number; + $hasBorder: boolean; +}; + +const IconCircleWrapper = styled.div` + width: ${({ $size }) => $size}px; + background: ${mapVariantToIconBackground}; + padding: ${({ $size }) => $size * 0.75}px; + border-radius: 50%; + box-shadow: inset 0 0 0 ${({ $hasBorder, $size }) => ($hasBorder ? $size / 4 : 0)}px + ${mapVariantToIconBorderColor}; + box-sizing: content-box; + + ${withFrameProps} +`; + +export type IconCircleProps = { + name: IconName; + size: IconSize | number; + hasBorder?: boolean; +} & IconCircleExclusiveColorOrVariant & + AllowedFrameProps; + +export const IconCircle = ({ + name, + size, + hasBorder = true, + iconColor, + variant, + ...rest +}: IconCircleProps) => { + const wrapperColorOrVariant: TransientProps = + iconColor === undefined ? { $variant: variant ?? 'primary' } : { $iconColor: iconColor }; + + const iconColorOrVariant: ExclusiveColorOrVariant = + iconColor === undefined + ? { variant: variant ?? 'primary' } + : { color: iconColor.foreground }; + + const iconSize = getIconSize(size); + const frameProps = pickAndPrepareFrameProps(rest, allowedIconCircleFrameProps); + + return ( + + + + ); +}; + +export type { IconCircleVariant, IconCircleColors }; diff --git a/packages/components/src/components/IconCircle/types.tsx b/packages/components/src/components/IconCircle/types.tsx new file mode 100644 index 00000000000..c813c094a60 --- /dev/null +++ b/packages/components/src/components/IconCircle/types.tsx @@ -0,0 +1,19 @@ +import { CSSColor } from '@trezor/theme'; + +import { UIVariant } from '../../config/types'; + +export const iconCircleVariants = [ + 'primary', + 'warning', + 'destructive', + 'info', + 'tertiary', +] as const; + +export type IconCircleVariant = Extract; + +export type IconCircleColors = { foreground: CSSColor; background: CSSColor }; + +export type IconCircleExclusiveColorOrVariant = + | { variant?: IconCircleVariant; iconColor?: undefined } + | { variant?: undefined; iconColor?: IconCircleColors }; diff --git a/packages/components/src/components/IconCircle/utils.tsx b/packages/components/src/components/IconCircle/utils.tsx new file mode 100644 index 00000000000..2e2358bc032 --- /dev/null +++ b/packages/components/src/components/IconCircle/utils.tsx @@ -0,0 +1,56 @@ +import { DefaultTheme } from 'styled-components'; + +import { Color, CSSColor } from '@trezor/theme'; + +import { IconCircleVariant, IconCircleExclusiveColorOrVariant } from './types'; +import { TransientProps } from '../../utils/transientProps'; + +type MapArgs = { + theme: DefaultTheme; + $hasBorder: boolean; +} & TransientProps; + +export const mapVariantToIconBorderColor = ({ $variant, theme, $iconColor }: MapArgs): CSSColor => { + if ($variant === undefined) { + return $iconColor?.foreground ?? 'transparent'; + } + + const colorMap: Record = { + primary: 'backgroundPrimarySubtleOnElevation0', + warning: 'backgroundAlertYellowSubtleOnElevation0', + destructive: 'backgroundAlertRedSubtleOnElevation0', + info: 'backgroundAlertBlueSubtleOnElevation0', + tertiary: 'backgroundTertiaryDefaultOnElevation0', + }; + + return theme[colorMap[$variant]]; +}; + +export const mapVariantToIconBackground = ({ + theme, + $hasBorder, + $iconColor, + $variant, +}: MapArgs): CSSColor => { + if ($variant === undefined) { + return $iconColor?.background ?? 'transparent'; + } + + const noBorderColorMap: Record = { + primary: 'backgroundPrimarySubtleOnElevation1', + warning: 'backgroundAlertYellowSubtleOnElevation1', + destructive: 'backgroundAlertRedSubtleOnElevation1', + info: 'backgroundAlertBlueSubtleOnElevation1', + tertiary: 'backgroundTertiaryDefaultOnElevation1', + }; + + const borderColorMap: Record = { + primary: 'backgroundPrimarySubtleOnElevation2', + warning: 'backgroundAlertYellowSubtleOnElevation2', + destructive: 'backgroundAlertRedSubtleOnElevation2', + info: 'backgroundAlertBlueSubtleOnElevation2', + tertiary: 'backgroundTertiaryDefaultOnElevation1', + }; + + return theme[($hasBorder ? borderColorMap : noBorderColorMap)[$variant]]; +}; diff --git a/packages/components/src/components/Image/Image.stories.tsx b/packages/components/src/components/Image/Image.stories.tsx index 9efd31fcea7..21dd8faf331 100644 --- a/packages/components/src/components/Image/Image.stories.tsx +++ b/packages/components/src/components/Image/Image.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Image as ImageComponent, ImageProps } from './Image'; import { PNG_IMAGES, SVG_IMAGES } from './images'; @@ -10,7 +11,7 @@ export default meta; export const Image: StoryObj = { args: { - image: 'EARLY_ACCESS', + image: 'BACKUP', width: undefined, height: undefined, imageSrc: undefined, diff --git a/packages/components/src/components/Image/Image.tsx b/packages/components/src/components/Image/Image.tsx index 4cbc1ff2695..c8b29cc4691 100644 --- a/packages/components/src/components/Image/Image.tsx +++ b/packages/components/src/components/Image/Image.tsx @@ -1,5 +1,7 @@ import { ImgHTMLAttributes } from 'react'; + import styled from 'styled-components'; + import { PngImage, SvgImage, PNG_IMAGES, SVG_IMAGES } from './images'; import { resolveStaticPath } from '../../utils/resolveStaticPath'; diff --git a/packages/components/src/components/Image/images.ts b/packages/components/src/components/Image/images.ts index c8450adc9ee..fe6cd2a0a14 100644 --- a/packages/components/src/components/Image/images.ts +++ b/packages/components/src/components/Image/images.ts @@ -10,6 +10,7 @@ export const SVG_IMAGES = { DEVICE_CONFIRM_TREZOR_T2B1: 'device-confirm-trezor-t3b1.svg', DEVICE_CONFIRM_TREZOR_T3B1: 'device-confirm-trezor-t3b1.svg', DEVICE_CONFIRM_TREZOR_T3T1: 'device-confirm-trezor-t3t1.svg', + DEVICE_CONFIRM_TREZOR_T3W1: 'device-confirm-trezor-t3w1.svg', SPINNER: 'spinner.svg', SPINNER_GREY: 'spinner-grey.svg', SPINNER_LIGHT_GREY: 'spinner-light-grey.svg', @@ -18,8 +19,6 @@ export const SVG_IMAGES = { DEVICE_ANOTHER_SESSION: 'device-another-session.svg', CONNECT_DEVICE: 'connect-device.svg', ERROR_404: 'error-404.svg', - EARLY_ACCESS: 'early-access.svg', - EARLY_ACCESS_DISABLE: 'early-access-disable.svg', INVITY_LOGO: 'invity-logo.svg', COINMARKET_AVATAR: 'coinmarket-avatar.svg', COINMARKET_SUCCESS: 'coinmarket-success.svg', @@ -39,6 +38,7 @@ export const SVG_IMAGES = { TREZOR_SAFE_PROMO_UNDERLINE: 'trezor-safe-promo-underline.svg', CONFIRM_EVM_EXPLANATION_ETH: 'confirm-evm-explanation-eth.svg', CONFIRM_EVM_EXPLANATION_OTHER: 'confirm-evm-explanation-other.svg', + GAINS_GRAPH: 'gains-graph.svg', } as const; export type PngImage = keyof typeof PNG_IMAGES; @@ -92,6 +92,8 @@ export const PNG_IMAGES = { DONT_DISCONNECT_TREZOR_T3B1_2x: 'dont-disconnect-trezor-t3b1@2x.png', DONT_DISCONNECT_TREZOR_T3T1: 'dont-disconnect-trezor-t3t1.png', DONT_DISCONNECT_TREZOR_T3T1_2x: 'dont-disconnect-trezor-t3t1@2x.png', + DONT_DISCONNECT_TREZOR_T3W1: 'dont-disconnect-trezor-t3w1.png', + DONT_DISCONNECT_TREZOR_T3W1_2x: 'dont-disconnect-trezor-t3w1@2x.png', TREZOR_T1B1: 'trezor-t1b1.png', TREZOR_T1B1_2x: 'trezor-t1b1@2x.png', TREZOR_T2T1: 'trezor-t2t1.png', @@ -102,6 +104,8 @@ export const PNG_IMAGES = { TREZOR_T3B1_2x: 'trezor-t3b1@2x.png', TREZOR_T3T1: 'trezor-t3t1.png', TREZOR_T3T1_2x: 'trezor-t3t1@2x.png', + TREZOR_T3W1: 'trezor-t3w1.png', + TREZOR_T3W1_2x: 'trezor-t3w1@2x.png', TREZOR_T1B1_LARGE: 'trezor-t1b1-large.png', TREZOR_T1B1_LARGE_2x: 'trezor-t1b1-large@2x.png', TREZOR_T2T1_LARGE: 'trezor-t2t1-large.png', @@ -112,6 +116,8 @@ export const PNG_IMAGES = { TREZOR_T3B1_LARGE_2x: 'trezor-t3b1-large@2x.png', TREZOR_T3T1_LARGE: 'trezor-t3t1-large.png', TREZOR_T3T1_LARGE_2x: 'trezor-t3t1-large@2x.png', + TREZOR_T3W1_LARGE: 'trezor-t3w1-large.png', + TREZOR_T3W1_LARGE_2x: 'trezor-t3w1-large@2x.png', TREZOR_T1B1_GHOST: 'trezor-t1b1-ghost.png', TREZOR_T1B1_GHOST_2x: 'trezor-t1b1-ghost@2x.png', TREZOR_T2T1_GHOST: 'trezor-t2t1-ghost.png', @@ -122,6 +128,8 @@ export const PNG_IMAGES = { TREZOR_T3B1_GHOST_2x: 'trezor-t3b1-ghost@2x.png', TREZOR_T3T1_GHOST: 'trezor-t3t1-ghost.png', TREZOR_T3T1_GHOST_2x: 'trezor-t3t1-ghost@2x.png', + TREZOR_T3W1_GHOST: 'trezor-t3w1-ghost.png', + TREZOR_T3W1_GHOST_2x: 'trezor-t3w1-ghost@2x.png', TREZOR_SAFE_PROMO_PRODUCTS: 'trezor-safe-promo-products.png', TREZOR_SAFE_PROMO_PRODUCTS_2x: 'trezor-safe-promo-products@2x.png', } as const; diff --git a/packages/components/src/components/InfoRow/InfoRow.stories.tsx b/packages/components/src/components/InfoRow/InfoRow.stories.tsx new file mode 100644 index 00000000000..ec339c91557 --- /dev/null +++ b/packages/components/src/components/InfoRow/InfoRow.stories.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { Meta, StoryObj } from '@storybook/react'; + +import { + InfoRow as InfoRowComponent, + allowedInfoRowFrameProps, + allowedInfoRowTextProps, +} from './InfoRow'; +import { flexDirection } from '../Flex/Flex'; +import { getFramePropsStory } from '../../utils/frameProps'; +import { getTextPropsStory } from '../typography/utils'; + +const meta: Meta = { + title: 'InfoRow', +} as Meta; +export default meta; + +export const InfoRow: StoryObj = { + render: props => ( + + Lorem ipsum + + ), + args: { + direction: 'column', + label: 'Label', + ...getFramePropsStory(allowedInfoRowFrameProps).args, + ...getTextPropsStory(allowedInfoRowTextProps).args, + }, + argTypes: { + direction: { + options: flexDirection, + control: { + type: 'radio', + }, + }, + label: { + control: { + type: 'text', + }, + }, + ...getFramePropsStory(allowedInfoRowFrameProps).argTypes, + ...getTextPropsStory(allowedInfoRowTextProps).argTypes, + }, +}; diff --git a/packages/components/src/components/InfoRow/InfoRow.tsx b/packages/components/src/components/InfoRow/InfoRow.tsx new file mode 100644 index 00000000000..ae361bb8aea --- /dev/null +++ b/packages/components/src/components/InfoRow/InfoRow.tsx @@ -0,0 +1,68 @@ +import { ReactNode } from 'react'; + +import styled from 'styled-components'; + +import { spacings, TypographyStyle } from '@trezor/theme'; + +import { + FrameProps, + FramePropsKeys, + pickAndPrepareFrameProps, + withFrameProps, +} from '../../utils/frameProps'; +import { TextPropsKeys, TextProps } from '../typography/utils'; +import { TransientProps } from '../../utils/transientProps'; +import { FlexDirection, Flex } from '../Flex/Flex'; +import { Text } from '../typography/Text/Text'; + +export const allowedInfoRowTextProps = ['typographyStyle'] as const satisfies TextPropsKeys[]; +type AllowedInfoRowTextProps = Pick; + +export const allowedInfoRowFrameProps = ['margin'] as const satisfies FramePropsKeys[]; +type AllowedFrameProps = Pick; + +type ContainerProps = TransientProps; + +const Container = styled.div` + width: 100%; + + ${withFrameProps} +`; + +export type InfoRowProps = AllowedFrameProps & + AllowedInfoRowTextProps & { + children?: ReactNode; + direction?: FlexDirection; + label: ReactNode; + labelTypographyStyle?: TypographyStyle; + }; + +export const InfoRow = ({ + children, + label, + direction = 'column', + typographyStyle = 'body', + labelTypographyStyle = 'hint', + ...rest +}: InfoRowProps) => { + const frameProps = pickAndPrepareFrameProps(rest, allowedInfoRowFrameProps); + const isRow = direction === 'row'; + + return ( + + + + {label} + + + {children} + + + + ); +}; diff --git a/packages/components/src/components/List/List.stories.tsx b/packages/components/src/components/List/List.stories.tsx index 4f08099bfef..9d809b6ce98 100644 --- a/packages/components/src/components/List/List.stories.tsx +++ b/packages/components/src/components/List/List.stories.tsx @@ -1,7 +1,9 @@ import React from 'react'; + import { Meta, StoryObj } from '@storybook/react'; import { spacings } from '@trezor/theme'; + import { Icon } from '../Icon/Icon'; import { List as ListComponent, diff --git a/packages/components/src/components/List/List.tsx b/packages/components/src/components/List/List.tsx index 16ef3a8cb04..232630cb65a 100644 --- a/packages/components/src/components/List/List.tsx +++ b/packages/components/src/components/List/List.tsx @@ -1,7 +1,9 @@ import { createContext, useContext } from 'react'; -import { spacings, SpacingValues } from '@trezor/theme'; import styled from 'styled-components'; + +import { spacings, SpacingValues } from '@trezor/theme'; + import { FrameProps, FramePropsKeys, @@ -34,10 +36,9 @@ type AllowedTextProps = Pick; export const bulletVerticalAlignments = uiVerticalAlignments; export type BulletVerticalAlignment = (typeof uiVerticalAlignments)[number]; -type ContainerProps = TransientProps & - TransientProps & { - $gap: SpacingValues; - }; +type ContainerProps = TransientProps & { + $gap: SpacingValues; +}; const Container = styled.ul` display: flex; diff --git a/packages/components/src/components/List/ListItem.tsx b/packages/components/src/components/List/ListItem.tsx index 638d0832dda..55aa6342a16 100644 --- a/packages/components/src/components/List/ListItem.tsx +++ b/packages/components/src/components/List/ListItem.tsx @@ -1,6 +1,9 @@ import { useEffect, useRef } from 'react'; -import { SpacingValues, spacingsPx, borders } from '@trezor/theme'; + import styled, { css } from 'styled-components'; + +import { SpacingValues, spacingsPx, borders } from '@trezor/theme'; + import { FlexAlignItems } from '../Flex/Flex'; import { useList, BulletVerticalAlignment } from './List'; diff --git a/packages/components/src/components/Markdown/Markdown.stories.tsx b/packages/components/src/components/Markdown/Markdown.stories.tsx index e845eeb37c7..0c41f39734a 100644 --- a/packages/components/src/components/Markdown/Markdown.stories.tsx +++ b/packages/components/src/components/Markdown/Markdown.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Markdown as MarkdownComponent } from './Markdown'; const meta: Meta = { diff --git a/packages/components/src/components/Markdown/Markdown.tsx b/packages/components/src/components/Markdown/Markdown.tsx index 67cb605bd47..3ad56b5f766 100644 --- a/packages/components/src/components/Markdown/Markdown.tsx +++ b/packages/components/src/components/Markdown/Markdown.tsx @@ -1,9 +1,11 @@ +import ReactMarkdown, { Options } from 'react-markdown'; + import styled from 'styled-components'; -import ReactMarkdown, { Options } from 'react-markdown'; -import { variables } from '../../config'; import { typography } from '@trezor/theme'; +import { variables } from '../../config'; + const StyledMarkdown = styled.div` ${typography.hint} diff --git a/packages/components/src/components/NewModal/NewModal.stories.tsx b/packages/components/src/components/NewModal/NewModal.stories.tsx index 0121aa1d309..3da32c7bfcf 100644 --- a/packages/components/src/components/NewModal/NewModal.stories.tsx +++ b/packages/components/src/components/NewModal/NewModal.stories.tsx @@ -1,13 +1,16 @@ import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; +import { ThemeProvider } from 'styled-components'; + import { allowedNewModalFrameProps, NewModal as ModalComponent, NewModalProps, variables, + intermediaryTheme, + IconCircle, } from '../../index'; -import { ThemeProvider } from 'styled-components'; -import { intermediaryTheme } from '../../index'; +import { newModalVariants, newModalSizes } from './types'; import { getFramePropsStory } from '../../utils/frameProps'; const Buttons = () => ( @@ -46,6 +49,8 @@ export default meta; export const NewModal: StoryObj = { args: { variant: 'primary', + iconName: undefined, + iconComponent: undefined, heading: 'Modal heading', description: 'Modal description', children: @@ -60,15 +65,28 @@ export const NewModal: StoryObj = { argTypes: { variant: { control: { - type: 'radio', + type: 'select', + }, + options: [...newModalVariants, undefined], + }, + iconComponent: { + options: ['nothing', 'purple'], + mapping: { + nothing: undefined, + purple: ( + + ), }, - options: ['primary', 'warning', 'destructive'], }, size: { control: { - type: 'radio', + type: 'select', }, - options: ['tiny', 'small', 'medium', 'large'], + options: newModalSizes, }, heading: { control: 'text', @@ -112,7 +130,7 @@ export const NewModal: StoryObj = { }, }, }, - icon: { + iconName: { options: ['none', ...variables.ICONS], mapping: { ...variables.ICONS, diff --git a/packages/components/src/components/NewModal/NewModal.tsx b/packages/components/src/components/NewModal/NewModal.tsx index c422161aea2..287204a3b1f 100644 --- a/packages/components/src/components/NewModal/NewModal.tsx +++ b/packages/components/src/components/NewModal/NewModal.tsx @@ -1,11 +1,15 @@ import { ReactNode } from 'react'; -import styled from 'styled-components'; import { useEvent } from 'react-use'; + +import styled from 'styled-components'; + import { borders, Elevation, mapElevationToBackground, prevElevation, + spacings, + negativeSpacings, spacingsPx, } from '@trezor/theme'; @@ -14,17 +18,15 @@ import { Text } from '../typography/Text/Text'; import { H3 } from '../typography/Heading/Heading'; import { ElevationContext, ElevationUp, useElevation } from '../ElevationContext/ElevationContext'; import { useScrollShadow } from '../../utils/useScrollShadow'; +import { IconCircle } from '../IconCircle/IconCircle'; +import { IconName } from '../Icon/Icon'; +import { Row } from '../Flex/Flex'; import { NewModalButton } from './NewModalButton'; import { NewModalContext } from './NewModalContext'; import { NewModalBackdrop } from './NewModalBackdrop'; import { NewModalProvider } from './NewModalProvider'; -import type { NewModalVariant, NewModalSize, NewModalAlignment } from './types'; -import { - mapVariantToIconBackground, - mapVariantToIconBorderColor, - mapModalSizeToWidth, -} from './utils'; -import { Icon, IconName } from '../Icon/Icon'; +import { NewModalSize, NewModalAlignment, NewModalVariant } from './types'; +import { mapModalSizeToWidth } from './utils'; import { FrameProps, FramePropsKeys, @@ -38,7 +40,6 @@ type AllowedFrameProps = Pick & { $elevation: Elevation; $size: NewModalSize } @@ -75,13 +76,6 @@ const HeadingContainer = styled.div` overflow: hidden; `; -// eslint-disable-next-line local-rules/no-override-ds-component -const Heading = styled(H3)` - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -`; - const ScrollContainer = styled.div` display: flex; flex-direction: column; @@ -105,43 +99,36 @@ const Footer = styled.footer` border-top: 1px solid ${({ theme }) => theme.borderElevation0}; `; -const IconWrapper = styled.div<{ $variant: NewModalVariant; $size: number; $isPushedTop: boolean }>` - width: ${({ $size }) => $size}px; - background: ${({ theme, $variant }) => mapVariantToIconBackground({ theme, $variant })}; - padding: ${spacingsPx.lg}; - border-radius: ${borders.radii.full}; - border: ${spacingsPx.sm} solid - ${({ theme, $variant }) => mapVariantToIconBorderColor({ theme, $variant })}; - box-sizing: content-box; - margin-bottom: ${spacingsPx.md}; - margin-top: ${({ $isPushedTop }) => ($isPushedTop ? `-${spacingsPx.md}` : 0)}; -`; +type ExclusiveIconNameOrComponent = + | { iconName?: IconName; iconComponent?: undefined } + | { iconName?: undefined; iconComponent?: ReactNode }; type NewModalProps = AllowedFrameProps & { - children?: ReactNode; variant?: NewModalVariant; + children?: ReactNode; heading?: ReactNode; description?: ReactNode; bottomContent?: ReactNode; onBackClick?: () => void; onCancel?: () => void; isBackdropCancelable?: boolean; - icon?: IconName; alignment?: NewModalAlignment; size?: NewModalSize; 'data-testid'?: string; -}; +} & ExclusiveIconNameOrComponent; -const _NewModalBase = ({ +const InnerNewModalBase = ({ children, - variant = 'primary', + variant, size = 'medium', heading, description, bottomContent, - icon, + iconName, + iconComponent, onBackClick, onCancel, + isBackdropCancelable, 'data-testid': dataTest = '@modal', ...rest }: NewModalProps) => { @@ -152,9 +139,10 @@ const _NewModalBase = ({ const { elevation } = useElevation(); const hasHeader = onBackClick || onCancel || heading || description; + const isIconPushedTop = onCancel !== undefined && !heading && !description && !onBackClick; useEvent('keydown', (e: KeyboardEvent) => { - if (onCancel && e.key === 'Escape') { + if (isBackdropCancelable && onCancel && e.key === 'Escape') { onCancel?.(); } }); @@ -181,7 +169,7 @@ const _NewModalBase = ({ )} - {heading && {heading}} + {heading &&

{heading}

} {description && ( {description} @@ -205,16 +193,18 @@ const _NewModalBase = ({ - {icon && ( - - - + {iconComponent ?? + (iconName && ( + + ))} + )} {children} @@ -229,11 +219,10 @@ const _NewModalBase = ({ ); }; - const NewModalBase = (props: NewModalProps) => ( - <_NewModalBase {...props} /> + ); diff --git a/packages/components/src/components/NewModal/NewModalBackdrop.tsx b/packages/components/src/components/NewModal/NewModalBackdrop.tsx index 5939346cf75..7ffc7189d2c 100644 --- a/packages/components/src/components/NewModal/NewModalBackdrop.tsx +++ b/packages/components/src/components/NewModal/NewModalBackdrop.tsx @@ -1,9 +1,11 @@ import { ReactNode } from 'react'; -import styled from 'styled-components'; import FocusLock from 'react-focus-lock'; -import { zIndices, spacings } from '@trezor/theme'; import { createPortal } from 'react-dom'; +import styled from 'styled-components'; + +import { zIndices, spacings } from '@trezor/theme'; + import { NewModalAlignment } from './types'; import { mapAlignmentToAlignItems, mapAlignmentToJustifyContent } from './utils'; import { useModalTarget } from './NewModalProvider'; @@ -42,6 +44,7 @@ export const NewModalBackdrop = ({ const modalTarget = useModalTarget(); const backdrop = ( + // eslint-disable-next-line jsx-a11y/no-autofocus {children} diff --git a/packages/components/src/components/NewModal/NewModalContext.tsx b/packages/components/src/components/NewModal/NewModalContext.tsx index b94f5479a73..8f436e34c4e 100644 --- a/packages/components/src/components/NewModal/NewModalContext.tsx +++ b/packages/components/src/components/NewModal/NewModalContext.tsx @@ -1,4 +1,5 @@ import { createContext, useContext } from 'react'; + import { NewModalVariant } from './types'; export const NewModalContext = createContext<{ diff --git a/packages/components/src/components/NewModal/types.tsx b/packages/components/src/components/NewModal/types.tsx index 5a0d01bd1f3..8b28791aea4 100644 --- a/packages/components/src/components/NewModal/types.tsx +++ b/packages/components/src/components/NewModal/types.tsx @@ -1,7 +1,9 @@ import { UIVariant, UISize, UIHorizontalAlignment, UIVerticalAlignment } from '../../config/types'; -export type NewModalVariant = Extract; +export const newModalVariants = ['primary', 'warning', 'destructive'] as const; +export type NewModalVariant = Extract; -export type NewModalSize = Extract; +export const newModalSizes = ['huge', 'large', 'medium', 'small', 'tiny'] as const; +export type NewModalSize = Extract; export type NewModalAlignment = { x: UIHorizontalAlignment; y: UIVerticalAlignment }; diff --git a/packages/components/src/components/NewModal/utils.tsx b/packages/components/src/components/NewModal/utils.tsx index bc5057f62ac..f3c25d7c84b 100644 --- a/packages/components/src/components/NewModal/utils.tsx +++ b/packages/components/src/components/NewModal/utils.tsx @@ -1,39 +1,13 @@ -import { Color, CSSColor } from '@trezor/theme'; -import { NewModalVariant, NewModalSize, NewModalAlignment } from './types'; -import { DefaultTheme } from 'styled-components'; +import { NewModalSize, NewModalAlignment } from './types'; import { UIVerticalAlignment, UIHorizontalAlignment } from '../../config/types'; -type MapArgs = { - $variant: NewModalVariant; - theme: DefaultTheme; -}; - -export const mapVariantToIconBackground = ({ $variant, theme }: MapArgs): CSSColor => { - const colorMap: Record = { - primary: 'backgroundPrimarySubtleOnElevation2', - warning: 'backgroundAlertYellowSubtleOnElevation2', - destructive: 'backgroundAlertRedSubtleOnElevation2', - }; - - return theme[colorMap[$variant]]; -}; - -export const mapVariantToIconBorderColor = ({ $variant, theme }: MapArgs): CSSColor => { - const colorMap: Record = { - primary: 'backgroundPrimarySubtleOnElevation0', - warning: 'backgroundAlertYellowSubtleOnElevation0', - destructive: 'backgroundAlertRedSubtleOnElevation0', - }; - - return theme[colorMap[$variant]]; -}; - export const mapModalSizeToWidth = (size: NewModalSize) => { const widthMap: Record = { tiny: 400, small: 600, medium: 680, large: 760, + huge: 960, }; return widthMap[size]; diff --git a/packages/components/src/components/Note/Note.stories.tsx b/packages/components/src/components/Note/Note.stories.tsx index 068de82c543..8dbebcdc98e 100644 --- a/packages/components/src/components/Note/Note.stories.tsx +++ b/packages/components/src/components/Note/Note.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Note as NoteComponent, NoteProps } from './Note'; const meta: Meta = { diff --git a/packages/components/src/components/Note/Note.tsx b/packages/components/src/components/Note/Note.tsx index 9fd581cbc80..f5ce682a71e 100644 --- a/packages/components/src/components/Note/Note.tsx +++ b/packages/components/src/components/Note/Note.tsx @@ -1,35 +1,25 @@ import { ReactNode } from 'react'; -import styled, { useTheme } from 'styled-components'; -import { spacings, spacingsPx } from '@trezor/theme'; +import { spacings } from '@trezor/theme'; import { Paragraph } from '../typography/Paragraph/Paragraph'; import { Icon } from '../Icon/Icon'; +import { Row } from '../Flex/Flex'; +import { FrameProps, FramePropsKeys } from '../../utils/frameProps'; -const Row = styled.div` - display: flex; - gap: ${spacingsPx.xs}; -`; +export const allowedNoteFrameProps = ['margin'] as const satisfies FramePropsKeys[]; +type AllowedFrameProps = Pick; -// eslint-disable-next-line local-rules/no-override-ds-component -const StyledParagraph = styled(Paragraph)<{ $color?: string }>` - color: ${({ $color }) => $color}; -`; - -export interface NoteProps { +export type NoteProps = AllowedFrameProps & { children: ReactNode; className?: string; -} - -export const Note = ({ children, className }: NoteProps) => { - const theme = useTheme(); - - return ( - - - - {children} - - - ); }; + +export const Note = ({ children, className, margin }: NoteProps) => ( + + + + {children} + + +); diff --git a/packages/components/src/components/ResizableBox/ResizableBox.stories.tsx b/packages/components/src/components/ResizableBox/ResizableBox.stories.tsx index 1a530f6ddfb..19edddb5cbf 100644 --- a/packages/components/src/components/ResizableBox/ResizableBox.stories.tsx +++ b/packages/components/src/components/ResizableBox/ResizableBox.stories.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { ResizableBoxProps, ResizableBox as ResizableBoxComponent } from './ResizableBox'; const Container = styled.div` diff --git a/packages/components/src/components/ResizableBox/ResizableBox.tsx b/packages/components/src/components/ResizableBox/ResizableBox.tsx index 72fabc73862..9964aebf3b0 100644 --- a/packages/components/src/components/ResizableBox/ResizableBox.tsx +++ b/packages/components/src/components/ResizableBox/ResizableBox.tsx @@ -1,8 +1,12 @@ -import styled, { css } from 'styled-components'; import { useCallback, useEffect, useRef, useState } from 'react'; + +import styled, { css } from 'styled-components'; + import { createCooldown } from '@trezor/utils'; import { ZIndexValues, zIndices } from '@trezor/theme'; +import { getSafeWindowSize } from '../../utils/getSafeWindowSize'; + type Direction = 'top' | 'left' | 'right' | 'bottom'; type Directions = Array; @@ -250,10 +254,10 @@ export const ResizableBox = ({ [direction, maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, newX, newY], ); - const startResizing = (direction: Direction) => { + const startResizing = (direction2: Direction) => { setIsResizing(true); setIsHovering(false); - setDirection(direction); + setDirection(direction2); }; useEffect(() => { @@ -290,11 +294,12 @@ export const ResizableBox = ({ window.onresize = () => { if (resizeCooldown() === true) { + const { windowHeight, windowWidth } = getSafeWindowSize(); if (updateHeightOnWindowResize) { - setNewHeight(getMaxResult(maxHeight, window.innerHeight)); + setNewHeight(getMaxResult(maxHeight, windowHeight)); } if (updateWidthOnWindowResize) { - setNewWidth(getMaxResult(maxWidth, window.innerWidth)); + setNewWidth(getMaxResult(maxWidth, windowWidth)); } } }; @@ -315,18 +320,18 @@ export const ResizableBox = ({ updateWidthOnWindowResize, ]); - const handleMouseOverDirection = (direction: Direction) => { + const handleMouseOverDirection = (direction2: Direction) => { if (!isResizing) { setIsHovering(true); - setDirection(direction); + setDirection(direction2); } }; const highlightDirection = isHovering || isResizing ? direction : null; - const handleMouseOver = (direction: Direction) => () => handleMouseOverDirection(direction); + const handleMouseOver = (direction2: Direction) => () => handleMouseOverDirection(direction2); - const handleMouseDown = (direction: Direction) => () => startResizing(direction); + const handleMouseDown = (direction2: Direction) => () => startResizing(direction2); const handleMouseOut = () => { if (isHovering) { @@ -338,10 +343,10 @@ export const ResizableBox = ({ } }; - const divsProps = (direction: Direction) => { + const divsProps = (direction2: Direction) => { return { - onMouseDown: handleMouseDown(direction), - onMouseOver: handleMouseOver(direction), + onMouseDown: handleMouseDown(direction2), + onMouseOver: handleMouseOver(direction2), onMouseOut: handleMouseOut, $highlightDirection: highlightDirection, $zIndex: zIndex, diff --git a/packages/components/src/components/ResizableBox/ResizableBoxExamples.stories.tsx b/packages/components/src/components/ResizableBox/ResizableBoxExamples.stories.tsx index 6381e50563c..24bce4ef473 100644 --- a/packages/components/src/components/ResizableBox/ResizableBoxExamples.stories.tsx +++ b/packages/components/src/components/ResizableBox/ResizableBoxExamples.stories.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { ResizableBox } from './ResizableBox'; const Container = styled.div` diff --git a/packages/components/src/components/Table/Table.stories.tsx b/packages/components/src/components/Table/Table.stories.tsx index 0b7ab6d55f9..cb32af84897 100644 --- a/packages/components/src/components/Table/Table.stories.tsx +++ b/packages/components/src/components/Table/Table.stories.tsx @@ -14,8 +14,13 @@ const meta: Meta = { } as Meta; export default meta; +interface TableProps { + colWidths?: { minWidth?: string | undefined; maxWidth?: string | undefined }[]; + isRowHighlightedOnHover?: boolean; +} + export const Table: StoryObj = { - render: props => ( + render: (props: TableProps) => ( @@ -26,7 +31,10 @@ export const Table: StoryObj = { {EXAMPLE_TOKENS.map((token, i) => ( - + {} : undefined} + > {token.name} {token.balance} {token.price} @@ -37,8 +45,29 @@ export const Table: StoryObj = { ), args: { ...getFramePropsStory(allowedTableFrameProps).args, + colWidths: 'none', + isRowHighlightedOnHover: true, }, argTypes: { ...getFramePropsStory(allowedTableFrameProps).argTypes, + colWidths: { + options: ['none', 'secondCol300px'], + mapping: { + none: undefined, + secondCol300px: [{}, { minWidth: '300px', maxWidth: '300px' }], + }, + control: { + type: 'select', + labels: { + none: 'undefined', + secondCol300px: 'second column 300px', + }, + }, + }, + isRowHighlightedOnHover: { + control: { + type: 'boolean', + }, + }, }, }; diff --git a/packages/components/src/components/Table/Table.tsx b/packages/components/src/components/Table/Table.tsx index 1bf4303411a..da0a985d208 100644 --- a/packages/components/src/components/Table/Table.tsx +++ b/packages/components/src/components/Table/Table.tsx @@ -1,7 +1,9 @@ -import { ReactNode } from 'react'; +import { createContext, ReactNode, useContext } from 'react'; + import styled from 'styled-components'; import { mapElevationToBackgroundToken } from '@trezor/theme'; + import { FrameProps, FramePropsKeys, withFrameProps } from '../../utils/frameProps'; import { makePropsTransient, TransientProps } from '../../utils/transientProps'; import { TableHeader } from './TableHeader'; @@ -14,6 +16,14 @@ import { useElevation } from '../ElevationContext/ElevationContext'; export const allowedTableFrameProps = ['margin'] as const satisfies FramePropsKeys[]; type AllowedFrameProps = Pick; +interface TableContextProps { + isRowHighlightedOnHover: boolean; +} + +const TableContext = createContext({ isRowHighlightedOnHover: false }); + +export const useTable = () => useContext(TableContext); + const Container = styled.table>` width: 100%; border-collapse: collapse; @@ -29,31 +39,46 @@ const ScrollContainer = styled.div` export type TableProps = AllowedFrameProps & { children: ReactNode; - colWidths?: string[]; + colWidths?: { + minWidth?: string; + maxWidth?: string; + width?: string; + }[]; + isRowHighlightedOnHover?: boolean; }; -export const Table = ({ children, margin, colWidths }: TableProps) => { +export const Table = ({ + children, + margin, + colWidths, + isRowHighlightedOnHover = false, +}: TableProps) => { const { scrollElementRef, onScroll, ShadowContainer, ShadowRight } = useScrollShadow(); const { parentElevation } = useElevation(); return ( - - - - {colWidths && ( - - {colWidths.map((width, index) => ( - - ))} - - )} - {children} - - - - + + + + + {colWidths && ( + + {colWidths.map((widths, index) => ( + + ))} + + )} + {children} + + + + + ); }; diff --git a/packages/components/src/components/Table/TableCell.tsx b/packages/components/src/components/Table/TableCell.tsx index 1022b7a5c97..34efa2b4009 100644 --- a/packages/components/src/components/Table/TableCell.tsx +++ b/packages/components/src/components/Table/TableCell.tsx @@ -1,16 +1,31 @@ import { ReactNode } from 'react'; -import styled from 'styled-components'; -import { typography, spacingsPx, Elevation, mapElevationToBackground } from '@trezor/theme'; +import styled, { css } from 'styled-components'; + +import { + typography, + spacings, + Elevation, + mapElevationToBackground, + SpacingValues, +} from '@trezor/theme'; import { useTableHeader } from './TableHeader'; import { UIHorizontalAlignment } from '../../config/types'; import { useElevation } from '../ElevationContext/ElevationContext'; +type Padding = { + top?: SpacingValues; + bottom?: SpacingValues; + left?: SpacingValues; + right?: SpacingValues; +}; + export type TableCellProps = { children?: ReactNode; colSpan?: number; align?: UIHorizontalAlignment; + padding?: Padding; }; const mapAlignmentToJustifyContent = (align: UIHorizontalAlignment) => { @@ -23,14 +38,20 @@ const mapAlignmentToJustifyContent = (align: UIHorizontalAlignment) => { return map[align]; }; -const Cell = styled.td<{ $isHeader: boolean; $elevation: Elevation }>` +const Cell = styled.td<{ $isHeader: boolean; $elevation: Elevation; $padding?: Padding }>` ${({ $isHeader }) => ($isHeader ? typography.hint : typography.body)} color: ${({ theme, $isHeader }) => ($isHeader ? theme.textSubdued : theme.textDefault)}; text-align: left; - padding: ${spacingsPx.sm} ${spacingsPx.lg}; max-width: 300px; overflow: hidden; + ${({ $padding }) => + $padding && + css` + padding: ${$padding.top ?? 0}px ${$padding.right ?? 0}px ${$padding.bottom ?? 0}px + ${$padding.left ?? 0}px; + `} + &:first-child { position: sticky; left: 0; @@ -44,7 +65,12 @@ const Content = styled.div<{ $align: UIHorizontalAlignment }>` justify-content: ${({ $align }) => mapAlignmentToJustifyContent($align)}; `; -export const TableCell = ({ children, colSpan = 1, align = 'left' }: TableCellProps) => { +export const TableCell = ({ + children, + colSpan = 1, + align = 'left', + padding = { top: spacings.sm, right: spacings.lg, bottom: spacings.sm, left: spacings.lg }, +}: TableCellProps) => { const isHeader = useTableHeader(); const { parentElevation } = useElevation(); @@ -54,6 +80,7 @@ export const TableCell = ({ children, colSpan = 1, align = 'left' }: TableCellPr colSpan={colSpan} $isHeader={isHeader} $elevation={parentElevation} + $padding={padding} > {children} diff --git a/packages/components/src/components/Table/TableHeader.tsx b/packages/components/src/components/Table/TableHeader.tsx index e3fdff18f64..92bb419a548 100644 --- a/packages/components/src/components/Table/TableHeader.tsx +++ b/packages/components/src/components/Table/TableHeader.tsx @@ -1,4 +1,5 @@ import { createContext, useContext, ReactNode } from 'react'; + import styled from 'styled-components'; import { Elevation, mapElevationToBorder } from '@trezor/theme'; diff --git a/packages/components/src/components/Table/TableRow.tsx b/packages/components/src/components/Table/TableRow.tsx index 8e547eb80a9..00b7c2d27df 100644 --- a/packages/components/src/components/Table/TableRow.tsx +++ b/packages/components/src/components/Table/TableRow.tsx @@ -1,17 +1,59 @@ import { ReactNode } from 'react'; + import styled, { css } from 'styled-components'; -import { Elevation, mapElevationToBorder } from '@trezor/theme'; +import { + Elevation, + mapElevationToBackground, + mapElevationToBorder, + nextElevation, +} from '@trezor/theme'; import { useElevation } from '../ElevationContext/ElevationContext'; +import { useTable } from './Table'; +import { useTableHeader } from './TableHeader'; -export const Row = styled.tr<{ $elevation: Elevation; $isCollapsed: boolean }>` - border-top: 1px solid ${mapElevationToBorder}; +export const Row = styled.tr<{ + $elevation: Elevation; + $isCollapsed: boolean; + $isHighlighted: boolean; + $isHeader: boolean; + $hasBorderTop: boolean; +}>` + ${({ $hasBorderTop, theme, $elevation }) => + $hasBorderTop && + css` + border-top: 1px solid ${mapElevationToBorder({ theme, $elevation })}; + `} &:first-child { border-top: 0; } + ${({ $isHighlighted, theme, $elevation, $isHeader }) => + $isHighlighted && + !$isHeader && + css` + &:hover { + background-color: ${mapElevationToBackground({ + theme, + $elevation: nextElevation[$elevation], + })}; + + & > td:first-child { + background: linear-gradient( + to right, + ${mapElevationToBackground({ + theme, + $elevation: nextElevation[$elevation], + })} + 90%, + rgba(0 0 0 / 0%) + ); + } + } + `} + ${({ $isCollapsed }) => $isCollapsed && css` @@ -19,18 +61,48 @@ export const Row = styled.tr<{ $elevation: Elevation; $isCollapsed: boolean }>` border-top: 0; opacity: 0; `} + + ${({ onClick }) => + onClick && + css` + &:hover { + cursor: pointer; + } + `} `; export interface TableRowProps { children: ReactNode; isCollapsed?: boolean; + isHighlightedOnHover?: boolean; + onClick?: () => void; + onHover?: (isHovering: boolean) => void; + hasBorderTop?: boolean; } -export const TableRow = ({ children, isCollapsed = false }: TableRowProps) => { +export const TableRow = ({ + children, + isCollapsed = false, + onClick, + onHover, + isHighlightedOnHover, + hasBorderTop = true, +}: TableRowProps) => { const { elevation } = useElevation(); + const isHeader = useTableHeader(); + const { isRowHighlightedOnHover } = useTable(); return ( - + onHover?.(true)} + onMouseLeave={() => onHover?.(false)} + > {children} ); diff --git a/packages/components/src/components/Timerange/Timerange.stories.tsx b/packages/components/src/components/Timerange/Timerange.stories.tsx index 5d2bf6cce4b..3397b4e3f06 100644 --- a/packages/components/src/components/Timerange/Timerange.stories.tsx +++ b/packages/components/src/components/Timerange/Timerange.stories.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { Timerange as TimerangeComponent, TimerangeProps } from './Timerange'; const Center = styled.div` diff --git a/packages/components/src/components/Timerange/Timerange.tsx b/packages/components/src/components/Timerange/Timerange.tsx index 73c6c456a40..ad39c88e705 100644 --- a/packages/components/src/components/Timerange/Timerange.tsx +++ b/packages/components/src/components/Timerange/Timerange.tsx @@ -1,11 +1,12 @@ import { useState, ReactNode } from 'react'; import { DateRange } from 'react-date-range'; + import styled, { css } from 'styled-components'; +import type { Locale } from 'date-fns'; + import { mediaQueries } from '@trezor/styles'; import { borders, spacingsPx, zIndices } from '@trezor/theme'; -import type { Locale } from 'date-fns'; - import { Button } from '../buttons/Button/Button'; type Selection = { diff --git a/packages/components/src/components/Tooltip/Tooltip.stories.tsx b/packages/components/src/components/Tooltip/Tooltip.stories.tsx index 339431bd32f..9dd0ae1bd1d 100644 --- a/packages/components/src/components/Tooltip/Tooltip.stories.tsx +++ b/packages/components/src/components/Tooltip/Tooltip.stories.tsx @@ -1,8 +1,12 @@ +import { useState } from 'react'; + import styled from 'styled-components'; -import { Tooltip as TooltipComponent, TooltipProps } from './Tooltip'; import { Meta, StoryObj } from '@storybook/react'; import { Placement } from '@floating-ui/react'; + import { Elevation, mapElevationToBackground, spacingsPx, zIndices } from '@trezor/theme'; + +import { Tooltip as TooltipComponent, TooltipProps } from './Tooltip'; import { ElevationContext, useElevation } from '../ElevationContext/ElevationContext'; import { TOOLTIP_DELAY_LONG, @@ -10,7 +14,6 @@ import { TOOLTIP_DELAY_NORMAL, TOOLTIP_DELAY_SHORT, } from './TooltipDelay'; -import { useState } from 'react'; import { Button } from '../buttons/Button/Button'; const Center = styled.div` diff --git a/packages/components/src/components/Tooltip/Tooltip.tsx b/packages/components/src/components/Tooltip/Tooltip.tsx index f98a3f4ff28..f60221c3dc5 100644 --- a/packages/components/src/components/Tooltip/Tooltip.tsx +++ b/packages/components/src/components/Tooltip/Tooltip.tsx @@ -1,15 +1,17 @@ -import styled, { ThemeProvider } from 'styled-components'; import { ReactNode, MutableRefObject } from 'react'; + +import styled, { ThemeProvider } from 'styled-components'; import { transparentize } from 'polished'; +import { Placement, ShiftOptions } from '@floating-ui/react'; + import { ZIndexValues, spacingsPx, spacings, zIndices } from '@trezor/theme'; import { Icon } from '../Icon/Icon'; import { TooltipContent, TooltipFloatingUi, TooltipTrigger } from './TooltipFloatingUi'; -import { Placement, ShiftOptions } from '@floating-ui/react'; import { TooltipBox, TooltipBoxProps } from './TooltipBox'; import { TooltipArrow } from './TooltipArrow'; import { TOOLTIP_DELAY_SHORT, TooltipDelay } from './TooltipDelay'; -import { intermediaryTheme } from '../..'; +import { intermediaryTheme } from '../../config/colors'; export type Cursor = 'inherit' | 'pointer' | 'help' | 'default' | 'not-allowed'; diff --git a/packages/components/src/components/Tooltip/TooltipArrow.tsx b/packages/components/src/components/Tooltip/TooltipArrow.tsx index 346ef76dd4b..4c375516c9d 100644 --- a/packages/components/src/components/Tooltip/TooltipArrow.tsx +++ b/packages/components/src/components/Tooltip/TooltipArrow.tsx @@ -1,6 +1,8 @@ import { FloatingArrow } from '@floating-ui/react'; -import { ArrowProps } from './TooltipFloatingUi'; + import { palette } from '@trezor/theme'; + +import { ArrowProps } from './TooltipFloatingUi'; import { TOOLTIP_BORDER_RADIUS } from './TooltipBox'; export const TooltipArrow = ({ ref, context }: ArrowProps) => ( diff --git a/packages/components/src/components/Tooltip/TooltipBox.tsx b/packages/components/src/components/Tooltip/TooltipBox.tsx index b48a1b6c31b..3b05063d05f 100644 --- a/packages/components/src/components/Tooltip/TooltipBox.tsx +++ b/packages/components/src/components/Tooltip/TooltipBox.tsx @@ -1,6 +1,9 @@ import { ReactElement, ReactNode } from 'react'; -import { borders, palette, spacings, spacingsPx, typography } from '@trezor/theme'; + import styled from 'styled-components'; + +import { borders, palette, spacings, spacingsPx, typography } from '@trezor/theme'; + import { Icon, IconName } from '../Icon/Icon'; export const TOOLTIP_BORDER_RADIUS = borders.radii.sm; @@ -29,9 +32,10 @@ const TooltipContainerStyled = styled.div` border-radius: ${TOOLTIP_BORDER_RADIUS}; text-align: left; border: solid 1.5px ${palette.darkGray100}; + margin: ${spacingsPx.xxxs}; max-width: ${props => props.$maxWidth}px; - ${typography.hint} + ${typography.hint} > div { padding: ${({ $isLarge, $isWithHeader }) => getContainerPadding($isLarge, $isWithHeader)}; } diff --git a/packages/components/src/components/Tooltip/TooltipFloatingUi.tsx b/packages/components/src/components/Tooltip/TooltipFloatingUi.tsx index 7745feded01..ff2561723aa 100644 --- a/packages/components/src/components/Tooltip/TooltipFloatingUi.tsx +++ b/packages/components/src/components/Tooltip/TooltipFloatingUi.tsx @@ -1,3 +1,19 @@ +import { + useState, + useMemo, + createContext, + useContext, + ReactNode, + HTMLProps, + forwardRef, + isValidElement, + cloneElement, + useRef, + RefObject, + CSSProperties, + MutableRefObject, +} from 'react'; + import { useFloating, autoUpdate, @@ -15,21 +31,6 @@ import { arrow, } from '@floating-ui/react'; import type { Placement, ShiftOptions, UseFloatingReturn } from '@floating-ui/react'; -import { - useState, - useMemo, - createContext, - useContext, - ReactNode, - HTMLProps, - forwardRef, - isValidElement, - cloneElement, - useRef, - RefObject, - CSSProperties, - MutableRefObject, -} from 'react'; /** * Based on https://floating-ui.com/docs/tooltip but heavily modified diff --git a/packages/components/src/components/VirtualizedList/VirtualizedList.stories.tsx b/packages/components/src/components/VirtualizedList/VirtualizedList.stories.tsx index b764a92990f..9462f98bf67 100644 --- a/packages/components/src/components/VirtualizedList/VirtualizedList.stories.tsx +++ b/packages/components/src/components/VirtualizedList/VirtualizedList.stories.tsx @@ -1,7 +1,9 @@ +import { useEffect, useState } from 'react'; + import { Meta, StoryFn } from '@storybook/react'; import styled from 'styled-components'; + import { VirtualizedList as VirtualizedListComponent } from './VirtualizedList'; -import { useEffect, useState } from 'react'; const meta: Meta = { title: 'VirtualizedList', diff --git a/packages/components/src/components/VirtualizedList/VirtualizedList.tsx b/packages/components/src/components/VirtualizedList/VirtualizedList.tsx index 2b7718c58ba..4ae6dd13404 100644 --- a/packages/components/src/components/VirtualizedList/VirtualizedList.tsx +++ b/packages/components/src/components/VirtualizedList/VirtualizedList.tsx @@ -1,7 +1,9 @@ -import { isChanged } from '@suite-common/suite-utils'; import React, { useState, useEffect, useCallback, forwardRef, useRef } from 'react'; + import styled from 'styled-components'; +import { isChanged } from '@suite-common/suite-utils'; + function debounce void>( func: T, wait: number, diff --git a/packages/components/src/components/animations/AnimationPrimitives.tsx b/packages/components/src/components/animations/AnimationPrimitives.tsx index a646cc8be04..dc4b99fb193 100644 --- a/packages/components/src/components/animations/AnimationPrimitives.tsx +++ b/packages/components/src/components/animations/AnimationPrimitives.tsx @@ -1,6 +1,7 @@ -import { borders } from '@trezor/theme'; import styled, { CSSProperties, css } from 'styled-components'; +import { borders } from '@trezor/theme'; + export type Shape = 'CIRCLE' | 'ROUNDED' | 'ROUNDED-SMALL'; export const AnimationWrapper = styled.div<{ diff --git a/packages/components/src/components/animations/DeviceAnimation.tsx b/packages/components/src/components/animations/DeviceAnimation.tsx index debcebf67d9..1f927e4c520 100644 --- a/packages/components/src/components/animations/DeviceAnimation.tsx +++ b/packages/components/src/components/animations/DeviceAnimation.tsx @@ -1,9 +1,13 @@ -import styled, { useTheme } from 'styled-components'; import { CSSProperties, MouseEventHandler, forwardRef } from 'react'; + +import styled, { useTheme } from 'styled-components'; + +import { DEFAULT_FLAGSHIP_MODEL } from '@suite-common/suite-constants'; +import { getNarrowedDeviceModelInternal } from '@suite-common/suite-utils'; import { DeviceModelInternal } from '@trezor/connect'; + import { AnimationWrapper, Shape } from './AnimationPrimitives'; import { resolveStaticPath } from '../../utils/resolveStaticPath'; -import { DEFAULT_FLAGSHIP_MODEL } from '@suite-common/suite-constants'; const StyledVideo = styled.video` width: 100%; @@ -54,23 +58,16 @@ export const DeviceAnimation = forwardRef { - let deviceModel: string = deviceModelInternal; - - // T2B1 looks the same as T3B1, thus uses the same animations. - if (deviceModelInternal === DeviceModelInternal.T2B1) { - deviceModel = DeviceModelInternal.T3B1; - // T2B1s with old packaging have two variants of security seal. - } else if (type === 'HOLOGRAM' && isOldT2B1Packaging) { - deviceModel = DeviceModelInternal.T2B1; - } - - return deviceModel.toLowerCase(); - }; + const deviceModelInFilename = ( + type === 'HOLOGRAM' && isOldT2B1Packaging + ? DeviceModelInternal.T2B1 + : getNarrowedDeviceModelInternal(deviceModelInternal) + ).toLowerCase(); return ( @@ -86,7 +83,7 @@ export const DeviceAnimation = forwardRef @@ -121,7 +118,7 @@ export const DeviceAnimation = forwardRef @@ -139,7 +136,7 @@ export const DeviceAnimation = forwardRef ` ${getFocusShadowStyle()} ${({ $variant, $isSubtle, $elevation }) => useVariantStyle($variant, $isSubtle, $elevation)} - &:disabled { background: ${({ theme }) => theme.backgroundNeutralDisabled}; color: ${({ theme }) => theme.textDisabled}; @@ -127,15 +129,13 @@ export type ButtonProps = SelectedHTMLButtonProps & textWrap?: boolean; }; -export const getIcon = ({ - icon, - size, - color, -}: { +type GetIconProps = { icon?: IconName | React.ReactElement; size?: number; - color?: string; -}) => { + color?: CSSColor; +}; + +export const getIcon = ({ icon, size, color }: GetIconProps) => { if (!icon) return null; if (typeof icon === 'string') { return ; diff --git a/packages/components/src/components/buttons/Button/buttons.stories.tsx b/packages/components/src/components/buttons/Button/buttons.stories.tsx index 1fc6df2080d..e120c3a9599 100644 --- a/packages/components/src/components/buttons/Button/buttons.stories.tsx +++ b/packages/components/src/components/buttons/Button/buttons.stories.tsx @@ -1,9 +1,11 @@ -import { Button } from '../../../index'; import { Meta, StoryFn } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + import { capitalizeFirstLetter } from '@trezor/utils'; + +import { Button } from '../../../index'; import { StoryColumn } from '../../../support/Story'; import { ButtonVariant } from '../buttonStyleUtils'; -import { action } from '@storybook/addon-actions'; const variants: Array = ['primary', 'tertiary', 'info', 'warning', 'destructive']; diff --git a/packages/components/src/components/buttons/ButtonGroup/ButtonGroup.tsx b/packages/components/src/components/buttons/ButtonGroup/ButtonGroup.tsx index eb561288197..c7631de2823 100644 --- a/packages/components/src/components/buttons/ButtonGroup/ButtonGroup.tsx +++ b/packages/components/src/components/buttons/ButtonGroup/ButtonGroup.tsx @@ -1,6 +1,9 @@ import React from 'react'; + import styled from 'styled-components'; + import { borders } from '@trezor/theme'; + import { ButtonProps } from '../Button/Button'; import { ButtonSize, ButtonVariant } from '../buttonStyleUtils'; import { IconButtonProps } from '../IconButton/IconButton'; diff --git a/packages/components/src/components/buttons/ButtonGroup/ButtonGroups.stories.tsx b/packages/components/src/components/buttons/ButtonGroup/ButtonGroups.stories.tsx index 3a2f79b7ed7..95dacead1c6 100644 --- a/packages/components/src/components/buttons/ButtonGroup/ButtonGroups.stories.tsx +++ b/packages/components/src/components/buttons/ButtonGroup/ButtonGroups.stories.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { Meta, StoryObj } from '@storybook/react'; + import { StoryColumn } from '../../../support/Story'; import { Button } from '../Button/Button'; import { ButtonGroup } from './ButtonGroup'; diff --git a/packages/components/src/components/buttons/IconButton/IconButton.stories.tsx b/packages/components/src/components/buttons/IconButton/IconButton.stories.tsx index 9fb9db388b8..1ba9795d09a 100644 --- a/packages/components/src/components/buttons/IconButton/IconButton.stories.tsx +++ b/packages/components/src/components/buttons/IconButton/IconButton.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { IconButton as IconButtonComponent, IconButtonProps } from './IconButton'; import { variables } from '../../../config'; import { buttonSizes, buttonVariants, subtleButtonVariants } from '../buttonStyleUtils'; diff --git a/packages/components/src/components/buttons/IconButton/IconButton.tsx b/packages/components/src/components/buttons/IconButton/IconButton.tsx index af676067806..39c7c441eda 100644 --- a/packages/components/src/components/buttons/IconButton/IconButton.tsx +++ b/packages/components/src/components/buttons/IconButton/IconButton.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import styled, { useTheme } from 'styled-components'; + import { Spinner } from '../../loaders/Spinner/Spinner'; import { ButtonContainer, ButtonProps, getIcon, IconOrComponent } from '../Button/Button'; import { ButtonVariant, getIconColor, getIconSize, getPadding } from '../buttonStyleUtils'; diff --git a/packages/components/src/components/buttons/IconButton/IconButtons.stories.tsx b/packages/components/src/components/buttons/IconButton/IconButtons.stories.tsx index e49974fc70c..f7442d3e9be 100644 --- a/packages/components/src/components/buttons/IconButton/IconButtons.stories.tsx +++ b/packages/components/src/components/buttons/IconButton/IconButtons.stories.tsx @@ -1,8 +1,10 @@ import React from 'react'; + +import { Meta, StoryObj } from '@storybook/react'; + import { IconButton } from '../../../index'; import { StoryColumn } from '../../../support/Story'; import { ButtonVariant } from '../buttonStyleUtils'; -import { Meta, StoryObj } from '@storybook/react'; const variants: Array> = ['primary', 'tertiary']; diff --git a/packages/components/src/components/buttons/PinButton/PinButton.stories.tsx b/packages/components/src/components/buttons/PinButton/PinButton.stories.tsx index 7d330768fdb..9cbcedeae7a 100644 --- a/packages/components/src/components/buttons/PinButton/PinButton.stories.tsx +++ b/packages/components/src/components/buttons/PinButton/PinButton.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { PinButton as PinButtonComponent } from './PinButton'; const meta: Meta = { diff --git a/packages/components/src/components/buttons/PinButton/PinButton.tsx b/packages/components/src/components/buttons/PinButton/PinButton.tsx index e881aba4bec..d22e9e57666 100644 --- a/packages/components/src/components/buttons/PinButton/PinButton.tsx +++ b/packages/components/src/components/buttons/PinButton/PinButton.tsx @@ -1,3 +1,7 @@ +import { ButtonHTMLAttributes } from 'react'; + +import styled from 'styled-components'; + import { Elevation, borders, @@ -5,8 +9,7 @@ import { mapElevationToBorder, spacingsPx, } from '@trezor/theme'; -import { ButtonHTMLAttributes } from 'react'; -import styled from 'styled-components'; + import { useElevation } from '../../ElevationContext/ElevationContext'; const Button = styled.button<{ $elevation: Elevation }>` diff --git a/packages/components/src/components/buttons/TextButton/TextButton.stories.tsx b/packages/components/src/components/buttons/TextButton/TextButton.stories.tsx index a467b60a466..666318f5977 100644 --- a/packages/components/src/components/buttons/TextButton/TextButton.stories.tsx +++ b/packages/components/src/components/buttons/TextButton/TextButton.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { TextButton as TextButtonComponent, TextButtonProps } from './TextButton'; import { variables } from '../../../config'; import { buttonSizes } from '../buttonStyleUtils'; diff --git a/packages/components/src/components/buttons/TextButton/TextButton.tsx b/packages/components/src/components/buttons/TextButton/TextButton.tsx index 1057bf04716..b5746b4c592 100644 --- a/packages/components/src/components/buttons/TextButton/TextButton.tsx +++ b/packages/components/src/components/buttons/TextButton/TextButton.tsx @@ -1,6 +1,9 @@ import React from 'react'; + import styled from 'styled-components'; + import { borders, spacingsPx, typography } from '@trezor/theme'; + import { ButtonProps, getIcon } from '../Button/Button'; import { ButtonSize, getIconSize, IconAlignment } from '../buttonStyleUtils'; import { Spinner } from '../../loaders/Spinner/Spinner'; @@ -9,12 +12,11 @@ import { focusStyleTransition, getFocusShadowStyle } from '../../../utils/utils' const TextButtonContainer = styled.button<{ $size: ButtonSize; $iconAlignment: IconAlignment; - $hasIcon: boolean; }>` display: flex; align-items: center; flex-direction: ${({ $iconAlignment }) => $iconAlignment === 'right' && 'row-reverse'}; - gap: ${({ $hasIcon }) => $hasIcon && spacingsPx.xs}; + gap: ${spacingsPx.xs}; height: ${({ $size: size }) => (size === 'small' ? 22 : 26)}px; padding: ${spacingsPx.xxs}; border: 1px solid transparent; @@ -68,12 +70,10 @@ export const TextButton = ({ ...rest }: TextButtonProps) => { const IconComponent = getIcon({ icon, size: getIconSize(size) }); - const Loader = ; return ( {!isLoading && icon && IconComponent} {isLoading && Loader} - {children} ); diff --git a/packages/components/src/components/buttons/TextButton/TextButtons.stories.tsx b/packages/components/src/components/buttons/TextButton/TextButtons.stories.tsx index 22eebfbc248..42fa8c88d59 100644 --- a/packages/components/src/components/buttons/TextButton/TextButtons.stories.tsx +++ b/packages/components/src/components/buttons/TextButton/TextButtons.stories.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { Meta, StoryFn } from '@storybook/react'; + import { TextButton } from '../../../index'; import { StoryColumn } from '../../../support/Story'; diff --git a/packages/components/src/components/buttons/buttonStyleUtils.ts b/packages/components/src/components/buttons/buttonStyleUtils.ts index 25c41071ba0..fb00519dad3 100644 --- a/packages/components/src/components/buttons/buttonStyleUtils.ts +++ b/packages/components/src/components/buttons/buttonStyleUtils.ts @@ -1,10 +1,11 @@ import { DefaultTheme, css, useTheme } from 'styled-components'; import { Color, Colors, Elevation, spacings, spacingsPx } from '@trezor/theme'; -import type { UIHorizontalAlignment, UISize, UIVariant } from '../../config/types'; import { hexToRgba } from '@suite-common/suite-utils'; import { capitalizeFirstLetter } from '@trezor/utils'; +import type { UIHorizontalAlignment, UISize, UIVariant } from '../../config/types'; + const SUBTLE_ALPHA = 0.12; const SUBTLE_ALPHA_HOVER = 0.2; @@ -14,7 +15,7 @@ export const buttonVariants = ['primary', 'tertiary', ...subtleButtonVariants] a export type ButtonVariant = Extract; -export const buttonSizes: Array = ['large', 'medium', 'small', 'tiny']; +export const buttonSizes = ['large', 'medium', 'small', 'tiny'] as const; export type ButtonSize = Extract; export type IconAlignment = Extract; diff --git a/packages/components/src/components/form/BottomText.tsx b/packages/components/src/components/form/BottomText.tsx index 2e1204590fd..63de78a3517 100644 --- a/packages/components/src/components/form/BottomText.tsx +++ b/packages/components/src/components/form/BottomText.tsx @@ -1,10 +1,25 @@ -import styled, { keyframes } from 'styled-components'; -import { spacingsPx, typography } from '@trezor/theme'; -import { getInputStateTextColor } from './InputStyles'; import { ReactNode } from 'react'; + +import styled, { keyframes } from 'styled-components'; + +import { spacings } from '@trezor/theme'; + +import { IconName, Icon, IconVariant } from '../Icon/Icon'; import { InputState } from './inputTypes'; +import { Row } from '../Flex/Flex'; +import { Text, TextVariant } from '../typography/Text/Text'; +import { UIVariant } from '../../config/types'; -export const BOTTOM_TEXT_MIN_HEIGHT = 26; // 1 line of text + top padding +export const mapInputStateToUIVariant = (inputState: InputState): UIVariant => { + const variantMap: Record = { + error: 'destructive', + primary: 'primary', + warning: 'warning', + default: 'tertiary', + }; + + return variantMap[inputState]; +}; const slideDown = keyframes` from { @@ -17,35 +32,38 @@ const slideDown = keyframes` } `; -export const Container = styled.div<{ $inputState?: InputState; $isDisabled?: boolean }>` - display: flex; - align-items: center; - gap: ${spacingsPx.xxs}; - padding: ${spacingsPx.xs} ${spacingsPx.sm} 0 ${spacingsPx.sm}; - min-height: ${BOTTOM_TEXT_MIN_HEIGHT}px; - color: ${({ $inputState, $isDisabled, theme }) => - $isDisabled ? theme.textDisabled : getInputStateTextColor($inputState, theme)}; - ${typography.label} +export const Container = styled.div` animation: ${slideDown} 0.18s ease-in-out forwards; `; -interface BottomTextProps { +type BottomTextProps = { inputState?: InputState; isDisabled?: boolean; iconComponent?: ReactNode; + iconName?: IconName; children: ReactNode; -} +}; export const BottomText = ({ - inputState, + inputState = 'default', isDisabled, iconComponent, + iconName, children, }: BottomTextProps) => { + const variant = isDisabled ? 'disabled' : mapInputStateToUIVariant(inputState); + return ( - - {iconComponent} - {children} + + + {iconComponent ?? + (iconName && ( + + ))} + + {children} + + ); }; diff --git a/packages/components/src/components/form/Checkbox/Checkbox.tsx b/packages/components/src/components/form/Checkbox/Checkbox.tsx index 17dac7db245..0926274b8eb 100755 --- a/packages/components/src/components/form/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/form/Checkbox/Checkbox.tsx @@ -1,5 +1,7 @@ import { EventHandler, SyntheticEvent, KeyboardEvent, ReactNode } from 'react'; + import styled, { useTheme } from 'styled-components'; + import { borders, Color, spacingsPx, typography } from '@trezor/theme'; import { KEYBOARD_CODE } from '../../../constants/keyboardEvents'; @@ -142,7 +144,7 @@ const CheckIcon = styled(Icon)<{ $isVisible: boolean }>` export const Label = styled.div<{ $isRed: boolean }>` display: flex; - justify-content: center; + flex: 1; text-align: left; user-select: none; color: ${({ theme, $isRed }) => $isRed && theme.textAlertRed}; @@ -206,12 +208,14 @@ export const Checkbox = ({ return ( diff --git a/packages/components/src/components/form/FormCell/FormCell.stories.tsx b/packages/components/src/components/form/FormCell/FormCell.stories.tsx new file mode 100644 index 00000000000..789acb1ed93 --- /dev/null +++ b/packages/components/src/components/form/FormCell/FormCell.stories.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { Meta, StoryObj } from '@storybook/react'; + +import { FormCell as FormCellComponent, allowedFormCellFrameProps } from './FormCell'; +import { SkeletonRectangle } from '../../skeletons/SkeletonRectangle'; +import { variables } from '../../../config'; +import { getFramePropsStory } from '../../../utils/frameProps'; + +const meta: Meta = { + title: 'Form', +} as Meta; +export default meta; + +export const FormCell: StoryObj = { + render: props => ( + + + + ), + args: { + inputState: 'error', + labelLeft: 'Label left', + labelRight: 'Label right', + labelHoverRight: 'Label hover right', + bottomText: 'Bottom text', + bottomTextIconName: 'info', + ...getFramePropsStory(allowedFormCellFrameProps).args, + }, + argTypes: { + bottomText: { control: 'text' }, + labelHoverRight: { control: 'text' }, + labelLeft: { control: 'text' }, + labelRight: { control: 'text' }, + inputState: { control: 'select', options: ['error', 'warning', 'primary'] }, + bottomTextIconName: { + options: ['none', ...variables.ICONS], + mapping: { + ...variables.ICONS, + none: undefined, + }, + control: { + type: 'select', + }, + }, + ...getFramePropsStory(allowedFormCellFrameProps).argTypes, + }, +}; diff --git a/packages/components/src/components/form/FormCell/FormCell.tsx b/packages/components/src/components/form/FormCell/FormCell.tsx new file mode 100644 index 00000000000..ab788ca8377 --- /dev/null +++ b/packages/components/src/components/form/FormCell/FormCell.tsx @@ -0,0 +1,108 @@ +import { ReactNode, useState } from 'react'; + +import styled from 'styled-components'; + +import { spacings } from '@trezor/theme'; + +import { + FrameProps, + FramePropsKeys, + pickAndPrepareFrameProps, + withFrameProps, +} from '../../../utils/frameProps'; +import { Column } from '../../Flex/Flex'; +import { InputState } from '../inputTypes'; +import { TopAddons } from '../TopAddons'; +import { BottomText } from '../BottomText'; +import { IconName } from '../../Icon/Icon'; +import { TransientProps } from '../../../utils/transientProps'; + +export const allowedFormCellFrameProps = [ + 'margin', + 'width', + 'maxWidth', +] as const satisfies FramePropsKeys[]; +type AllowedFrameProps = Pick; + +const formCellProps = [ + 'labelHoverRight', + 'labelLeft', + 'labelRight', + 'bottomText', + 'bottomTextIconComponent', + 'inputState', + 'isDisabled', + 'className', + ...allowedFormCellFrameProps, +] as const satisfies (keyof FormCellProps)[]; + +export const pickFormCellProps = (props: Record): Partial => + formCellProps.reduce( + (acc: Partial, prop: string) => ({ ...acc, [prop]: props[prop] }), + {}, + ); + +const Wrapper = styled.div>` + width: 100%; + + ${withFrameProps} +`; + +export type FormCellProps = AllowedFrameProps & { + labelHoverRight?: React.ReactNode; + labelLeft?: React.ReactNode; + labelRight?: React.ReactNode; + bottomText?: ReactNode; + bottomTextIconComponent?: ReactNode; + bottomTextIconName?: IconName; + inputState?: InputState; + isDisabled?: boolean; + children: ReactNode; + className?: string; +}; + +export const FormCell = ({ + children, + labelLeft, + labelRight, + labelHoverRight, + bottomText, + bottomTextIconComponent, + bottomTextIconName, + inputState, + isDisabled, + className, + ...rest +}: FormCellProps) => { + const [isHovered, setIsHovered] = useState(false); + const frameProps = pickAndPrepareFrameProps(rest, allowedFormCellFrameProps); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + + {children} + {bottomText && ( + + {bottomText} + + )} + + + ); +}; diff --git a/packages/components/src/components/form/Input/Input.stories.tsx b/packages/components/src/components/form/Input/Input.stories.tsx index 9ca6ed3ecb4..1cb8d34a625 100644 --- a/packages/components/src/components/form/Input/Input.stories.tsx +++ b/packages/components/src/components/form/Input/Input.stories.tsx @@ -1,8 +1,16 @@ import { ChangeEvent } from 'react'; + import { useArgs } from '@storybook/client-api'; import { Meta, StoryObj } from '@storybook/react'; -import { Input as InputComponent, InputProps } from './Input'; +import { + Input as InputComponent, + InputProps, + allowedInputFrameProps, + allowedInputTextProps, +} from './Input'; +import { getFramePropsStory } from '../../../utils/frameProps'; +import { getTextPropsStory } from '../../typography/utils'; const meta: Meta = { title: 'Form', @@ -13,7 +21,8 @@ const meta: Meta = { size: 'large', inputState: null, innerAddonAlign: 'right', - hasBottomPadding: true, + ...getFramePropsStory(allowedInputFrameProps).args, + ...getTextPropsStory(allowedInputTextProps).args, }, argTypes: { bottomText: { control: 'text' }, @@ -28,12 +37,7 @@ const meta: Meta = { }, options: ['large', 'small'], }, - inputState: { - control: { - type: 'radio', - }, - options: [null, 'warning', 'error'], - }, + inputState: { control: 'select', options: ['error', 'warning', 'primary'] }, innerAddonAlign: { control: { type: 'radio', @@ -46,6 +50,8 @@ const meta: Meta = { }, options: [null, 'hover', 'always'], }, + ...getFramePropsStory(allowedInputFrameProps).argTypes, + ...getTextPropsStory(allowedInputTextProps).argTypes, }, } as Meta; export default meta; diff --git a/packages/components/src/components/form/Input/Input.tsx b/packages/components/src/components/form/Input/Input.tsx index a7068830bac..c46db9c9b12 100644 --- a/packages/components/src/components/form/Input/Input.tsx +++ b/packages/components/src/components/form/Input/Input.tsx @@ -1,7 +1,10 @@ -import { useState, Ref, ReactNode, ReactElement, InputHTMLAttributes } from 'react'; -import styled, { useTheme } from 'styled-components'; +import { useState, Ref, ReactElement, InputHTMLAttributes } from 'react'; import { useMeasure } from 'react-use'; -import { spacingsPx, spacings, typography, TypographyStyle } from '@trezor/theme'; + +import styled from 'styled-components'; + +import { spacingsPx, spacings, typography } from '@trezor/theme'; + import { Icon } from '../../Icon/Icon'; import { baseInputStyle, @@ -10,24 +13,32 @@ import { Label, LABEL_TRANSFORM, } from '../InputStyles'; -import { BOTTOM_TEXT_MIN_HEIGHT, BottomText } from '../BottomText'; -import { InputState, InputSize } from '../inputTypes'; -import { TopAddons } from '../TopAddons'; +import { InputSize } from '../inputTypes'; +import { + FormCell, + FormCellProps, + allowedFormCellFrameProps, + pickFormCellProps, +} from '../FormCell/FormCell'; import { useElevation } from '../../ElevationContext/ElevationContext'; import { UIHorizontalAlignment } from '../../../config/types'; -import { TextPropsKeys, withTextProps, TextProps as TextPropsCommon } from '../../typography/utils'; +import { + TextPropsKeys, + withTextProps, + TextProps, + pickAndPrepareTextProps, +} from '../../typography/utils'; import { TransientProps } from '../../../utils/transientProps'; +import { FrameProps } from '../../../utils/frameProps'; -export const allowedInputTextProps = ['typographyStyle'] as const satisfies TextPropsKeys[]; -type AllowedInputTextProps = Pick; +export const allowedInputFrameProps = allowedFormCellFrameProps; +type AllowedFrameProps = Pick; -const Wrapper = styled.div<{ $width?: number; $hasBottomPadding: boolean }>` - display: inline-flex; - flex-direction: column; - width: ${({ $width }) => ($width ? `${$width}px` : '100%')}; - padding-bottom: ${({ $hasBottomPadding }) => - $hasBottomPadding ? `${BOTTOM_TEXT_MIN_HEIGHT}px` : '0'}; -`; +export const allowedInputTextProps = [ + 'typographyStyle', + 'align', +] as const satisfies TextPropsKeys[]; +type AllowedTextProps = Pick; interface StyledInputProps extends BaseInputProps { $size: InputSize; @@ -39,7 +50,7 @@ interface StyledInputProps extends BaseInputProps { const getExtraAddonPadding = (size: InputSize) => (size === 'small' ? spacings.sm : spacings.md) + spacings.xs; -const StyledInput = styled.input>` +const StyledInput = styled.input>` padding: 0 ${spacingsPx.md}; padding-left: ${({ $leftAddonWidth, $size }) => $leftAddonWidth ? `${$leftAddonWidth + getExtraAddonPadding($size)}px` : undefined}; @@ -48,12 +59,13 @@ const StyledInput = styled.input `${INPUT_HEIGHTS[$size as InputSize]}px`}; ${baseInputStyle} ${({ $size }) => $size === 'small' && typography.hint}; - ${withTextProps} &:disabled { pointer-events: auto; cursor: not-allowed; } + + ${withTextProps} `; const InputWrapper = styled.div` @@ -84,61 +96,45 @@ const InputLabel = styled(Label)` type innerAddonAlignment = Extract; -export interface InputProps extends Omit, 'size'> { - value?: string; - innerRef?: Ref; - label?: ReactElement | string; - labelHoverRight?: React.ReactNode; - labelLeft?: React.ReactNode; - labelRight?: React.ReactNode; - innerAddon?: ReactElement; - /** - * @description pass `null` if bottom text can be `undefined` - */ - bottomText?: ReactNode; - bottomTextIconComponent?: ReactNode; - isDisabled?: boolean; - size?: InputSize; - className?: string; - 'data-testid'?: string; - inputState?: InputState; // TODO: do we need this? we only have the error state right now - innerAddonAlign?: innerAddonAlignment; - hasBottomPadding?: boolean; - typographyStyle?: TypographyStyle; - /** - * @description the clear button replaces the addon on the right side - */ - showClearButton?: 'hover' | 'always'; - onClear?: () => void; -} +export type InputProps = Omit, 'size'> & + AllowedFrameProps & + AllowedTextProps & + Omit & { + value?: string; + innerRef?: Ref; + label?: ReactElement | string; + innerAddon?: ReactElement; + size?: InputSize; + 'data-testid'?: string; + innerAddonAlign?: innerAddonAlignment; + hasBottomPadding?: boolean; + /** + * @description the clear button replaces the addon on the right side + */ + showClearButton?: 'hover' | 'always'; + onClear?: () => void; + }; const Input = ({ value, innerRef, inputState, label, - labelLeft, - labelRight, - labelHoverRight, innerAddon, - bottomTextIconComponent, innerAddonAlign = 'right', - bottomText, size = 'large', - isDisabled, 'data-testid': dataTest, showClearButton, placeholder, + isDisabled, onClear, hasBottomPadding = true, - typographyStyle = 'body', - className, ...rest }: InputProps) => { const [isHovered, setIsHovered] = useState(false); - - const theme = useTheme(); const { elevation } = useElevation(); + const textProps = pickAndPrepareTextProps(rest, allowedInputTextProps); + const formCellProps = pickFormCellProps(rest); const hasShowClearButton = (showClearButton === 'always' || (showClearButton === 'hover' && isHovered)) && @@ -149,20 +145,11 @@ const Input = ({ const [measureRightAddon, { width: rightAddonWidth }] = useMeasure(); return ( - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - $hasBottomPadding={hasBottomPadding === true && bottomText === null} - className={className} - > - - - + + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > {innerAddon && innerAddonAlign === 'left' && ( {innerAddon} @@ -174,13 +161,7 @@ const Input = ({ {!hasShowClearButton && innerAddon} {hasShowClearButton && ( - + )} )} @@ -200,9 +181,9 @@ const Input = ({ $leftAddonWidth={leftAddonWidth} $rightAddonWidth={rightAddonWidth} $isWithLabel={!!label} - $typographyStyle={typographyStyle} placeholder={placeholder || ''} // needed for uncontrolled inputs data-testid={dataTest} + {...textProps} {...rest} /> @@ -212,17 +193,7 @@ const Input = ({ )} - - {bottomText && ( - - {bottomText} - - )} - + ); }; diff --git a/packages/components/src/components/form/InputStyles.ts b/packages/components/src/components/form/InputStyles.ts index c5aa3f26140..0e1ed09effa 100644 --- a/packages/components/src/components/form/InputStyles.ts +++ b/packages/components/src/components/form/InputStyles.ts @@ -1,4 +1,5 @@ import styled, { css, DefaultTheme } from 'styled-components'; + import { borders, spacingsPx, diff --git a/packages/components/src/components/form/Radio/Radio.stories.tsx b/packages/components/src/components/form/Radio/Radio.stories.tsx index 604e27a3dc0..8c38067465c 100644 --- a/packages/components/src/components/form/Radio/Radio.stories.tsx +++ b/packages/components/src/components/form/Radio/Radio.stories.tsx @@ -71,7 +71,7 @@ export const RadioButtonGroup: StoryObj = { // eslint-disable-next-line const [{ option }, updateArgs] = useArgs(); - const setOption = (option: string) => updateArgs({ option }); + const setOption = (option2: string) => updateArgs({ option: option2 }); return ( diff --git a/packages/components/src/components/form/Radio/Radio.tsx b/packages/components/src/components/form/Radio/Radio.tsx index e60fc22a591..25d6c59396a 100755 --- a/packages/components/src/components/form/Radio/Radio.tsx +++ b/packages/components/src/components/form/Radio/Radio.tsx @@ -1,5 +1,7 @@ import { EventHandler, KeyboardEvent, ReactNode, SyntheticEvent } from 'react'; + import styled from 'styled-components'; + import { Color, borders } from '@trezor/theme'; import { KEYBOARD_CODE } from '../../../constants/keyboardEvents'; diff --git a/packages/components/src/components/form/Range/Range.tsx b/packages/components/src/components/form/Range/Range.tsx index 1e94733617c..9416ae2676e 100644 --- a/packages/components/src/components/form/Range/Range.tsx +++ b/packages/components/src/components/form/Range/Range.tsx @@ -6,6 +6,7 @@ import { KeyboardEventHandler, ChangeEventHandler, } from 'react'; + import styled, { css, CSSObject } from 'styled-components'; import { borders, spacingsPx, typography } from '@trezor/theme'; diff --git a/packages/components/src/components/form/Select/Select.stories.tsx b/packages/components/src/components/form/Select/Select.stories.tsx index 90ae578a8d9..dc56e31d38a 100644 --- a/packages/components/src/components/form/Select/Select.stories.tsx +++ b/packages/components/src/components/form/Select/Select.stories.tsx @@ -25,7 +25,8 @@ export const Select: StoryObj = { render: ({ ...args }) => { // eslint-disable-next-line const [{ option }, updateArgs] = useArgs(); - const setOption = (option: { label: string; value: 'string' }) => updateArgs({ option }); + const setOption = (option2: { label: string; value: 'string' }) => + updateArgs({ option: option2 }); return ; }, @@ -34,7 +35,6 @@ export const Select: StoryObj = { isClean: false, isDisabled: false, isSearchable: false, - hasBottomPadding: true, size: 'large', minValueWidth: 'initial', isMenuOpen: undefined, @@ -66,11 +66,9 @@ export const Select: StoryObj = { bottomText: { control: { type: 'text' }, }, - hasBottomPadding: { - control: { - type: 'boolean', - }, - }, + labelHoverRight: { control: 'text' }, + labelLeft: { control: 'text' }, + labelRight: { control: 'text' }, size: { control: { type: 'radio', @@ -90,12 +88,7 @@ export const Select: StoryObj = { type: 'boolean', }, }, - inputState: { - control: { - type: 'radio', - }, - options: [null, 'warning', 'error'], - }, + inputState: { control: 'select', options: ['error', 'warning', 'primary'] }, placeholder: { control: { type: 'text' }, }, diff --git a/packages/components/src/components/form/Select/Select.tsx b/packages/components/src/components/form/Select/Select.tsx index 623d09fc0c3..473c7fc819d 100644 --- a/packages/components/src/components/form/Select/Select.tsx +++ b/packages/components/src/components/form/Select/Select.tsx @@ -1,6 +1,8 @@ import { useCallback, useRef, ReactNode, useMemo } from 'react'; import ReactSelect, { Props as ReactSelectProps, StylesConfig, SelectInstance } from 'react-select'; + import styled, { css, DefaultTheme, useTheme } from 'styled-components'; + import { borders, spacings, @@ -14,8 +16,13 @@ import { } from '@trezor/theme'; import { INPUT_HEIGHTS, LABEL_TRANSFORM, Label, baseInputStyle } from '../InputStyles'; -import { BOTTOM_TEXT_MIN_HEIGHT, BottomText } from '../BottomText'; -import { InputState, InputSize } from '../inputTypes'; +import { InputSize } from '../inputTypes'; +import { + FormCell, + FormCellProps, + allowedFormCellFrameProps, + pickFormCellProps, +} from '../FormCell/FormCell'; import { Control, ControlComponentProps, @@ -27,10 +34,15 @@ import { useOnKeyDown } from './useOnKeyDown'; import { useDetectPortalTarget } from './useDetectPortalTarget'; import { DROPDOWN_MENU, menuStyle } from '../../Dropdown/menuStyle'; import { useElevation } from '../../ElevationContext/ElevationContext'; +import { Spinner } from '../../loaders/Spinner/Spinner'; import { TransientProps } from '../../../utils/transientProps'; +import { FrameProps } from '../../../utils/frameProps'; const reactSelectClassNamePrefix = 'react-select'; +export const allowedSelectFrameProps = allowedFormCellFrameProps; +type AllowedFrameProps = Pick; + const createSelectStyle = ( theme: DefaultTheme, elevation: Elevation, @@ -107,16 +119,14 @@ type WrapperProps = TransientProps< > & { $isWithLabel: boolean; $isWithPlaceholder: boolean; - $hasBottomPadding: boolean; $elevation: Elevation; + $isLoading?: boolean; }; -const Wrapper = styled.div` +const SelectWrapper = styled.div` /* stylelint-disable selector-class-pattern */ - position: relative; width: 100%; - padding-bottom: ${({ $hasBottomPadding: hasBottomPadding }) => - hasBottomPadding ? `${BOTTOM_TEXT_MIN_HEIGHT}px` : '0'}; + position: relative; ${({ $isClean }) => !$isClean && @@ -176,7 +186,7 @@ const Wrapper = styled.div` .${reactSelectClassNamePrefix}__single-value { position: static; - display: flex; + display: ${({ $isLoading }) => ($isLoading ? 'none' : 'flex')}; align-items: center; justify-content: ${({ $isClean }) => ($isClean ? 'flex-end' : 'flex-start')}; max-width: initial; @@ -244,61 +254,64 @@ const SelectLabel = styled(Label)` } `; +const SpinnerWrapper = styled.div` + position: absolute; + top: 50%; + left: ${spacingsPx.md}; + transform: translateY(-50%); +`; + // Prevent closing the menu when scrolling through options. const closeMenuOnScroll = (e: Event) => !(e.target as Element)?.className?.startsWith(reactSelectClassNamePrefix); export type Option = any; -interface CommonProps extends Omit, 'onChange' | 'menuIsOpen'> { - isClean?: boolean; - label?: ReactNode; - size?: InputSize; - /** - * @description pass `null` if bottom text can be `undefined` - */ - bottomText?: ReactNode; - hasBottomPadding?: boolean; - minValueWidth?: string; // TODO: should be probably removed - inputState?: InputState; - isMenuOpen?: boolean; - onChange?: (value: Option, ref?: SelectInstance | null) => void; - 'data-testid'?: string; -} - // Make sure isSearchable can't be defined if useKeyPressScroll===true // If useKeyPressScroll is false or undefined, isSearchable is a boolean value type KeyPressScrollProps = | { useKeyPressScroll: true; isSearchable?: never } | { useKeyPressScroll?: false; isSearchable?: boolean }; -export type SelectProps = CommonProps & KeyPressScrollProps; +export type SelectProps = KeyPressScrollProps & + AllowedFrameProps & + Omit & + Omit, 'onChange' | 'menuIsOpen'> & { + isClean?: boolean; + label?: ReactNode; + size?: InputSize; + hasBottomPadding?: boolean; + minValueWidth?: string; + isMenuOpen?: boolean; + isLoading?: boolean; + onChange?: (value: Option, ref?: SelectInstance | null) => void; + 'data-testid'?: string; + }; export const Select = ({ - className, isClean = false, label, size = 'large', - bottomText, hasBottomPadding = true, useKeyPressScroll, isSearchable = false, minValueWidth = 'initial', isMenuOpen, - inputState, components, onChange, placeholder, isDisabled = false, + isLoading = false, 'data-testid': dataTest, ...props }: SelectProps) => { const selectRef = useRef>(null); const { elevation } = useElevation(); - const theme = useTheme(); const onKeyDown = useOnKeyDown(selectRef, useKeyPressScroll); const menuPortalTarget = useDetectPortalTarget(selectRef); + const formCellProps = pickFormCellProps(props); + const isRenderedInModal = menuPortalTarget !== null; const handleOnChange = useCallback['onChange']>( @@ -336,49 +349,50 @@ export const Select = ({ ); return ( - - - - {label && ( - - {label} - - )} - - {bottomText && ( - - {bottomText} - - )} - + + + + + {isLoading && ( + + + + )} + + {label && ( + + {label} + + )} + + ); }; diff --git a/packages/components/src/components/form/Select/customComponents.tsx b/packages/components/src/components/form/Select/customComponents.tsx index c2297931bd7..2143922bc43 100644 --- a/packages/components/src/components/form/Select/customComponents.tsx +++ b/packages/components/src/components/form/Select/customComponents.tsx @@ -1,4 +1,5 @@ import { components, ControlProps, OptionProps, GroupHeadingProps } from 'react-select'; + import type { Option as OptionType } from './Select'; export interface ControlComponentProps extends ControlProps { diff --git a/packages/components/src/components/form/Select/useDetectPortalTarget.ts b/packages/components/src/components/form/Select/useDetectPortalTarget.ts index 8b90d84a507..22757fc43e9 100644 --- a/packages/components/src/components/form/Select/useDetectPortalTarget.ts +++ b/packages/components/src/components/form/Select/useDetectPortalTarget.ts @@ -1,5 +1,6 @@ import { useState, useEffect, RefObject } from 'react'; import { SelectInstance } from 'react-select'; + import { MODAL_CONTENT_ID } from '../../modals/Modal/Modal'; import type { Option } from './Select'; diff --git a/packages/components/src/components/form/Select/useOnKeyDown.ts b/packages/components/src/components/form/Select/useOnKeyDown.ts index 793d98fe465..633a557df82 100644 --- a/packages/components/src/components/form/Select/useOnKeyDown.ts +++ b/packages/components/src/components/form/Select/useOnKeyDown.ts @@ -1,5 +1,6 @@ import { useRef, useCallback, RefObject, KeyboardEvent } from 'react'; import { GroupBase, Options, OptionsOrGroups, SelectInstance } from 'react-select'; + import type { Option } from './Select'; /** Custom Type Guards to check if options are grouped or not */ diff --git a/packages/components/src/components/form/SelectBar/SelectBar.tsx b/packages/components/src/components/form/SelectBar/SelectBar.tsx index 11e07f99718..e3680cc965e 100644 --- a/packages/components/src/components/form/SelectBar/SelectBar.tsx +++ b/packages/components/src/components/form/SelectBar/SelectBar.tsx @@ -1,10 +1,32 @@ import { useState, useEffect, ReactNode, useCallback, KeyboardEvent } from 'react'; + import styled, { css } from 'styled-components'; + import { breakpointMediaQueries } from '@trezor/styles'; -import { borders, spacings, spacingsPx, typography } from '@trezor/theme'; -import { focusStyleTransition, getFocusShadowStyle } from '../../../utils/utils'; +import { + borders, + spacings, + spacingsPx, + typography, + Elevation, + mapElevationToBackground, + nextElevation, +} from '@trezor/theme'; -const Wrapper = styled.div<{ $isFullWidth?: boolean }>` +import { focusStyleTransition, getFocusShadowStyle } from '../../../utils/utils'; +import { useElevation } from '../../ElevationContext/ElevationContext'; +import { + FrameProps, + FramePropsKeys, + pickAndPrepareFrameProps, + withFrameProps, +} from '../../../utils/frameProps'; +import { TransientProps } from '../../../utils/transientProps'; + +export const allowedSelectBarFrameProps = ['margin', 'width'] as const satisfies FramePropsKeys[]; +type AllowedFrameProps = Pick; + +const Wrapper = styled.div & { $isFullWidth?: boolean }>` display: flex; align-items: center; gap: ${spacingsPx.sm}; @@ -15,6 +37,8 @@ const Wrapper = styled.div<{ $isFullWidth?: boolean }>` align-items: flex-start; width: 100%; } + + ${withFrameProps} `; const Label = styled.span` @@ -35,14 +59,18 @@ const getTranslateValue = (index: number) => { const getPuckWidth = (optionsCount: number) => `calc((100% - 8px - ${(optionsCount - 1) * spacings.xxs}px) / ${optionsCount})`; -const Options = styled.div<{ $optionsCount: number; $isFullWidth?: boolean }>` +const Options = styled.div<{ + $optionsCount: number; + $isFullWidth?: boolean; + $elevation: Elevation; +}>` position: relative; display: grid; grid-auto-columns: ${({ $optionsCount }) => `minmax(${getPuckWidth($optionsCount)}, 1fr)`}; grid-auto-flow: column; gap: ${spacingsPx.xxs}; padding: ${spacingsPx.xxs}; - background: ${({ theme }) => theme.backgroundSurfaceElevation0}; + background: ${mapElevationToBackground}; border-radius: ${borders.radii.full}; width: ${({ $isFullWidth }) => ($isFullWidth ? '100%' : 'auto')}; @@ -53,16 +81,16 @@ const Options = styled.div<{ $optionsCount: number; $isFullWidth?: boolean }>` } `; -const Puck = styled.div<{ $optionsCount: number; $selectedIndex: number }>` +const Puck = styled.div<{ $optionsCount: number; $selectedIndex: number; $elevation: Elevation }>` position: absolute; left: 4px; top: 4px; bottom: 4px; width: ${({ $optionsCount }) => getPuckWidth($optionsCount)}; padding: ${spacingsPx.xxs} ${spacingsPx.xl}; - background: ${({ theme }) => theme.backgroundSurfaceElevation1}; + background: ${mapElevationToBackground}; border-radius: ${borders.radii.full}; - box-shadow: ${({ theme }) => theme.boxShadowBase}; + box-shadow: ${({ theme, $elevation }) => $elevation === 1 && theme.boxShadowBase}; transform: ${({ $selectedIndex }) => `translateX(${getTranslateValue($selectedIndex)})`}; transition: transform 0.175s cubic-bezier(1, 0.02, 0.38, 0.74), @@ -125,12 +153,12 @@ const Option = styled.div<{ $isSelected: boolean; $isDisabled: boolean }>` type ValueTypes = number | string | boolean; -interface Option { +type Option = { label: ReactNode; value: V; -} +}; -export interface SelectBarProps { +export type SelectBarProps = { label?: ReactNode; options: Option[]; selectedOption?: V; @@ -138,7 +166,8 @@ export interface SelectBarProps { isDisabled?: boolean; isFullWidth?: boolean; className?: string; -} + 'data-testid'?: string; +} & AllowedFrameProps; // Generic type V is determined by selectedOption/options values export const SelectBar: (props: SelectBarProps) => JSX.Element = ({ @@ -149,9 +178,12 @@ export const SelectBar: (props: SelectBarProps) => JSX. isDisabled = false, isFullWidth, className, + 'data-testid': dataTest, ...rest }) => { const [selectedOptionIn, setSelected] = useState(selectedOption); + const { elevation } = useElevation(); + const frameProps = pickAndPrepareFrameProps(rest, allowedSelectBarFrameProps); useEffect(() => { if (selectedOption !== undefined) { @@ -203,13 +235,23 @@ export const SelectBar: (props: SelectBarProps) => JSX. const selectedIndex = options.findIndex(option => option.value === selectedOptionIn); return ( - + {label && } - + diff --git a/packages/components/src/components/form/Switch/Switch.tsx b/packages/components/src/components/form/Switch/Switch.tsx index 3ef680f6624..2f1e09e8aa5 100755 --- a/packages/components/src/components/form/Switch/Switch.tsx +++ b/packages/components/src/components/form/Switch/Switch.tsx @@ -1,7 +1,10 @@ import { ReactNode } from 'react'; + import styled, { css } from 'styled-components'; + import { borders, spacingsPx, typography } from '@trezor/theme'; import { getWeakRandomId } from '@trezor/utils'; + import { getInputColor, getLabelColor, @@ -142,6 +145,8 @@ export const Switch = ({ return ( = { rows: 5, maxLength: 500, characterCount: true, - hasBottomPadding: true, }, argTypes: { isDisabled: { @@ -35,10 +36,10 @@ export const Textarea: StoryObj = { }, }, label: { - control: { type: 'text' }, + control: 'text', }, placeholder: { - control: { type: 'text' }, + control: 'text', }, rows: { control: { @@ -51,14 +52,17 @@ export const Textarea: StoryObj = { maxLength: { control: { type: 'number' }, }, + labelLeft: { + control: 'text', + }, labelHoverRight: { - control: { type: 'text' }, + control: 'text', }, labelRight: { - control: { type: 'text' }, + control: 'text', }, bottomText: { - control: { type: 'text' }, + control: 'text', }, innerRef: { table: { @@ -68,7 +72,7 @@ export const Textarea: StoryObj = { }, }, value: { - control: { type: 'text' }, + control: 'text', }, characterCount: { control: { @@ -80,12 +84,7 @@ export const Textarea: StoryObj = { }, }, }, - inputState: { - control: { - type: 'radio', - }, - options: [null, 'warning', 'error'], - }, + inputState: { control: 'select', options: ['error', 'warning', 'primary'] }, hasBottomPadding: { control: { type: 'boolean', diff --git a/packages/components/src/components/form/Textarea/Textarea.tsx b/packages/components/src/components/form/Textarea/Textarea.tsx index c295c65ba6d..bd909c9628e 100644 --- a/packages/components/src/components/form/Textarea/Textarea.tsx +++ b/packages/components/src/components/form/Textarea/Textarea.tsx @@ -1,5 +1,7 @@ +import { Ref, ReactNode, TextareaHTMLAttributes } from 'react'; + import styled from 'styled-components'; -import { useState, Ref, ReactNode, TextareaHTMLAttributes } from 'react'; + import { spacingsPx, Elevation } from '@trezor/theme'; import { InputState } from '../inputTypes'; @@ -11,21 +13,10 @@ import { INPUT_PADDING_TOP, LABEL_TRANSFORM, } from '../InputStyles'; -import { BOTTOM_TEXT_MIN_HEIGHT, BottomText } from '../BottomText'; -import { TopAddons } from '../TopAddons'; +import { FormCell, FormCellProps, pickFormCellProps } from '../FormCell/FormCell'; import { CharacterCount, CharacterCountProps } from './CharacterCount'; import { useElevation } from '../../ElevationContext/ElevationContext'; -const Wrapper = styled.div<{ $hasBottomPadding: boolean }>` - width: 100%; - position: relative; - display: flex; - flex-direction: column; - justify-content: flex-start; - padding-bottom: ${({ $hasBottomPadding }) => - $hasBottomPadding ? `${BOTTOM_TEXT_MIN_HEIGHT}px` : '0'}; -`; - const TextareaWrapper = styled(InputWrapper)<{ disabled?: boolean; // intentionally not transient, disabled is HTML prop $elevation: Elevation; @@ -74,57 +65,35 @@ const TextareaLabel = styled(Label)` } `; -export interface TextareaProps extends TextareaHTMLAttributes { - isDisabled?: boolean; - label?: ReactNode; - labelHoverRight?: ReactNode; - labelRight?: ReactNode; - innerRef?: Ref; - /** - * @description pass `null` if bottom text can be `undefined` - */ - bottomText?: ReactNode; - inputState?: InputState; - hasBottomPadding?: boolean; - value?: string; - characterCount?: CharacterCountProps['characterCount']; - 'data-testid'?: string; -} +export type TextareaProps = TextareaHTMLAttributes & + Omit & { + isDisabled?: boolean; + label?: ReactNode; + innerRef?: Ref; + hasBottomPadding?: boolean; + value?: string; + characterCount?: CharacterCountProps['characterCount']; + 'data-testid'?: string; + }; export const Textarea = ({ - className, value, maxLength, - labelHoverRight, isDisabled = false, innerRef, label, - inputState, - bottomText, placeholder, rows = 5, - labelRight, + inputState, characterCount, - hasBottomPadding = true, 'data-testid': dataTest, ...rest }: TextareaProps) => { - const [isHovered, setIsHovered] = useState(false); const { elevation } = useElevation(); + const formCellProps = pickFormCellProps(rest); return ( - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - $hasBottomPadding={hasBottomPadding === true && bottomText === null} - > - - + {label}} - - {bottomText && ( - - {bottomText} - - )} - + ); }; diff --git a/packages/components/src/components/form/TopAddons.tsx b/packages/components/src/components/form/TopAddons.tsx index a3fdc9827ef..d75e6daf7a6 100644 --- a/packages/components/src/components/form/TopAddons.tsx +++ b/packages/components/src/components/form/TopAddons.tsx @@ -1,42 +1,22 @@ import React from 'react'; -import styled from 'styled-components'; -import { spacingsPx } from '@trezor/theme'; -const Container = styled.div<{ $hasLeftAddon?: boolean }>` - display: flex; - justify-content: ${({ $hasLeftAddon }) => ($hasLeftAddon ? 'space-between' : 'flex-end')}; - align-items: flex-end; - gap: ${spacingsPx.xs}; - min-height: 30px; - padding-bottom: 6px; -`; +import styled from 'styled-components'; -export const RightAddonWrapper = styled.div` - display: flex; - gap: 6px; -`; +import { spacings } from '@trezor/theme'; -export const RightAddon = styled.div` - display: flex; - align-items: center; -`; +import { Row } from '../Flex/Flex'; export const HoverAddonRight = styled.div<{ $isVisible?: boolean }>` opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)}; transition: opacity 0.1s ease-out; `; -export const LeftAddon = styled.div` - display: flex; - align-items: center; -`; - -interface TopAddonsProps { +type TopAddonsProps = { isHovered?: boolean; addonLeft?: React.ReactNode; addonRight?: React.ReactNode; hoverAddonRight?: React.ReactNode; -} +}; export const TopAddons = ({ isHovered, @@ -53,16 +33,20 @@ export const TopAddons = ({ } return ( - - {addonLeft && {addonLeft}} + + {addonLeft && {addonLeft}} {isWithRightLabel && ( - + {hoverAddonRight && ( {hoverAddonRight} )} - {addonRight && {addonRight}} - + {addonRight && {addonRight}} + )} - + ); }; diff --git a/packages/components/src/components/form/inputTypes.ts b/packages/components/src/components/form/inputTypes.ts index 608e6ae841e..d2aafbd0f80 100644 --- a/packages/components/src/components/form/inputTypes.ts +++ b/packages/components/src/components/form/inputTypes.ts @@ -1,5 +1,5 @@ import { UISize } from '../../config/types'; -export type InputState = 'warning' | 'error' | 'primary'; +export type InputState = 'warning' | 'error' | 'primary' | 'default'; export type InputSize = Extract; diff --git a/packages/components/src/components/loaders/LoadingContent/LoadingContent.stories.tsx b/packages/components/src/components/loaders/LoadingContent/LoadingContent.stories.tsx index 13081b7f922..2d16178f4ea 100644 --- a/packages/components/src/components/loaders/LoadingContent/LoadingContent.stories.tsx +++ b/packages/components/src/components/loaders/LoadingContent/LoadingContent.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { H1, LoadingContent as LoadingContentComponent, LoadingContentProps } from '../../../index'; const meta: Meta = { diff --git a/packages/components/src/components/loaders/LoadingContent/LoadingContent.tsx b/packages/components/src/components/loaders/LoadingContent/LoadingContent.tsx index 62603ca36d1..1eeeb9f720d 100644 --- a/packages/components/src/components/loaders/LoadingContent/LoadingContent.tsx +++ b/packages/components/src/components/loaders/LoadingContent/LoadingContent.tsx @@ -1,5 +1,7 @@ import { ReactNode, useState, useEffect } from 'react'; + import styled, { css } from 'styled-components'; + import { Spinner } from '../Spinner/Spinner'; const LoadingWrapper = styled.div` diff --git a/packages/components/src/components/loaders/ProgressBar/ProgressBar.stories.tsx b/packages/components/src/components/loaders/ProgressBar/ProgressBar.stories.tsx index 7c4a8ad1a57..ba82870a6d5 100644 --- a/packages/components/src/components/loaders/ProgressBar/ProgressBar.stories.tsx +++ b/packages/components/src/components/loaders/ProgressBar/ProgressBar.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { ProgressBar as ProgressBarComponent, ProgressBarProps } from './ProgressBar'; const meta: Meta = { diff --git a/packages/components/src/components/loaders/ProgressBar/ProgressBar.tsx b/packages/components/src/components/loaders/ProgressBar/ProgressBar.tsx index 76343684d15..96b610b273e 100644 --- a/packages/components/src/components/loaders/ProgressBar/ProgressBar.tsx +++ b/packages/components/src/components/loaders/ProgressBar/ProgressBar.tsx @@ -1,6 +1,7 @@ -import { borders } from '@trezor/theme'; import styled from 'styled-components'; +import { borders } from '@trezor/theme'; + const Wrapper = styled.div` background: ${({ theme }) => theme.backgroundNeutralSubdued}; width: 100%; diff --git a/packages/components/src/components/loaders/ProgressPie/ProgressPie.stories.tsx b/packages/components/src/components/loaders/ProgressPie/ProgressPie.stories.tsx index eb0d2d48cb1..c3ababa25fd 100644 --- a/packages/components/src/components/loaders/ProgressPie/ProgressPie.stories.tsx +++ b/packages/components/src/components/loaders/ProgressPie/ProgressPie.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { ProgressPie as ProgressPieComponent, ProgressPieProps } from './ProgressPie'; const meta: Meta = { diff --git a/packages/components/src/components/loaders/ProgressPie/ProgressPie.tsx b/packages/components/src/components/loaders/ProgressPie/ProgressPie.tsx index 8941fe4fcb2..1547cf215f3 100644 --- a/packages/components/src/components/loaders/ProgressPie/ProgressPie.tsx +++ b/packages/components/src/components/loaders/ProgressPie/ProgressPie.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react'; + import styled from 'styled-components'; const Container = styled.div<{ diff --git a/packages/components/src/components/loaders/Spinner/Spinner.stories.tsx b/packages/components/src/components/loaders/Spinner/Spinner.stories.tsx index 81aca151198..fabd17abd93 100644 --- a/packages/components/src/components/loaders/Spinner/Spinner.stories.tsx +++ b/packages/components/src/components/loaders/Spinner/Spinner.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Spinner as SpinnerComponent, SpinnerProps, allowedSpinnerFrameProps } from './Spinner'; import { getFramePropsStory } from '../../../utils/frameProps'; diff --git a/packages/components/src/components/loaders/Spinner/Spinner.tsx b/packages/components/src/components/loaders/Spinner/Spinner.tsx index 31b1058652f..b17aaed948f 100644 --- a/packages/components/src/components/loaders/Spinner/Spinner.tsx +++ b/packages/components/src/components/loaders/Spinner/Spinner.tsx @@ -1,6 +1,8 @@ import { useState } from 'react'; + import Lottie from 'lottie-react'; import styled from 'styled-components'; + import animationStart from './animationData/refresh-spinner-start.json'; import animationMiddle from './animationData/refresh-spinner-middle.json'; import animationEnd from './animationData/refresh-spinner-end-success.json'; diff --git a/packages/components/src/components/loaders/Stepper/Stepper.stories.tsx b/packages/components/src/components/loaders/Stepper/Stepper.stories.tsx index cbbe0fd68e8..3e7791ee086 100644 --- a/packages/components/src/components/loaders/Stepper/Stepper.stories.tsx +++ b/packages/components/src/components/loaders/Stepper/Stepper.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Stepper as StepperComponent, StepperProps } from './Stepper'; const meta: Meta = { diff --git a/packages/components/src/components/loaders/Stepper/Stepper.tsx b/packages/components/src/components/loaders/Stepper/Stepper.tsx index 764e2612ab3..5e9ee5235e4 100644 --- a/packages/components/src/components/loaders/Stepper/Stepper.tsx +++ b/packages/components/src/components/loaders/Stepper/Stepper.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import { borders, spacingsPx } from '@trezor/theme'; const Container = styled.div<{ $maxWidth: number }>` diff --git a/packages/components/src/components/modals/DialogModal/DialogModal.stories.tsx b/packages/components/src/components/modals/DialogModal/DialogModal.stories.tsx index 75b7897dca6..4ffd033529e 100644 --- a/packages/components/src/components/modals/DialogModal/DialogModal.stories.tsx +++ b/packages/components/src/components/modals/DialogModal/DialogModal.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { DialogModal as ModalComponent, Button, DialogModalProps } from '../../../index'; import { variables } from '../../../config'; diff --git a/packages/components/src/components/modals/DialogModal/DialogModal.tsx b/packages/components/src/components/modals/DialogModal/DialogModal.tsx index 6ca8294107e..bf92ab9ccac 100644 --- a/packages/components/src/components/modals/DialogModal/DialogModal.tsx +++ b/packages/components/src/components/modals/DialogModal/DialogModal.tsx @@ -1,8 +1,11 @@ +import { ReactNode } from 'react'; + import styled from 'styled-components'; + import { borders, spacingsPx, typography } from '@trezor/theme'; + import { Icon, IconName } from '../../Icon/Icon'; import { Modal, ModalProps } from '../Modal/Modal'; -import { ReactNode } from 'react'; const BodyHeading = styled.div` margin-bottom: ${spacingsPx.xs}; diff --git a/packages/components/src/components/modals/Modal/Backdrop.tsx b/packages/components/src/components/modals/Modal/Backdrop.tsx index 0531615229b..ffa2b952230 100644 --- a/packages/components/src/components/modals/Modal/Backdrop.tsx +++ b/packages/components/src/components/modals/Modal/Backdrop.tsx @@ -1,5 +1,7 @@ import { ReactNode } from 'react'; + import styled, { css } from 'styled-components'; + import { zIndices } from '@trezor/theme'; export type ModalAlignment = { x: 'center' | 'left'; y: 'center' | 'top' }; @@ -13,7 +15,7 @@ export type BackdropProps = { const Wrapper = styled.div<{ $alignment: ModalAlignment }>` position: absolute; - z-index: ${zIndices.modal}; + z-index: ${zIndices.legacyModal}; inset: 0; display: flex; flex-direction: column; diff --git a/packages/components/src/components/modals/Modal/Modal.stories.tsx b/packages/components/src/components/modals/Modal/Modal.stories.tsx index 7fe6e9d6c7d..aebc56f081e 100644 --- a/packages/components/src/components/modals/Modal/Modal.stories.tsx +++ b/packages/components/src/components/modals/Modal/Modal.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Modal as ModalComponent, Button, ModalProps } from '../../../index'; import { Icon } from '../../Icon/Icon'; diff --git a/packages/components/src/components/modals/Modal/Modal.tsx b/packages/components/src/components/modals/Modal/Modal.tsx index 9d884dae45c..dc814c895e6 100644 --- a/packages/components/src/components/modals/Modal/Modal.tsx +++ b/packages/components/src/components/modals/Modal/Modal.tsx @@ -1,7 +1,8 @@ import { useCallback, useState, ReactNode, useEffect } from 'react'; +import { useEvent } from 'react-use'; import styled, { css, useTheme } from 'styled-components'; -import { useEvent } from 'react-use'; + import { borders, spacings, spacingsPx, typography } from '@trezor/theme'; import { Stepper } from '../../loaders/Stepper/Stepper'; @@ -54,7 +55,7 @@ const Header = styled.header` `; const BACK_ICON_WIDTH = spacingsPx.xxxl; -// eslint-disable-next-line local-rules/no-override-ds-component + const BackIcon = styled(Icon)` position: relative; width: ${BACK_ICON_WIDTH}; @@ -95,7 +96,6 @@ type HeadingSize = keyof typeof HEADING_SIZES; type HeadingProps = { $isWithIcon?: boolean; $headingSize: HeadingSize }; -// eslint-disable-next-line local-rules/no-override-ds-component const Heading = styled(H3)` ${({ $isWithIcon }) => $isWithIcon && diff --git a/packages/components/src/components/skeletons/SkeletonCircle.stories.tsx b/packages/components/src/components/skeletons/SkeletonCircle.stories.tsx index 79ca80dc133..2e46a667b2e 100644 --- a/packages/components/src/components/skeletons/SkeletonCircle.stories.tsx +++ b/packages/components/src/components/skeletons/SkeletonCircle.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { SkeletonCircle as SkeletonCircleComponent, SkeletonCircleProps } from './SkeletonCircle'; const meta: Meta = { diff --git a/packages/components/src/components/skeletons/SkeletonCircle.tsx b/packages/components/src/components/skeletons/SkeletonCircle.tsx index c2cf1d98c90..6926817b517 100644 --- a/packages/components/src/components/skeletons/SkeletonCircle.tsx +++ b/packages/components/src/components/skeletons/SkeletonCircle.tsx @@ -1,7 +1,9 @@ import styled, { css } from 'styled-components'; + +import { Elevation, mapElevationToBackground } from '@trezor/theme'; + import { getValue, shimmerEffect } from './utils'; import { SkeletonBaseProps } from './types'; -import { Elevation, mapElevationToBackground } from '@trezor/theme'; import { useElevation } from '../ElevationContext/ElevationContext'; import { TransientProps } from '../../utils/transientProps'; diff --git a/packages/components/src/components/skeletons/SkeletonRectangle.stories.tsx b/packages/components/src/components/skeletons/SkeletonRectangle.stories.tsx index aba04a8c7f1..70a836674e1 100644 --- a/packages/components/src/components/skeletons/SkeletonRectangle.stories.tsx +++ b/packages/components/src/components/skeletons/SkeletonRectangle.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { SkeletonRectangle as SkeletonRectangleComponent, SkeletonRectangleProps, diff --git a/packages/components/src/components/skeletons/SkeletonRectangle.tsx b/packages/components/src/components/skeletons/SkeletonRectangle.tsx index 2b0244467cb..b9b8d0f82a4 100644 --- a/packages/components/src/components/skeletons/SkeletonRectangle.tsx +++ b/packages/components/src/components/skeletons/SkeletonRectangle.tsx @@ -1,7 +1,9 @@ import styled, { css } from 'styled-components'; + +import { Elevation, borders, mapElevationToBackground } from '@trezor/theme'; + import { SkeletonBaseProps } from './types'; import { getValue, shimmerEffect } from './utils'; -import { Elevation, borders, mapElevationToBackground } from '@trezor/theme'; import { useElevation } from '../ElevationContext/ElevationContext'; import { TransientProps } from '../../utils/transientProps'; diff --git a/packages/components/src/components/skeletons/SkeletonSpread.stories.tsx b/packages/components/src/components/skeletons/SkeletonSpread.stories.tsx index b73a202b307..99260d771c2 100644 --- a/packages/components/src/components/skeletons/SkeletonSpread.stories.tsx +++ b/packages/components/src/components/skeletons/SkeletonSpread.stories.tsx @@ -1,7 +1,8 @@ import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + import { SkeletonSpread as SkeletonSpreadComponent, SkeletonSpreadProps } from './SkeletonSpread'; import { SkeletonCircle } from './SkeletonCircle'; -import styled from 'styled-components'; import { ElevationContext } from '../ElevationContext/ElevationContext'; const Container = styled.div` diff --git a/packages/components/src/components/skeletons/SkeletonSpread.tsx b/packages/components/src/components/skeletons/SkeletonSpread.tsx index ee17ea2304e..4cc0d379ce7 100644 --- a/packages/components/src/components/skeletons/SkeletonSpread.tsx +++ b/packages/components/src/components/skeletons/SkeletonSpread.tsx @@ -1,11 +1,11 @@ import styled from 'styled-components'; + import { SkeletonStack, SkeletonStackProps } from './SkeletonStack'; export interface SkeletonSpreadProps extends SkeletonStackProps { $spaceAround?: boolean; } -// eslint-disable-next-line local-rules/no-override-ds-component export const SkeletonSpread = styled(SkeletonStack)` justify-content: ${props => (props.$spaceAround ? 'space-around' : 'space-between')}; `; diff --git a/packages/components/src/components/skeletons/SkeletonStack.stories.tsx b/packages/components/src/components/skeletons/SkeletonStack.stories.tsx index c9d3508e56d..04db1ac42cc 100644 --- a/packages/components/src/components/skeletons/SkeletonStack.stories.tsx +++ b/packages/components/src/components/skeletons/SkeletonStack.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { SkeletonStack as SkeletonStackComponent, SkeletonStackProps } from './SkeletonStack'; import { SkeletonCircle } from './SkeletonCircle'; import { ElevationContext } from '../ElevationContext/ElevationContext'; diff --git a/packages/components/src/components/skeletons/utils.ts b/packages/components/src/components/skeletons/utils.ts index d8be77b695b..f9e1e61b684 100644 --- a/packages/components/src/components/skeletons/utils.ts +++ b/packages/components/src/components/skeletons/utils.ts @@ -1,5 +1,7 @@ -import { Elevation, mapElevationToBackground } from '@trezor/theme'; import { css, keyframes } from 'styled-components'; + +import { Elevation, mapElevationToBackground } from '@trezor/theme'; + import { mapElevationToSkeletonForeground } from './colors'; const SHINE = keyframes` diff --git a/packages/components/src/components/typography/Code/Code.stories.tsx b/packages/components/src/components/typography/Code/Code.stories.tsx new file mode 100644 index 00000000000..dede65b54c6 --- /dev/null +++ b/packages/components/src/components/typography/Code/Code.stories.tsx @@ -0,0 +1,17 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { Code } from './Code'; + +const meta: Meta = { + title: 'Code', + component: Code, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'ABC', + }, +}; diff --git a/packages/components/src/components/typography/Code/Code.tsx b/packages/components/src/components/typography/Code/Code.tsx new file mode 100644 index 00000000000..977c95c0a58 --- /dev/null +++ b/packages/components/src/components/typography/Code/Code.tsx @@ -0,0 +1,22 @@ +import React, { type ReactNode } from 'react'; + +import styled from 'styled-components'; + +import { borders, spacingsPx } from '@trezor/theme'; + +const StyledCode = styled.code` + font-family: RobotoMono, 'PixelOperatorMono8', monospace; + display: inline; + font-size: inherit; + line-height: 1.5; + font-weight: 400; + letter-spacing: -0.4px; + padding: 0 ${() => spacingsPx.xxxs}; + background-color: ${({ theme }) => theme.backgroundNeutralSubtleOnElevation1}; + box-shadow: inset 0 0 0 1px ${({ theme }) => theme.borderElevation0}; + border-radius: ${() => borders.radii.xxs}; +`; + +export const Code: React.FC<{ children: ReactNode }> = ({ children }) => { + return {children}; +}; diff --git a/packages/components/src/components/typography/Heading/Heading.stories.tsx b/packages/components/src/components/typography/Heading/Heading.stories.tsx index c053fbcb0c9..d25aadaf8be 100644 --- a/packages/components/src/components/typography/Heading/Heading.stories.tsx +++ b/packages/components/src/components/typography/Heading/Heading.stories.tsx @@ -1,12 +1,15 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; -import { H1, H2, H3, allowedHeadingFrameProps, allowedHeadingTextProps } from './Heading'; + +import { H1, H2, H3, H4 } from './Heading'; import { getFramePropsStory } from '../../../utils/frameProps'; import { getTextPropsStory } from '../utils'; +import { allowedTextFrameProps, allowedTextTextProps, textVariants } from '../Text/Text'; const Wrapper = styled.div` display: flex; flex-direction: column; + overflow: hidden; `; const meta: Meta = { @@ -20,14 +23,21 @@ export const Heading: StoryObj = {

This is heading 1

This is heading 2

This is heading 3

+

This is heading 4

), args: { - ...getTextPropsStory(allowedHeadingTextProps).args, - ...getFramePropsStory(allowedHeadingFrameProps).args, + ...getTextPropsStory(allowedTextTextProps).args, + ...getFramePropsStory(allowedTextFrameProps).args, }, argTypes: { - ...getTextPropsStory(allowedHeadingTextProps).argTypes, - ...getFramePropsStory(allowedHeadingFrameProps).argTypes, + variant: { + control: { + type: 'select', + }, + options: textVariants, + }, + ...getTextPropsStory(allowedTextTextProps).argTypes, + ...getFramePropsStory(allowedTextFrameProps).argTypes, }, }; diff --git a/packages/components/src/components/typography/Heading/Heading.tsx b/packages/components/src/components/typography/Heading/Heading.tsx index d9d511898f0..bcbf9ddb95b 100644 --- a/packages/components/src/components/typography/Heading/Heading.tsx +++ b/packages/components/src/components/typography/Heading/Heading.tsx @@ -1,146 +1,16 @@ -import styled from 'styled-components'; +import { TypographyStyle } from '@trezor/theme'; -import { Color } from '@trezor/theme'; -import { - FrameProps, - FramePropsKeys, - pickAndPrepareFrameProps, - withFrameProps, -} from '../../../utils/frameProps'; -import { makePropsTransient, TransientProps } from '../../../utils/transientProps'; -import { TextProps as TextPropsCommon, TextPropsKeys, withTextProps } from '../utils'; +import { Text, TextProps } from '../Text/Text'; -export const allowedHeadingTextProps: TextPropsKeys[] = ['typographyStyle', 'textWrap', 'align']; -type AllowedHeadingTextProps = Pick; - -export const allowedHeadingFrameProps = ['margin'] as const satisfies FramePropsKeys[]; -type AllowedFrameProps = Pick; - -type HeadingProps = TransientProps & - TransientProps & { - $color?: Color; - }; - -const Heading = styled.h1` - ${({ $color, theme }) => $color && `color: ${theme[$color]}`}; - - ${withTextProps} - ${withFrameProps} -`; - -type HProps = AllowedFrameProps & - AllowedHeadingTextProps & { - color?: Color; - children: React.ReactNode; - className?: string; - onClick?: () => void; - 'data-testid'?: string; - }; - -export const H1 = ({ - color, - align, - typographyStyle = 'titleLarge', - textWrap, - onClick, - 'data-testid': dataTest, - children, - className, - ...rest -}: HProps) => { - const frameProps = pickAndPrepareFrameProps(rest, allowedHeadingFrameProps); - - return ( - - {children} - - ); -}; - -export const H2 = ({ - color, - align, - typographyStyle = 'titleMedium', - textWrap, - onClick, - 'data-testid': dataTest, - children, - className, - ...rest -}: HProps) => { - const frameProps = pickAndPrepareFrameProps(rest, allowedHeadingFrameProps); - - return ( - +const createHeading = + (as: 'h1' | 'h2' | 'h3' | 'h4', defaultTypographyStyle: TypographyStyle) => + ({ children, ...rest }: TextProps) => ( + {children} - +
); -}; - -export const H3 = ({ - color, - align, - typographyStyle = 'titleSmall', - textWrap, - onClick, - 'data-testid': dataTest, - children, - className, - ...rest -}: HProps) => { - const frameProps = pickAndPrepareFrameProps(rest, allowedHeadingFrameProps); - return ( - - {children} - - ); -}; - -export const H4 = ({ - color, - align, - typographyStyle = 'highlight', - textWrap, - onClick, - 'data-testid': dataTest, - children, - className, - ...rest -}: HProps) => { - const frameProps = pickAndPrepareFrameProps(rest, allowedHeadingFrameProps); - - return ( - - {children} - - ); -}; +export const H1 = createHeading('h1', 'titleLarge'); +export const H2 = createHeading('h2', 'titleMedium'); +export const H3 = createHeading('h3', 'titleSmall'); +export const H4 = createHeading('h4', 'highlight'); diff --git a/packages/components/src/components/typography/Link/Link.stories.tsx b/packages/components/src/components/typography/Link/Link.stories.tsx index 22066c78a98..0f158d4562d 100644 --- a/packages/components/src/components/typography/Link/Link.stories.tsx +++ b/packages/components/src/components/typography/Link/Link.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Link as LinkComponent, LinkProps } from '../../../index'; const meta: Meta = { diff --git a/packages/components/src/components/typography/Link/Link.tsx b/packages/components/src/components/typography/Link/Link.tsx index a60566d3561..d4058186c08 100644 --- a/packages/components/src/components/typography/Link/Link.tsx +++ b/packages/components/src/components/typography/Link/Link.tsx @@ -1,6 +1,9 @@ import { ReactNode, MouseEvent } from 'react'; + import styled, { css } from 'styled-components'; + import { TypographyStyle, spacingsPx, typography, typographyStylesBase } from '@trezor/theme'; + import { Icon, IconName } from '../../Icon/Icon'; type AProps = { diff --git a/packages/components/src/components/typography/Paragraph/Paragraph.stories.tsx b/packages/components/src/components/typography/Paragraph/Paragraph.stories.tsx index 948a75b3d1f..656aef28702 100644 --- a/packages/components/src/components/typography/Paragraph/Paragraph.stories.tsx +++ b/packages/components/src/components/typography/Paragraph/Paragraph.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { Paragraph as P } from './Paragraph'; import { getFramePropsStory } from '../../../utils/frameProps'; import { getTextPropsStory } from '../utils'; @@ -19,19 +20,6 @@ export const Paragraph: StoryObj = { ...getTextPropsStory(allowedTextTextProps).args, }, argTypes: { - typographyStyle: { - control: 'select', - options: [ - 'titleLarge', - 'titleMedium', - 'titleSmall', - 'highlight', - 'body', - 'callout', - 'hint', - 'label', - ], - }, variant: { control: { type: 'select', diff --git a/packages/components/src/components/typography/Paragraph/Paragraph.tsx b/packages/components/src/components/typography/Paragraph/Paragraph.tsx index f81f563230a..9ae742fbec5 100644 --- a/packages/components/src/components/typography/Paragraph/Paragraph.tsx +++ b/packages/components/src/components/typography/Paragraph/Paragraph.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Text, TextProps } from '../Text/Text'; export const Paragraph = ({ children, ...rest }: TextProps) => { diff --git a/packages/components/src/components/typography/Text/Text.stories.tsx b/packages/components/src/components/typography/Text/Text.stories.tsx index 5c1d39fd3c7..8c84f1c8b10 100644 --- a/packages/components/src/components/typography/Text/Text.stories.tsx +++ b/packages/components/src/components/typography/Text/Text.stories.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { allowedTextFrameProps, allowedTextTextProps, Text as TextComponent } from './Text'; import { getFramePropsStory } from '../../../utils/frameProps'; import { getTextPropsStory } from '../utils'; diff --git a/packages/components/src/components/typography/Text/Text.tsx b/packages/components/src/components/typography/Text/Text.tsx index 5b342dcc382..7676533dc01 100644 --- a/packages/components/src/components/typography/Text/Text.tsx +++ b/packages/components/src/components/typography/Text/Text.tsx @@ -1,20 +1,29 @@ +import { ReactNode } from 'react'; + import styled from 'styled-components'; + import { CSSColor, Color, Colors } from '@trezor/theme'; -import { ReactNode } from 'react'; -import { makePropsTransient, TransientProps } from '../../../utils/transientProps'; + +import { TransientProps } from '../../../utils/transientProps'; import { FrameProps, FramePropsKeys, pickAndPrepareFrameProps, withFrameProps, } from '../../../utils/frameProps'; -import { TextPropsKeys, withTextProps, TextProps as TextPropsCommon } from '../utils'; +import { + TextPropsKeys, + withTextProps, + TextProps as TextPropsCommon, + pickAndPrepareTextProps, +} from '../utils'; import { uiVariants } from '../../../config/types'; export const allowedTextTextProps = [ 'typographyStyle', 'textWrap', 'align', + 'ellipsisLineCount', ] as const satisfies TextPropsKeys[]; type AllowedTextTextProps = Pick; @@ -41,6 +50,7 @@ const variantColorMap: Record = { warning: 'textAlertYellow', destructive: 'textAlertRed', purple: 'textAlertPurple', + disabled: 'textDisabled', }; type ColorProps = { @@ -56,11 +66,11 @@ const getColorForTextVariant = ({ $variant, theme, $color }: ColorProps): CSSCol }; type StyledTextProps = ExclusiveColorOrVariant & - TransientProps & - TransientProps; + TransientProps; const StyledText = styled.span` color: ${getColorForTextVariant}; + ${withTextProps} ${withFrameProps} `; @@ -69,6 +79,7 @@ export type TextProps = { children: ReactNode; className?: string; as?: string; + onClick?: () => void; 'data-testid'?: string; } & ExclusiveColorOrVariant & AllowedFrameProps & @@ -79,22 +90,22 @@ export const Text = ({ color, children, className, - typographyStyle, - textWrap, - align, as = 'span', 'data-testid': dataTest, + onClick, ...rest }: TextProps) => { const frameProps = pickAndPrepareFrameProps(rest, allowedTextFrameProps); + const textProps = pickAndPrepareTextProps(rest, allowedTextTextProps); return ( {children} diff --git a/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.stories.tsx b/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.stories.tsx index 960bd860776..18c99733291 100644 --- a/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.stories.tsx +++ b/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.stories.tsx @@ -1,9 +1,10 @@ import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; + import { TruncateWithTooltip as TruncateWithTooltipComponent, TruncateWithTooltipProps, } from './TruncateWithTooltip'; -import styled from 'styled-components'; const Container = styled.div` overflow: hidden; diff --git a/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.tsx b/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.tsx index 165679a2abb..decf5aa4c54 100644 --- a/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.tsx +++ b/packages/components/src/components/typography/TruncateWithTooltip/TruncateWithTooltip.tsx @@ -1,5 +1,7 @@ -import styled from 'styled-components'; import { useEffect, useRef, useState } from 'react'; + +import styled from 'styled-components'; + import { Tooltip, Cursor } from '../../Tooltip/Tooltip'; import { TooltipDelay } from '../../Tooltip/TooltipDelay'; @@ -29,8 +31,13 @@ export const TruncateWithTooltip = ({ useEffect(() => { if (!containerRef.current || !scrollWidth || !scrollHeight) return; const resizeObserver = new ResizeObserver(entries => { - const { inlineSize: elementWidth, blockSize: elementHeight } = - entries[0].borderBoxSize?.[0]; + const borderBoxSize = entries[0].borderBoxSize?.[0]; + if (!borderBoxSize) { + return; + } + + const { inlineSize: elementWidth, blockSize: elementHeight } = borderBoxSize; + setIsTooltipVisible( scrollWidth > Math.ceil(elementWidth) || scrollHeight > Math.ceil(elementHeight), ); diff --git a/packages/components/src/components/typography/typography.stories.tsx b/packages/components/src/components/typography/typography.stories.tsx index f6473c92daa..e437df113a6 100644 --- a/packages/components/src/components/typography/typography.stories.tsx +++ b/packages/components/src/components/typography/typography.stories.tsx @@ -1,6 +1,7 @@ +import { Meta, StoryFn } from '@storybook/react'; + import { StoryColumn } from '../../support/Story'; import { H1, H2, Paragraph, Link } from '../../index'; -import { Meta, StoryFn } from '@storybook/react'; const meta: Meta = { title: 'Typography', @@ -23,49 +24,56 @@ export const AllTypography: StoryFn = () => ( feugiat vel mi. - type="titleLarge"
+ type="titleLarge" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type="titleMedium"
+ type="titleMedium" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type ="titleSmall"
+ type="titleSmall" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type="highlight"
+ type="highlight" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type="callout"
+ type="callout" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type="hint"
+ type="hint" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, feugiat vel mi.
- type="label"
+ type="label" +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a ante quis lectus eleifend rutrum. Aenean tincidunt odio vel fermentum ultricies. Sed suscipit interdum eros, eget placerat lorem pulvinar in. Ut elit orci, rhoncus eu porta vel, diff --git a/packages/components/src/components/typography/utils.tsx b/packages/components/src/components/typography/utils.tsx index f70136b73ee..5c980549b77 100644 --- a/packages/components/src/components/typography/utils.tsx +++ b/packages/components/src/components/typography/utils.tsx @@ -1,8 +1,9 @@ +import { css } from 'styled-components'; + import { typography, TypographyStyle, typographyStyles } from '@trezor/theme'; -import { TransientProps } from '../../utils/transientProps'; + +import { TransientProps, makePropsTransient } from '../../utils/transientProps'; import { UIHorizontalAlignment, uiHorizontalAlignments } from '../../config/types'; -import { css } from 'styled-components'; -import { makePropsTransient } from '../../utils/transientProps'; export const textWraps = ['balance', 'break-word']; export type TextWrap = (typeof textWraps)[number]; @@ -11,6 +12,7 @@ export type TextProps = { typographyStyle?: TypographyStyle; textWrap?: TextWrap; align?: UIHorizontalAlignment; + ellipsisLineCount?: number; }; export type TextPropsKeys = keyof TextProps; @@ -25,7 +27,12 @@ export const pickAndPrepareTextProps = ( allowedTextProps.reduce((acc, item) => ({ ...acc, [item]: props[item] }), {}), ); -export const withTextProps = ({ $textWrap, $typographyStyle, $align }: TransientTextProps) => { +export const withTextProps = ({ + $textWrap, + $typographyStyle, + $align, + $ellipsisLineCount = 0, +}: TransientTextProps) => { return css` ${$textWrap && css` @@ -40,6 +47,19 @@ export const withTextProps = ({ $textWrap, $typographyStyle, $align }: Transient css` text-align: ${$align}; `} + ${$ellipsisLineCount > 0 && + css` + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + `} + ${$ellipsisLineCount > 1 && + css` + white-space: initial; + -webkit-line-clamp: ${$ellipsisLineCount}; + display: -webkit-box; + -webkit-box-orient: vertical; + `} `; }; @@ -66,6 +86,13 @@ const getStorybookType = (key: TextPropsKeys) => { type: 'select', }, }; + case 'ellipsisLineCount': + return { + control: { + type: 'number', + min: 0, + }, + }; default: return { control: { @@ -94,6 +121,7 @@ export const getTextPropsStory = (allowedTextProps: Array) => { ...(allowedTextProps.includes('textWrap') ? { textWrap: undefined } : {}), ...(allowedTextProps.includes('typographyStyle') ? { typographyStyle: undefined } : {}), ...(allowedTextProps.includes('align') ? { align: undefined } : {}), + ...(allowedTextProps.includes('ellipsisLineCount') ? { hasEllipsis: undefined } : {}), }, argTypes, }; diff --git a/packages/components/src/config/types.ts b/packages/components/src/config/types.ts index 5af08e70348..e2433ecaa80 100644 --- a/packages/components/src/config/types.ts +++ b/packages/components/src/config/types.ts @@ -6,10 +6,11 @@ export const uiVariants = [ 'info', 'warning', 'destructive', + 'disabled', ] as const; export type UIVariant = (typeof uiVariants)[number]; -export const uiSizes = ['large', 'medium', 'small', 'tiny'] as const; +export const uiSizes = ['huge', 'large', 'medium', 'small', 'tiny'] as const; export type UISize = (typeof uiSizes)[number]; export const uiHorizontalAlignments = ['left', 'center', 'right'] as const; diff --git a/packages/components/src/images/coins/ada.svg b/packages/components/src/images/coins/ada.svg deleted file mode 100644 index 13f8cacd49d..00000000000 --- a/packages/components/src/images/coins/ada.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/bch.svg b/packages/components/src/images/coins/bch.svg deleted file mode 100644 index 4d4953f530d..00000000000 --- a/packages/components/src/images/coins/bch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/bnb.svg b/packages/components/src/images/coins/bnb.svg deleted file mode 100644 index 42200ed25c9..00000000000 --- a/packages/components/src/images/coins/bnb.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/btc.svg b/packages/components/src/images/coins/btc.svg deleted file mode 100644 index 3145115b021..00000000000 --- a/packages/components/src/images/coins/btc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/btctestnet.svg b/packages/components/src/images/coins/btctestnet.svg deleted file mode 100644 index 0d63ab962f1..00000000000 --- a/packages/components/src/images/coins/btctestnet.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/btg.svg b/packages/components/src/images/coins/btg.svg deleted file mode 100644 index 46b004a45e3..00000000000 --- a/packages/components/src/images/coins/btg.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/components/src/images/coins/dash.svg b/packages/components/src/images/coins/dash.svg deleted file mode 100644 index 8253297c56a..00000000000 --- a/packages/components/src/images/coins/dash.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/components/src/images/coins/dgb.svg b/packages/components/src/images/coins/dgb.svg deleted file mode 100644 index 8aa60ba58bd..00000000000 --- a/packages/components/src/images/coins/dgb.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/doge.svg b/packages/components/src/images/coins/doge.svg deleted file mode 100644 index a78eb50bb48..00000000000 --- a/packages/components/src/images/coins/doge.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/dsol.svg b/packages/components/src/images/coins/dsol.svg deleted file mode 100644 index 7a24a48b42f..00000000000 --- a/packages/components/src/images/coins/dsol.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/packages/components/src/images/coins/etc.svg b/packages/components/src/images/coins/etc.svg deleted file mode 100644 index b5ab9b89652..00000000000 --- a/packages/components/src/images/coins/etc.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/components/src/images/coins/eth.svg b/packages/components/src/images/coins/eth.svg deleted file mode 100644 index 50b5e7da3f0..00000000000 --- a/packages/components/src/images/coins/eth.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/components/src/images/coins/ltc.svg b/packages/components/src/images/coins/ltc.svg deleted file mode 100644 index cd7239b3130..00000000000 --- a/packages/components/src/images/coins/ltc.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/nmc.svg b/packages/components/src/images/coins/nmc.svg deleted file mode 100644 index a8b635fa68b..00000000000 --- a/packages/components/src/images/coins/nmc.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/pol.svg b/packages/components/src/images/coins/pol.svg deleted file mode 100644 index 9222cc41ec7..00000000000 --- a/packages/components/src/images/coins/pol.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/components/src/images/coins/sol.svg b/packages/components/src/images/coins/sol.svg deleted file mode 100644 index 44557700148..00000000000 --- a/packages/components/src/images/coins/sol.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/packages/components/src/images/coins/tada.svg b/packages/components/src/images/coins/tada.svg deleted file mode 100644 index 5832b53043f..00000000000 --- a/packages/components/src/images/coins/tada.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/thol.svg b/packages/components/src/images/coins/thol.svg deleted file mode 100644 index dddbe3a8020..00000000000 --- a/packages/components/src/images/coins/thol.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/tsep.svg b/packages/components/src/images/coins/tsep.svg deleted file mode 100644 index dddbe3a8020..00000000000 --- a/packages/components/src/images/coins/tsep.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/txrp.svg b/packages/components/src/images/coins/txrp.svg deleted file mode 100644 index c6a21ce3562..00000000000 --- a/packages/components/src/images/coins/txrp.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/vtc.svg b/packages/components/src/images/coins/vtc.svg deleted file mode 100644 index a92548d7ea3..00000000000 --- a/packages/components/src/images/coins/vtc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/xrp.svg b/packages/components/src/images/coins/xrp.svg deleted file mode 100644 index 948e9311603..00000000000 --- a/packages/components/src/images/coins/xrp.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/packages/components/src/images/coins/zec.svg b/packages/components/src/images/coins/zec.svg deleted file mode 100644 index 9901c0267be..00000000000 --- a/packages/components/src/images/coins/zec.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 8f3fa5eecf7..ea3db70e489 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -27,6 +27,7 @@ export * from './components/Dropdown/Dropdown'; export type { GroupedMenuItems } from './components/Dropdown/Menu'; export * from './components/ElevationContext/ElevationContext'; export * from './components/Flex/Flex'; +export { FormCell, type FormCellProps } from './components/form/FormCell/FormCell'; export * from './components/form/Input/Input'; export * from './components/form/InputStyles'; export * from './components/form/Radio/Radio'; @@ -41,6 +42,13 @@ export * from './components/HotkeyBadge/HotkeyBadge'; export * from './components/Image/Image'; export * from './components/Image/images'; export * from './components/Icon/Icon'; +export { + IconCircle, + type IconCircleProps, + type IconCircleVariant, + type IconCircleColors, +} from './components/IconCircle/IconCircle'; +export { InfoRow, type InfoRowProps } from './components/InfoRow/InfoRow'; export * from './components/loaders/LoadingContent/LoadingContent'; export * from './components/loaders/ProgressBar/ProgressBar'; export * from './components/loaders/ProgressPie/ProgressPie'; @@ -65,6 +73,7 @@ export * from './components/typography/Heading/Heading'; export * from './components/typography/Link/Link'; export * from './components/typography/Paragraph/Paragraph'; export * from './components/typography/Text/Text'; +export * from './components/typography/Code/Code'; export * from './components/typography/TruncateWithTooltip/TruncateWithTooltip'; export * from './components/Banner/Banner'; export { Table, type TableProps } from './components/Table/Table'; @@ -77,6 +86,7 @@ export * from './constants/keyboardEvents'; export * from './utils/useScrollShadow'; export * from './utils/transientProps'; export { useMediaQuery } from './utils/useMediaQuery'; +export { getSafeWindowSize } from './utils/getSafeWindowSize'; export { intermediaryTheme } from './config/colors'; export type { SuiteThemeColors } from './config/colors'; diff --git a/packages/components/src/support/Story.tsx b/packages/components/src/support/Story.tsx index ab437b298a7..53a440117c6 100644 --- a/packages/components/src/support/Story.tsx +++ b/packages/components/src/support/Story.tsx @@ -1,4 +1,5 @@ import styled, { ThemeProvider } from 'styled-components'; + import { intermediaryTheme } from '../index'; const Wrapper = styled.div` diff --git a/packages/components/src/utils/frameProps.tsx b/packages/components/src/utils/frameProps.tsx index 883c2b8e63a..54ffaa1b85a 100644 --- a/packages/components/src/utils/frameProps.tsx +++ b/packages/components/src/utils/frameProps.tsx @@ -1,12 +1,16 @@ import { css } from 'styled-components'; -import { makePropsTransient, TransientProps } from './transientProps'; + import { SpacingValues } from '@trezor/theme'; +import { makePropsTransient, TransientProps } from './transientProps'; + type Margin = { top?: SpacingValues | 'auto'; bottom?: SpacingValues | 'auto'; left?: SpacingValues | 'auto'; right?: SpacingValues | 'auto'; + horizontal?: SpacingValues | 'auto'; + vertical?: SpacingValues | 'auto'; }; const overflows = [ 'auto', @@ -67,10 +71,10 @@ export const withFrameProps = ({ return css` ${$margin && css` - ${$margin.top ? `margin-top: ${getValueWithUnit($margin.top)};` : ''} - ${$margin.bottom ? `margin-bottom: ${getValueWithUnit($margin.bottom)};` : ''} - ${$margin.left ? `margin-left: ${getValueWithUnit($margin.left)};` : ''} - ${$margin.right ? `margin-right: ${getValueWithUnit($margin.right)};` : ''} + margin: ${getValueWithUnit($margin.top ?? $margin.vertical ?? 0)} + ${getValueWithUnit($margin.right ?? $margin.horizontal ?? 0)} + ${getValueWithUnit($margin.bottom ?? $margin.vertical ?? 0)} + ${getValueWithUnit($margin.left ?? $margin.horizontal ?? 0)}; `} ${$minWidth && @@ -171,6 +175,8 @@ export const getFramePropsStory = (allowedFrameProps: Array) => right: undefined, bottom: undefined, left: undefined, + horizontal: undefined, + vertical: undefined, }, } : {}), diff --git a/packages/components/src/utils/getSafeWindowSize.ts b/packages/components/src/utils/getSafeWindowSize.ts new file mode 100644 index 00000000000..d1ab2f07fcc --- /dev/null +++ b/packages/components/src/utils/getSafeWindowSize.ts @@ -0,0 +1,19 @@ +const roundDownFloatOrFallback = (value: string, fallback: number) => { + const parsed = Math.floor(parseFloat(value)); + + return Number.isNaN(parsed) ? fallback : parsed; +}; + +/** + * Get window size and try to round it down if it's a fractional number. + * Can happen at Windows with scaling enabled, see https://github.com/trezor/trezor-suite/issues/15195 + */ +export const getSafeWindowSize = () => { + // window.innerWidth is always integer, but computedStyle bears the actual value if it's fractional + const computedStyle = window.getComputedStyle(document.body); + // but we have to convert from 'px' to number, so let's do it safely with fallback + const windowWidth = roundDownFloatOrFallback(computedStyle.width, window.innerWidth); + const windowHeight = roundDownFloatOrFallback(computedStyle.height, window.innerHeight); + + return { windowWidth, windowHeight }; +}; diff --git a/packages/components/src/utils/useScrollShadow.tsx b/packages/components/src/utils/useScrollShadow.tsx index 53cbf818814..c6abfb19ace 100644 --- a/packages/components/src/utils/useScrollShadow.tsx +++ b/packages/components/src/utils/useScrollShadow.tsx @@ -1,8 +1,10 @@ import { useEffect, useRef, useState } from 'react'; -import styled, { CSSObject } from 'styled-components'; -import { useElevation } from '../components/ElevationContext/ElevationContext'; + +import styled, { CSSObject, DefaultTheme } from 'styled-components'; + import { Color, Elevation, mapElevationToBackground } from '@trezor/theme'; -import { DefaultTheme } from 'styled-components'; + +import { useElevation } from '../components/ElevationContext/ElevationContext'; import { UIHorizontalAlignment, UIVerticalAlignment } from '../config/types'; type GradientDirection = Exclude; diff --git a/packages/components/styled.d.ts b/packages/components/styled.d.ts index ff56311cfb2..83b3a85013a 100644 --- a/packages/components/styled.d.ts +++ b/packages/components/styled.d.ts @@ -1,6 +1,7 @@ // import original module declarations import 'styled-components'; import { BoxShadows, Colors } from '@trezor/theme'; + import { SuiteThemeColors } from './src/config/colors'; declare module 'styled-components' { diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 1ef55129990..7aed3d4037d 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -26,12 +26,14 @@ { "path": "../../suite-common/validators" }, + { "path": "../asset-utils" }, { "path": "../connect" }, { "path": "../dom-utils" }, { "path": "../env-utils" }, { "path": "../react-utils" }, { "path": "../styles" }, { "path": "../theme" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../eslint" } ] } diff --git a/packages/connect-analytics/package.json b/packages/connect-analytics/package.json index e0fe8041813..b6cbf058d24 100644 --- a/packages/connect-analytics/package.json +++ b/packages/connect-analytics/package.json @@ -12,7 +12,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "build:lib": "yarn g:rimraf lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib" diff --git a/packages/connect-common/files/coins-eth.json b/packages/connect-common/files/coins-eth.json index bdb18954db7..468c0c35724 100644 --- a/packages/connect-common/files/coins-eth.json +++ b/packages/connect-common/files/coins-eth.json @@ -23,7 +23,7 @@ { "blockchain_link": { "type": "blockbook", - "url": ["https://bsc1.trezor.io"] + "url": ["https://bsc1.trezor.io", "https://bsc2.trezor.io"] }, "chain": "bsc", "chain_id": 56, @@ -80,6 +80,26 @@ "T3T1": "2.6.1" } }, + { + "blockchain_link": { + "type": "blockbook", + "url": ["https://op1.trezor.io", "https://op2.trezor.io"] + }, + "chain": "op", + "chain_id": 10, + "coingecko_id": "optimistic-ethereum", + "is_testnet": false, + "name": "Optimism", + "shortcut": "OP", + "slip44": 1, + "support": { + "T1B1": "1.9.4", + "T2B1": "2.6.1", + "T2T1": "2.3.5", + "T3B1": "2.8.1", + "T3T1": "2.6.1" + } + }, { "blockchain_link": { "type": "blockbook", diff --git a/packages/connect-common/files/firmware/t3w1/releases.json b/packages/connect-common/files/firmware/t3w1/releases.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/packages/connect-common/files/firmware/t3w1/releases.json @@ -0,0 +1 @@ +[] diff --git a/packages/connect-common/package.json b/packages/connect-common/package.json index 2e26e81620f..58d0ed0eeb3 100644 --- a/packages/connect-common/package.json +++ b/packages/connect-common/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/connect-common", - "version": "0.2.2", + "version": "0.2.3-beta.2", "author": "Trezor ", "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/connect-common", "keywords": [ @@ -31,7 +31,6 @@ "typings": "lib/index.d.ts" }, "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest", "build:lib": "yarn g:rimraf lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", "type-check": "yarn g:tsc --build tsconfig.json", diff --git a/packages/connect-common/src/messageChannel/abstract.ts b/packages/connect-common/src/messageChannel/abstract.ts index 79fcb620271..37d3161ff77 100644 --- a/packages/connect-common/src/messageChannel/abstract.ts +++ b/packages/connect-common/src/messageChannel/abstract.ts @@ -3,9 +3,7 @@ * this file is bundled into content script so be careful what you are importing not to bloat the bundle */ -import { Deferred, createDeferred } from '@trezor/utils'; -import { TypedEmitter } from '@trezor/utils'; -import { scheduleAction } from '@trezor/utils'; +import { Deferred, createDeferred, TypedEmitter, scheduleAction } from '@trezor/utils'; // TODO: so logger should be probably moved to connect common, or this file should be moved to connect // import type { Log } from '@trezor/connect/src/utils/debug'; @@ -50,7 +48,9 @@ export abstract class AbstractMessageChannel< protected messageID = 0; public isConnected = false; + abstract connect(): void; + abstract disconnect(): void; private readonly handshakeMaxRetries = 5; @@ -221,7 +221,7 @@ export abstract class AbstractMessageChannel< if (!usePromise) { try { this.sendFn(message); - } catch (err) { + } catch { if (useQueue) { this.messagesQueue.push(message); } @@ -236,7 +236,7 @@ export abstract class AbstractMessageChannel< try { this.sendFn(message); - } catch (err) { + } catch { if (useQueue) { this.messagesQueue.push(message); } diff --git a/packages/connect-common/src/storage.ts b/packages/connect-common/src/storage.ts index 9a1d0b5b91c..471405e3926 100644 --- a/packages/connect-common/src/storage.ts +++ b/packages/connect-common/src/storage.ts @@ -17,7 +17,7 @@ export interface Permission { */ export interface PreferredDevice { label?: string; - path: string; + path: string & { __type: 'DeviceUniquePath' }; state?: string; internalState?: string; internalStateExpiration?: number; @@ -73,7 +73,7 @@ class Storage extends TypedEmitter { const newState = getNewState(getPermanentStorage()); localStorage.setItem(storageName, JSON.stringify(newState)); this.emit('changed', newState); - } catch (err) { + } catch { // memory storage is fallback of the last resort console.warn('long term storage not available'); memoryStorage = getNewState(memoryStorage); @@ -104,7 +104,7 @@ class Storage extends TypedEmitter { try { return getPermanentStorage(); - } catch (err) { + } catch { // memory storage is fallback of the last resort console.warn('long term storage not available'); diff --git a/packages/connect-common/src/test/storage.test.ts b/packages/connect-common/src/test/storage.test.ts index 200316673c4..eab24509a2c 100644 --- a/packages/connect-common/src/test/storage.test.ts +++ b/packages/connect-common/src/test/storage.test.ts @@ -1,4 +1,4 @@ -import { storage } from '..'; +import { storage } from '../storage'; const origin = 'foo.bar'; diff --git a/packages/connect-examples/electron-main-process/eslint.config.mjs b/packages/connect-examples/electron-main-process/eslint.config.mjs new file mode 100644 index 00000000000..5d6bafcceb0 --- /dev/null +++ b/packages/connect-examples/electron-main-process/eslint.config.mjs @@ -0,0 +1,19 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['**/build-electron/*'], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/src/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-examples/electron-main-process/package.json b/packages/connect-examples/electron-main-process/package.json index 7777ce817d5..52cba059a08 100644 --- a/packages/connect-examples/electron-main-process/package.json +++ b/packages/connect-examples/electron-main-process/package.json @@ -55,7 +55,8 @@ "@trezor/connect": "workspace:*" }, "devDependencies": { - "electron": "32.1.2", - "electron-builder": "25.0.5" + "@trezor/eslint": "workspace:*", + "electron": "33.1.0", + "electron-builder": "25.1.8" } } diff --git a/packages/connect-examples/electron-main-process/src/electron.js b/packages/connect-examples/electron-main-process/src/electron.js index 1704aa14749..6c43d11fdc5 100644 --- a/packages/connect-examples/electron-main-process/src/electron.js +++ b/packages/connect-examples/electron-main-process/src/electron.js @@ -1,6 +1,7 @@ const { app, ipcMain, BrowserWindow } = require('electron'); const path = require('path'); const url = require('url'); + const { initTrezorConnect, callTrezorConnect } = require('./trezor-connect-ipc'); let mainWindow; @@ -34,7 +35,7 @@ app.on('ready', init); // Quit when all windows are closed. app.on('window-all-closed', () => { - // On macOS it is common for applications and their menu bar + // On macOS, it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit(); diff --git a/packages/connect-examples/electron-main-process/tsconfig.json b/packages/connect-examples/electron-main-process/tsconfig.json index 2f2029a0f6d..ee581a94a31 100644 --- a/packages/connect-examples/electron-main-process/tsconfig.json +++ b/packages/connect-examples/electron-main-process/tsconfig.json @@ -2,5 +2,8 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./libDev" }, "include": ["."], - "references": [{ "path": "../../connect" }] + "references": [ + { "path": "../../connect" }, + { "path": "../../eslint" } + ] } diff --git a/packages/connect-examples/electron-renderer-with-assets/eslint.config.mjs b/packages/connect-examples/electron-renderer-with-assets/eslint.config.mjs new file mode 100644 index 00000000000..0d4aa8b45d2 --- /dev/null +++ b/packages/connect-examples/electron-renderer-with-assets/eslint.config.mjs @@ -0,0 +1,22 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['**/build-electron/*', '**/build-renderer/*'], + }, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/src/**', + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-examples/electron-renderer-with-assets/package.json b/packages/connect-examples/electron-renderer-with-assets/package.json index c724f26b64f..70de1105576 100644 --- a/packages/connect-examples/electron-renderer-with-assets/package.json +++ b/packages/connect-examples/electron-renderer-with-assets/package.json @@ -57,17 +57,20 @@ }, "devDependencies": { "@trezor/connect": "workspace:*", + "@trezor/connect-web": "workspace:*", + "@trezor/eslint": "workspace:*", "babel-loader": "^9.1.3", "concurrently": "^8.2.2", "copy-webpack-plugin": "^12.0.2", - "electron": "32.1.2", - "electron-builder": "25.0.5", + "electron": "33.1.0", + "electron-builder": "25.1.8", "html-webpack-plugin": "^5.6.0", "terser-webpack-plugin": "^5.3.9", "wait-on": "^7.0.1", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", + "webpack-plugin-serve": "^1.6.0", "worker-loader": "^3.0.8" } } diff --git a/packages/connect-examples/electron-renderer-with-assets/tsconfig.json b/packages/connect-examples/electron-renderer-with-assets/tsconfig.json index 2f2029a0f6d..030ca8643b3 100644 --- a/packages/connect-examples/electron-renderer-with-assets/tsconfig.json +++ b/packages/connect-examples/electron-renderer-with-assets/tsconfig.json @@ -2,5 +2,9 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./libDev" }, "include": ["."], - "references": [{ "path": "../../connect" }] + "references": [ + { "path": "../../connect" }, + { "path": "../../connect-web" }, + { "path": "../../eslint" } + ] } diff --git a/packages/connect-examples/electron-renderer-with-popup/eslint.config.mjs b/packages/connect-examples/electron-renderer-with-popup/eslint.config.mjs new file mode 100644 index 00000000000..604a589cef6 --- /dev/null +++ b/packages/connect-examples/electron-renderer-with-popup/eslint.config.mjs @@ -0,0 +1,21 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['**/build-electron/*', '**/build-renderer/*'], + }, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/src/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-examples/electron-renderer-with-popup/package.json b/packages/connect-examples/electron-renderer-with-popup/package.json index d5f5bed36df..56bf0543130 100644 --- a/packages/connect-examples/electron-renderer-with-popup/package.json +++ b/packages/connect-examples/electron-renderer-with-popup/package.json @@ -52,7 +52,8 @@ } }, "devDependencies": { - "electron": "32.1.2", - "electron-builder": "25.0.5" + "@trezor/eslint": "workspace:*", + "electron": "33.1.0", + "electron-builder": "25.1.8" } } diff --git a/packages/connect-examples/electron-renderer-with-popup/tsconfig.json b/packages/connect-examples/electron-renderer-with-popup/tsconfig.json index de4bd65a31b..a440c33b942 100644 --- a/packages/connect-examples/electron-renderer-with-popup/tsconfig.json +++ b/packages/connect-examples/electron-renderer-with-popup/tsconfig.json @@ -2,5 +2,5 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./libDev" }, "include": ["."], - "references": [] + "references": [{ "path": "../../eslint" }] } diff --git a/packages/connect-examples/deeplink-expo/.gitignore b/packages/connect-examples/mobile-expo/.gitignore similarity index 100% rename from packages/connect-examples/deeplink-expo/.gitignore rename to packages/connect-examples/mobile-expo/.gitignore diff --git a/packages/connect-examples/deeplink-expo/App.tsx b/packages/connect-examples/mobile-expo/App.tsx similarity index 91% rename from packages/connect-examples/deeplink-expo/App.tsx rename to packages/connect-examples/mobile-expo/App.tsx index ba4d3d5811e..0c04fee7cae 100644 --- a/packages/connect-examples/deeplink-expo/App.tsx +++ b/packages/connect-examples/mobile-expo/App.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { StatusBar } from 'expo-status-bar'; import * as Linking from 'expo-linking'; -import TrezorConnect from '@trezor/connect-deeplink'; +import TrezorConnect from '@trezor/connect-mobile'; const styles = StyleSheet.create({ container: { @@ -22,6 +22,7 @@ const styles = StyleSheet.create({ export const App = () => { const [errorData, setErrorData] = useState(null); const [successData, setSuccessData] = useState(null); + const isEmulator = true; const initialize = () => { TrezorConnect.init({ @@ -29,13 +30,14 @@ export const App = () => { email: 'developer@xyz.com', appUrl: 'http://your.application.com', }, + // for local development purposes. for production, leave it undefined to use the default value. + connectSrc: isEmulator ? 'trezorsuitelite://connect' : undefined, deeplinkOpen: url => { // eslint-disable-next-line no-console console.log('deeplinkOpen', url); Linking.openURL(url); }, deeplinkCallbackUrl: Linking.createURL('/connect'), - // deeplinkUrl: 'https://dev.suite.sldev.cz/connect/develop/deeplink/', }); }; diff --git a/packages/connect-examples/deeplink-expo/README.md b/packages/connect-examples/mobile-expo/README.md similarity index 75% rename from packages/connect-examples/deeplink-expo/README.md rename to packages/connect-examples/mobile-expo/README.md index 045a7fa4901..9feaaf60873 100644 --- a/packages/connect-examples/deeplink-expo/README.md +++ b/packages/connect-examples/mobile-expo/README.md @@ -1,6 +1,6 @@ -## Deep link example with Expo +## Mobile example with Expo -`@trezor/connect-deeplink` running with a React Native + Expo app +`@trezor/connect-mobile` running with a React Native + Expo app ### Run it diff --git a/packages/connect-examples/deeplink-expo/app.json b/packages/connect-examples/mobile-expo/app.json similarity index 72% rename from packages/connect-examples/deeplink-expo/app.json rename to packages/connect-examples/mobile-expo/app.json index be288d3acbe..cf0cfbc7048 100644 --- a/packages/connect-examples/deeplink-expo/app.json +++ b/packages/connect-examples/mobile-expo/app.json @@ -1,7 +1,7 @@ { "expo": { - "name": "connect-deeplink-example", - "slug": "connect-deeplink-example", + "name": "connect-mobile-example", + "slug": "connect-mobile-example", "version": "1.0.0", "orientation": "portrait", "userInterfaceStyle": "light", diff --git a/packages/connect-examples/deeplink-expo/babel.config.js b/packages/connect-examples/mobile-expo/babel.config.js similarity index 100% rename from packages/connect-examples/deeplink-expo/babel.config.js rename to packages/connect-examples/mobile-expo/babel.config.js diff --git a/packages/connect-examples/deeplink-expo/index.js b/packages/connect-examples/mobile-expo/index.js similarity index 100% rename from packages/connect-examples/deeplink-expo/index.js rename to packages/connect-examples/mobile-expo/index.js diff --git a/packages/connect-examples/deeplink-expo/package.json b/packages/connect-examples/mobile-expo/package.json similarity index 85% rename from packages/connect-examples/deeplink-expo/package.json rename to packages/connect-examples/mobile-expo/package.json index 6b1da406517..8d337b14eb4 100644 --- a/packages/connect-examples/deeplink-expo/package.json +++ b/packages/connect-examples/mobile-expo/package.json @@ -1,5 +1,5 @@ { - "name": "connect-deeplink-expo-example", + "name": "connect-mobile-example", "version": "1.0.0", "main": "index.js", "scripts": { @@ -9,7 +9,7 @@ "web": "expo start --web" }, "dependencies": { - "@trezor/connect-deeplink": "workspace:*", + "@trezor/connect-mobile": "workspace:*", "expo": "51.0.31", "expo-linking": "6.3.1", "expo-status-bar": "1.12.1", diff --git a/packages/connect-examples/deeplink-expo/tsconfig.json b/packages/connect-examples/mobile-expo/tsconfig.json similarity index 73% rename from packages/connect-examples/deeplink-expo/tsconfig.json rename to packages/connect-examples/mobile-expo/tsconfig.json index 969501df44d..a3f46dccb10 100644 --- a/packages/connect-examples/deeplink-expo/tsconfig.json +++ b/packages/connect-examples/mobile-expo/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./libDev" }, "references": [ - { "path": "../../connect-deeplink" } + { "path": "../../connect-mobile" } ] } diff --git a/packages/connect-examples/update-webextensions-sw.js b/packages/connect-examples/update-webextensions-sw.js index abd4c5568b9..6b8e412522b 100644 --- a/packages/connect-examples/update-webextensions-sw.js +++ b/packages/connect-examples/update-webextensions-sw.js @@ -66,19 +66,22 @@ rootPaths.forEach(dir => { const isJustCopied = ['.png'].some(ext => p.endsWith(ext)); if (isJustCopied) { fs.copyFileSync(path.join(rootPath, 'src', p), path.join(rootPath, buildFolder, p)); + return; } fs.readFile(path.join(rootPath, 'src', p), 'utf-8', (err, contents) => { if (err) { console.log(err); + return; } const replaced = contents.replace(DEFAULT_SRC, trezorConnectSrc); - fs.writeFile(path.join(rootPath, buildFolder, p), replaced, 'utf-8', err => { - if (err) { - console.log(err); + fs.writeFile(path.join(rootPath, buildFolder, p), replaced, 'utf-8', err2 => { + if (err2) { + console.log(err2); + return; } }); diff --git a/packages/connect-examples/update-webextensions.js b/packages/connect-examples/update-webextensions.js index e3a87302555..84a8dfdb372 100644 --- a/packages/connect-examples/update-webextensions.js +++ b/packages/connect-examples/update-webextensions.js @@ -41,7 +41,7 @@ rootPaths.forEach(dir => { const usbPermissionsScriptPath = path.join(vendorPath, 'trezor-usb-permissions.js'); const usbPermissionsHtmlPath = path.join(rootPath, 'trezor-usb-permissions.html'); const contentScriptPath = path.join(vendorPath, 'trezor-content-script.js'); - const backgroundScriptPath = path.join(rootPath, 'background.js'); + // const backgroundScriptPath = path.join(rootPath, 'background.js'); fs.rmSync(buildPath, { recursive: true, force: true }); if (!fs.existsSync(buildPath)) { @@ -52,9 +52,9 @@ rootPaths.forEach(dir => { } [inlineScriptPath, usbPermissionsScriptPath, usbPermissionsHtmlPath, contentScriptPath].forEach( - path => { - if (fs.existsSync(path)) { - fs.rmSync(path); + path2 => { + if (fs.existsSync(path2)) { + fs.rmSync(path2); } }, ); @@ -105,19 +105,22 @@ rootPaths.forEach(dir => { const isJustCopied = ['.png'].some(ext => p.endsWith(ext)); if (isJustCopied) { fs.copyFileSync(path.join(rootPath, 'src', p), path.join(rootPath, buildFolder, p)); + return; } fs.readFile(path.join(rootPath, 'src', p), 'utf-8', (err, contents) => { if (err) { console.log(err); + return; } const replaced = contents.replace(DEFAULT_SRC, trezorConnectSrc); - fs.writeFile(path.join(rootPath, buildFolder, p), replaced, 'utf-8', err => { - if (err) { - console.log(err); + fs.writeFile(path.join(rootPath, buildFolder, p), replaced, 'utf-8', err2 => { + if (err2) { + console.log(err2); + return; } }); diff --git a/packages/connect-examples/webextension-mv2/.eslintrc.js b/packages/connect-examples/webextension-mv2/.eslintrc.js deleted file mode 100644 index f9cfed3318c..00000000000 --- a/packages/connect-examples/webextension-mv2/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - rules: { - 'no-underscore-dangle': 'off', // underscore is used - camelcase: 'off', // camelcase is used - }, - ignorePatterns: ['**/vendor*/'], -}; diff --git a/packages/connect-examples/webextension-mv2/eslint.config.mjs b/packages/connect-examples/webextension-mv2/eslint.config.mjs new file mode 100644 index 00000000000..3c0f12191bb --- /dev/null +++ b/packages/connect-examples/webextension-mv2/eslint.config.mjs @@ -0,0 +1,15 @@ +// Todo: figure out the better way, if this would be package, we would use "@trezor/eslint": "workspace:*" in package.json +import parentConfig from '../../../eslint.config.mjs'; + +export default [ + ...parentConfig, + { + ignores: ['**/vendor*/'], + }, + { + rules: { + 'no-underscore-dangle': 'off', + camelcase: 'off', + }, + }, +]; diff --git a/packages/connect-examples/webextension-mv2/src/background.js b/packages/connect-examples/webextension-mv2/src/background.js index 46111c4cff9..802ecf01635 100644 --- a/packages/connect-examples/webextension-mv2/src/background.js +++ b/packages/connect-examples/webextension-mv2/src/background.js @@ -1,8 +1,8 @@ /** -TrezorConnect is loaded in background script but it is triggered from content script. -- call for TrezorConnect action -- show a notification with response -*/ + TrezorConnect is loaded in background script but it is triggered from content script. + - call for TrezorConnect action + - show a notification with response + */ const DEFAULT_SRC = 'https://connect.trezor.io/9/'; @@ -25,21 +25,23 @@ function getAddress() { async function sendMessageToContentScript(tabID, type, data = null) { try { const response = await chrome.tabs.sendMessage(tabID, { type, data }); + return response; - } catch (error) { + } catch { return null; } } chrome.runtime.onMessage.addListener((message, sender) => { const { tab } = sender; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { type, data } = message; if (type === 'getAddress') { getAddress().then(response => { - const message = response.success + const message2 = response.success ? `BTC Address: ${response.payload.address}` : `Error: ${response.payload.error}`; - sendMessageToContentScript(tab.id, 'getAddress', message); + sendMessageToContentScript(tab.id, 'getAddress', message2); }); } else if (type === 'pageLoaded') { loadTrezorConnect().then(() => { diff --git a/packages/connect-examples/webextension-mv2/src/connect-manager.js b/packages/connect-examples/webextension-mv2/src/connect-manager.js index f82c34681b2..270b0886d0b 100644 --- a/packages/connect-examples/webextension-mv2/src/connect-manager.js +++ b/packages/connect-examples/webextension-mv2/src/connect-manager.js @@ -1,9 +1,11 @@ async function sendMessageToBackground(type, data = null) { try { const response = await chrome.runtime.sendMessage({ type, data }); + return response; } catch (error) { console.error('sendMessageToBackground error: ', error); + return null; } } diff --git a/packages/connect-examples/webextension-mv3-sw-ts/eslint.config.mjs b/packages/connect-examples/webextension-mv3-sw-ts/eslint.config.mjs new file mode 100644 index 00000000000..52f1c6716cf --- /dev/null +++ b/packages/connect-examples/webextension-mv3-sw-ts/eslint.config.mjs @@ -0,0 +1,21 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-console': 'off', + + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/src/**', // Examples are just for development + '**/webpack.config.js', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-examples/webextension-mv3-sw-ts/package.json b/packages/connect-examples/webextension-mv3-sw-ts/package.json index e12c5599f36..659680f4ec7 100644 --- a/packages/connect-examples/webextension-mv3-sw-ts/package.json +++ b/packages/connect-examples/webextension-mv3-sw-ts/package.json @@ -7,7 +7,6 @@ "main": "src/index", "scripts": { "depcheck": "yarn g:depcheck", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "type-check": "yarn g:tsc --build", "build": "webpack --mode production" }, @@ -15,8 +14,9 @@ "@trezor/connect-webextension": "workspace:^" }, "devDependencies": { + "@trezor/eslint": "workspace:^", "copy-webpack-plugin": "^12.0.2", "html-webpack-plugin": "^5.6.0", - "webpack": "^5.94.0" + "webpack": "^5.96.1" } } diff --git a/packages/connect-examples/webextension-mv3-sw-ts/src/service-worker.ts b/packages/connect-examples/webextension-mv3-sw-ts/src/service-worker.ts index f7f8d8a1f03..bd79a30c011 100644 --- a/packages/connect-examples/webextension-mv3-sw-ts/src/service-worker.ts +++ b/packages/connect-examples/webextension-mv3-sw-ts/src/service-worker.ts @@ -32,12 +32,14 @@ chrome.runtime.onInstalled.addListener((details: chrome.runtime.InstalledDetails }).then(res => { sendResponse(res); // Send the response back to the sender }); + // Return true to indicate you want to send a response asynchronously return true; } else if (message.action === 'getFeatures') { TrezorConnect.getFeatures().then(res => { sendResponse(res); // Send the response back to the sender }); + // Return true to indicate you want to send a response asynchronously return true; } diff --git a/packages/connect-examples/webextension-mv3-sw-ts/tsconfig.json b/packages/connect-examples/webextension-mv3-sw-ts/tsconfig.json index 9296fa9f98a..54a73de3094 100644 --- a/packages/connect-examples/webextension-mv3-sw-ts/tsconfig.json +++ b/packages/connect-examples/webextension-mv3-sw-ts/tsconfig.json @@ -5,6 +5,7 @@ "types": ["chrome"] }, "references": [ - { "path": "../../connect-webextension" } + { "path": "../../connect-webextension" }, + { "path": "../../eslint" } ] } diff --git a/packages/connect-examples/webextension-mv3-sw/src/serviceWorker.js b/packages/connect-examples/webextension-mv3-sw/src/serviceWorker.js index d7c332f1686..643e7209024 100644 --- a/packages/connect-examples/webextension-mv3-sw/src/serviceWorker.js +++ b/packages/connect-examples/webextension-mv3-sw/src/serviceWorker.js @@ -30,12 +30,14 @@ chrome.runtime.onInstalled.addListener(details => { }).then(res => { sendResponse(res); // Send the response back to the sender }); + // Return true to indicate you want to send a response asynchronously return true; } else if (message.action === 'getFeatures') { TrezorConnect.getFeatures().then(res => { sendResponse(res); // Send the response back to the sender }); + // Return true to indicate you want to send a response asynchronously return true; } diff --git a/packages/connect-explorer-theme/package.json b/packages/connect-explorer-theme/package.json index f9bf61aaa97..5540125c7c6 100644 --- a/packages/connect-explorer-theme/package.json +++ b/packages/connect-explorer-theme/package.json @@ -32,7 +32,7 @@ "escape-string-regexp": "^5.0.0", "flexsearch": "^0.7.31", "focus-visible": "^5.2.1", - "git-url-parse": "^13.1.0", + "git-url-parse": "^15.0.0", "intersection-observer": "^0.12.2", "match-sorter": "^6.3.1", "next-seo": "^6.0.0", @@ -46,7 +46,7 @@ "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", "@testing-library/react": "^14.0.0", "@types/flexsearch": "^0.7.3", - "@types/git-url-parse": "^9.0.1", + "@types/git-url-parse": "^9.0.3", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.7", "concurrently": "^8.0.0", diff --git a/packages/connect-explorer-theme/src/components/anchor.tsx b/packages/connect-explorer-theme/src/components/anchor.tsx index e1e1e2b6d64..4f7f35d6acd 100644 --- a/packages/connect-explorer-theme/src/components/anchor.tsx +++ b/packages/connect-explorer-theme/src/components/anchor.tsx @@ -1,4 +1,3 @@ -// eslint-disable-next-line no-restricted-imports -- only in this file we determine either we include as child of based of `newNextLinkBehavior` value import type { ComponentProps, ReactElement } from 'react'; import { forwardRef } from 'react'; diff --git a/packages/connect-explorer-theme/src/components/search.tsx b/packages/connect-explorer-theme/src/components/search.tsx index dfd49d88fb0..56001d35793 100644 --- a/packages/connect-explorer-theme/src/components/search.tsx +++ b/packages/connect-explorer-theme/src/components/search.tsx @@ -155,6 +155,7 @@ export function Search({ leaveFrom="nx-opacity-100" leaveTo="nx-opacity-0" > + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} {renderList && ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
setShow(false)} /> )} diff --git a/packages/connect-explorer-theme/src/components/sidebar.tsx b/packages/connect-explorer-theme/src/components/sidebar.tsx index 7fd51fac761..aaa16291c50 100644 --- a/packages/connect-explorer-theme/src/components/sidebar.tsx +++ b/packages/connect-explorer-theme/src/components/sidebar.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ import type { ReactElement } from 'react'; import { createContext, useEffect, useMemo, useRef, useState } from 'react'; @@ -110,6 +109,7 @@ export function Sidebar({ {includePlaceholder && asPopover ? (
) : null} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
; }, h1: props => ( + // eslint-disable-next-line jsx-a11y/heading-has-content

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/connect-explorer/src/actions/methodActions.ts b/packages/connect-explorer/src/actions/methodActions.ts index ac777b0cec4..392241bfafd 100644 --- a/packages/connect-explorer/src/actions/methodActions.ts +++ b/packages/connect-explorer/src/actions/methodActions.ts @@ -1,7 +1,8 @@ import { TSchema } from '@sinclair/typebox'; import JSON5 from 'json5'; -import TrezorConnect from '@trezor/connect-web'; +import TrezorConnect, { TrezorConnect as TrezorConnectType } from '@trezor/connect-web'; +import TrezorConnectMobile from '@trezor/connect-mobile'; import { getDeepValue } from '@trezor/schema-utils/src/utils'; import { GetState, Dispatch, Field } from '../types'; @@ -19,7 +20,7 @@ export const SET_METHOD_PROCESSING = 'method_set_processing'; export type MethodAction = | { type: typeof SET_METHOD; methodConfig: any } - | { type: typeof SET_SCHEMA; method: keyof typeof TrezorConnect; schema: TSchema } + | { type: typeof SET_SCHEMA; method: keyof TrezorConnectType; schema: TSchema } | { type: typeof FIELD_CHANGE; field: Field; value: any } | { type: typeof FIELD_DATA_CHANGE; field: Field; data: any } | { type: typeof ADD_BATCH; field: Field; item: any } @@ -80,10 +81,12 @@ export const onSetManualMode = (manualMode: boolean) => ({ }); export const onSubmit = () => async (dispatch: Dispatch, getState: GetState) => { - const { method } = getState(); + const { method, connect } = getState(); if (!method?.name) throw new Error('method name not specified'); dispatch({ type: SET_METHOD_PROCESSING, payload: true }); - const connectMethod = TrezorConnect[method.name]; + const trezorConnectImpl = + connect.options?.coreMode === 'deeplink' ? TrezorConnectMobile : TrezorConnect; + const connectMethod = trezorConnectImpl[method.name]; if (typeof connectMethod !== 'function') { dispatch( onResponse({ diff --git a/packages/connect-explorer/src/actions/trezorConnectActions.ts b/packages/connect-explorer/src/actions/trezorConnectActions.ts index d56caf9a952..c8ba3c357cd 100644 --- a/packages/connect-explorer/src/actions/trezorConnectActions.ts +++ b/packages/connect-explorer/src/actions/trezorConnectActions.ts @@ -4,6 +4,7 @@ import TrezorConnect, { TRANSPORT_EVENT, WEBEXTENSION, } from '@trezor/connect-web'; +import TrezorConnectMobile from '@trezor/connect-mobile'; import { TrezorConnectDevice, Dispatch, Field, GetState } from '../types'; @@ -30,7 +31,7 @@ export function onSelectDevice(path: string) { }; } -export const onConnectOptionChange = (option: string, value: any) => ({ +export const onConnectOptionChange = (option: Field, value: any) => ({ type: ACTIONS.ON_CHANGE_CONNECT_OPTION, payload: { option, @@ -122,7 +123,7 @@ export const init = try { // Verify if valid by loading core.js, if this file exists we can assume the connectSrc is valid await testLoadingScript(options.connectSrc + 'js/core.js'); - } catch (err) { + } catch { dispatch({ type: ACTIONS.ON_INIT_ERROR, payload: `Invalid connectSrc: ${options.connectSrc}`, @@ -144,6 +145,17 @@ export const init = const urlParams = new URLSearchParams(window.location.search); const coreMode = (urlParams.get('core-mode') as ConnectOptions['coreMode']) || 'auto'; + // localhost + dev server + let _sessionsBackgroundUrl = process.env.CONNECT_EXPLORER_FULL_URL + ? `${process.env.CONNECT_EXPLORER_FULL_URL}/workers/sessions-background-sharedworker.js` + : window.origin + '/workers/sessions-background-sharedworker.js'; + + // connect.trezor.io/9 || connect.trezor.io/9.x.y + if (window.location.origin.endsWith('connect.trezor.io')) { + _sessionsBackgroundUrl = + 'https://connect.trezor.io/9/workers/sessions-background-sharedworker.js'; + } + const connectOptions = { coreMode, transportReconnect: true, @@ -156,11 +168,30 @@ export const init = }, trustedHost: false, connectSrc: window.__TREZOR_CONNECT_SRC, + _sessionsBackgroundUrl, ...options, }; try { - await TrezorConnect.init(connectOptions); + if (connectOptions.coreMode === 'deeplink') { + await TrezorConnectMobile.init({ + ...connectOptions, + deeplinkOpen(url) { + window.open(url, '_blank'); + }, + deeplinkCallbackUrl: + (process.env.CONNECT_EXPLORER_FULL_URL || window.location.origin) + + '/callback', + }); + const bc = new BroadcastChannel('trezor_connect_callback'); + bc.onmessage = e => { + if (e.data.type === 'popup_callback') { + TrezorConnectMobile.handleDeeplink(e.data.url); + } + }; + } else { + await TrezorConnect.init(connectOptions); + } } catch (err) { dispatch({ type: ACTIONS.ON_INIT_ERROR, payload: err.message }); @@ -174,6 +205,7 @@ export const onSubmitInit = () => async (dispatch: Dispatch, getState: GetState) const { connect } = getState(); // Disposing TrezorConnect to init it again. await TrezorConnect.dispose(); + await TrezorConnectMobile.dispose(); return dispatch(init(connect.options)); }; diff --git a/packages/connect-explorer/src/components/BetaOnly.tsx b/packages/connect-explorer/src/components/BetaOnly.tsx new file mode 100644 index 00000000000..18995f7d04b --- /dev/null +++ b/packages/connect-explorer/src/components/BetaOnly.tsx @@ -0,0 +1,11 @@ +export const isBetaOnly = !process.env.CONNECT_EXPLORER_FULL_URL?.startsWith( + 'https://connect.trezor.io/9/', +); + +export const BetaOnly = (props: React.PropsWithChildren) => { + if (isBetaOnly) { + return <>{props.children}; + } + + return null; +}; diff --git a/packages/connect-explorer/src/components/CommonParamsLink.tsx b/packages/connect-explorer/src/components/CommonParamsLink.tsx index 74d224ebacd..1affce866bf 100644 --- a/packages/connect-explorer/src/components/CommonParamsLink.tsx +++ b/packages/connect-explorer/src/components/CommonParamsLink.tsx @@ -7,7 +7,6 @@ const Floating = styled.div` margin-top: -2rem; `; -// eslint-disable-next-line local-rules/no-override-ds-component const StyledLink = styled(Link)` color: ${({ theme }) => theme.textPrimaryDefault}; text-decoration: underline; diff --git a/packages/connect-explorer/src/components/GuideIndex.tsx b/packages/connect-explorer/src/components/GuideIndex.tsx index f369df52b82..f7e01a60fcc 100644 --- a/packages/connect-explorer/src/components/GuideIndex.tsx +++ b/packages/connect-explorer/src/components/GuideIndex.tsx @@ -1,11 +1,13 @@ +import { ReactNode } from 'react'; + import { getPagesUnderRoute } from 'nextra/context'; import Link from 'next/link'; -import { ReactNode } from 'react'; +import styled from 'styled-components'; import { Card as TrezorCard, H3, Paragraph, Button } from '@trezor/components'; -import styled from 'styled-components'; import { spacingsPx } from '@trezor/theme'; +// eslint-disable-next-line local-rules/no-override-ds-component const SectionCard = styled(TrezorCard)` margin-bottom: ${spacingsPx.xl}; `; diff --git a/packages/connect-explorer/src/components/Settings.tsx b/packages/connect-explorer/src/components/Settings.tsx new file mode 100644 index 00000000000..af00274eb73 --- /dev/null +++ b/packages/connect-explorer/src/components/Settings.tsx @@ -0,0 +1,94 @@ +import styled from 'styled-components'; + +import { Button } from '@trezor/components'; + +import * as trezorConnectActions from '../actions/trezorConnectActions'; +import { useSelector, useActions } from '../hooks'; +import { getField } from '../components/Method'; +import { isBetaOnly } from '../components/BetaOnly'; + +export const SettingsContent = styled.section` + flex: 1; + padding: 10px 20px; + display: flex; + flex-direction: column; +`; + +export const ConfirmationMessage = styled.div` + margin-top: 20px; + color: green; + font-weight: bold; +`; + +export const ErrorMessage = styled(ConfirmationMessage)` + color: red; +`; + +export const Settings = () => { + const connectOptions = useSelector(state => ({ + trustedHost: state.connect?.options?.trustedHost, + connectSrc: state.connect?.options?.connectSrc, + coreMode: state?.connect?.options?.coreMode, + })); + + const initError = useSelector(state => state.connect?.initError); + const isInitSuccess = useSelector(state => state.connect?.isInitSuccess || false); + const isHandshakeConfirmed = useSelector(state => state.connect?.isHandshakeConfirmed || false); + const actions = useActions({ + onSubmitInit: trezorConnectActions.onSubmitInit, + onFieldChange: trezorConnectActions.onConnectOptionChange, + }); + + const submitButton = 'Init Connect'; + const fields = [ + { + name: 'trustedHost', + type: 'checkbox' as const, + key: 'trustedHost', + value: connectOptions?.trustedHost || false, + }, + { + name: 'coreMode', + type: 'select' as const, + key: 'coreMode', + value: connectOptions?.coreMode || 'auto', + data: [ + { value: 'auto', label: 'Auto' }, + { value: 'iframe', label: 'Iframe' }, + { value: 'popup', label: 'Popup' }, + ...(isBetaOnly ? [{ value: 'deeplink', label: 'Deeplink (mobile)' }] : []), + ], + }, + { + name: 'connectSrc', + type: 'input' as const, + key: 'connectSrc', + value: connectOptions?.connectSrc || '', + }, + ]; + + return ( + + {/* @ts-expect-error: actions is simplified for this case */} + {fields.map(field => getField(field, { actions }))} + + {initError && ( + + Init error: {initError} + + )} + {isInitSuccess && ( + + Init success! + + )} + {isHandshakeConfirmed && ( + + Handshake confirmed! + + )} + + ); +}; diff --git a/packages/connect-explorer/src/components/ZoomableIllustration.tsx b/packages/connect-explorer/src/components/ZoomableIllustration.tsx index 8d33328c94b..0012c5aac65 100644 --- a/packages/connect-explorer/src/components/ZoomableIllustration.tsx +++ b/packages/connect-explorer/src/components/ZoomableIllustration.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Zoom from 'react-medium-image-zoom'; -import { useRouter } from 'next/router'; +import { useRouter } from 'next/router'; import { createGlobalStyle, useTheme, styled } from 'styled-components'; import 'react-medium-image-zoom/dist/styles.css'; diff --git a/packages/connect-explorer/src/components/fields/ArrayWrapper.tsx b/packages/connect-explorer/src/components/fields/ArrayWrapper.tsx index 0d22e70f520..852e67a7317 100644 --- a/packages/connect-explorer/src/components/fields/ArrayWrapper.tsx +++ b/packages/connect-explorer/src/components/fields/ArrayWrapper.tsx @@ -2,9 +2,10 @@ import { ReactNode } from 'react'; import styled from 'styled-components'; -import type { FieldWithBundle } from '../../types'; import { Icon } from '@trezor/components'; +import type { FieldWithBundle } from '../../types'; + interface AddButtonProps { field: FieldWithBundle; onAdd: () => void; diff --git a/packages/connect-explorer/src/components/icons/IconMobile.tsx b/packages/connect-explorer/src/components/icons/IconMobile.tsx new file mode 100644 index 00000000000..e5e986c1bcf --- /dev/null +++ b/packages/connect-explorer/src/components/icons/IconMobile.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +function IconMobile() { + return ( + + + + ); +} + +export default IconMobile; diff --git a/packages/connect-explorer/src/data/methods/management/index.ts b/packages/connect-explorer/src/data/methods/management/index.ts index 2ebcbe68848..880e838b08a 100644 --- a/packages/connect-explorer/src/data/methods/management/index.ts +++ b/packages/connect-explorer/src/data/methods/management/index.ts @@ -1,5 +1,6 @@ import getFeatures from './getFeatures'; import resetDevice from './resetDevice'; +import loadDevice from './loadDevice'; import wipeDevice from './wipeDevice'; import applyFlags from './applyFlags'; import applySettings from './applySettings'; @@ -9,10 +10,10 @@ import changeLanguage from './changeLanguage'; import changeWipeCode from './changeWipeCode'; import recoverDevice from './recoverDevice'; import firmwareUpdate from './firmwareUpdate'; -import rebootToBootloader from './rebootToBootloader'; export default [ ...getFeatures, + ...loadDevice, ...resetDevice, ...wipeDevice, ...applyFlags, @@ -23,5 +24,4 @@ export default [ ...changeWipeCode, ...recoverDevice, ...firmwareUpdate, - ...rebootToBootloader, ]; diff --git a/packages/connect-explorer/src/data/methods/management/loadDevice.ts b/packages/connect-explorer/src/data/methods/management/loadDevice.ts new file mode 100644 index 00000000000..77fbfd1e227 --- /dev/null +++ b/packages/connect-explorer/src/data/methods/management/loadDevice.ts @@ -0,0 +1,63 @@ +const name = 'loadDevice'; +const docs = 'methods/loadDevice.md'; + +export default [ + { + url: '/method/loadDevice', + name, + docs, + submitButton: 'Load device', + fields: [ + { + name: 'mnemonics', + label: 'Mnemonics', + type: 'input', + optional: false, + value: ['all all all all all all all all all all all all'], + }, + { + name: 'label', + label: 'Label', + type: 'input', + optional: true, + value: 'Meow trezor', + }, + { + name: 'pin', + label: 'Pin', + type: 'input', + optional: true, + }, + { + name: 'passphrase_protection', + label: 'Passphrase protection', + type: 'checkbox', + optional: true, + }, + { + name: 'skip_checksum', + label: 'Skip checksum', + type: 'checkbox', + optional: true, + }, + { + name: 'u2f_counter', + label: 'U2F counter', + type: 'number', + optional: true, + }, + { + name: 'no_backup', + label: 'No backup', + type: 'checkbox', + optional: true, + }, + { + name: 'needs_backup', + label: 'Needs backup', + type: 'checkbox', + optional: true, + }, + ], + }, +]; diff --git a/packages/connect-explorer/src/data/methods/management/rebootToBootloader.ts b/packages/connect-explorer/src/data/methods/management/rebootToBootloader.ts deleted file mode 100644 index 3a08fcbdfed..00000000000 --- a/packages/connect-explorer/src/data/methods/management/rebootToBootloader.ts +++ /dev/null @@ -1,23 +0,0 @@ -const name = 'rebootToBootloader'; - -export default [ - { - url: `/method/rebootToBootloader`, - name, - submitButton: 'Reboot to bootloader', - fields: [ - { - name: 'boot_command', - label: 'boot_command', - type: 'select', - optional: false, - value: 0, - placeholder: 'Select', - data: [ - { value: 0, label: 'STOP_AND_WAIT' }, - { value: 1, label: 'INSTALL_UPGRADE' }, - ], - }, - ], - }, -]; diff --git a/packages/connect-explorer/src/pages/_app.tsx b/packages/connect-explorer/src/pages/_app.tsx index 9176f1cd475..732673bbaae 100644 --- a/packages/connect-explorer/src/pages/_app.tsx +++ b/packages/connect-explorer/src/pages/_app.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useState } from 'react'; import { Provider } from 'react-redux'; -import { ThemeProvider as NextThemeProvider } from 'next-themes'; +import { ThemeProvider as NextThemeProvider, useTheme } from 'next-themes'; import { ThemeProvider } from 'styled-components'; import type { AppProps } from 'next/app'; -import { useTheme } from 'next-themes'; import Head from 'next/head'; import { useRouter } from 'next/router'; diff --git a/packages/connect-explorer/src/pages/_meta.json b/packages/connect-explorer/src/pages/_meta.json index 65a2f1571e4..bfddddd1a63 100644 --- a/packages/connect-explorer/src/pages/_meta.json +++ b/packages/connect-explorer/src/pages/_meta.json @@ -9,5 +9,9 @@ "test": { "title": "Test", "display": "hidden" + }, + "callback": { + "title": "Callback", + "display": "hidden" } } diff --git a/packages/connect-explorer/src/pages/callback.mdx b/packages/connect-explorer/src/pages/callback.mdx new file mode 100644 index 00000000000..fb77584a588 --- /dev/null +++ b/packages/connect-explorer/src/pages/callback.mdx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; + +import { Button } from '@trezor/components'; + +export const CallbackHandler = () => { + useEffect(() => { + const bc = new BroadcastChannel('trezor_connect_callback'); + bc.postMessage({ type: 'popup_callback', url: window.location.href }); + }, []); + + return
; + +}; + +# Trezor Connect Callback + +Callback received. You can close this window now. + + + + diff --git a/packages/connect-explorer/src/pages/changelog.mdx b/packages/connect-explorer/src/pages/changelog.mdx index 4a98128a31d..18de55d9b1e 100644 --- a/packages/connect-explorer/src/pages/changelog.mdx +++ b/packages/connect-explorer/src/pages/changelog.mdx @@ -2,14 +2,15 @@ icon: log --- +import Markdown from 'react-markdown'; + import rehypeSectionize from '@hbsnow/rehype-sectionize'; import remarkGfm from 'remark-gfm'; import remarkGemoji from 'remark-gemoji'; import useSWR from 'swr'; -import Markdown from 'react-markdown'; -import { useMDXComponents } from '@trezor/connect-explorer-theme'; import { Callout } from 'nextra/components'; +import { useMDXComponents } from '@trezor/connect-explorer-theme'; import { spacings } from '@trezor/theme'; import { CollapsibleBox } from '@trezor/components'; import { isNewer } from '@trezor/utils/src/versionUtils'; @@ -30,8 +31,8 @@ export const loadChangelog = branch => { content = content.replace(/^(#+)/gm, '$1#'); // Add links to commit hashes content = content.replace( - /([a-f0-9]{7,10})([\),])/g, - `[\$1](https://github.com/trezor/trezor-suite/commit/\$1)$2`, + /([a-f0-9]{7,10})([),])/g, + `[$1](https://github.com/trezor/trezor-suite/commit/$1)$2`, ); const versionOverview = `## Version overview\n\n` + content.substring(0, content.indexOf('##')); const changelog = content.substring(content.indexOf('##')); diff --git a/packages/connect-explorer/src/pages/details/deeplinking.mdx b/packages/connect-explorer/src/pages/details/deeplinking.mdx new file mode 100644 index 00000000000..b4c67277cbe --- /dev/null +++ b/packages/connect-explorer/src/pages/details/deeplinking.mdx @@ -0,0 +1,72 @@ +import { Callout } from 'nextra/components'; + +import { BetaOnly } from '../../components/BetaOnly'; + +# Deep linking specification + + + +To support connecting to Trezor devices from 3rd party apps on mobile devices, Trezor Suite Lite provides a communication interface via deep linking. + + + **This feature is still in beta and is subject to change. It's currently available only in + development builds of Trezor Suite Lite.** + + +## Base URL + +The base URL for deep linking is different depending on the environment. + +| Environment | Base URL | +| ----------- | ------------------------------------------------------ | +| Production | currently unavailable | +| Development | `https://dev.suite.sldev.cz/connect/develop/deeplink/` | +| Local | `trezorsuitelite://connect` | + +## Query parameters + +The method call is specified using the query parameters. + +| Parameter | Type | Required | Description | +| ---------- | ----------- | -------- | -------------------------------------------------------- | +| `method` | string | yes | The name of the Connect method to call (eg. getAddress). | +| `params` | JSON object | yes | The parameters for the method call encoded as JSON. | +| `callback` | string | yes | The URL to redirect to after the method call is made. | + +## Callback + +To receive the result of the method call, the app must specify a callback URL. The callback URL is called with the result of the method call. + +The following query parameters are passed in the callback URL: + +| Parameter | Type | Description | +| ---------- | ----------- | --------------------------------------------------------------------------- | +| `id` | integer | ID of the call | +| `response` | JSON object | Result of the method call, equivalent to the object returned by the method. | + +## Example + +Let's imagine we want to convert the following call to a deep link: + +``` +const address = await TrezorConnect.getAddress({ + coin: 'btc', + path: "m/44'/0'/0'/0/0", +}); +``` + +The parameters would be: + +- **method**: `getAddress` +- **params**: `{"coin":"btc","path":"m/44'/0'/0'/0/0"}` +- **callback**: `https://httpbin.org/get` (as an example) + +The encoded deep link URL would then be: + +``` +trezorsuitelite://connect?method=getAddress¶ms=%7B%22coin%22%3A%22btc%22%2C%22path%22%3A%22m%2F44%27%2F0%27%2F0%27%2F0%2F0%22%7D&callback=https%3A%2F%2Fhttpbin.org%2Fget +``` + +When the user returns to the app, the callback URL is called with the result of the method call. + + diff --git a/packages/connect-explorer/src/pages/guides/webextension-implementation-tutorial.mdx b/packages/connect-explorer/src/pages/guides/webextension-implementation-tutorial.mdx index 73b892938d4..bd43b6d8bd3 100644 --- a/packages/connect-explorer/src/pages/guides/webextension-implementation-tutorial.mdx +++ b/packages/connect-explorer/src/pages/guides/webextension-implementation-tutorial.mdx @@ -287,7 +287,7 @@ export const SectionCard = styled(TrezorCard)` }, "permissions": ["scripting"], "host_permissions": [ - "*://connect.trezor.io/9/*", + "*://connect.trezor.io/9/*" ] } ``` diff --git a/packages/connect-explorer/src/pages/index.mdx b/packages/connect-explorer/src/pages/index.mdx index 5a500b3980a..3d93a2bb29e 100644 --- a/packages/connect-explorer/src/pages/index.mdx +++ b/packages/connect-explorer/src/pages/index.mdx @@ -8,18 +8,14 @@ import styled from 'styled-components'; import Link from 'next/link'; import { spacings, spacingsPx } from '@trezor/theme'; -import { - Button, - Card as TrezorCard, - CollapsibleBox, - variables, - Paragraph, -} from '@trezor/components'; +import { Button, Card as TrezorCard, CollapsibleBox, variables } from '@trezor/components'; import IconNode from '../components/icons/IconNode'; import IconWeb from '../components/icons/IconWeb'; import IconExtension from '../components/icons/IconExtension'; +import IconMobile from '../components/icons/IconMobile'; import ZoomableIllustration from '../components/ZoomableIllustration'; +import { BetaOnly } from '../components/BetaOnly'; export const SectionCard = ({ children }) => ( {children} @@ -106,10 +102,13 @@ export const ExampleHeading = styled.h3` Depending on your environment you need to chose the right package and follow the particular guide: - + } title="Node.js" href="#nodejs" /> } title="Web" href="#web" /> } title="Web extension" href="#web-extension" /> + + } title="Mobile" href="#mobile" /> + @@ -441,7 +440,7 @@ export const ExampleHeading = styled.h3` "host_permissions": ["*://connect.trezor.io/9/*"] "background": { "service_worker": "serviceWorker.js" - }, + } ``` For manual content script injection, you can [find more information in the README](/readme/connect-webextension). @@ -498,3 +497,139 @@ export const ExampleHeading = styled.h3` + + + + + +### Mobile + + + + + + Mobile + Using deep linking + + + + + + + + + + + #### About + + `@trezor/connect-mobile` is a package that allows you to communicate with Trezor devices from your mobile app. + This package is somewhat different from the other SDKs, as it doesn't provide a direct API to the device. Instead, it uses deep linking to open the Trezor Suite app and communicate with the device. + + #### Use deep linking without SDK + + If you are not able to use the SDK, for example on non-supported platforms, you can still use deep linking to open the Trezor Suite app and communicate with the device. + + This can be done by implementing the following specification. + + + + + + + + Examples: + - [Expo App example](https://github.com/trezor/trezor-suite/tree/develop/packages/connect-examples/mobile-expo) + + + + + + {

Installation of the package

} + + Simply install the package using your preferred package manager: + + ```bash + npm install @trezor/connect-mobile + # or + yarn add @trezor/connect-mobile + ``` + + {

Initialization of the API

} + + ```javascript + import * as Linking from 'expo-linking'; + + TrezorConnect.init({ + manifest: { + email: 'developer@xyz.com', + appUrl: 'http://your.application.com', + }, + deeplinkOpen: url => { + Linking.openURL(url); + }, + deeplinkCallbackUrl: Linking.createURL('/connect'), + }); + ``` + + Trezor Connect Manifest requires that you, as a Trezor Connect integrator, share your email and application URL with us. This provides us with the ability to reach you in case of any required maintenance. This subscription is mandatory. + + The deeplink SDK needs two extra parameters: `deeplinkOpen` and `deeplinkCallbackUrl`. The `deeplinkOpen` function is used to open the Trezor Suite app, and the `deeplinkCallbackUrl` is the URL that the app will redirect to after the operation is complete. + + In this example, we are using the `Linking` API from Expo to open the Trezor Suite app, however this may be different depending on your mobile app's environment. + + {

Handling callbacks

} + + The app needs to pass results from the callback URL back to the SDK. This can be done by listening to the URL and passing the data to the SDK using the `handleDeeplink` method. + + ```javascript + useEffect(() => { + const subscription = Linking.addEventListener('url', event => { + TrezorConnect.handleDeeplink(event.url); + }); + + return () => subscription?.remove(); + }, []); + ``` + + {

How to use?

} + + Here is an example of how to get the device's public key: + + ```javascript + TrezorConnect.getPublicKey({ + path: "m/44'/0'/0'/0/0", + showOnTrezor: true, + }); + ``` + + More methods with detailed explanation can be found on the left under the 'Coin methods' section. You can also try the Method Testing Tool, where you can try interacting with the device by yourself. +
+
+ +
+ +
diff --git a/packages/connect-explorer/src/pages/methods/bitcoin/getAccountInfo.mdx b/packages/connect-explorer/src/pages/methods/bitcoin/getAccountInfo.mdx index 6a1aea00ee3..eea9b944eb2 100644 --- a/packages/connect-explorer/src/pages/methods/bitcoin/getAccountInfo.mdx +++ b/packages/connect-explorer/src/pages/methods/bitcoin/getAccountInfo.mdx @@ -42,7 +42,7 @@ export const UsingPathSchema = Type.Object({ export const UsingPubkeySchema = Type.Object({ descriptor: Type.String({ - description: 'public key of account', + description: 'public key or address of account', }), coin: Type.String({ description: @@ -106,7 +106,7 @@ export const GetAccountInfoSchema = Type.Intersect([ export const paramDescriptions = { path: 'minimum length is `3`. [read more](/details/path)', coin: 'determines network definition specified in [coins.json](https://github.com/trezor/trezor-suite/blob/develop/packages/connect-common/files/coins.json) file. Coin `shortcut`, `name` or `label` can be used.', - descriptor: 'public key of account', + descriptor: 'public key or address of account', page: 'transaction history page index, subject of `details: txs`', pageSize: 'transaction history page size, subject of `details: txs`', from: 'transaction history from block filter, subject of `details: txs`', @@ -136,7 +136,7 @@ const result = await TrezorConnect.getAccountInfo(params); -#### Using public key +#### Using public key or address diff --git a/packages/connect-explorer/src/pages/methods/device/loadDevice.mdx b/packages/connect-explorer/src/pages/methods/device/loadDevice.mdx new file mode 100644 index 00000000000..b1f09481f7c --- /dev/null +++ b/packages/connect-explorer/src/pages/methods/device/loadDevice.mdx @@ -0,0 +1,86 @@ +import { Callout } from 'nextra/components'; + +import { PROTO } from '@trezor/connect/src/constants'; + +import { ParamsTable } from '../../../components/ParamsTable'; +import { CommonParamsLink } from '../../../components/CommonParamsLink'; +import { ApiPlayground } from '../../../components/ApiPlayground'; +import loadDevice from '../../../data/methods/management/loadDevice.ts'; + + + +export const paramDescriptions = { + mnemonics: '', + label: '', + u2fCounter: 'Default value is set to current time stamp in seconds.', + pin: '', + passphraseProtection: '', + skipBackup: '', + noBackup: 'create a seedless device', +}; + +## Load device + + + **Management command** - this method is restricted to Trezor.io and can't be used in 3rd party + applications. + + + **Management command** - this method is not going to work on Trezor with production firmware, it + requires a debug firmware. More details: + https://docs.trezor.io/trezor-firmware/tests/device-tests.html#extended-testing-and-debugging + +Load seed and related internal settings. The devices has to be wiped before initiating this command. + +```javascript +const result = await TrezorConnect.loadDevice(params); +``` + +### Params + + + +#### LoadDevice + + + +### Example + +```javascript +TrezorConnect.loadDevice({ + mnemonics: ['all all all all all all all all all all all all'], +}); +``` + +### Result + +Success type + +```javascript +{ + success: true, + payload: { + message: 'Device loaded' + }, + device: { + path: '1', + state: { + deriveCardano: false + }, + instance: 0 + } +} +``` + +Error + +```javascript +{ + success: false, + payload: { + error: string // error message + code: string // error code + } + device: undefined +} +``` diff --git a/packages/connect-explorer/src/pages/readme/[name].mdx b/packages/connect-explorer/src/pages/readme/[name].mdx index 3296b9dbed7..0002bd71e1f 100644 --- a/packages/connect-explorer/src/pages/readme/[name].mdx +++ b/packages/connect-explorer/src/pages/readme/[name].mdx @@ -3,8 +3,14 @@ import { buildDynamicMDX, buildDynamicMeta } from 'nextra/remote'; import rehypeSectionize from '@hbsnow/rehype-sectionize'; import remarkGemoji from 'remark-gemoji'; +import { isBetaOnly } from '../../components/BetaOnly'; + export const getStaticPaths = () => { - const packages = ["connect", "connect-web", "connect-webextension"] + const packages = ["connect", "connect-web", "connect-webextension"]; + if(isBetaOnly) { + packages.push("connect-mobile"); + } + const paths = packages.map((name) => ({ params: { name, @@ -30,8 +36,8 @@ export const getStaticProps = ({ params }) => { content = content.replace(/^(#+)/gm, '$1#'); // Add links to commit hashes content = content.replace( - /([a-f0-9]{7,10})([\),])/g, - `[\$1](https://github.com/trezor/trezor-suite/commit/\$1)$2`, + /([a-f0-9]{7,10})([),])/g, + `[$1](https://github.com/trezor/trezor-suite/commit/$1)$2`, ); content = `---\ntitle: ${name}\n---\n${content}`; diff --git a/packages/connect-explorer/src/pages/settings.mdx b/packages/connect-explorer/src/pages/settings.mdx index 7d3308a156c..874570c71d7 100644 --- a/packages/connect-explorer/src/pages/settings.mdx +++ b/packages/connect-explorer/src/pages/settings.mdx @@ -1,96 +1,6 @@ -import styled from 'styled-components'; - -import { Button } from '@trezor/components'; import { Callout } from 'nextra/components'; -import * as trezorConnectActions from '../actions/trezorConnectActions'; -import { useSelector, useActions } from '../hooks'; -import { getField } from '../components/Method'; - -export const SettingsContent = styled.section` - flex: 1; - padding: 10px 20px; - display: flex; - flex-direction: column; -`; - -export const ConfirmationMessage = styled.div` - margin-top: 20px; - color: green; - font-weight: bold; -`; - -export const ErrorMessage = styled(ConfirmationMessage)` - color: red; -`; - -export const Settings = () => { - const connectOptions = useSelector(state => ({ - trustedHost: state.connect?.options?.trustedHost, - connectSrc: state.connect?.options?.connectSrc, - coreMode: state?.connect?.options?.coreMode, - })); - - const initError = useSelector(state => state.connect?.initError); - const isInitSuccess = useSelector(state => state.connect?.isInitSuccess || false); - const isHandshakeConfirmed = useSelector(state => state.connect?.isHandshakeConfirmed || false); - const actions = useActions({ - onSubmitInit: trezorConnectActions.onSubmitInit, - onFieldChange: trezorConnectActions.onConnectOptionChange, - }); - - const submitButton = 'Init Connect'; - const fields = [ - { - name: 'trustedHost', - type: 'checkbox', - key: 'trustedHost', - value: connectOptions?.trustedHost || false, - }, - { - name: 'coreMode', - type: 'select', - key: 'coreMode', - value: connectOptions?.coreMode || 'auto', - data: [ - { value: 'auto', label: 'Auto' }, - { value: 'iframe', label: 'Iframe' }, - { value: 'popup', label: 'Popup' }, - ], - }, - { - name: 'connectSrc', - type: 'input', - key: 'connectSrc', - value: connectOptions?.connectSrc || '', - }, - ]; - - return ( - - {fields.map(field => getField(field, { actions }))} - - {initError && ( - - Init error: {initError} - - )} - {isInitSuccess && ( - - Init success! - - )} - {isHandshakeConfirmed && ( - - Handshake confirmed! - - )} - - ); - -}; +import { Settings } from '../components/Settings'; ## Settings diff --git a/packages/connect-explorer/src/reducers/methodCommon.ts b/packages/connect-explorer/src/reducers/methodCommon.ts index a2febc66346..7c42d4c00b7 100644 --- a/packages/connect-explorer/src/reducers/methodCommon.ts +++ b/packages/connect-explorer/src/reducers/methodCommon.ts @@ -1,11 +1,11 @@ -import TrezorConnect from '@trezor/connect-web'; +import type { TrezorConnect } from '@trezor/connect-web'; import { TSchema } from '@trezor/schema-utils'; import { setDeepValue } from '@trezor/schema-utils/src/utils'; import { Field, FieldBasic, isFieldBasic } from '../types'; export interface MethodState { - name?: keyof typeof TrezorConnect; + name?: keyof TrezorConnect; submitButton?: string; fields: Field[]; params: Record; diff --git a/packages/connect-explorer/src/reducers/methodInit.ts b/packages/connect-explorer/src/reducers/methodInit.ts index 40befcf51ac..2ecfcf0ea54 100644 --- a/packages/connect-explorer/src/reducers/methodInit.ts +++ b/packages/connect-explorer/src/reducers/methodInit.ts @@ -1,6 +1,6 @@ import { TSchema, Kind, OptionalKind } from '@sinclair/typebox'; -import type TrezorConnect from '@trezor/connect-web'; +import type { TrezorConnect } from '@trezor/connect-web'; import { MethodState, @@ -165,7 +165,7 @@ export const getMethodState = (methodConfig?: Partial) => { }; // Get method state from TypeBox schema -export const getMethodStateFromSchema = (method: keyof typeof TrezorConnect, schema: TSchema) => { +export const getMethodStateFromSchema = (method: keyof TrezorConnect, schema: TSchema) => { return { ...getMethodState({ name: method, diff --git a/packages/connect-explorer/src/reducers/methodReducer.ts b/packages/connect-explorer/src/reducers/methodReducer.ts index 238f80f5e21..83d2b425e48 100644 --- a/packages/connect-explorer/src/reducers/methodReducer.ts +++ b/packages/connect-explorer/src/reducers/methodReducer.ts @@ -46,10 +46,7 @@ const findField = (state: MethodState, field: Field) => { // Update field value const onFieldChange = (state: MethodState, _field: Field, value: any) => { - const newState = { - ...JSON.parse(JSON.stringify(state)), - ...state, - }; + const newState = JSON.parse(JSON.stringify(state)); const field = findField(newState, _field); if (!field || !isFieldBasic(field)) return state; field.value = value; diff --git a/packages/connect-explorer/src/reducers/trezorConnectReducer.ts b/packages/connect-explorer/src/reducers/trezorConnectReducer.ts index ec600143b04..99ca6da9642 100644 --- a/packages/connect-explorer/src/reducers/trezorConnectReducer.ts +++ b/packages/connect-explorer/src/reducers/trezorConnectReducer.ts @@ -3,7 +3,7 @@ import TrezorConnect, { DEVICE } from '@trezor/connect-web'; import * as ACTIONS from '../actions/index'; import { TrezorConnectDevice, Action, Field } from '../types'; -type ConnectState = { +export type ConnectState = { devices: TrezorConnectDevice[]; selectedDevice?: string; options?: Parameters<(typeof TrezorConnect)['init']>[0]; diff --git a/packages/connect-explorer/src/types/index.ts b/packages/connect-explorer/src/types/index.ts index b3187465245..cd55d763f09 100644 --- a/packages/connect-explorer/src/types/index.ts +++ b/packages/connect-explorer/src/types/index.ts @@ -20,7 +20,7 @@ export type TrezorConnectDevice = KnownDevice | UnknownDevice | UnreadableDevice export interface FieldData { value: string; label: string; - affectedValue: string; + affectedValue?: string; } // Field path diff --git a/packages/connect-explorer/tsconfig.json b/packages/connect-explorer/tsconfig.json index ba288a9840b..ce360c089b1 100644 --- a/packages/connect-explorer/tsconfig.json +++ b/packages/connect-explorer/tsconfig.json @@ -18,11 +18,13 @@ { "path": "../components" }, { "path": "../connect" }, { "path": "../connect-explorer-theme" }, + { "path": "../connect-mobile" }, { "path": "../connect-web" }, { "path": "../connect-webextension" }, { "path": "../protobuf" }, { "path": "../schema-utils" }, { "path": "../theme" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../eslint" } ] } diff --git a/packages/connect-iframe/eslint.config.mjs b/packages/connect-iframe/eslint.config.mjs new file mode 100644 index 00000000000..0091a8c91d2 --- /dev/null +++ b/packages/connect-iframe/eslint.config.mjs @@ -0,0 +1,20 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'import/no-default-export': 'off', // Todo: shall be fixed + + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-iframe/package.json b/packages/connect-iframe/package.json index 60a20adbf3c..a3c55393a5e 100644 --- a/packages/connect-iframe/package.json +++ b/packages/connect-iframe/package.json @@ -3,7 +3,6 @@ "version": "9.0.0", "private": true, "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "build:iframe": "TS_NODE_PROJECT=\"tsconfig.json\" yarn webpack --config ./webpack/prod.webpack.config.ts --stats-children", "build:core-module": "TS_NODE_PROJECT=\"tsconfig.json\" yarn webpack --config ./webpack/core.webpack.config.ts --stats-children", "build": "yarn rimraf build && yarn build:iframe && yarn build:core-module", @@ -20,13 +19,14 @@ "devDependencies": { "@babel/preset-typescript": "^7.24.7", "@trezor/env-utils": "workspace:*", + "@trezor/eslint": "workspace:*", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "es6-promise": "^4.2.8", "html-webpack-plugin": "^5.6.0", "rimraf": "^6.0.1", "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-merge": "^6.0.1", "worker-loader": "^3.0.8" diff --git a/packages/connect-iframe/src/connectSettings.ts b/packages/connect-iframe/src/connectSettings.ts index 052e8de1ffc..2a403fb8132 100644 --- a/packages/connect-iframe/src/connectSettings.ts +++ b/packages/connect-iframe/src/connectSettings.ts @@ -76,7 +76,6 @@ export const parseConnectSettings = ( } if (disableWebUsb) { - settings.webusb = false; // allow all but WebUsbTransport settings.transports = settings.transports?.filter( transport => transport !== 'WebUsbTransport', diff --git a/packages/connect-iframe/src/index.ts b/packages/connect-iframe/src/index.ts index 74f95a7da9f..9feeb5f8eba 100644 --- a/packages/connect-iframe/src/index.ts +++ b/packages/connect-iframe/src/index.ts @@ -28,8 +28,9 @@ import { getOrigin } from '@trezor/connect/src/utils/urlUtils'; import { suggestBridgeInstaller } from '@trezor/connect/src/data/transportInfo'; import { suggestUdevInstaller } from '@trezor/connect/src/data/udevInfo'; import { storage, getSystemInfo, getInstallerPackage } from '@trezor/connect-common'; -import { parseConnectSettings, isOriginWhitelisted } from './connectSettings'; import { analytics, EventType } from '@trezor/connect-analytics'; + +import { parseConnectSettings, isOriginWhitelisted } from './connectSettings'; // @ts-expect-error (typescript does not know this is worker constructor, this is done by webpack) import LogWorker from './sharedLoggerWorker'; import { initLogWriterWithWorker } from './sharedLoggerUtils'; @@ -314,7 +315,7 @@ const init = async (payload: IFrameInit['payload'], origin: string) => { // throws DOMException: The operation is insecure. _popupMessagePort = new BroadcastChannel(broadcastID); _popupMessagePort.onmessage = message => handleMessage(message); - } catch (error) { + } catch { // popup will use MessageChannel fallback communication } } diff --git a/packages/connect-iframe/src/sharedLoggerUtils.ts b/packages/connect-iframe/src/sharedLoggerUtils.ts index 63690ddf40a..c021aa03cea 100644 --- a/packages/connect-iframe/src/sharedLoggerUtils.ts +++ b/packages/connect-iframe/src/sharedLoggerUtils.ts @@ -6,10 +6,10 @@ interface LogWorkerClass extends SharedWorker { let logWorker: SharedWorker | undefined; -const logWriterFactory = (logWorker: SharedWorker | undefined) => (): LogWriter => ({ +const logWriterFactory = (logWorker2: SharedWorker | undefined) => (): LogWriter => ({ add: (message: LogMessage) => { - if (logWorker) { - logWorker.port.postMessage({ type: 'add-log', data: message }); + if (logWorker2) { + logWorker2.port.postMessage({ type: 'add-log', data: message }); } }, }); diff --git a/packages/connect-iframe/tsconfig.json b/packages/connect-iframe/tsconfig.json index b85b5eb4b90..22e8c593575 100644 --- a/packages/connect-iframe/tsconfig.json +++ b/packages/connect-iframe/tsconfig.json @@ -9,6 +9,7 @@ { "path": "../connect" }, { "path": "../connect-analytics" }, { "path": "../connect-common" }, - { "path": "../env-utils" } + { "path": "../env-utils" }, + { "path": "../eslint" } ] } diff --git a/packages/connect-iframe/webpack/base.webpack.config.ts b/packages/connect-iframe/webpack/base.webpack.config.ts index 1b73e0ea59f..8a8238ae09b 100644 --- a/packages/connect-iframe/webpack/base.webpack.config.ts +++ b/packages/connect-iframe/webpack/base.webpack.config.ts @@ -3,6 +3,7 @@ import { execSync } from 'child_process'; import webpack from 'webpack'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin'; + import { version } from '../package.json'; const COMMON_DATA_SRC = '../../packages/connect-common/files'; diff --git a/packages/connect-deeplink/README.md b/packages/connect-mobile/README.md similarity index 66% rename from packages/connect-deeplink/README.md rename to packages/connect-mobile/README.md index 4604d578a9f..c7ae4c9bf4b 100644 --- a/packages/connect-deeplink/README.md +++ b/packages/connect-mobile/README.md @@ -1,8 +1,10 @@ -# @trezor/connect-deeplink +# @trezor/connect-mobile -[![NPM](https://img.shields.io/npm/v/@trezor/connect-deeplink.svg)](https://www.npmjs.org/package/@trezor/connect-deeplink) +[![NPM](https://img.shields.io/npm/v/@trezor/connect-mobile.svg)](https://www.npmjs.org/package/@trezor/connect-mobile) -The `@trezor/connect-deeplink` package provides an implementation of `@trezor/connect` which uses deep links to communicate with the Trezor Suite Lite app. +The `@trezor/connect-mobile` package provides an implementation of `@trezor/connect` which uses deep links to communicate with the Trezor Suite Lite app. + +## 🚧 BETA version, work in progress 🚧 Currently the library is still under development, only supports read-only methods and does not communicate with the production Suite Lite app. @@ -13,7 +15,7 @@ To run a dev version of the Suite mobile app follow the instructions in [@suite- To use the library, you need to initialize it with the `deeplinkOpen` and `deeplinkCallbackUrl` settings. ```javascript -import TrezorConnect from '@trezor/connect-deeplink'; +import TrezorConnect from '@trezor/connect-mobile'; TrezorConnect.init({ manifest: { @@ -43,4 +45,4 @@ useEffect(() => { ## Example -The [Connect deeplink example](https://github.com/trezor/trezor-suite/tree/develop/packages/connect-examples/deeplink-expo) shows how to use the library in a React Native + Expo app. +The [Connect mobile example](https://github.com/trezor/trezor-suite/tree/develop/packages/connect-examples/mobile-expo) shows how to use the library in a React Native + Expo app. diff --git a/packages/connect-mobile/package.json b/packages/connect-mobile/package.json new file mode 100644 index 00000000000..403d1712d1c --- /dev/null +++ b/packages/connect-mobile/package.json @@ -0,0 +1,31 @@ +{ + "name": "@trezor/connect-mobile", + "version": "0.0.1-beta.1", + "license": "See LICENSE.md in repo root", + "sideEffects": false, + "main": "src/index.ts", + "publishConfig": { + "main": "lib/index.js" + }, + "npmPublishAccess": "public", + "files": [ + "lib/" + ], + "scripts": { + "depcheck": "yarn g:depcheck", + "type-check": "yarn g:tsc --build", + "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", + "prepublishOnly": "yarn tsx ../../scripts/prepublishNPM.js", + "prepublish": "yarn tsx ../../scripts/prepublish.js" + }, + "devDependencies": { + "tsx": "^4.16.3" + }, + "dependencies": { + "@trezor/connect": "workspace:^", + "@trezor/utils": "workspace:^" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } +} diff --git a/packages/connect-deeplink/src/index.ts b/packages/connect-mobile/src/index.ts similarity index 85% rename from packages/connect-deeplink/src/index.ts rename to packages/connect-mobile/src/index.ts index b4b8422a0bc..f6400f9e387 100644 --- a/packages/connect-deeplink/src/index.ts +++ b/packages/connect-mobile/src/index.ts @@ -4,22 +4,21 @@ import * as ERRORS from '@trezor/connect/src/constants/errors'; import { parseConnectSettings } from '@trezor/connect/src/data/connectSettings'; import type { CallMethodPayload } from '@trezor/connect/src/events/call'; import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory'; -import type { TrezorConnect as TrezorConnectType } from '@trezor/connect/src/types'; import type { + ConnectSettingsMobile, ConnectSettings, - ConnectSettingsPublic, Manifest, Response, } from '@trezor/connect/src/types'; import { Login } from '@trezor/connect/src/types/api/requestLogin'; import { Deferred, createDeferred } from '@trezor/utils'; +import { InitFullSettings } from '@trezor/connect/src/types/api/init'; -export class TrezorConnectDeeplink implements ConnectFactoryDependencies { +export class TrezorConnectDeeplink implements ConnectFactoryDependencies { public eventEmitter = new EventEmitter(); private _settings: ConnectSettings; private messagePromises: Record> = {}; private messageID = 0; - private defaultDeeplinkUrl = 'trezorsuitelite://connect'; public constructor() { this._settings = { @@ -40,7 +39,7 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { }; } - public init(settings: Partial) { + public init(settings: InitFullSettings) { if (!settings.deeplinkOpen) { throw new Error('TrezorConnect native requires "deeplinkOpen" setting.'); } @@ -48,7 +47,6 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { ...parseConnectSettings({ ...this._settings, ...settings }), deeplinkOpen: settings.deeplinkOpen, deeplinkCallbackUrl: settings.deeplinkCallbackUrl, - deeplinkUrl: settings.deeplinkUrl || this.defaultDeeplinkUrl, }; return Promise.resolve(); @@ -84,16 +82,6 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { throw ERRORS.TypedError('Method_InvalidPackage'); } - public renderWebUSBButton() {} - - public disableWebUSB() { - throw ERRORS.TypedError('Method_InvalidPackage'); - } - - public requestWebUSBDevice() { - throw ERRORS.TypedError('Method_InvalidPackage'); - } - public cancel(error?: string) { this.resolveMessagePromises({ success: false, @@ -125,6 +113,11 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { return; } + if (!this.messagePromises[id]) { + // Most likely old ID, ignore + return; + } + const responseParam = parsedUrl.searchParams.get('response'); if (!responseParam) { this.messagePromises[id].resolve({ @@ -140,7 +133,9 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { let parsedParams; try { parsedParams = JSON.parse(responseParam); - } catch {} + } catch { + /* empty */ + } if (!parsedParams) { this.messagePromises[id].resolve({ @@ -156,7 +151,7 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { delete this.messagePromises[id]; } - resolveMessagePromises(resolvePayload: Record) { + private resolveMessagePromises(resolvePayload: Record) { Object.keys(this.messagePromises).forEach(id => { this.messagePromises[id as any].resolve({ id, @@ -187,24 +182,26 @@ export class TrezorConnectDeeplink implements ConnectFactoryDependencies { } const impl = new TrezorConnectDeeplink(); -const TrezorConnect: TrezorConnectType & { - handleDeeplink: (url: string) => void; -} = { - ...factory({ +const TrezorConnect = factory< + ConnectSettingsMobile, + { + handleDeeplink: (url: string) => void; + } +>( + { eventEmitter: impl.eventEmitter, init: impl.init.bind(impl), call: impl.call.bind(impl), manifest: impl.manifest.bind(impl), requestLogin: impl.requestLogin.bind(impl), uiResponse: impl.uiResponse.bind(impl), - renderWebUSBButton: impl.renderWebUSBButton.bind(impl), - disableWebUSB: impl.disableWebUSB.bind(impl), - requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl), cancel: impl.cancel.bind(impl), dispose: impl.dispose.bind(impl), - }), - handleDeeplink: impl.handleDeeplink.bind(impl), -}; + }, + { + handleDeeplink: impl.handleDeeplink.bind(impl), + }, +); // eslint-disable-next-line import/no-default-export export default TrezorConnect; diff --git a/packages/connect-deeplink/tsconfig.json b/packages/connect-mobile/tsconfig.json similarity index 100% rename from packages/connect-deeplink/tsconfig.json rename to packages/connect-mobile/tsconfig.json diff --git a/packages/connect-mobile/tsconfig.lib.json b/packages/connect-mobile/tsconfig.lib.json new file mode 100644 index 00000000000..dfc917a9a0d --- /dev/null +++ b/packages/connect-mobile/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.lib.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src"], + "references": [ + { + "path": "../connect" + }, + { + "path": "../utils" + } + ] +} diff --git a/packages/connect-plugin-ethereum/CHANGELOG.md b/packages/connect-plugin-ethereum/CHANGELOG.md index d6ab00a6ecd..76aaa81a529 100644 --- a/packages/connect-plugin-ethereum/CHANGELOG.md +++ b/packages/connect-plugin-ethereum/CHANGELOG.md @@ -1,6 +1,6 @@ # UNRELEASED -- updated `@metamask/eth-sig-util` from `^7.0.1` to `^7.0.3` +- updated `@metamask/eth-sig-util` from `^7.0.1` to `^8.0.0` # 9.0.3 diff --git a/packages/connect-plugin-ethereum/__tests__/index.test.ts b/packages/connect-plugin-ethereum/__tests__/index.test.ts index d9e44c1f079..2d1e751c582 100644 --- a/packages/connect-plugin-ethereum/__tests__/index.test.ts +++ b/packages/connect-plugin-ethereum/__tests__/index.test.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import commonFixtures from '../../../submodules/trezor-common/tests/fixtures/ethereum/sign_typed_data.json'; import { transformTypedData } from '../src/index'; diff --git a/packages/connect-plugin-ethereum/package.json b/packages/connect-plugin-ethereum/package.json index 6499479b1a6..5688c46ec20 100644 --- a/packages/connect-plugin-ethereum/package.json +++ b/packages/connect-plugin-ethereum/package.json @@ -26,14 +26,13 @@ "lib/" ], "peerDependencies": { - "@metamask/eth-sig-util": "^7.0.3", + "@metamask/eth-sig-util": "^8.0.0", "tslib": "^2.6.2" }, "devDependencies": { - "@metamask/eth-sig-util": "^7.0.3" + "@metamask/eth-sig-util": "^8.0.0" }, "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js", "type-check": "yarn g:tsc --build tsconfig.json", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib" diff --git a/packages/connect-plugin-ethereum/src/index.ts b/packages/connect-plugin-ethereum/src/index.ts index 605487701f2..b9808a1a476 100644 --- a/packages/connect-plugin-ethereum/src/index.ts +++ b/packages/connect-plugin-ethereum/src/index.ts @@ -68,4 +68,5 @@ export const transformTypedData = ( }; }; +// eslint-disable-next-line import/no-default-export export default transformTypedData; diff --git a/packages/connect-plugin-stellar/CHANGELOG.md b/packages/connect-plugin-stellar/CHANGELOG.md index c7320c79b67..99902dbda30 100644 --- a/packages/connect-plugin-stellar/CHANGELOG.md +++ b/packages/connect-plugin-stellar/CHANGELOG.md @@ -1,6 +1,6 @@ # UNRELEASED -- chore: update @stellar/stellar-sdk to ^12.1.0 +- chore: update @stellar/stellar-sdk to ^12.1.3 # 9.0.3 diff --git a/packages/connect-plugin-stellar/eslint.config.mjs b/packages/connect-plugin-stellar/eslint.config.mjs new file mode 100644 index 00000000000..7985d792067 --- /dev/null +++ b/packages/connect-plugin-stellar/eslint.config.mjs @@ -0,0 +1,10 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'import/no-default-export': 'off', // Todo: fix and solve + }, + }, +]; diff --git a/packages/connect-plugin-stellar/package.json b/packages/connect-plugin-stellar/package.json index 1549b21d2b3..59e3984467e 100644 --- a/packages/connect-plugin-stellar/package.json +++ b/packages/connect-plugin-stellar/package.json @@ -26,18 +26,18 @@ "lib/" ], "peerDependencies": { - "@stellar/stellar-sdk": "^12.1.0", + "@stellar/stellar-sdk": "^12.1.3", "@trezor/connect": "9.x.x", "tslib": "^2.6.2" }, "devDependencies": { - "@stellar/stellar-sdk": "^12.1.0" + "@stellar/stellar-sdk": "^12.1.3", + "@trezor/eslint": "workspace:*" }, "dependencies": { "@trezor/utils": "workspace:*" }, "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "jest -c ../../jest.config.base.js", "type-check": "yarn g:tsc --build tsconfig.json", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib" diff --git a/packages/connect-plugin-stellar/src/index.ts b/packages/connect-plugin-stellar/src/index.ts index bf42c2f23e5..21cb64240c3 100644 --- a/packages/connect-plugin-stellar/src/index.ts +++ b/packages/connect-plugin-stellar/src/index.ts @@ -9,6 +9,7 @@ import { MemoHash, MemoReturn, } from '@stellar/stellar-sdk'; + import { BigNumber } from '@trezor/utils/src/bigNumber'; /** diff --git a/packages/connect-plugin-stellar/tsconfig.json b/packages/connect-plugin-stellar/tsconfig.json index a8cca951753..72cf2ecc55c 100644 --- a/packages/connect-plugin-stellar/tsconfig.json +++ b/packages/connect-plugin-stellar/tsconfig.json @@ -2,5 +2,8 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./libDev" }, "include": ["."], - "references": [{ "path": "../utils" }] + "references": [ + { "path": "../utils" }, + { "path": "../eslint" } + ] } diff --git a/packages/connect-plugin-stellar/tsconfig.lib.json b/packages/connect-plugin-stellar/tsconfig.lib.json index 8cd606846ba..59c8b0c6763 100644 --- a/packages/connect-plugin-stellar/tsconfig.lib.json +++ b/packages/connect-plugin-stellar/tsconfig.lib.json @@ -7,6 +7,9 @@ "references": [ { "path": "../utils" + }, + { + "path": "../eslint" } ] } diff --git a/packages/connect-popup/e2e/playwright.config.ts b/packages/connect-popup/e2e/playwright.config.ts index e3379a05507..2b18f455a80 100644 --- a/packages/connect-popup/e2e/playwright.config.ts +++ b/packages/connect-popup/e2e/playwright.config.ts @@ -12,4 +12,6 @@ const config: PlaywrightTestConfig = { ...devices[process.env.MOBILE ? 'Pixel 7' : 'Desktop Chrome'], }, }; + +// eslint-disable-next-line import/no-default-export export default config; diff --git a/packages/connect-popup/e2e/support/helpers.ts b/packages/connect-popup/e2e/support/helpers.ts index 257ac0147ba..af4b47f1852 100644 --- a/packages/connect-popup/e2e/support/helpers.ts +++ b/packages/connect-popup/e2e/support/helpers.ts @@ -22,14 +22,14 @@ export const log = (...val: string[]) => { // Next.js uses client side routing, so we use this to navigate without reloading the page export const nextJsGoto = async (page: Page, url: string) => { - await page.evaluate(url => { - (window as any).router.push(url); + await page.evaluate(url2 => { + (window as any).router.push(url2); }, url); }; -export const formatUrl = (baseUrl: string, path: string) => { +export const formatUrl = (baseUrl: string, path2: string) => { const [baseUrlWithoutParams, params] = baseUrl.split('?'); - const [pathWithoutParams, pathParams] = path.split('?'); + const [pathWithoutParams, pathParams] = path2.split('?'); return `${baseUrlWithoutParams}${pathWithoutParams}?${params ? `${params}&` : ''}${pathParams ?? ''}`; }; diff --git a/packages/connect-popup/e2e/tests/__fixtures__/methods.ts b/packages/connect-popup/e2e/tests/__fixtures__/methods.ts index 2f005005ce8..6a9fd709fe6 100644 --- a/packages/connect-popup/e2e/tests/__fixtures__/methods.ts +++ b/packages/connect-popup/e2e/tests/__fixtures__/methods.ts @@ -309,6 +309,26 @@ const wipeDevice = [ }, ]; +const loadDevice = [ + { + dir: 'device', + url: 'loadDevice', + device: { + wiped: true, + }, + views: [ + { + selector: '.device-management >> visible=true', + screenshot: { + name: 'load-device', + }, + next: 'button.confirm >> visible=true', + }, + followDevice, + ], + }, +]; + /*const resetDevice = [ { url: 'resetDevice', @@ -693,6 +713,7 @@ export const fixtures = [ // management methods // note that it is not so important to test these as they are not available to 3rd party ...wipeDevice, + ...loadDevice, // todo: resetDevice also breaks next test in queue and is flaky itself // ...resetDevice, // todo: missing diff --git a/packages/connect-popup/e2e/tests/analytics.test.ts b/packages/connect-popup/e2e/tests/analytics.test.ts index 9a9b20b049b..380347e5099 100644 --- a/packages/connect-popup/e2e/tests/analytics.test.ts +++ b/packages/connect-popup/e2e/tests/analytics.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test'; import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + import { waitAndClick } from '../support/helpers'; const url = process.env.URL || 'http://localhost:8088/'; diff --git a/packages/connect-popup/e2e/tests/browser-support.test.ts b/packages/connect-popup/e2e/tests/browser-support.test.ts index 58cca779072..8bab9442706 100644 --- a/packages/connect-popup/e2e/tests/browser-support.test.ts +++ b/packages/connect-popup/e2e/tests/browser-support.test.ts @@ -1,6 +1,8 @@ import { test, expect, Page, devices } from '@playwright/test'; + import { ensureDirectoryExists } from '@trezor/node-utils'; import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + import { formatUrl, log, waitAndClick } from '../support/helpers'; const url = process.env.URL || 'http://localhost:8088/'; diff --git a/packages/connect-popup/e2e/tests/methods.test.ts b/packages/connect-popup/e2e/tests/methods.test.ts index 9cddbd281d7..88c333c1ea1 100644 --- a/packages/connect-popup/e2e/tests/methods.test.ts +++ b/packages/connect-popup/e2e/tests/methods.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; +import { ensureDirectoryExists } from '@trezor/node-utils'; + import { fixtures } from './__fixtures__/methods'; import { buildOverview } from '../support/buildOverview'; -import { ensureDirectoryExists } from '@trezor/node-utils'; import { getContexts, log, formatUrl, openPopup, setConnectSettings } from '../support/helpers'; const url = process.env.URL || 'http://localhost:8088/'; @@ -62,6 +63,7 @@ filteredFixtures.forEach(f => { // - fixture require different device than prev fixture, or // - fixture is retried // FIXME: always reset for now, due to flaky tests with bridge bug + // eslint-disable-next-line no-constant-binary-expression,no-constant-condition if (true || JSON.stringify(device) !== JSON.stringify(f.device) || retry) { device = f.device; await TrezorUserEnvLink.stopBridge(); diff --git a/packages/connect-popup/e2e/tests/passphrase.test.ts b/packages/connect-popup/e2e/tests/passphrase.test.ts index 252eec90ca8..df3f759854b 100644 --- a/packages/connect-popup/e2e/tests/passphrase.test.ts +++ b/packages/connect-popup/e2e/tests/passphrase.test.ts @@ -1,5 +1,7 @@ import { test, Page, BrowserContext } from '@playwright/test'; + import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + import { findElementByDataTest, formatUrl, @@ -372,7 +374,7 @@ test('passphrase mismatch', async ({ page }) => { timeout: 10000, }); await popup.click('.explain.unacquired'); - } catch (error) { + } catch { // May appear or not } diff --git a/packages/connect-popup/e2e/tests/permissions.test.ts b/packages/connect-popup/e2e/tests/permissions.test.ts index 3d6837c3803..f5d8dc8d1b2 100644 --- a/packages/connect-popup/e2e/tests/permissions.test.ts +++ b/packages/connect-popup/e2e/tests/permissions.test.ts @@ -1,5 +1,7 @@ import { test, chromium, Page } from '@playwright/test'; + import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + import { setConnectSettings, waitAndClick } from '../support/helpers'; const url = process.env.URL || 'http://localhost:8088/'; diff --git a/packages/connect-popup/e2e/tests/popup-close.test.ts b/packages/connect-popup/e2e/tests/popup-close.test.ts index 2833a1cb9eb..2849a75021b 100644 --- a/packages/connect-popup/e2e/tests/popup-close.test.ts +++ b/packages/connect-popup/e2e/tests/popup-close.test.ts @@ -1,6 +1,8 @@ import { test, Page, BrowserContext } from '@playwright/test'; + import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; import { addDashesToSpaces, createTimeoutPromise } from '@trezor/utils'; + import { findElementByDataTest, waitAndClick, @@ -186,7 +188,7 @@ test(`device disconnected during device interaction`, async ({ page, context }) try { log('waiting to click @connect-ui/error-close-button'); await popup.click("button[data-testid='@connect-ui/error-close-button']"); - } catch (error) { + } catch { // Sometimes this crashes with error that the page is already closed. } @@ -229,7 +231,7 @@ test('when user cancels permissions in popup it closes automatically', async ({ await popup.waitForLoadState('load'); - if (isCoreInPopup) { + if (isWebExtension || isCoreInPopup) { await popup.waitForSelector('.select-device-list button.list', { state: 'visible' }); await popup.click('.select-device-list button.list'); } diff --git a/packages/connect-popup/e2e/tests/popup-pages.test.ts b/packages/connect-popup/e2e/tests/popup-pages.test.ts index 84b3ed32ff3..2501726d290 100644 --- a/packages/connect-popup/e2e/tests/popup-pages.test.ts +++ b/packages/connect-popup/e2e/tests/popup-pages.test.ts @@ -1,5 +1,8 @@ import { test, Page, BrowserContext, expect } from '@playwright/test'; +import fs from 'fs'; + import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + import { getContexts, openPopup, @@ -9,8 +12,6 @@ import { waitAndClick, } from '../support/helpers'; -import fs from 'fs'; - const url = process.env.URL || 'http://localhost:8088/'; const bridgeVersion = '2.0.33'; diff --git a/packages/connect-popup/e2e/tests/transport.test.ts b/packages/connect-popup/e2e/tests/transport.test.ts index d977fa15c4a..21bb1d86059 100644 --- a/packages/connect-popup/e2e/tests/transport.test.ts +++ b/packages/connect-popup/e2e/tests/transport.test.ts @@ -1,9 +1,11 @@ import { test, expect, firefox, chromium, Page, BrowserContext } from '@playwright/test'; -import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; -import { waitAndClick, log, formatUrl } from '../support/helpers'; import http from 'http'; import https from 'https'; +import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; + +import { waitAndClick, log, formatUrl } from '../support/helpers'; + const url = process.env.URL || 'http://localhost:8088/'; // different origin URL for testing cross-origin requests @@ -129,8 +131,9 @@ fixtures.forEach(f => { const afterCleanup = await f.setup?.(context); - log(`going to: ${url}${f.queryString}#/method/verifyMessage`); - await page.goto(formatUrl(url, `methods/bitcoin/verifyMessage/${f.queryString}`)); + const formattedUrl = formatUrl(url, `methods/bitcoin/verifyMessage/${f.queryString}`); + log(`going to: ${formattedUrl}`); + await page.goto(formattedUrl); log('waiting for explorer to load'); await waitAndClick(page, ['@api-playground/collapsible-box']); await page.waitForSelector("button[data-testid='@submit-button']", { diff --git a/packages/connect-popup/e2e/tests/unchained.test.ts b/packages/connect-popup/e2e/tests/unchained.test.ts index 17c3a975a81..099829b8ff9 100644 --- a/packages/connect-popup/e2e/tests/unchained.test.ts +++ b/packages/connect-popup/e2e/tests/unchained.test.ts @@ -1,6 +1,7 @@ /* eslint no-await-in-loop: 0 */ import { test, expect, Page } from '@playwright/test'; + import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; const connectUrl = process.env.URL ? process.env.URL : 'https://connect.trezor.io/9/'; @@ -102,7 +103,7 @@ const signTransaction = async (page: Page, iteration: number) => { }); await TrezorUserEnvLink.send({ type: 'emulator-press-yes' }); await popup.waitForTimeout(501); - } catch (err) { + } catch { confirmOnTrezorScreenStilVisible = false; } } diff --git a/packages/connect-popup/e2e/tests/webextension-example.test.ts b/packages/connect-popup/e2e/tests/webextension-example.test.ts index a762b3978f8..68b00e79465 100644 --- a/packages/connect-popup/e2e/tests/webextension-example.test.ts +++ b/packages/connect-popup/e2e/tests/webextension-example.test.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-shadow import { test, expect, chromium } from '@playwright/test'; import path from 'path'; diff --git a/packages/connect-popup/eslint.config.mjs b/packages/connect-popup/eslint.config.mjs new file mode 100644 index 00000000000..4dbf151aab2 --- /dev/null +++ b/packages/connect-popup/eslint.config.mjs @@ -0,0 +1,20 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-console': 'off', + 'no-restricted-syntax': 'off', // Todo: fix and solve + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-popup/package.json b/packages/connect-popup/package.json index 41db6cafa1b..d79c6212ce6 100644 --- a/packages/connect-popup/package.json +++ b/packages/connect-popup/package.json @@ -4,7 +4,6 @@ "private": true, "sideEffects": false, "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "lint:styles": "npx stylelint './src/**/*{.ts,.tsx}' --cache --config ../../.stylelintrc", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", @@ -36,6 +35,7 @@ "@playwright/browser-firefox": "^1.46.1", "@playwright/browser-webkit": "^1.46.1", "@playwright/test": "^1.46.1", + "@trezor/eslint": "workspace:*", "@trezor/node-utils": "workspace:*", "@trezor/trezor-user-env-link": "workspace:*", "@trezor/utils": "workspace:*", @@ -45,7 +45,7 @@ "html-webpack-plugin": "^5.6.0", "rimraf": "^6.0.1", "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", "webpack-merge": "^6.0.1", diff --git a/packages/connect-popup/src/deeplink.tsx b/packages/connect-popup/src/deeplink.tsx index a965066eff2..ae9e7c2dd30 100644 --- a/packages/connect-popup/src/deeplink.tsx +++ b/packages/connect-popup/src/deeplink.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import styled, { ThemeProvider } from 'styled-components'; import { createRoot } from 'react-dom/client'; @@ -50,10 +51,10 @@ const DeeplinkFallback = () => { You have opened a deep link to the Trezor Connect mobile application. Please install the application from{' '}
- Google Play + Google Play {' or '} - App Store. + App Store. diff --git a/packages/connect-popup/src/index.tsx b/packages/connect-popup/src/index.tsx index e2aea21941a..f3618a3c356 100644 --- a/packages/connect-popup/src/index.tsx +++ b/packages/connect-popup/src/index.tsx @@ -22,11 +22,12 @@ import type { Core } from '@trezor/connect/src/core'; import { config } from '@trezor/connect/src/data/config'; import { parseConnectSettings } from '@trezor/connect-iframe/src/connectSettings'; import { initLogWriterWithSrcPath } from '@trezor/connect-iframe/src/sharedLoggerUtils'; - import { reactEventBus } from '@trezor/connect-ui/src/utils/eventBus'; import { ErrorViewProps } from '@trezor/connect-ui/src/views/Error'; import { analytics, EventType } from '@trezor/connect-analytics'; import { getSystemInfo } from '@trezor/connect-common'; +import { initLog, setLogWriter, LogWriter } from '@trezor/connect/src/utils/debug'; +import { DEFAULT_DOMAIN } from '@trezor/connect/src/data/version'; import * as view from './view'; import { @@ -38,8 +39,6 @@ import { showView, } from './view/common'; import { isPhishingDomain } from './utils/isPhishingDomain'; -import { initLog, setLogWriter, LogWriter } from '@trezor/connect/src/utils/debug'; -import { DEFAULT_DOMAIN } from '@trezor/connect/src/data/version'; const INTERVAL_CHECK_PARENT_ALIVE_MS = 1000; const INTERVAL_HANDSHAKE_TIMEOUT_MS = 90 * 1000; @@ -58,7 +57,7 @@ const escapeHtml = (payload: any) => { div.appendChild(document.createTextNode(JSON.stringify(payload))); return JSON.parse(div.innerHTML); - } catch (error) { + } catch { // do nothing } }; @@ -193,6 +192,7 @@ const handleResponseEvent = (data: MethodResponseMessage) => { default: fail({ type: 'error', + code, detail: 'response-event-error', message: ('error' in data.payload && data.payload.error) || 'Unknown error', }); @@ -320,6 +320,22 @@ const handleMessageInCoreMode = ( info: method.info, }); reactEventBus.dispatch({ type: 'state-update', payload: getState() }); + + const { settings } = getState(); + const transport = core.getTransportInfo(); + analytics.report({ + type: EventType.AppReady, + payload: { + version: settings?.version, + origin: settings?.origin, + referrerApp: settings?.manifest?.appUrl, + referrerEmail: settings?.manifest?.email, + method: method?.name, + payload: method?.payload ? Object.keys(method.payload) : undefined, + transportType: transport?.type, + transportVersion: transport?.version, + }, + }); }); } @@ -504,8 +520,8 @@ const initCoreInIframe = async (payload: PopupInit['payload']) => { }; // handle POPUP.HANDSHAKE message from iframe or npm-client -const handshake = (handshake: PopupHandshake, origin: string) => { - const { payload, ...handshakeRest } = handshake; +const handshake = (handshake2: PopupHandshake, origin: string) => { + const { payload, ...handshakeRest } = handshake2; log.debug('handshake with origin: ', origin, 'payload: ', payload); if (!payload) return; @@ -535,7 +551,7 @@ const handshake = (handshake: PopupHandshake, origin: string) => { const core = ensureCore(); core.handleMessage(handshakeRest); } - reactEventBus.dispatch({ type: 'state-update', payload: handshake.payload }); + reactEventBus.dispatch({ type: 'state-update', payload: handshake2.payload }); log.debug('handshake done'); }; diff --git a/packages/connect-popup/src/log.tsx b/packages/connect-popup/src/log.tsx index 12c7b1dd9cb..95397127d8c 100644 --- a/packages/connect-popup/src/log.tsx +++ b/packages/connect-popup/src/log.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; + import styled, { ThemeProvider } from 'styled-components'; import { createRoot } from 'react-dom/client'; @@ -102,7 +103,7 @@ const useLogWorker = (setLogs: React.Dispatch>) => { try { logWorker = new SharedWorker('./workers/shared-logger-worker.js'); - } catch (error) { + } catch { console.warn('Failed to initialize SharedWorker'); } diff --git a/packages/connect-popup/src/static/deeplink.html b/packages/connect-popup/src/static/deeplink.html index a19d5b56e17..454103ddc5b 100644 --- a/packages/connect-popup/src/static/deeplink.html +++ b/packages/connect-popup/src/static/deeplink.html @@ -16,10 +16,10 @@ - - + + - + @@ -27,7 +27,7 @@ diff --git a/packages/connect-popup/src/types/eth-phishing-detect.d.ts b/packages/connect-popup/src/types/eth-phishing-detect.d.ts index 0c89fbe8d6b..34424c00151 100644 --- a/packages/connect-popup/src/types/eth-phishing-detect.d.ts +++ b/packages/connect-popup/src/types/eth-phishing-detect.d.ts @@ -1,3 +1,4 @@ declare module 'eth-phishing-detect' { + // eslint-disable-next-line import/no-default-export export default function (domain: string): boolean; } diff --git a/packages/connect-popup/src/view/browser.ts b/packages/connect-popup/src/view/browser.ts index 782170d5c52..c193b80efc1 100644 --- a/packages/connect-popup/src/view/browser.ts +++ b/packages/connect-popup/src/view/browser.ts @@ -2,6 +2,7 @@ import { SystemInfo } from '@trezor/connect'; import { storage } from '@trezor/connect-common'; + import { container, showView } from './common'; export const initBrowserView = (systemInfo: SystemInfo): Promise => { diff --git a/packages/connect-popup/src/view/common.tsx b/packages/connect-popup/src/view/common.tsx index 29f3b98b480..dbc9cfda61b 100644 --- a/packages/connect-popup/src/view/common.tsx +++ b/packages/connect-popup/src/view/common.tsx @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/common.js +import { createRoot } from 'react-dom/client'; + import { POPUP, ERRORS, @@ -8,12 +10,11 @@ import { CoreRequestMessage, CoreEventMessage, } from '@trezor/connect'; -import { createRoot } from 'react-dom/client'; - import { ConnectUI, State, getDefaultState } from '@trezor/connect-ui'; -import { StyleSheetWrapper } from './react/StylesSheetWrapper'; import { reactEventBus } from '@trezor/connect-ui/src/utils/eventBus'; +import { StyleSheetWrapper } from './react/StylesSheetWrapper'; + export const header: HTMLElement = document.getElementsByTagName('header')[0]; export const container: HTMLElement = document.getElementById('container')!; export const views: HTMLElement = document.getElementById('views')!; @@ -33,10 +34,10 @@ export const createTooltip = (text: string) => { export const clearLegacyView = () => { // clear and hide legacy views - const container = document.getElementById('container'); - if (container) { - container.innerHTML = ''; - container.style.display = 'none'; + const container2 = document.getElementById('container'); + if (container2) { + container2.innerHTML = ''; + container2.style.display = 'none'; } }; @@ -78,7 +79,7 @@ export const getIframeElement = () => { if (frames[i].location.host === window.location.host) { iframe = frames[i]; } - } catch (error) { + } catch { // do nothing, try next entry } } @@ -106,9 +107,9 @@ export const initMessageChannelWithIframe = async ( const handshakeLoader = (api: Pick) => Promise.race([ new Promise(resolve => { - api.addEventListener('message', function handler(event) { + api.addEventListener('message', function messageHandler(event) { if (event.data.type === POPUP.HANDSHAKE) { - api.removeEventListener('message', handler); + api.removeEventListener('message', messageHandler); resolve(true); } }); @@ -146,7 +147,7 @@ export const initMessageChannelWithIframe = async ( // otherwise close BroadcastChannel and try to use MessageChannel fallback broadcast.close(); broadcast.removeEventListener('message', handler); - } catch (error) { + } catch { // silent error. use MessageChannel as fallback communication } } diff --git a/packages/connect-popup/src/view/confirmation.ts b/packages/connect-popup/src/view/confirmation.ts index 8bf81d8f8a7..f5806df33cf 100644 --- a/packages/connect-popup/src/view/confirmation.ts +++ b/packages/connect-popup/src/view/confirmation.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/confirmation.js import { UI, createUiResponse, UiRequestConfirmation } from '@trezor/connect'; + import { container, showView, postMessage } from './common'; export const initConfirmationView = (data: UiRequestConfirmation['payload']) => { diff --git a/packages/connect-popup/src/view/firmwareNotCompatible.ts b/packages/connect-popup/src/view/firmwareNotCompatible.ts index 5844b275cf3..1378f0f6ba7 100644 --- a/packages/connect-popup/src/view/firmwareNotCompatible.ts +++ b/packages/connect-popup/src/view/firmwareNotCompatible.ts @@ -1,9 +1,10 @@ // origin https://github.com/trezor/connect/blob/develop/src/js/popup/view/firmwareNotCompatible.js import { UI, createUiResponse, UiRequestUnexpectedDeviceMode } from '@trezor/connect'; -import { showView, postMessage, getState } from './common'; import { getFirmwareVersion } from '@trezor/device-utils'; +import { showView, postMessage, getState } from './common'; + export const firmwareNotCompatible = (device: UiRequestUnexpectedDeviceMode['payload']) => { const view = showView('firmware-not-compatible'); diff --git a/packages/connect-popup/src/view/firmwareNotSupported.ts b/packages/connect-popup/src/view/firmwareNotSupported.ts index f59ad2fd9d3..5ada49d8373 100644 --- a/packages/connect-popup/src/view/firmwareNotSupported.ts +++ b/packages/connect-popup/src/view/firmwareNotSupported.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/firmwareNotSupported.js import { UiRequestUnexpectedDeviceMode } from '@trezor/connect'; + import { showView } from './common'; export const firmwareNotSupported = (device: UiRequestUnexpectedDeviceMode['payload']) => { diff --git a/packages/connect-popup/src/view/firmwareRequiredUpdate.ts b/packages/connect-popup/src/view/firmwareRequiredUpdate.ts index 4d7001a708f..411d740b419 100644 --- a/packages/connect-popup/src/view/firmwareRequiredUpdate.ts +++ b/packages/connect-popup/src/view/firmwareRequiredUpdate.ts @@ -2,6 +2,7 @@ import { UiRequestUnexpectedDeviceMode } from '@trezor/connect'; import { SUITE_FIRMWARE_URL } from '@trezor/urls'; + import { showView } from './common'; export const firmwareRequiredUpdate = (device: UiRequestUnexpectedDeviceMode['payload']) => { diff --git a/packages/connect-popup/src/view/invalidPassphrase.ts b/packages/connect-popup/src/view/invalidPassphrase.ts index f33978b16fb..5924c0e6333 100644 --- a/packages/connect-popup/src/view/invalidPassphrase.ts +++ b/packages/connect-popup/src/view/invalidPassphrase.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/invalidPassphrase.js import { UI, createUiResponse, UiRequestDeviceAction } from '@trezor/connect'; + import { container, showView, postMessage } from './common'; export const initInvalidPassphraseView = (_payload: UiRequestDeviceAction['payload']) => { diff --git a/packages/connect-popup/src/view/passphraseOnDevice.ts b/packages/connect-popup/src/view/passphraseOnDevice.ts index 36f4cbd2e54..09776bdfa97 100644 --- a/packages/connect-popup/src/view/passphraseOnDevice.ts +++ b/packages/connect-popup/src/view/passphraseOnDevice.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/passphraseOnDevice.js import { UiRequestDeviceAction } from '@trezor/connect'; + import { container, showView } from './common'; export const passphraseOnDeviceView = (payload: UiRequestDeviceAction['payload']) => { diff --git a/packages/connect-popup/src/view/permissions.ts b/packages/connect-popup/src/view/permissions.ts index dfaf71a6499..cc2d80f2b95 100644 --- a/packages/connect-popup/src/view/permissions.ts +++ b/packages/connect-popup/src/view/permissions.ts @@ -2,6 +2,7 @@ import { UI, createUiResponse, UiRequestPermission } from '@trezor/connect'; import { analytics, EventType } from '@trezor/connect-analytics'; + import { container, showView, postMessage, createTooltip, getState } from './common'; const getPermissionText = (permissionType: string, _deviceName: string) => { diff --git a/packages/connect-popup/src/view/pin.ts b/packages/connect-popup/src/view/pin.ts index 65c4a592197..826769368e2 100644 --- a/packages/connect-popup/src/view/pin.ts +++ b/packages/connect-popup/src/view/pin.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/pin.js import { UI, createUiResponse, UiRequestDeviceAction } from '@trezor/connect'; + import { container, showView, postMessage } from './common'; const isSubmitButtonDisabled = (isDisabled: boolean) => { diff --git a/packages/connect-popup/src/view/react/StylesSheetWrapper.tsx b/packages/connect-popup/src/view/react/StylesSheetWrapper.tsx index 403b9af421e..a9b2af0faf5 100644 --- a/packages/connect-popup/src/view/react/StylesSheetWrapper.tsx +++ b/packages/connect-popup/src/view/react/StylesSheetWrapper.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react'; + import { StyleSheetManager } from 'styled-components'; interface StyleSheetWrapperProps { diff --git a/packages/connect-popup/src/view/requestButton.ts b/packages/connect-popup/src/view/requestButton.ts index 5f2a0e5fe83..44924f86d71 100644 --- a/packages/connect-popup/src/view/requestButton.ts +++ b/packages/connect-popup/src/view/requestButton.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/requestButton.js import { UiRequestButton } from '@trezor/connect'; + import { container, showView } from './common'; let toastTimeout: NodeJS.Timeout | undefined; diff --git a/packages/connect-popup/src/view/selectAccount.ts b/packages/connect-popup/src/view/selectAccount.ts index 25765e34e0a..32a0f6cf51c 100644 --- a/packages/connect-popup/src/view/selectAccount.ts +++ b/packages/connect-popup/src/view/selectAccount.ts @@ -7,6 +7,7 @@ import { UiRequestSelectAccount, DiscoveryAccountType, } from '@trezor/connect'; + import { container, showView, postMessage } from './common'; const setHeader = (payload: UiRequestSelectAccount['payload']) => { diff --git a/packages/connect-popup/src/view/selectDevice.ts b/packages/connect-popup/src/view/selectDevice.ts index 5f46ad7f5bd..9f9e231a42a 100644 --- a/packages/connect-popup/src/view/selectDevice.ts +++ b/packages/connect-popup/src/view/selectDevice.ts @@ -12,9 +12,10 @@ import { } from '@trezor/connect'; import { TREZOR_USB_DESCRIPTORS } from '@trezor/transport/src/constants'; import { SUITE_URL, SUITE_UDEV_URL, TREZOR_SUPPORT_URL } from '@trezor/urls'; -import { container, getState, showView, postMessage } from './common'; import { reactEventBus } from '@trezor/connect-ui/src/utils/eventBus'; +import { container, getState, showView, postMessage } from './common'; + const initWebUsbButton = (showLoader: boolean) => { const webusbContainer = container.getElementsByClassName('webusb')[0] as HTMLElement; webusbContainer.style.display = 'flex'; @@ -57,6 +58,7 @@ const initWebUsbButton = (showLoader: boolean) => { filters: TREZOR_USB_DESCRIPTORS, }); + // eslint-disable-next-line @typescript-eslint/no-shadow const { iframe, core } = getState(); if (iframe) { diff --git a/packages/connect-popup/src/view/selectFee.ts b/packages/connect-popup/src/view/selectFee.ts index 255f110009c..41baf35336c 100644 --- a/packages/connect-popup/src/view/selectFee.ts +++ b/packages/connect-popup/src/view/selectFee.ts @@ -9,6 +9,7 @@ import { SelectFeeLevel, } from '@trezor/connect'; import { formatAmount, formatTime } from '@trezor/connect/src/utils/formatUtils'; + import { container, showView, postMessage } from './common'; const fees: SelectFeeLevel[] = []; diff --git a/packages/connect-popup/src/view/word.ts b/packages/connect-popup/src/view/word.ts index 0db8f117ae3..4c91de58cab 100644 --- a/packages/connect-popup/src/view/word.ts +++ b/packages/connect-popup/src/view/word.ts @@ -1,9 +1,10 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/view/word.js import { UI, createUiResponse, UiRequestDeviceAction } from '@trezor/connect'; -import { container, showView, postMessage } from './common'; import { bip39 } from '@trezor/crypto-utils'; +import { container, showView, postMessage } from './common'; + const initWordPlainView = (payload: UiRequestDeviceAction['payload']) => { showView('word-plain'); diff --git a/packages/connect-popup/tsconfig.json b/packages/connect-popup/tsconfig.json index cf08ba98670..0e722bc91b0 100644 --- a/packages/connect-popup/tsconfig.json +++ b/packages/connect-popup/tsconfig.json @@ -16,6 +16,7 @@ { "path": "../device-utils" }, { "path": "../transport" }, { "path": "../urls" }, + { "path": "../eslint" }, { "path": "../node-utils" }, { "path": "../trezor-user-env-link" }, { "path": "../utils" } diff --git a/packages/connect-popup/webpack/dev.webpack.config.ts b/packages/connect-popup/webpack/dev.webpack.config.ts index 0455850df4b..85aa832852d 100644 --- a/packages/connect-popup/webpack/dev.webpack.config.ts +++ b/packages/connect-popup/webpack/dev.webpack.config.ts @@ -1,6 +1,7 @@ import path from 'path'; import { merge } from 'webpack-merge'; import { WebpackPluginServe } from 'webpack-plugin-serve'; + import prod from './prod.webpack.config'; const dev = { @@ -19,4 +20,5 @@ const dev = { ], }; +// eslint-disable-next-line import/no-default-export export default merge([prod, dev]); diff --git a/packages/connect-popup/webpack/prod.webpack.config.ts b/packages/connect-popup/webpack/prod.webpack.config.ts index 3840b4b4fbe..0d52b98006c 100644 --- a/packages/connect-popup/webpack/prod.webpack.config.ts +++ b/packages/connect-popup/webpack/prod.webpack.config.ts @@ -3,9 +3,12 @@ import webpack, { DefinePlugin } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; +import { execSync } from 'child_process'; + import * as URLS from '@trezor/urls'; +import { DEEPLINK_VERSION } from '@trezor/connect/src/data/version'; + import { version } from '../package.json'; -import { execSync } from 'child_process'; const STATIC_SRC = path.join(__dirname, '../src/static'); const DIST = path.resolve(__dirname, '../build'); @@ -83,14 +86,19 @@ const config: webpack.Configuration = { minify: false, urls: URLS, }), - new HtmlWebpackPlugin({ - chunks: ['deeplink'], - template: `${STATIC_SRC}/deeplink.html`, - filename: 'deeplink/index.html', - inject: false, - minify: false, - urls: URLS, - }), + // deeplink fallback page for all versions + ...Array.from( + { length: DEEPLINK_VERSION }, + (_, i) => + new HtmlWebpackPlugin({ + chunks: ['deeplink'], + template: `${STATIC_SRC}/deeplink.html`, + filename: `deeplink/${i + 1}/index.html`, + inject: false, + minify: false, + urls: URLS, + }), + ), new HtmlWebpackPlugin({ chunks: ['log'], template: `${STATIC_SRC}/log.html`, @@ -143,4 +151,5 @@ const config: webpack.Configuration = { }, }; +// eslint-disable-next-line import/no-default-export export default config; diff --git a/packages/connect-ui/package.json b/packages/connect-ui/package.json index 921660f6fca..7b0fdc33df9 100644 --- a/packages/connect-ui/package.json +++ b/packages/connect-ui/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "lint:styles": "npx stylelint './src/**/*{.ts,.tsx}' --cache --config ../../.stylelintrc", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" diff --git a/packages/connect-ui/src/components/Passphrase/PassphraseTypeCard.tsx b/packages/connect-ui/src/components/Passphrase/PassphraseTypeCard.tsx index bad33322e7d..3136dbb7f01 100644 --- a/packages/connect-ui/src/components/Passphrase/PassphraseTypeCard.tsx +++ b/packages/connect-ui/src/components/Passphrase/PassphraseTypeCard.tsx @@ -363,6 +363,7 @@ export const PassphraseTypeCard = (props: PassphraseTypeCardLegacyProps) => { ) : null } inputState={isTooLong ? 'error' : undefined} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={!isAndroid()} innerAddon={ { icon: 'question', title: 'Action not completed', detail: { - steps: [{props.message}], + steps: [ + props.code === 'Device_MissingCapabilityBtcOnly' ? ( + <> + {props.message} + + + ) : ( + {props.message} + ), + ], }, }); } diff --git a/packages/connect-web/.eslintrc.js b/packages/connect-web/.eslintrc.js deleted file mode 100644 index a4fa9fb2181..00000000000 --- a/packages/connect-web/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - rules: { - 'no-underscore-dangle': 'off', // underscore is used - camelcase: 'off', // camelcase is used - }, -}; diff --git a/packages/connect-web/README.md b/packages/connect-web/README.md index b466017c9a4..f8a7e674524 100644 --- a/packages/connect-web/README.md +++ b/packages/connect-web/README.md @@ -54,3 +54,18 @@ For more instructions [refer to this document](https://github.com/trezor/trezor- - install node_modules: `yarn && yarn build:libs` - generate certs `yarn workspace @trezor/connect-web predev` - It is possible to run local dev server with iframe and popup using: `yarn workspace @trezor/connect-web dev` Note: don't forget to visit `https://localhost:8088/` and allow self-signed certificate. No UI is displayed here. + +## TrezorConnect Support Matrix + +The table below details the support for different environments by TrezorConnect for integrating Trezor devices, including the use of WebUSB and the need for Trezor Bridge. + +| Environment | Chrome | Firefox | Safari | Chrome Android | Firefox Android | Notes | +| ----------------------------- | :----: | :-----: | :----: | :------------: | :-------------: | ----------------------------------------------------------------------- | +| Web (WebUSB) | ✓ | ✗ | ✗ | ✓ | ✗ | WebUSB is fully supported where indicated. (Chromium based browsers) | +| Web (Bridge) | ✓ | ✓ | ✗ | ✗ | ✗ | Trezor Bridge is required where WebUSB is not supported. (e.g. Firefox) | +| WebExtension (WebUSB, Bridge) | ✓ | ✓ | ✗ | ✓ | ✗ | Requires Trezor Bridge on platforms not supporting WebUSB. | + +## Key Differences + +- **WebUSB**: Allows direct communication with Trezor devices via the browser. Supported by most modern browsers but may have limitations on mobile devices and is not supported by Safari. +- **Trezor Bridge**: A service that runs with Trezor Suite or Standalone that facilitates communication between your Trezor device and a web browser. Required for browsers that do not support WebUSB or for a more stable connection on desktop environments. diff --git a/packages/connect-web/eslint.config.mjs b/packages/connect-web/eslint.config.mjs new file mode 100644 index 00000000000..7000dd889a8 --- /dev/null +++ b/packages/connect-web/eslint.config.mjs @@ -0,0 +1,22 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-underscore-dangle': 'off', // underscore is used + camelcase: 'off', // camelcase is used + 'jest/valid-expect': 'off', // because of cypress tests + 'import/no-default-export': 'off', // Todo: shall be fixed + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-web/package.json b/packages/connect-web/package.json index fb194105987..f2de9a32b6c 100644 --- a/packages/connect-web/package.json +++ b/packages/connect-web/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/connect-web", - "version": "9.4.2", + "version": "9.4.3-beta.2", "author": "Trezor ", "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/connect-web", "description": "High-level javascript interface for Trezor hardware wallet in web environment.", @@ -29,7 +29,6 @@ ], "scripts": { "predev": "node webpack/generate_dev_cert.js", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "test:unit": "yarn g:jest", @@ -53,7 +52,8 @@ "@playwright/browser-firefox": "^1.46.1", "@playwright/browser-webkit": "^1.46.1", "@playwright/test": "^1.46.1", - "@types/chrome": "^0.0.270", + "@trezor/eslint": "workspace:*", + "@types/chrome": "^0.0.278", "@types/w3c-web-usb": "^1.0.10", "babel-loader": "^9.1.3", "html-webpack-plugin": "^5.6.0", @@ -61,7 +61,7 @@ "selfsigned": "^2.4.1", "terser-webpack-plugin": "^5.3.9", "tsx": "^4.16.3", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-merge": "^6.0.1", "webpack-plugin-serve": "^1.6.0", diff --git a/packages/connect-web/src/iframe/index.ts b/packages/connect-web/src/iframe/index.ts index b134ec3c80b..da4fd0b4cd1 100644 --- a/packages/connect-web/src/iframe/index.ts +++ b/packages/connect-web/src/iframe/index.ts @@ -6,6 +6,7 @@ import { IFRAME, CoreRequestMessage } from '@trezor/connect/src/events'; import type { ConnectSettings } from '@trezor/connect/src/types'; import { getOrigin } from '@trezor/connect/src/utils/urlUtils'; import { setLogWriter, LogMessage, LogWriter } from '@trezor/connect/src/utils/debug'; + import css from './inlineStyles'; export let instance: HTMLIFrameElement | null; @@ -18,7 +19,7 @@ export const dispose = () => { if (instance && instance.parentNode) { try { instance.parentNode.removeChild(instance); - } catch (e) { + } catch { // do nothing } } @@ -85,9 +86,6 @@ export const init = async (settings: ConnectSettings) => { } instance.setAttribute('src', src); - if (settings.webusb) { - console.warn('webusb option is deprecated. use `transports: ["WebUsbTransport"] instead`'); - } if (navigator.usb) { instance.setAttribute('allow', 'usb'); } @@ -111,7 +109,7 @@ export const init = async (settings: ConnectSettings) => { return; } - } catch (e) { + } catch { // empty } diff --git a/packages/connect-web/src/impl/core-in-iframe.ts b/packages/connect-web/src/impl/core-in-iframe.ts index f6efc1dc298..a4e19acbd26 100644 --- a/packages/connect-web/src/impl/core-in-iframe.ts +++ b/packages/connect-web/src/impl/core-in-iframe.ts @@ -19,18 +19,24 @@ import { CoreEventMessage, CallMethodPayload, } from '@trezor/connect/src/events'; -import type { ConnectSettings, DeviceIdentity, Manifest } from '@trezor/connect/src/types'; +import type { + ConnectSettings, + ConnectSettingsWeb, + DeviceIdentity, + Manifest, +} from '@trezor/connect/src/types'; import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory'; import { Log, initLog } from '@trezor/connect/src/utils/debug'; import { config } from '@trezor/connect/src/data/config'; import { DeferredManager, createDeferredManager } from '@trezor/utils/src/createDeferredManager'; +import { InitFullSettings } from '@trezor/connect/src/types/api/init'; import * as iframe from '../iframe'; import * as popup from '../popup'; import webUSBButton from '../webusb/button'; import { parseConnectSettings } from '../connectSettings'; -export class CoreInIframe implements ConnectFactoryDependencies { +export class CoreInIframe implements ConnectFactoryDependencies { public eventEmitter = new EventEmitter(); protected _settings: ConnectSettings; @@ -149,7 +155,7 @@ export class CoreInIframe implements ConnectFactoryDependencies { } } - public async init(settings: Partial = {}) { + public async init(settings: InitFullSettings) { if (iframe.instance) { throw ERRORS.TypedError('Init_AlreadyInitialized'); } @@ -221,6 +227,7 @@ export class CoreInIframe implements ConnectFactoryDependencies { // auto init with default settings try { + // @ts-expect-error manifest could have been changed in the meantime, hard to check this await this.init(this._settings); } catch (error) { if (this._popupManager) { @@ -341,7 +348,7 @@ export class CoreInIframe implements ConnectFactoryDependencies { try { await window.navigator.usb.requestDevice({ filters: config.webusb }); iframe.postMessage({ type: TRANSPORT.REQUEST_DEVICE }); - } catch (_err) { + } catch { // user hits cancel gets "DOMException: No device selected." // no need to log this } @@ -351,17 +358,21 @@ export class CoreInIframe implements ConnectFactoryDependencies { const impl = new CoreInIframe(); // Exported to enable using directly -export const TrezorConnect = factory({ - // Bind all methods due to shadowing `this` - eventEmitter: impl.eventEmitter, - init: impl.init.bind(impl), - call: impl.call.bind(impl), - manifest: impl.manifest.bind(impl), - requestLogin: impl.requestLogin.bind(impl), - uiResponse: impl.uiResponse.bind(impl), - renderWebUSBButton: impl.renderWebUSBButton.bind(impl), - disableWebUSB: impl.disableWebUSB.bind(impl), - requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl), - cancel: impl.cancel.bind(impl), - dispose: impl.dispose.bind(impl), -}); +export const TrezorConnect = factory( + { + // Bind all methods due to shadowing `this` + eventEmitter: impl.eventEmitter, + init: impl.init.bind(impl), + call: impl.call.bind(impl), + manifest: impl.manifest.bind(impl), + requestLogin: impl.requestLogin.bind(impl), + uiResponse: impl.uiResponse.bind(impl), + cancel: impl.cancel.bind(impl), + dispose: impl.dispose.bind(impl), + }, + { + renderWebUSBButton: impl.renderWebUSBButton.bind(impl), + disableWebUSB: impl.disableWebUSB.bind(impl), + requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl), + }, +); diff --git a/packages/connect-web/src/impl/core-in-popup.ts b/packages/connect-web/src/impl/core-in-popup.ts index 58732b4071b..966b202c94b 100644 --- a/packages/connect-web/src/impl/core-in-popup.ts +++ b/packages/connect-web/src/impl/core-in-popup.ts @@ -13,23 +13,24 @@ import { import * as ERRORS from '@trezor/connect/src/constants/errors'; import type { ConnectSettings, - ConnectSettingsPublic, + ConnectSettingsWeb, Manifest, Response, } from '@trezor/connect/src/types'; import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory'; import { initLog, setLogWriter, LogMessage, LogWriter, Log } from '@trezor/connect/src/utils/debug'; -import * as popup from '../popup'; - -import { parseConnectSettings } from '../connectSettings'; import { Login } from '@trezor/connect/src/types/api/requestLogin'; import { createDeferred } from '@trezor/utils'; +import { InitFullSettings } from '@trezor/connect/src/types/api/init'; + +import { parseConnectSettings } from '../connectSettings'; +import * as popup from '../popup'; /** * Base class for CoreInPopup methods for TrezorConnect factory. * This implementation is directly used here in connect-web, but it is also extended in connect-webextension. */ -export class CoreInPopup implements ConnectFactoryDependencies { +export class CoreInPopup implements ConnectFactoryDependencies { public eventEmitter = new EventEmitter(); protected _settings: ConnectSettings; @@ -81,7 +82,7 @@ export class CoreInPopup implements ConnectFactoryDependencies { } } - public init(settings: Partial = {}): Promise { + public init(settings: InitFullSettings<{}>): Promise { const oldSettings = parseConnectSettings({ ...this._settings, }); @@ -240,9 +241,6 @@ export const TrezorConnect = factory({ manifest: impl.manifest.bind(impl), requestLogin: impl.requestLogin.bind(impl), uiResponse: impl.uiResponse.bind(impl), - renderWebUSBButton: impl.renderWebUSBButton.bind(impl), - disableWebUSB: impl.disableWebUSB.bind(impl), - requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl), cancel: impl.cancel.bind(impl), dispose: impl.dispose.bind(impl), }); diff --git a/packages/connect-web/src/index.ts b/packages/connect-web/src/index.ts index 7ead4ff22a5..052bf1c0994 100644 --- a/packages/connect-web/src/index.ts +++ b/packages/connect-web/src/index.ts @@ -1,107 +1,60 @@ import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory'; +import { TrezorConnectDynamic } from '@trezor/connect/src/impl/dynamic'; +import type { ConnectSettingsPublic, ConnectSettingsWeb } from '@trezor/connect'; + import { CoreInIframe } from './impl/core-in-iframe'; import { CoreInPopup } from './impl/core-in-popup'; -import ProxyEventEmitter from './utils/proxy-event-emitter'; -import type { ConnectSettings, ConnectSettingsPublic, Manifest } from '@trezor/connect/src/types'; -import EventEmitter from 'events'; -import { CallMethodPayload } from '@trezor/connect/src/events'; import { getEnv } from './connectSettings'; -type TrezorConnectType = 'core-in-popup' | 'iframe'; - const IFRAME_ERRORS = ['Init_IframeBlocked', 'Init_IframeTimeout', 'Transport_Missing']; -/** - * Implementation of TrezorConnect that can dynamically switch between iframe and core-in-popup implementations - */ -export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies { - public eventEmitter: EventEmitter; - - private currentTarget: TrezorConnectType = 'iframe'; - private coreInIframeImpl: CoreInIframe; - private coreInPopupImpl: CoreInPopup; - - private lastSettings?: Partial; - - public constructor() { - this.coreInIframeImpl = new CoreInIframe(); - this.coreInPopupImpl = new CoreInPopup(); - this.eventEmitter = new ProxyEventEmitter([ - this.coreInIframeImpl.eventEmitter, - this.coreInPopupImpl.eventEmitter, - ]); - } - - private getTarget() { - return this.currentTarget === 'iframe' ? this.coreInIframeImpl : this.coreInPopupImpl; - } - - private async switchTarget(target: TrezorConnectType) { - if (this.currentTarget === target) { - return; - } - - await this.getTarget().dispose(); - this.currentTarget = target; - await this.getTarget().init(this.lastSettings); - } - - public manifest(manifest: Manifest) { - this.lastSettings = { - ...this.lastSettings, - manifest, - }; - - this.getTarget().manifest(manifest); - } - - public async init(settings: Partial = {}) { - const env = getEnv(); - if (settings.coreMode === 'iframe' || settings.popup === false || env === 'webextension') { - this.currentTarget = 'iframe'; +type ConnectWebExtraMethods = { + renderWebUSBButton: (className?: string) => void; + disableWebUSB: () => void; + requestWebUSBDevice: () => void; +}; + +const impl = new TrezorConnectDynamic< + 'iframe' | 'core-in-popup', + ConnectSettingsWeb, + ConnectFactoryDependencies & ConnectWebExtraMethods +>({ + implementations: [ + { + type: 'iframe', + impl: new CoreInIframe(), + }, + { + type: 'core-in-popup', + impl: new CoreInPopup(), + }, + ], + getInitTarget: (settings: Partial) => { + if (settings.coreMode === 'iframe') { + return 'iframe'; } else if (settings.coreMode === 'popup') { - this.currentTarget = 'core-in-popup'; + return 'core-in-popup'; } else { - // Default to auto mode with iframe as the first choice - settings.coreMode = 'auto'; - this.currentTarget = 'iframe'; - } - - // Save settings for later use - this.lastSettings = settings; - - // Initialize the target - try { - return await this.getTarget().init(settings); - } catch (error) { - // Handle iframe errors by switching to core-in-popup - if (await this.handleErrorFallback(error.code)) { - return await this.getTarget().init(settings); + if (settings.coreMode && settings.coreMode !== 'auto') { + console.warn(`Invalid coreMode: ${settings.coreMode}`); } - throw error; - } - } - - public async call(params: CallMethodPayload) { - const response = await this.getTarget().call(params); - if (!response.success) { - if (await this.handleErrorFallback(response.payload.code)) { - return await this.getTarget().call(params); - } + return 'iframe'; } + }, + handleErrorFallback: async (errorCode: string) => { + const env = getEnv(); - return response; - } + const isCoreModeDisabled = impl.lastSettings?.popup === false || env === 'webextension'; + const isCoreModeAuto = + impl.lastSettings?.coreMode === 'auto' || impl.lastSettings?.coreMode === undefined; - private async handleErrorFallback(errorCode: string) { // Handle iframe errors by switching to core-in-popup - if (this.lastSettings?.coreMode === 'auto' && IFRAME_ERRORS.includes(errorCode)) { + if (!isCoreModeDisabled && isCoreModeAuto && IFRAME_ERRORS.includes(errorCode)) { // Check if WebUSB is available and enabled - const webUsbUnavailableInBrowser = !navigator.usb; + const webUsbUnavailableInBrowser = !(navigator as any)?.usb; const webUsbDisabledInSettings = - this.lastSettings.transports?.includes('WebUsbTransport') === false || - this.lastSettings.webusb === false; + impl.lastSettings?.transports?.includes('WebUsbTransport') === false; if ( errorCode === 'Transport_Missing' && (webUsbUnavailableInBrowser || webUsbDisabledInSettings) @@ -110,60 +63,32 @@ export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies { return false; } - await this.switchTarget('core-in-popup'); + await impl.switchTarget('core-in-popup'); return true; } return false; - } - - public requestLogin(params: any) { - return this.getTarget().requestLogin(params); - } - - public uiResponse(params: any) { - return this.getTarget().uiResponse(params); - } - - public renderWebUSBButton() { - return this.getTarget().renderWebUSBButton(); - } - - public disableWebUSB() { - return this.getTarget().disableWebUSB(); - } - - public requestWebUSBDevice() { - return this.getTarget().requestWebUSBDevice(); - } - - public cancel(error?: string) { - return this.getTarget().cancel(error); - } - - public dispose() { - this.eventEmitter.removeAllListeners(); - - return this.getTarget().dispose(); - } -} - -const impl = new TrezorConnectDynamicImpl(); - -const TrezorConnect = factory({ - eventEmitter: impl.eventEmitter, - init: impl.init.bind(impl), - call: impl.call.bind(impl), - manifest: impl.manifest.bind(impl), - requestLogin: impl.requestLogin.bind(impl), - uiResponse: impl.uiResponse.bind(impl), - renderWebUSBButton: impl.renderWebUSBButton.bind(impl), - disableWebUSB: impl.disableWebUSB.bind(impl), - requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl), - cancel: impl.cancel.bind(impl), - dispose: impl.dispose.bind(impl), + }, }); +const TrezorConnect = factory( + { + eventEmitter: impl.eventEmitter, + init: impl.init.bind(impl), + call: impl.call.bind(impl), + manifest: impl.manifest.bind(impl), + requestLogin: impl.requestLogin.bind(impl), + uiResponse: impl.uiResponse.bind(impl), + cancel: impl.cancel.bind(impl), + dispose: impl.dispose.bind(impl), + }, + { + renderWebUSBButton: impl.getTarget().renderWebUSBButton.bind(impl), + disableWebUSB: impl.getTarget().disableWebUSB.bind(impl), + requestWebUSBDevice: impl.getTarget().requestWebUSBDevice.bind(impl), + }, +); + export default TrezorConnect; export * from '@trezor/connect/src/exports'; diff --git a/packages/connect-web/src/popup/index.ts b/packages/connect-web/src/popup/index.ts index b76e095c326..1019596eb3f 100644 --- a/packages/connect-web/src/popup/index.ts +++ b/packages/connect-web/src/popup/index.ts @@ -1,19 +1,20 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/popup/PopupManager.js import EventEmitter from 'events'; + import { createDeferred, Deferred, scheduleAction } from '@trezor/utils'; import { POPUP, IFRAME, UI, CoreEventMessage, IFrameLoaded } from '@trezor/connect/src/events'; import type { ConnectSettings } from '@trezor/connect/src/types'; import { getOrigin } from '@trezor/connect/src/utils/urlUtils'; import { CONTENT_SCRIPT_VERSION, VERSION } from '@trezor/connect/src/data/version'; -import { showPopupRequest } from './showPopupRequest'; import { Log } from '@trezor/connect/src/utils/debug'; - -import { ServiceWorkerWindowChannel } from '../channels/serviceworker-window'; import { AbstractMessageChannel, Message, } from '@trezor/connect-common/src/messageChannel/abstract'; + +import { showPopupRequest } from './showPopupRequest'; +import { ServiceWorkerWindowChannel } from '../channels/serviceworker-window'; import { WindowWindowChannel } from '../channels/window-window'; // Util diff --git a/packages/connect-web/src/webextension/README.md b/packages/connect-web/src/webextension/README.md index 77dedb276f0..5c5a5b494ab 100644 --- a/packages/connect-web/src/webextension/README.md +++ b/packages/connect-web/src/webextension/README.md @@ -1,6 +1,6 @@ # Integrate @trezor/connect with a web extension -**Note: only manifest version 2 is supported for now** +**Note: Only manifest version 2 web extensions are supported by this package. For manifest version 3, use the [@trezor/connect-webextension](https://github.com/trezor/trezor-suite/tree/develop/packages/connect-webextension) package** [Example of a web extension project for both Google Chrome & Firefox](../../../connect-examples/webextension/README.md) diff --git a/packages/connect-web/src/webextension/extensionPermissions.ts b/packages/connect-web/src/webextension/extensionPermissions.ts index 5a84a4ff116..a71c7ceca58 100644 --- a/packages/connect-web/src/webextension/extensionPermissions.ts +++ b/packages/connect-web/src/webextension/extensionPermissions.ts @@ -35,7 +35,7 @@ const init = (label: string) => { await usb.requestDevice({ filters: config.webusb }); sendMessage(WEBEXTENSION.USB_PERMISSIONS_CLOSE, '*'); broadcastPermissionFinished(); - } catch (error) { + } catch { // empty } } diff --git a/packages/connect-web/src/webextension/trezor-usb-permissions.js b/packages/connect-web/src/webextension/trezor-usb-permissions.js index 9d56e26e1c4..2ae008707d3 100644 --- a/packages/connect-web/src/webextension/trezor-usb-permissions.js +++ b/packages/connect-web/src/webextension/trezor-usb-permissions.js @@ -1,4 +1,4 @@ -const VERSION = '9.4.2'; +const VERSION = '9.4.3-beta.2'; const versionN = VERSION.split('.').map(s => parseInt(s, 10)); const isBeta = VERSION.includes('beta'); diff --git a/packages/connect-web/src/webusb/index.ts b/packages/connect-web/src/webusb/index.ts index 4a53f8f6fbe..bb43f1ee252 100644 --- a/packages/connect-web/src/webusb/index.ts +++ b/packages/connect-web/src/webusb/index.ts @@ -16,7 +16,7 @@ const onload = () => { if (usb) { try { await usb.requestDevice({ filters: config.webusb }); - } catch (error) { + } catch { // empty } } diff --git a/packages/connect-web/tsconfig.json b/packages/connect-web/tsconfig.json index a9b9b19bebd..33ef6527d75 100644 --- a/packages/connect-web/tsconfig.json +++ b/packages/connect-web/tsconfig.json @@ -23,6 +23,7 @@ "references": [ { "path": "../connect" }, { "path": "../connect-common" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../eslint" } ] } diff --git a/packages/connect-web/tsconfig.lib.json b/packages/connect-web/tsconfig.lib.json index 28965c9985a..0120e049f8f 100644 --- a/packages/connect-web/tsconfig.lib.json +++ b/packages/connect-web/tsconfig.lib.json @@ -15,6 +15,9 @@ }, { "path": "../utils" + }, + { + "path": "../eslint" } ] } diff --git a/packages/connect-web/webpack/inline.webpack.config.ts b/packages/connect-web/webpack/inline.webpack.config.ts index 1dd8abc4874..0e5101d024e 100644 --- a/packages/connect-web/webpack/inline.webpack.config.ts +++ b/packages/connect-web/webpack/inline.webpack.config.ts @@ -1,6 +1,7 @@ import webpack from 'webpack'; import path from 'path'; import TerserPlugin from 'terser-webpack-plugin'; + import prod from './prod.webpack.config'; // Generate inline script hosted on https://connect.trezor.io/X/trezor-connect.js diff --git a/packages/connect-webextension/.eslintrc.js b/packages/connect-webextension/.eslintrc.js deleted file mode 100644 index e67123643c6..00000000000 --- a/packages/connect-webextension/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - 'no-underscore-dangle': 'off', // underscore is used - }, -}; diff --git a/packages/connect-webextension/README.md b/packages/connect-webextension/README.md index 2ba76ca491f..fff1e52e097 100644 --- a/packages/connect-webextension/README.md +++ b/packages/connect-webextension/README.md @@ -4,7 +4,7 @@ [![NPM](https://img.shields.io/npm/v/@trezor/connect-webextension.svg)](https://www.npmjs.org/package/@trezor/connect-webextension) [![Known Vulnerabilities](https://snyk.io/test/github/trezor/connect-webextension/badge.svg?targetFile=package.json)](https://snyk.io/test/github/trezor/trezor-suite?targetFile=packages/connect-webextension/package.json) -The @trezor/connect-webextension package provides an implementation of @trezor/connect designed specifically for use within web extensions. Key features include: +The `@trezor/connect-webextension` package provides an implementation of `@trezor/connect` designed specifically for use within web extensions. Key features include: - Compatibility with service worker environments. - Full access to the TrezorConnect API. @@ -24,7 +24,7 @@ For a seamless integration, especially with background processes, modify your ex "host_permissions": ["*://connect.trezor.io/9/*"] "background": { "service_worker": "serviceWorker.js" - }, + } ``` The content script will be injected automatically by the library using the scripting permission. diff --git a/packages/connect-webextension/eslint.config.mjs b/packages/connect-webextension/eslint.config.mjs new file mode 100644 index 00000000000..f7251bade75 --- /dev/null +++ b/packages/connect-webextension/eslint.config.mjs @@ -0,0 +1,19 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-underscore-dangle': 'off', // underscore is used + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/webpack/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/connect-webextension/package.json b/packages/connect-webextension/package.json index 1bd1ccd2fd7..6d9fc98c0e5 100644 --- a/packages/connect-webextension/package.json +++ b/packages/connect-webextension/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/connect-webextension", - "version": "9.4.2", + "version": "9.4.3-beta.2", "author": "Trezor ", "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/connect-webextension", "description": "High-level javascript interface for Trezor hardware wallet in webextension serviceworker environment.", @@ -30,7 +30,6 @@ "!lib/proxy" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", "build:content-script": "TS_NODE_PROJECT=\"tsconfig.lib.json\" webpack --config ./webpack/content-script.webpack.config.ts", "build:inline": "TS_NODE_PROJECT=\"tsconfig.lib.json\" webpack --config ./webpack/inline.webpack.config.ts", @@ -46,14 +45,15 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.24.7", + "@trezor/eslint": "workspace:*", "@trezor/node-utils": "workspace:*", "@trezor/trezor-user-env-link": "workspace:*", - "@types/chrome": "^0.0.270", + "@types/chrome": "^0.0.278", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "rimraf": "^6.0.1", "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-merge": "^6.0.1", "webpack-plugin-serve": "^1.6.0", diff --git a/packages/connect-webextension/src/index.ts b/packages/connect-webextension/src/index.ts index c71ce17a669..9258cb261b9 100644 --- a/packages/connect-webextension/src/index.ts +++ b/packages/connect-webextension/src/index.ts @@ -3,13 +3,14 @@ import { POPUP, ConnectSettings, Manifest, - ConnectSettingsPublic, + ConnectSettingsWebextension, } from '@trezor/connect/src/exports'; import { factory } from '@trezor/connect/src/factory'; import { initLog } from '@trezor/connect/src/utils/debug'; // Import as src not lib due to webpack issues with inlining content script later import { ServiceWorkerWindowChannel } from '@trezor/connect-web/src/channels/serviceworker-window'; import { CoreInPopup } from '@trezor/connect-web/src/impl/core-in-popup'; +import { InitFullSettings } from '@trezor/connect/src/types/api/init'; import { parseConnectSettings } from './connectSettings'; @@ -41,7 +42,7 @@ class CoreInPopupWebextension extends CoreInPopup { this.popupManagerLogger = initLog('@trezor/connect-webextension/popupManager'); } - public init(settings: Partial = {}): Promise { + public init(settings: InitFullSettings): Promise { if (settings._extendWebextensionLifetime) { extendLifetime(); } @@ -59,9 +60,6 @@ const TrezorConnect = factory({ manifest: methods.manifest.bind(methods), requestLogin: methods.requestLogin.bind(methods), uiResponse: methods.uiResponse.bind(methods), - renderWebUSBButton: methods.renderWebUSBButton.bind(methods), - disableWebUSB: methods.disableWebUSB.bind(methods), - requestWebUSBDevice: methods.requestWebUSBDevice.bind(methods), cancel: methods.cancel.bind(methods), dispose: methods.dispose.bind(methods), }); diff --git a/packages/connect-webextension/src/proxy/index.ts b/packages/connect-webextension/src/proxy/index.ts index e019c003a43..209a4b2f501 100644 --- a/packages/connect-webextension/src/proxy/index.ts +++ b/packages/connect-webextension/src/proxy/index.ts @@ -103,26 +103,11 @@ const uiResponse = () => { throw ERRORS.TypedError('Method_InvalidPackage'); }; -const renderWebUSBButton = () => { - // Not needed here - webUSB pairing happens in popup. - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - const requestLogin = () => { // Not needed here - Not used here. throw ERRORS.TypedError('Method_InvalidPackage'); }; -const disableWebUSB = () => { - // Not needed here - webUSB pairing happens in popup. - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - -const requestWebUSBDevice = () => { - // Not needed here - webUSB pairing happens in popup. - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - const TrezorConnect = factory({ eventEmitter, manifest, @@ -130,9 +115,6 @@ const TrezorConnect = factory({ call, requestLogin, uiResponse, - renderWebUSBButton, - disableWebUSB, - requestWebUSBDevice, cancel, dispose, }); diff --git a/packages/connect-webextension/tsconfig.json b/packages/connect-webextension/tsconfig.json index 1de2775067c..865c4a1398f 100644 --- a/packages/connect-webextension/tsconfig.json +++ b/packages/connect-webextension/tsconfig.json @@ -11,6 +11,7 @@ { "path": "../connect-common" }, { "path": "../connect-web" }, { "path": "../utils" }, + { "path": "../eslint" }, { "path": "../node-utils" }, { "path": "../trezor-user-env-link" } ] diff --git a/packages/connect-webextension/tsconfig.lib.json b/packages/connect-webextension/tsconfig.lib.json index 2600ac9935e..b2f9e227332 100644 --- a/packages/connect-webextension/tsconfig.lib.json +++ b/packages/connect-webextension/tsconfig.lib.json @@ -19,6 +19,9 @@ { "path": "../utils" }, + { + "path": "../eslint" + }, { "path": "../node-utils" }, diff --git a/packages/connect/.eslintrc.js b/packages/connect/.eslintrc.js deleted file mode 100644 index 0de74deae0e..00000000000 --- a/packages/connect/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - rules: { - 'no-bitwise': 'off', // airbnb-base: used in hardending - 'no-underscore-dangle': 'off', // underscore is used - camelcase: 'off', // camelcase is used - 'no-console': 'warn', - 'no-await-in-loop': 'off', // used in legacy trezor-connect codebase - }, -}; diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index 0ab12f364f2..d790b336876 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -1,15 +1,96 @@ -| Package | Stable | Canary | -| :------------------------------: | :----: | :----: | -| npm @trezor/connect | 9.4.2 | - | -| npm @trezor/connect-web | 9.4.2 | - | -| npm @trezor/connect-webextension | 9.4.2 | - | +| Package | Stable | Canary | +| :------------------------------: | :----: | :----------: | +| npm @trezor/connect | 9.4.2 | 9.4.3-beta.2 | +| npm @trezor/connect-web | 9.4.2 | 9.4.3-beta.2 | +| npm @trezor/connect-webextension | 9.4.2 | 9.4.3-beta.2 | -| Deployment | Stable | Canary | -| :----------------: | :----: | :----: | -| connect.trezor.io/ | 9.4.2 | - | +| Deployment | Stable | Canary | +| :----------------: | :----: | :----------: | +| connect.trezor.io/ | 9.4.2 | 9.4.3-beta.2 | Use the persistent link [connect.trezor.io/9](https://connect.trezor.io/9/) to access the latest stable version of Connect Explorer. +# 9.4.3-beta.2 + +## Feature + +- new (non public) method TrezorConnect.loadDevice (14aec5d5b6, f8a47401ec, 0d5b592ae7, e46c95ef9d, 18b5cbc9d3, 580f3a9325) +- make TrezorConnect.init transportReconnect implicitly true (19aeb1a0f7) +- in connect popup improve error handling screen for missing capabilities in FW (d2c8870944) +- Support for t3w1 support in types (f98990dfa0, 01cdee48f1, e9827eeaa8) +- remove auto-acquire after device was released elsewhere (dfa070fa2d, cdd2ac1ec7) +- implement random strategy for utxo sorting (6c3137fdf8) +- new public method TrezorConnect.blockchainEvmRpcCall (a507d1322a) +- make `manifest.appUrl` available as part of `Device.transportSessionOwner` (c98f7f8bf4) + +## Fixes + +- local url in mobile example (e4365278aa) +- emit changed event with state change in keepSession (f8bcaf7203) +- don't trump Cancel with DeviceCommand disposed (d2f876e47c) +- WebUSB sessions sync in all deployments (13ec59a53d) +- skip fw hash check when binary too tiny (DX) (584d742ccc) +- usedElsewhere only for localSession (8fc98762ec) +- device.unreadableError is always string (aeab689d34) +- api.dispose on transport.stop (e79130e88e) +- analytics for core in popup (f08deff345) +- deeplink core mode from options (3eb7512b89) + +## Chores and improvements + +- try to reuse initialized (passphrase) session (422476c9c0) +- uncouple enableFirmwareHashCheck setting from binFilesBaseUrl (768596bbe5) +- add sortingStrategy param to connect, deprecate skipPermutation param in composeTransaction (283d3f1eae) +- Eslint improvements (1ad7b6f9b1, 12b6fb18b9, f1bdc399e5, e22b683733, 5d1104bfeb, 4a3cdc7893, 5e33e3abb5) +- add recommended checks from eslint-plugin-jest (55d + +### Dependencies update + +- chore(connect): update @ethereumjs libs (2d6465f, d6bc8c5020) +- bump electron,electron-builder and webpack (2b80c45c5f, e7a980bcf3, fc04fea1de) + +# 9.4.3-beta.1 + +This release introduces `@trezor/connect-mobile`, which allows mobile apps to integrate with Trezor Suite mobile using deeplinks. +This package is still beta and work in progress. + +### Connect Mobile related changes + +- chore(connect-mobile): update package.json for publishing (fb157ef318) +- feat(connect-explorer): hiding beta-only content (21e0d42c6d) +- feat(connect-explorer): call deeplinks from method tester (f2a5f7460e) +- docs(connect): mobile connect popup (9f8f1c1bd2) +- chore(connect-popup): move deeplink fallback page to versioned url (e86e6aa5b3) +- chore: rename connect-deeplink to connect-mobile (d1c2bd7464) +- feat(connect): use separate versioning for deeplink protocol (40f3b53a34) +- feat(module-connect-popup): add version check to app link parser (fe8499ec1c) +- chore(connect-examples): change from deeplinkUrl to connectSrc (1ff479c545) +- feat(connect): take deeplinkUrl from connectSrc (1b88b3b72b) + +### Other changes + +- refactor(connect-web): use dynamic from connect package (887889288f) +- refactor(connect): dynamic connect (96e81fe5bf) +- fix(connect): make getBinaryOptional async (e583e84c32) +- test(connect-popup): fix log going to (9dee606f43) +- feat(connect): fw hash check automatically for T1B1 (0fd023dbe8) +- docs(connect-web): Support matrix (44ed16a45d) +- chore: update backends for bsc and op (458f0fe3d9) +- docs(connect-explorer): mention address can be used in getAccountInfo (722a2b2b7f) +- chore(connect): cleanup some duplicities in fw download util (53456df94d) +- chore(connect): a cosmetic change in Device types (b718af4a2b) +- chore(connect): use import from @trezor/connect-analytics (1ee270b1cd) +- chore(protobuf): update messages.json (71bbde850b) +- feat(connect): add Optimism (c2fb244649) +- chore(connect): pol only to pol backend (fe589cf6bc) +- chore(connect): rename 'getAssetByUrl' to a more apt fn name 'tryLocalAssetRequire' (f5e8f256ae) +- docs(connect-explorer): remove trailing commas from json excerpts (dc0147900d) +- refactor(transport): background sessions improved (7644107353) +- refactor(connect): connect error codes typing (57815188d1) +- chore(connect): update device authenticity config (063d68379f) +- docs: fix header level in connect changelog (ac53d354cc) +- fix(connect): add other-error to FirmwareRevisionCheckResult errors (4361aba5b7) + # 9.4.2 This release fixes an issue with TypeScript and certain libraries not being resolved correctly in the previous version. diff --git a/packages/connect/README.md b/packages/connect/README.md index 101cef35161..3b9b74ae093 100644 --- a/packages/connect/README.md +++ b/packages/connect/README.md @@ -1,6 +1,6 @@ # @trezor/connect -API version 9.4.2 +API version 9.4.3-beta.2 [![Build Status](https://github.com/trezor/trezor-suite/actions/workflows/test-connect.yml/badge.svg)](https://github.com/trezor/trezor-suite/actions/workflows/test-connect.yml) [![NPM](https://img.shields.io/npm/v/@trezor/connect.svg)](https://www.npmjs.org/package/@trezor/connect) diff --git a/packages/connect/e2e/README.md b/packages/connect/e2e/README.md index aebdcb79791..5f9db855961 100644 --- a/packages/connect/e2e/README.md +++ b/packages/connect/e2e/README.md @@ -36,7 +36,7 @@ Those data are automatically downloaded from backend defined in `coins.json` by _Note: Backends hosted on `*.trezor.io` are limiting requests per min._ _Too many requests from not whitelisted origins may be penalized with temporary ban. ("All backends are down" error)_ -Backend connection will be omitted in case of providing `refTxs` so even coins without officially supported backends (like zcash testnet) may sign a transaction in _"offline mode"_. [see docs](../docs/method/signTransaction.md) +Backend connection will be omitted in case of providing `refTxs` so even coins without officially supported backends (like zcash testnet) may sign a transaction in _"offline mode"_. [see docs](https://connect.trezor.io/9/methods/bitcoin/signTransaction/) To reduce network traffic `Github Actions CI` is using **cached** (offline) mode and whitelisted `GitLab CI` is using **default** (online) mode. diff --git a/packages/connect/e2e/__fixtures__/cardanoGetAddress.ts b/packages/connect/e2e/__fixtures__/cardanoGetAddress.ts index eea18c680b9..8317204bf99 100644 --- a/packages/connect/e2e/__fixtures__/cardanoGetAddress.ts +++ b/packages/connect/e2e/__fixtures__/cardanoGetAddress.ts @@ -1,8 +1,17 @@ -import { NETWORK_IDS, PROTOCOL_MAGICS } from '../../src/constants/cardano'; import { MessagesSchema } from '@trezor/protobuf'; +import { NETWORK_IDS, PROTOCOL_MAGICS } from '../../src/constants/cardano'; + const { CardanoAddressType } = MessagesSchema; +const legacyResults = { + minConnectVersion: { + // older FW does support Cardano but Connect does not + rules: ['<2.4.3', '1'], + payload: false, + }, +}; + export default { method: 'cardanoGetAddress', setup: { @@ -496,5 +505,5 @@ export default { }, result: false, }, - ], + ].map(test => ({ ...test, legacyResults: [legacyResults.minConnectVersion] })), }; diff --git a/packages/connect/e2e/__fixtures__/cardanoGetAddressDerivations.ts b/packages/connect/e2e/__fixtures__/cardanoGetAddressDerivations.ts index 4696d2c6cd4..e2ebff8ff38 100644 --- a/packages/connect/e2e/__fixtures__/cardanoGetAddressDerivations.ts +++ b/packages/connect/e2e/__fixtures__/cardanoGetAddressDerivations.ts @@ -1,11 +1,19 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore -import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/cardano/get_base_address.derivations.json'; - import { MessagesSchema } from '@trezor/protobuf'; +import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/cardano/get_base_address.derivations.json'; + const { CardanoAddressType, CardanoDerivationType } = MessagesSchema; +const legacyResults = { + minConnectVersion: { + // older FW does support Cardano but Connect does not + rules: ['<2.4.3', '1'], + payload: false, + }, +}; + export default { method: 'cardanoGetAddress', setup: { @@ -28,5 +36,6 @@ export default { result: { address: result.expected_address, }, + legacyResults: [legacyResults.minConnectVersion], })), }; diff --git a/packages/connect/e2e/__fixtures__/cardanoGetNativeScriptHash.ts b/packages/connect/e2e/__fixtures__/cardanoGetNativeScriptHash.ts index 219aea93755..86b3498c6b5 100644 --- a/packages/connect/e2e/__fixtures__/cardanoGetNativeScriptHash.ts +++ b/packages/connect/e2e/__fixtures__/cardanoGetNativeScriptHash.ts @@ -2,6 +2,14 @@ import { MessagesSchema } from '@trezor/protobuf'; const { CardanoNativeScriptHashDisplayFormat, CardanoNativeScriptType } = MessagesSchema; +const legacyResults = { + minConnectVersion: { + // older FW does support Cardano but Connect does not + rules: ['<2.4.3', '1'], + payload: false, + }, +}; + export default { method: 'cardanoGetNativeScriptHash', setup: { @@ -306,5 +314,5 @@ export default { scriptHash: '4a6b4288459bf34668c0b281f922691460caf0c7c09caee3a726c27a', }, }, - ], + ].map(test => ({ ...test, legacyResults: [legacyResults.minConnectVersion] })), }; diff --git a/packages/connect/e2e/__fixtures__/cardanoGetPublicKey.ts b/packages/connect/e2e/__fixtures__/cardanoGetPublicKey.ts index 73a66fbef9d..304b2a885a0 100644 --- a/packages/connect/e2e/__fixtures__/cardanoGetPublicKey.ts +++ b/packages/connect/e2e/__fixtures__/cardanoGetPublicKey.ts @@ -1,3 +1,11 @@ +const legacyResults = { + minConnectVersion: { + // older FW does support Cardano but Connect does not + rules: ['<2.4.3', '1'], + payload: false, + }, +}; + export default { method: 'cardanoGetPublicKey', setup: { @@ -61,5 +69,5 @@ export default { '5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1f123474e140a2c360b01f0fa66f2f22e2e965a5b07a80358cf75f77abbd66088', }, }, - ], + ].map(t => ({ ...t, legacyResults: [legacyResults.minConnectVersion] })), }; diff --git a/packages/connect/e2e/__fixtures__/cardanoSignTransaction.ts b/packages/connect/e2e/__fixtures__/cardanoSignTransaction.ts index b1841193bd8..59f5937e3c2 100644 --- a/packages/connect/e2e/__fixtures__/cardanoSignTransaction.ts +++ b/packages/connect/e2e/__fixtures__/cardanoSignTransaction.ts @@ -1,7 +1,7 @@ -import { NETWORK_IDS, PROTOCOL_MAGICS } from '../../src/constants/cardano'; - import { MessagesSchema } from '@trezor/protobuf'; +import { NETWORK_IDS, PROTOCOL_MAGICS } from '../../src/constants/cardano'; + const { CardanoAddressType, CardanoCVoteRegistrationFormat, @@ -438,7 +438,11 @@ const SCRIPT_DATA_HASH = 'd593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a const TOTAL_COLLATERAL = '1000'; const legacyResults = { - // FW <2.6.0 is not supported by Connect at all + minConnectVersion: { + // older FW does support Cardano but Connect does not + rules: ['<2.4.3', '1'], + payload: false, + }, beforeConway: { // older FW doesn't support Conway certificates rules: ['<2.7.1', '1'], @@ -2898,5 +2902,11 @@ export default { auxiliaryDataSupplement: undefined, }, }, - ], + ].map(test => { + if (test.legacyResults) { + return test; + } + + return { ...test, legacyResults: [legacyResults.minConnectVersion] }; + }), }; diff --git a/packages/connect/e2e/__fixtures__/composeTransaction.ts b/packages/connect/e2e/__fixtures__/composeTransaction.ts index 65e989e5740..b1dda10b3e9 100644 --- a/packages/connect/e2e/__fixtures__/composeTransaction.ts +++ b/packages/connect/e2e/__fixtures__/composeTransaction.ts @@ -91,6 +91,7 @@ export default { feePerByte: '1', max: undefined, totalSpent: '6639', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDWITNESS', sequence: 0xffffffff }], outputs: [ { amount: '2787', script_type: 'PAYTOWITNESS' }, @@ -102,7 +103,7 @@ export default { }, { description: - 'Bitcoin (Bech32/P2WPKH): precompose without outputs permutation, with baseFee and custom sequence', + 'Bitcoin (Bech32/P2WPKH): precompose without outputs permutation (skipPermutation: true), with baseFee and custom sequence', params: { account: BECH32_ACCOUNT, feeLevels: FEE_LEVELS, @@ -125,6 +126,42 @@ export default { feePerByte: '2.176056338028169', max: undefined, totalSpent: '6806', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic + inputs: [{ script_type: 'SPENDWITNESS', sequence: 1 }], + outputs: [ + { amount: '6497', script_type: 'PAYTOADDRESS' }, + { amount: '2620', script_type: 'PAYTOWITNESS' }, // skipped permutation + ], + outputsPermutation: [0, 1], + }, + ], + }, + { + description: + "Bitcoin (Bech32/P2WPKH): precompose without outputs permutation (sortingStrategy: 'none'), with baseFee and custom sequence", + params: { + account: BECH32_ACCOUNT, + feeLevels: FEE_LEVELS, + outputs: [ + { + address: '36JkLACrdxARqXXffZk91V9W6SJvghKaVK', + amount: '6497', + }, + ], + baseFee: 167, + sortingStrategy: 'none', + sequence: 1, + coin: 'btc', + }, + result: [ + { + type: 'final', + bytes: 142, + fee: '309', + feePerByte: '2.176056338028169', + max: undefined, + totalSpent: '6806', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDWITNESS', sequence: 1 }], outputs: [ { amount: '6497', script_type: 'PAYTOADDRESS' }, @@ -176,10 +213,11 @@ export default { bytes: 142, fee: '142', totalSpent: '1142', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ amount: '9426', script_type: 'SPENDWITNESS' }], outputs: [ - { amount: '1000', script_type: 'PAYTOADDRESS' }, { amount: '8284', script_type: 'PAYTOWITNESS' }, + { amount: '1000', script_type: 'PAYTOADDRESS' }, ], }, ], @@ -236,14 +274,15 @@ export default { bytes: 278, fee: '278', totalSpent: '16678', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [ - { amount: '7086' }, - { amount: '9426' }, { amount: '309896' }, // NOTE: this utxo is used because required utxo is not enough to cover fee + { amount: '9426' }, + { amount: '7086' }, ], outputs: [ - { amount: '16400', script_type: 'PAYTOADDRESS' }, { amount: '309730', script_type: 'PAYTOWITNESS' }, + { amount: '16400', script_type: 'PAYTOADDRESS' }, ], }, ], @@ -295,6 +334,7 @@ export default { feePerByte: '1', max: '9315', totalSpent: '9426', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDWITNESS' }], outputs: [{ amount: '9315', script_type: 'PAYTOADDRESS' }], }, @@ -320,14 +360,15 @@ export default { fee: '226000', max: undefined, totalSpent: '100226000', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDADDRESS' }], outputs: [ + { amount: '399774000', script_type: 'PAYTOADDRESS' }, { address: 'DDn7UV1CrqVefzwrHyw7H2zEZZKqfzR2ZD', amount: '100000000', script_type: 'PAYTOADDRESS', }, - { amount: '399774000', script_type: 'PAYTOADDRESS' }, ], }, ], @@ -352,14 +393,15 @@ export default { fee: '1226000', // NOTE: +0.01 DOGE per dust limit output max: undefined, totalSpent: '1326000', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDADDRESS' }], outputs: [ + { amount: '498674000', script_type: 'PAYTOADDRESS' }, { address: 'DDn7UV1CrqVefzwrHyw7H2zEZZKqfzR2ZD', amount: '100000', script_type: 'PAYTOADDRESS', }, - { amount: '498674000', script_type: 'PAYTOADDRESS' }, ], }, ], @@ -389,16 +431,17 @@ export default { fee: '1000000', // NOTE: +0.01 DOGE per dust limit output + ~0.08 DOGE dust limit change max: undefined, totalSpent: '500000000', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDADDRESS' }], outputs: [ { address: 'DDn7UV1CrqVefzwrHyw7H2zEZZKqfzR2ZD', - amount: '200000000', + amount: '299000000', script_type: 'PAYTOADDRESS', }, { address: 'DDn7UV1CrqVefzwrHyw7H2zEZZKqfzR2ZD', - amount: '299000000', + amount: '200000000', script_type: 'PAYTOADDRESS', }, ], @@ -425,6 +468,7 @@ export default { fee: '192000', max: '499808000', totalSpent: '500000000', + // Order of inputs & outputs is important as we mock randomness in test to make it deterministic inputs: [{ script_type: 'SPENDADDRESS' }], outputs: [{ amount: '499808000', script_type: 'PAYTOADDRESS' }], }, diff --git a/packages/connect/e2e/__fixtures__/ethereumGetAddress.ts b/packages/connect/e2e/__fixtures__/ethereumGetAddress.ts index e7ba4ad07ab..1998c8f2dd6 100644 --- a/packages/connect/e2e/__fixtures__/ethereumGetAddress.ts +++ b/packages/connect/e2e/__fixtures__/ethereumGetAddress.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/getaddress.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumGetPublicKey.ts b/packages/connect/e2e/__fixtures__/ethereumGetPublicKey.ts index e3315150ddb..4a44dd5cebf 100644 --- a/packages/connect/e2e/__fixtures__/ethereumGetPublicKey.ts +++ b/packages/connect/e2e/__fixtures__/ethereumGetPublicKey.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/getpublickey.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumSignMessage.ts b/packages/connect/e2e/__fixtures__/ethereumSignMessage.ts index c0f7f676937..796d4da33d6 100644 --- a/packages/connect/e2e/__fixtures__/ethereumSignMessage.ts +++ b/packages/connect/e2e/__fixtures__/ethereumSignMessage.ts @@ -1,5 +1,6 @@ import { arrayPartition } from '@trezor/utils'; -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ + +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/signmessage.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumSignTransaction.ts b/packages/connect/e2e/__fixtures__/ethereumSignTransaction.ts index 5b9be2ca82b..d58440b9a1e 100644 --- a/packages/connect/e2e/__fixtures__/ethereumSignTransaction.ts +++ b/packages/connect/e2e/__fixtures__/ethereumSignTransaction.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/sign_tx.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip155.ts b/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip155.ts index d4197bf19eb..d240888f066 100644 --- a/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip155.ts +++ b/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip155.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/sign_tx_eip155.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip1559.ts b/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip1559.ts index 1ae797c30fc..10581364c57 100644 --- a/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip1559.ts +++ b/packages/connect/e2e/__fixtures__/ethereumSignTransactionEip1559.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/sign_tx_eip1559.json'; diff --git a/packages/connect/e2e/__fixtures__/ethereumSignTypedData.ts b/packages/connect/e2e/__fixtures__/ethereumSignTypedData.ts index 6d23644567e..2f1e7ae405e 100644 --- a/packages/connect/e2e/__fixtures__/ethereumSignTypedData.ts +++ b/packages/connect/e2e/__fixtures__/ethereumSignTypedData.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ -// @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/sign_typed_data.json'; const ethereumDefinitionFixture = [ diff --git a/packages/connect/e2e/__fixtures__/ethereumVerifyMessage.ts b/packages/connect/e2e/__fixtures__/ethereumVerifyMessage.ts index 3bd49cc1a31..458f712dc7a 100644 --- a/packages/connect/e2e/__fixtures__/ethereumVerifyMessage.ts +++ b/packages/connect/e2e/__fixtures__/ethereumVerifyMessage.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/ethereum/verifymessage.json'; diff --git a/packages/connect/e2e/__fixtures__/index.ts b/packages/connect/e2e/__fixtures__/index.ts index 533c622e437..0d28a2a8c11 100644 --- a/packages/connect/e2e/__fixtures__/index.ts +++ b/packages/connect/e2e/__fixtures__/index.ts @@ -1,72 +1,73 @@ -import applyFlags from './applyFlags'; -import applySettings from './applySettings'; -import binanceSignTransaction from './binanceSignTransaction'; -import binanceGetPublicKey from './binanceGetPublicKey'; -import binanceGetAddress from './binanceGetAddress'; -import cardanoGetAddress from './cardanoGetAddress'; -import cardanoGetAddressDerivations from './cardanoGetAddressDerivations'; -import cardanoGetNativeScriptHash from './cardanoGetNativeScriptHash'; -import cardanoGetPublicKey from './cardanoGetPublicKey'; -import cardanoSignTransaction from './cardanoSignTransaction'; -import changeLanguage from './changeLanguage'; -import composeTransaction from './composeTransaction'; -import eosGetPublicKey from './eosGetPublicKey'; -import eosSignTransaction from './eosSignTransaction'; -import ethereumGetAddress from './ethereumGetAddress'; -import ethereumGetPublicKey from './ethereumGetPublicKey'; -import ethereumSignMessage from './ethereumSignMessage'; -import ethereumSignTransaction from './ethereumSignTransaction'; -import ethereumSignTransactionEip155 from './ethereumSignTransactionEip155'; -import ethereumSignTransactionEip1559 from './ethereumSignTransactionEip1559'; -import ethereumSignTypedData from './ethereumSignTypedData'; -import ethereumVerifyMessage from './ethereumVerifyMessage'; -import getAccountDescriptor from './getAccountDescriptor'; -import getAccountInfo from './getAccountInfo'; -import getAddress from './getAddress'; -import getAddressMultisig from './getAddressMultisig'; -import getAddressSegwit from './getAddressSegwit'; -import getFeatures from './getFeatures'; -import getFirmwareHash from './getFirmwareHash'; -import getOwnershipId from './getOwnershipId'; -import getOwnershipProof from './getOwnershipProof'; -import getPublicKey from './getPublicKey'; -import getPublicKeyBip48 from './getPublicKeyBip48'; -import nemGetAddress from './nemGetAddress'; -import nemSignTransactionMosaic from './nemSignTransactionMosaic'; -import nemSignTransactionMultisig from './nemSignTransactionMultisig'; -import nemSignTransactionOthers from './nemSignTransactionOthers'; -import nemSignTransactionTransfer from './nemSignTransactionTransfer'; -import rippleGetAddress from './rippleGetAddress'; -import rippleSignTransaction from './rippleSignTransaction'; -import signMessage from './signMessage'; -import signTransaction from './signTransaction'; -import signTransactionBcash from './signTransactionBcash'; -import signTransactionBech32 from './signTransactionBech32'; -import signTransactionBgold from './signTransactionBgold'; -import signTransactionDash from './signTransactionDash'; -import signTransactionDecred from './signTransactionDecred'; -import signTransactionDoge from './signTransactionDoge'; -import signTransactionExternal from './signTransactionExternal'; -import signTransactionKomodo from './signTransactionKomodo'; -import signTransactionMultisig from './signTransactionMultisig'; -import signTransactionMultisigChange from './signTransactionMultisigChange'; -import signTransactionPaymentRequest from './signTransactionPaymentRequest'; -import signTransactionPeercoin from './signTransactionPeercoin'; -import signTransactionReplace from './signTransactionReplace'; -import signTransactionSegwit from './signTransactionSegwit'; -import signTransactionTaproot from './signTransactionTaproot'; -import signTransactionZcash from './signTransactionZcash'; -import solanaGetAddress from './solanaGetAddress'; -import solanaGetPublicKey from './solanaGetPublicKey'; -import solanaSignTransaction from './solanaSignTransaction'; -import stellarGetAddress from './stellarGetAddress'; -import stellarSignTransaction from './stellarSignTransaction'; -import tezosGetAddress from './tezosGetAddress'; -import tezosGetPublicKey from './tezosGetPublicKey'; -import tezosSignTransaction from './tezosSignTransaction'; -import verifyMessage from './verifyMessage'; -import verifyMessageSegwit from './verifyMessageSegwit'; -import verifyMessageSegwitNative from './verifyMessageSegwitNative'; +export { default as applyFlags } from './applyFlags'; +export { default as applySettings } from './applySettings'; +export { default as binanceSignTransaction } from './binanceSignTransaction'; +export { default as binanceGetPublicKey } from './binanceGetPublicKey'; +export { default as binanceGetAddress } from './binanceGetAddress'; +export { default as cardanoGetAddress } from './cardanoGetAddress'; +export { default as cardanoGetAddressDerivations } from './cardanoGetAddressDerivations'; +export { default as cardanoGetNativeScriptHash } from './cardanoGetNativeScriptHash'; +export { default as cardanoGetPublicKey } from './cardanoGetPublicKey'; +export { default as cardanoSignTransaction } from './cardanoSignTransaction'; +export { default as changeLanguage } from './changeLanguage'; +export { default as composeTransaction } from './composeTransaction'; +export { default as eosGetPublicKey } from './eosGetPublicKey'; +export { default as eosSignTransaction } from './eosSignTransaction'; +export { default as ethereumGetAddress } from './ethereumGetAddress'; +export { default as ethereumGetPublicKey } from './ethereumGetPublicKey'; +export { default as ethereumSignMessage } from './ethereumSignMessage'; +export { default as ethereumSignTransaction } from './ethereumSignTransaction'; +export { default as ethereumSignTransactionEip155 } from './ethereumSignTransactionEip155'; +export { default as ethereumSignTransactionEip1559 } from './ethereumSignTransactionEip1559'; +export { default as ethereumSignTypedData } from './ethereumSignTypedData'; +export { default as ethereumVerifyMessage } from './ethereumVerifyMessage'; +export { default as getAccountDescriptor } from './getAccountDescriptor'; +export { default as getAccountInfo } from './getAccountInfo'; +export { default as getAddress } from './getAddress'; +export { default as getAddressMultisig } from './getAddressMultisig'; +export { default as getAddressSegwit } from './getAddressSegwit'; +export { default as getFeatures } from './getFeatures'; +export { default as getFirmwareHash } from './getFirmwareHash'; +export { default as getOwnershipId } from './getOwnershipId'; +export { default as getOwnershipProof } from './getOwnershipProof'; +export { default as getPublicKey } from './getPublicKey'; +export { default as getPublicKeyBip48 } from './getPublicKeyBip48'; +export { default as nemGetAddress } from './nemGetAddress'; +export { default as nemSignTransactionMosaic } from './nemSignTransactionMosaic'; +export { default as nemSignTransactionMultisig } from './nemSignTransactionMultisig'; +export { default as nemSignTransactionOthers } from './nemSignTransactionOthers'; +export { default as nemSignTransactionTransfer } from './nemSignTransactionTransfer'; +export { default as rippleGetAddress } from './rippleGetAddress'; +export { default as rippleSignTransaction } from './rippleSignTransaction'; +export { default as signMessage } from './signMessage'; +export { default as signTransaction } from './signTransaction'; +export { default as signTransactionBcash } from './signTransactionBcash'; +export { default as signTransactionBech32 } from './signTransactionBech32'; +export { default as signTransactionBgold } from './signTransactionBgold'; +export { default as signTransactionDash } from './signTransactionDash'; +export { default as signTransactionDecred } from './signTransactionDecred'; +export { default as signTransactionDoge } from './signTransactionDoge'; +export { default as signTransactionExternal } from './signTransactionExternal'; +export { default as signTransactionKomodo } from './signTransactionKomodo'; +export { default as signTransactionMultisig } from './signTransactionMultisig'; +export { default as signTransactionMultisigChange } from './signTransactionMultisigChange'; +export { default as signTransactionPaymentRequest } from './signTransactionPaymentRequest'; +export { default as signTransactionPeercoin } from './signTransactionPeercoin'; +export { default as signTransactionReplace } from './signTransactionReplace'; +export { default as signTransactionSegwit } from './signTransactionSegwit'; +export { default as signTransactionTaproot } from './signTransactionTaproot'; +export { default as signTransactionZcash } from './signTransactionZcash'; +export { default as solanaGetAddress } from './solanaGetAddress'; +export { default as solanaGetPublicKey } from './solanaGetPublicKey'; +export { default as solanaSignTransaction } from './solanaSignTransaction'; +export { default as stellarGetAddress } from './stellarGetAddress'; +export { default as stellarSignTransaction } from './stellarSignTransaction'; +export { default as tezosGetAddress } from './tezosGetAddress'; +export { default as tezosGetPublicKey } from './tezosGetPublicKey'; +export { default as tezosSignTransaction } from './tezosSignTransaction'; +export { default as verifyMessage } from './verifyMessage'; +export { default as verifyMessageSegwit } from './verifyMessageSegwit'; +export { default as verifyMessageSegwitNative } from './verifyMessageSegwitNative'; +export { default as loadDevice } from './loadDevice'; // TODO: add fixtures for missing dependencies https://github.com/trezor/trezor-suite/issues/5353 // backupDevice @@ -87,103 +88,9 @@ import verifyMessageSegwitNative from './verifyMessageSegwitNative'; // getDeviceState // getSettings // pushTransaction -// rebootToBootloader // recoveryDevice // requestLogin // resetDevice // setProxy // tezosSignTransaction // wipeDevice - -let fixtures = [ - applyFlags, - applySettings, - binanceGetAddress, - binanceGetPublicKey, - binanceSignTransaction, - cardanoGetAddress, - cardanoGetAddressDerivations, - cardanoGetNativeScriptHash, - cardanoGetPublicKey, - cardanoSignTransaction, - changeLanguage, - composeTransaction, - eosGetPublicKey, - eosSignTransaction, - ethereumGetAddress, - ethereumGetPublicKey, - ethereumSignMessage, - ethereumSignTransaction, - ethereumSignTransactionEip155, - ethereumSignTransactionEip1559, - ethereumSignTypedData, - ethereumVerifyMessage, - getAccountDescriptor, - getAccountInfo, - getAddress, - getAddressMultisig, - getAddressSegwit, - getFeatures, - getFirmwareHash, - getOwnershipId, - getOwnershipProof, - getPublicKey, - getPublicKeyBip48, - nemGetAddress, - nemSignTransactionMosaic, - nemSignTransactionMultisig, - nemSignTransactionOthers, - nemSignTransactionTransfer, - rippleGetAddress, - rippleSignTransaction, - signMessage, - signTransaction, - signTransactionBcash, - signTransactionBech32, - signTransactionBgold, - signTransactionDash, - signTransactionDecred, - signTransactionDoge, - signTransactionExternal, - signTransactionKomodo, - signTransactionMultisig, - signTransactionMultisigChange, - signTransactionPaymentRequest, - signTransactionPeercoin, - signTransactionReplace, - signTransactionSegwit, - signTransactionTaproot, - signTransactionZcash, - solanaGetAddress, - solanaGetPublicKey, - solanaSignTransaction, - stellarGetAddress, - stellarSignTransaction, - tezosGetAddress, - tezosGetPublicKey, - tezosSignTransaction, - verifyMessage, - verifyMessageSegwit, - verifyMessageSegwitNative, -]; - -const includedMethods = process.env.TESTS_INCLUDED_METHODS; -const excludedMethods = process.env.TESTS_EXCLUDED_METHODS; -if (includedMethods) { - const methodsArr = includedMethods.split(','); - fixtures = fixtures.filter(f => methodsArr.some(includedM => includedM === f.method)); -} else if (excludedMethods) { - const methodsArr = excludedMethods.split(','); - fixtures = fixtures.filter(f => !methodsArr.includes(f.method)); -} - -// sort by mnemonic to avoid emu re-loading -const result = fixtures.sort((a, b) => { - if (!a.setup.mnemonic || !b.setup.mnemonic) return 0; - if (a.setup.mnemonic > b.setup.mnemonic) return 1; - if (b.setup.mnemonic > a.setup.mnemonic) return -1; - - return 0; -}); - -export default result; diff --git a/packages/connect/e2e/__fixtures__/loadDevice.ts b/packages/connect/e2e/__fixtures__/loadDevice.ts new file mode 100644 index 00000000000..804fb11e1e9 --- /dev/null +++ b/packages/connect/e2e/__fixtures__/loadDevice.ts @@ -0,0 +1,18 @@ +export default { + method: 'loadDevice', + setup: { + wiped: true, + mnemonic: '', + }, + tests: [ + { + description: 'Load device', + params: { + mnemonics: ['all all all all all all all all all all all all'], + }, + result: { + message: 'Device loaded', + }, + }, + ], +}; diff --git a/packages/connect/e2e/__fixtures__/stellarSignTransaction.ts b/packages/connect/e2e/__fixtures__/stellarSignTransaction.ts index 22818af2705..c4666a2ab64 100644 --- a/packages/connect/e2e/__fixtures__/stellarSignTransaction.ts +++ b/packages/connect/e2e/__fixtures__/stellarSignTransaction.ts @@ -1,8 +1,9 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore -import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/stellar/sign_tx.json'; import * as Messages from '@trezor/protobuf/src/messages'; +import commonFixtures from '../../../../submodules/trezor-common/tests/fixtures/stellar/sign_tx.json'; + // operations are in protobuf format (snake_case) const transformAsset = (asset: any) => ({ diff --git a/packages/connect/e2e/__txcache__/gen-reftx.ts b/packages/connect/e2e/__txcache__/gen-reftx.ts index 468a2ca1cb0..0b064064c70 100644 --- a/packages/connect/e2e/__txcache__/gen-reftx.ts +++ b/packages/connect/e2e/__txcache__/gen-reftx.ts @@ -1,6 +1,7 @@ import * as BitcoinJs from '@trezor/utxo-lib'; import type { TxInput, TxOutput } from '@trezor/utxo-lib/src/transaction/base'; import { bufferUtils } from '@trezor/utils'; + import { RefTransaction } from '../../src'; // Referenced transaction generator script. diff --git a/packages/connect/e2e/__wscache__/server.js b/packages/connect/e2e/__wscache__/server.js index 00a5b23518a..9837576a51d 100644 --- a/packages/connect/e2e/__wscache__/server.js +++ b/packages/connect/e2e/__wscache__/server.js @@ -1,4 +1,5 @@ const WebSocket = require('ws'); + const { blockbookFixtures } = require('./blockbook'); const { rippleFixtures } = require('./ripple'); const { blockfrostFixtures } = require('./blockfrost'); @@ -36,7 +37,7 @@ const createServer = () => { try { const data = fn(params, request); ws.send(JSON.stringify({ ...data, id: request.id })); - } catch (e) { + } catch { // empty } }; diff --git a/packages/connect/e2e/common.setup.ts b/packages/connect/e2e/common.setup.ts index 7b2c951c210..89f3a425ffc 100644 --- a/packages/connect/e2e/common.setup.ts +++ b/packages/connect/e2e/common.setup.ts @@ -1,6 +1,4 @@ -import TrezorConnect from '../src'; import { versionUtils } from '@trezor/utils'; -import { UI } from '../src/events'; import { TrezorUserEnvLink, type TrezorUserEnvLinkClass, @@ -9,6 +7,9 @@ import { } from '@trezor/trezor-user-env-link'; import { ApplySettings } from '@trezor/protobuf/src/messages-schema'; +import { UI } from '../src/events'; +import TrezorConnect from '../src'; + const emulatorStartOpts = (process.env.emulatorStartOpts as StartEmu) || global.emulatorStartOpts || {}; @@ -33,8 +34,10 @@ type Options = { pin?: string; label?: string; settings?: ApplySettings; + wiped?: boolean; }; export const setup = async ( + // eslint-disable-next-line @typescript-eslint/no-shadow TrezorUserEnvLink: TrezorUserEnvLinkClass, options?: Partial, ) => { @@ -50,20 +53,22 @@ export const setup = async ( // the test using the same transport await TrezorUserEnvLink.stopBridge(); - if (!options?.mnemonic) return true; // skip setup if test is not using the device (composeTransaction) + if (!options?.mnemonic && !options.wiped) return true; // skip setup if test is not using the device (composeTransaction) await TrezorUserEnvLink.startEmu(emulatorStartOpts); - const mnemonic = options.mnemonic || MNEMONICS.mnemonic_all; + if (!options.wiped) { + const mnemonic = options.mnemonic || MNEMONICS.mnemonic_all; - await TrezorUserEnvLink.setupEmu({ - ...options, - mnemonic, - pin: options.pin || '', - passphrase_protection: !!options.passphrase_protection, - label: options.label || 'TrezorT', - needs_backup: false, - }); + await TrezorUserEnvLink.setupEmu({ + ...options, + mnemonic, + pin: options.pin || '', + passphrase_protection: !!options.passphrase_protection, + label: options.label || 'TrezorT', + needs_backup: false, + }); + } if (options.settings) { // allow apply-settings to fail, older FW may not know some flags yet @@ -82,6 +87,7 @@ export const setup = async ( }; export const initTrezorConnect = async ( + // eslint-disable-next-line @typescript-eslint/no-shadow TrezorUserEnvLink: TrezorUserEnvLinkClass, options?: Partial[0]>, ) => { @@ -123,6 +129,7 @@ export const initTrezorConnect = async ( debug: false, popup: false, pendingTransportEvent: true, + transportReconnect: false, connectSrc: process.env.TREZOR_CONNECT_SRC, // custom source for karma tests ...options, }); diff --git a/packages/connect/e2e/karma.config.js b/packages/connect/e2e/karma.config.js index eba6f9ccf19..3836c2a0b1e 100644 --- a/packages/connect/e2e/karma.config.js +++ b/packages/connect/e2e/karma.config.js @@ -2,7 +2,7 @@ const path = require('path'); const webpack = require('webpack'); module.exports = config => { - const singleRun = process.env.KARMA_SINGLE_RUN === 'false' ? false : true; + const singleRun = process.env.KARMA_SINGLE_RUN !== 'false'; config.set({ basePath: path.resolve(__dirname, '../..'), // NOTE: "[monorepo-root]/packages", to have access to other packages diff --git a/packages/connect/e2e/karma.setup.js b/packages/connect/e2e/karma.setup.js index 73851c17b47..432d600aec8 100644 --- a/packages/connect/e2e/karma.setup.js +++ b/packages/connect/e2e/karma.setup.js @@ -50,6 +50,7 @@ jasmine.getEnv().beforeAll(() => { return match; }, {}); + // eslint-disable-next-line jest/no-standalone-expect expect(actual).toEqual(jasmine.objectContaining(nested(expected))); return success; diff --git a/packages/connect/e2e/run.ts b/packages/connect/e2e/run.ts index c7021a236a5..f16e2b26afa 100644 --- a/packages/connect/e2e/run.ts +++ b/packages/connect/e2e/run.ts @@ -25,7 +25,7 @@ const getEmulatorOptions = (availableFirmwares: Firmwares) => { : 'T2T1'; const latest = getLatestFirmware(model); - if (!latest) { + if (firmwareArg?.endsWith('-latest') && !latest) { // should never happen throw new Error('could not translate n-latest into specific firmware version'); } diff --git a/packages/connect/e2e/tests/device/authenticateDevice.test.ts b/packages/connect/e2e/tests/device/authenticateDevice.test.ts index 15d62016ea0..8bc7c083ed0 100644 --- a/packages/connect/e2e/tests/device/authenticateDevice.test.ts +++ b/packages/connect/e2e/tests/device/authenticateDevice.test.ts @@ -1,6 +1,6 @@ import { DeviceAuthenticityConfig } from '@trezor/connect/src/data/deviceAuthenticityConfigTypes'; -import TrezorConnect from '../../../src'; +import TrezorConnect from '../../../src'; import { getController, setup, initTrezorConnect } from '../../common.setup'; const controller = getController(); @@ -38,6 +38,10 @@ describe('TrezorConnect.authenticateDevice', () => { '04ba6084cb9fba7c86d5d5a86108a91d55a27056da4eabbedde88a95e1cae8bce3620889167aaf7f2db166998f950984aa195e868f96e22803c3cd991be31d39e7', ], }, + T3W1: { + rootPubKeys: ['you shall not pass'], + caPubKeys: ['you shall not pass'], + }, } as DeviceAuthenticityConfig; it('validation successful', async () => { diff --git a/packages/connect/e2e/tests/device/authorizeCoinjoin.test.ts b/packages/connect/e2e/tests/device/authorizeCoinjoin.test.ts index 8d83cd42785..2935255ada6 100644 --- a/packages/connect/e2e/tests/device/authorizeCoinjoin.test.ts +++ b/packages/connect/e2e/tests/device/authorizeCoinjoin.test.ts @@ -1,5 +1,4 @@ import TrezorConnect from '../../../src'; - import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; const controller = getController(); diff --git a/packages/connect/e2e/tests/device/cancel.test.ts b/packages/connect/e2e/tests/device/cancel.test.ts index 10f0338cd5d..1cd9ed9d9d9 100644 --- a/packages/connect/e2e/tests/device/cancel.test.ts +++ b/packages/connect/e2e/tests/device/cancel.test.ts @@ -1,4 +1,5 @@ import { StartEmu, SetupEmu } from '@trezor/trezor-user-env-link'; + import { getController, initTrezorConnect } from '../../common.setup'; import TrezorConnect from '../../../src'; diff --git a/packages/connect/e2e/tests/device/cancelCoinjoinAuthorization.test.ts b/packages/connect/e2e/tests/device/cancelCoinjoinAuthorization.test.ts index f83dcc1c2c3..6f1e2d3114f 100644 --- a/packages/connect/e2e/tests/device/cancelCoinjoinAuthorization.test.ts +++ b/packages/connect/e2e/tests/device/cancelCoinjoinAuthorization.test.ts @@ -1,5 +1,4 @@ import TrezorConnect, { Success, PROTO, Unsuccessful } from '../../../src'; - import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; describe('TrezorConnect.cancelCoinjoinAuthorization', () => { diff --git a/packages/connect/e2e/tests/device/checkFirmwareAuthenticity.test.ts b/packages/connect/e2e/tests/device/checkFirmwareAuthenticity.test.ts deleted file mode 100644 index c1b6e63a64a..00000000000 --- a/packages/connect/e2e/tests/device/checkFirmwareAuthenticity.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import TrezorConnect from '../../../src'; - -import { getController, setup, initTrezorConnect } from '../../common.setup'; - -const controller = getController(); - -describe('TrezorConnect.checkFirmwareAuthenticity', () => { - beforeAll(async () => { - await setup(controller, { - mnemonic: 'mnemonic_all', - }); - await initTrezorConnect(controller); - }); - - afterAll(async () => { - controller.dispose(); - await TrezorConnect.dispose(); - }); - - it('sometimes valid sometimes not, depends on circumstances', async () => { - const result = await TrezorConnect.checkFirmwareAuthenticity({}); - - if (result.success) { - // when running with emulator, hashes will never match. - expect(typeof result.payload.valid).toEqual('boolean'); - expect(typeof result.payload.expectedFirmwareHash).toEqual('string'); - expect(typeof result.payload.actualFirmwareHash).toEqual('string'); - } - }); -}); diff --git a/packages/connect/e2e/tests/device/info.test.ts b/packages/connect/e2e/tests/device/info.test.ts new file mode 100644 index 00000000000..4f68d1a6376 --- /dev/null +++ b/packages/connect/e2e/tests/device/info.test.ts @@ -0,0 +1,24 @@ +import TrezorConnect from '../../../src'; +import { getController, initTrezorConnect } from '../../common.setup'; + +describe('__info common param', () => { + beforeAll(async () => { + await TrezorConnect.dispose(); + + await initTrezorConnect(getController()); + }); + + afterAll(async () => { + await TrezorConnect.dispose(); + }); + + it('common param __info - only method info is returned', async () => { + const result = await TrezorConnect.getFeatures({ + __info: true, + }); + if (!result.success) throw new Error(result.payload.error); + // @ts-expect-error todo: types not finished + expect(result.payload.useDevice).toEqual(true); + expect(result.payload.minor_version).toEqual(undefined); + }); +}); diff --git a/packages/connect/e2e/tests/device/keepSession.test.ts b/packages/connect/e2e/tests/device/keepSession.test.ts index 6a85c4304ec..a6c6f20e644 100644 --- a/packages/connect/e2e/tests/device/keepSession.test.ts +++ b/packages/connect/e2e/tests/device/keepSession.test.ts @@ -1,5 +1,4 @@ -import TrezorConnect from '../../../src'; - +import TrezorConnect, { StaticSessionId } from '../../../src'; import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; const controller = getController(); @@ -9,6 +8,7 @@ describe('keepSession common param', () => { await TrezorConnect.dispose(); await setup(controller, { mnemonic: 'mnemonic_all', + passphrase_protection: true, }); await initTrezorConnect(controller); }); @@ -19,6 +19,10 @@ describe('keepSession common param', () => { }); conditionalTest(['1', '<2.3.2'], 'keepSession with changing useCardanoDerivation', async () => { + TrezorConnect.on('ui-request_passphrase', () => { + TrezorConnect.uiResponse({ type: 'ui-receive_passphrase', payload: { value: 'a' } }); + }); + const noDerivation = await TrezorConnect.getAccountDescriptor({ coin: 'ada', path: "m/1852'/1815'/0'/0/0", @@ -38,5 +42,35 @@ describe('keepSession common param', () => { }); if (!enableDerivation.success) throw new Error(enableDerivation.payload.error); expect(enableDerivation.payload.descriptor).toBeDefined(); + + const { device } = enableDerivation; + if (!device || !device.state) throw new Error('Device not found'); + + // change device instance to simulate app reload + // passphrase request should not be called + TrezorConnect.removeAllListeners('ui-request_passphrase'); + // modify instance in staticSessionId + const staticSessionId = device.state.staticSessionId?.replace( + ':0', + ':1', + ) as StaticSessionId; + const keepCardanoDerivation = await TrezorConnect.getAccountDescriptor({ + coin: 'ada', + path: "m/1852'/1815'/0'/0/0", + device: { + // change instance to new but use already initialized state + instance: 1, + state: { + ...device.state, + staticSessionId, + }, + path: device.path, + }, + // useCardanoDerivation: true, // NOTE: not required, its in the state + }); + if (!keepCardanoDerivation.success) throw new Error(keepCardanoDerivation.payload.error); + expect(keepCardanoDerivation.payload.descriptor).toEqual( + enableDerivation.payload.descriptor, + ); }); }); diff --git a/packages/connect/e2e/tests/device/methods.test.ts b/packages/connect/e2e/tests/device/methods.test.ts index 2ee7a64c815..f03352a8b21 100644 --- a/packages/connect/e2e/tests/device/methods.test.ts +++ b/packages/connect/e2e/tests/device/methods.test.ts @@ -1,6 +1,5 @@ import TrezorConnect from '../../../src'; -import fixtures from '../../__fixtures__'; - +import * as fixtures from '../../__fixtures__'; import { getController, skipTest, @@ -11,6 +10,58 @@ import { let controller: ReturnType | undefined; +// After the removal bip69, we sort inputs and outputs randomly +// So we need to mock the source of randomness for all tests, so the fixtures are deterministic. + +// However, we run those test both in Node.js and in browser environment, +// so we need to mock the source of randomness in both environments + +// This is mock of randomnes for Karma (web environment) +if (typeof window !== 'undefined') { + window.crypto.getRandomValues = array => { + if (array instanceof Uint32Array) { + array[0] = 4; + } + + return array; + }; +} + +// In Karma web environment, there is no `jest`, so we fake one +if (typeof jest === 'undefined') { + globalThis.jest = { mock: () => undefined } as any; +} + +// Jest.mock() MUST be called in global scope, if we put it into condition it won't work. +jest.mock('@trezor/utils', () => ({ + ...jest.requireActual('@trezor/utils'), + getRandomInt: (min: number, max: number) => min + (4 % max), // 4 is truly random number, I rolled the dice +})); + +const getFixtures = () => { + const includedMethods = process.env.TESTS_INCLUDED_METHODS; + const excludedMethods = process.env.TESTS_EXCLUDED_METHODS; + let subset = Object.values(fixtures); + if (includedMethods) { + const methodsArr = includedMethods.split(','); + subset = subset.filter(f => methodsArr.some(includedM => includedM === f.method)); + } else if (excludedMethods) { + const methodsArr = excludedMethods.split(','); + subset = subset.filter(f => !methodsArr.includes(f.method)); + } + + // sort by mnemonic to avoid emu re-loading + const result = subset?.sort((a, b) => { + if (!a.setup.mnemonic || !b.setup.mnemonic) return 0; + if (a.setup.mnemonic > b.setup.mnemonic) return 1; + if (b.setup.mnemonic > a.setup.mnemonic) return -1; + + return 0; + }); + + return result || []; +}; + describe(`TrezorConnect methods`, () => { afterAll(() => { // reset controller at the end @@ -20,7 +71,7 @@ describe(`TrezorConnect methods`, () => { } }); - fixtures.forEach((testCase: TestCase) => { + getFixtures().forEach((testCase: TestCase) => { describe(`TrezorConnect.${testCase.method}`, () => { beforeAll(async () => { await TrezorConnect.dispose(); diff --git a/packages/connect/e2e/tests/device/override.test.ts b/packages/connect/e2e/tests/device/override.test.ts index b3b2ac42410..1e21737c221 100644 --- a/packages/connect/e2e/tests/device/override.test.ts +++ b/packages/connect/e2e/tests/device/override.test.ts @@ -1,5 +1,4 @@ import TrezorConnect from '../../../src'; - import { getController, setup, initTrezorConnect } from '../../common.setup'; const controller = getController(); diff --git a/packages/connect/e2e/tests/device/passphrase.test.ts b/packages/connect/e2e/tests/device/passphrase.test.ts index 8aa03fad36a..02a0786bf32 100644 --- a/packages/connect/e2e/tests/device/passphrase.test.ts +++ b/packages/connect/e2e/tests/device/passphrase.test.ts @@ -1,5 +1,4 @@ import TrezorConnect from '../../../src'; - import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; const controller = getController(); diff --git a/packages/connect/e2e/tests/device/setBusy.test.ts b/packages/connect/e2e/tests/device/setBusy.test.ts index f3ffdc504f9..0dc2f69077f 100644 --- a/packages/connect/e2e/tests/device/setBusy.test.ts +++ b/packages/connect/e2e/tests/device/setBusy.test.ts @@ -1,5 +1,4 @@ import TrezorConnect from '../../../src'; - import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; const controller = getController(); diff --git a/packages/connect/e2e/tests/device/unlockPath.test.ts b/packages/connect/e2e/tests/device/unlockPath.test.ts index 3c41c7cae8f..4a7506401b7 100644 --- a/packages/connect/e2e/tests/device/unlockPath.test.ts +++ b/packages/connect/e2e/tests/device/unlockPath.test.ts @@ -1,5 +1,4 @@ import TrezorConnect from '../../../src'; - import { getController, setup, conditionalTest, initTrezorConnect } from '../../common.setup'; const controller = getController(); diff --git a/packages/connect/e2e/utils.ts b/packages/connect/e2e/utils.ts index f939c45603f..b943388cfe2 100644 --- a/packages/connect/e2e/utils.ts +++ b/packages/connect/e2e/utils.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars let Trezor: { getController: (testName?: string) => any; setup: (controller: any, options: any) => any; diff --git a/packages/connect/eslint.config.mjs b/packages/connect/eslint.config.mjs new file mode 100644 index 00000000000..1838b0075e2 --- /dev/null +++ b/packages/connect/eslint.config.mjs @@ -0,0 +1,20 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-bitwise': 'off', // airbnb-base: used in hardending + 'no-underscore-dangle': 'off', // underscore is used + camelcase: 'off', // camelcase is used + 'no-console': 'warn', + 'no-await-in-loop': 'off', // used in legacy trezor-connect codebase + 'jest/no-jasmine-globals': 'off', // Because of the Karma tests that uses Jasmine + 'jest/no-standalone-expect': [ + 'error', + { additionalTestBlockFunctions: ['conditionalTest'] }, + ], + 'import/no-default-export': 'off', // Todo: shall be solved one day, but now its heavily used + }, + }, +]; diff --git a/packages/connect/package.json b/packages/connect/package.json index 1de183bfa4e..841ad84b5cd 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/connect", - "version": "9.4.2", + "version": "9.4.3-beta.2", "author": "Trezor ", "homepage": "https://github.com/trezor/trezor-suite/tree/develop/packages/connect", "description": "High-level javascript interface for Trezor hardware wallet.", @@ -52,7 +52,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "jest --version && jest", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", @@ -70,8 +69,8 @@ }, "dependencies": { "@babel/preset-typescript": "^7.24.7", - "@ethereumjs/common": "^4.3.0", - "@ethereumjs/tx": "^5.3.0", + "@ethereumjs/common": "^4.4.0", + "@ethereumjs/tx": "^5.4.0", "@fivebinaries/coin-selection": "2.2.1", "@trezor/blockchain-link": "workspace:*", "@trezor/blockchain-link-types": "workspace:*", @@ -89,6 +88,7 @@ "cross-fetch": "^4.0.0" }, "devDependencies": { + "@trezor/eslint": "workspace:*", "@trezor/trezor-user-env-link": "workspace:*", "@types/karma": "^6.3.8", "babel-loader": "^9.1.3", @@ -100,10 +100,12 @@ "karma-jasmine-async": "^0.0.1", "karma-sourcemap-loader": "^0.4.0", "karma-webpack": "^5.0.1", + "node-fetch": "^2.6.4", "node-libs-browser": "^2.2.1", "ts-node": "^10.9.1", "tsx": "^4.16.3", - "webpack": "^5.94.0", + "web3-utils": "^4.3.2", + "webpack": "^5.96.1", "ws": "^8.18.0" }, "peerDependencies": { diff --git a/packages/connect/setupJest.ts b/packages/connect/setupJest.ts index f0f1c3525a7..893737511e1 100644 --- a/packages/connect/setupJest.ts +++ b/packages/connect/setupJest.ts @@ -1,6 +1,7 @@ /* WARNING! This file should be imported ONLY in tests! */ import { AbstractApiTransport, UsbApi } from '@trezor/transport'; + import { DeviceModelInternal, type Features, type FirmwareRelease } from './src/types'; class TestTransport extends AbstractApiTransport { @@ -32,12 +33,15 @@ const createTransportApi = (override = {}) => // payload: Buffer.from('3f23230002000000060a046d656f77', 'hex'), // proto.Success }); }, + listen: () => {}, + dispose: () => {}, ...override, }) as unknown as UsbApi; export const createTestTransport = (apiMethods = {}) => new TestTransport({ api: createTransportApi(apiMethods), + id: 'foo-bar-id', }); export const getDeviceFeatures = (feat?: Partial): Features => ({ diff --git a/packages/connect/src/api/applyFlags.ts b/packages/connect/src/api/applyFlags.ts index a342871ed2c..0a728ef7186 100644 --- a/packages/connect/src/api/applyFlags.ts +++ b/packages/connect/src/api/applyFlags.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ApplyFlags.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; export default class ApplyFlags extends AbstractMethod<'applyFlags', PROTO.ApplyFlags> { init() { diff --git a/packages/connect/src/api/applySettings.ts b/packages/connect/src/api/applySettings.ts index c49067919b9..94391678170 100644 --- a/packages/connect/src/api/applySettings.ts +++ b/packages/connect/src/api/applySettings.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ApplySettings.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; import { ApplySettings as ApplySettingsSchema } from '../types/api/applySettings'; export default class ApplySettings extends AbstractMethod<'applySettings', PROTO.ApplySettings> { diff --git a/packages/connect/src/api/authenticateDevice.ts b/packages/connect/src/api/authenticateDevice.ts index 6d0afb7a925..5bbe34ed379 100644 --- a/packages/connect/src/api/authenticateDevice.ts +++ b/packages/connect/src/api/authenticateDevice.ts @@ -1,10 +1,11 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { UI } from '../events'; import { getFirmwareRange } from './common/paramsValidator'; import { deviceAuthenticityConfig } from '../data/deviceAuthenticityConfig'; import { AuthenticateDeviceParams } from '../types/api/authenticateDevice'; import { getRandomChallenge, verifyAuthenticityProof } from './firmware/verifyAuthenticityProof'; -import { Assert } from '@trezor/schema-utils'; export default class AuthenticateDevice extends AbstractMethod< 'authenticateDevice', diff --git a/packages/connect/src/api/authorizeCoinjoin.ts b/packages/connect/src/api/authorizeCoinjoin.ts index 5053c309429..4d1be821cea 100644 --- a/packages/connect/src/api/authorizeCoinjoin.ts +++ b/packages/connect/src/api/authorizeCoinjoin.ts @@ -1,9 +1,10 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { validatePath, getScriptType } from '../utils/pathUtils'; import { getBitcoinNetwork } from '../data/coinInfo'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; import { AuthorizeCoinjoin as AuthorizeCoinjoinSchema } from '../types/api/authorizeCoinjoin'; export default class AuthorizeCoinjoin extends AbstractMethod< diff --git a/packages/connect/src/api/backupDevice.ts b/packages/connect/src/api/backupDevice.ts index 93f002cbdd6..68d3c0479db 100644 --- a/packages/connect/src/api/backupDevice.ts +++ b/packages/connect/src/api/backupDevice.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/BackupDevice.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; export default class BackupDevice extends AbstractMethod<'backupDevice', PROTO.BackupDevice> { init() { diff --git a/packages/connect/src/api/binance/api/binanceGetAddress.ts b/packages/connect/src/api/binance/api/binanceGetAddress.ts index 3cec7c8b827..7ae967f1ea4 100644 --- a/packages/connect/src/api/binance/api/binanceGetAddress.ts +++ b/packages/connect/src/api/binance/api/binanceGetAddress.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/BinanceGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -21,6 +22,7 @@ export default class BinanceGetAddress extends AbstractMethod<'binanceGetAddress init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Binance']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('BNB'), this.firmwareRange); // create a bundle with only one batch if bundle doesn't exists diff --git a/packages/connect/src/api/binance/api/binanceGetPublicKey.ts b/packages/connect/src/api/binance/api/binanceGetPublicKey.ts index a54121a708d..c9f15a1eb0b 100644 --- a/packages/connect/src/api/binance/api/binanceGetPublicKey.ts +++ b/packages/connect/src/api/binance/api/binanceGetPublicKey.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/BinanceGetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; import type { PROTO } from '../../../constants'; -import { Assert } from '@trezor/schema-utils'; import { Bundle, GetPublicKey as GetPublicKeySchema } from '../../../types'; export default class BinanceGetPublicKey extends AbstractMethod< @@ -17,6 +18,7 @@ export default class BinanceGetPublicKey extends AbstractMethod< init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Binance']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('BNB'), this.firmwareRange); // create a bundle with only one batch if bundle doesn't exists diff --git a/packages/connect/src/api/binance/api/binanceSignTransaction.ts b/packages/connect/src/api/binance/api/binanceSignTransaction.ts index 4c8ef69e521..9b82cb0f509 100644 --- a/packages/connect/src/api/binance/api/binanceSignTransaction.ts +++ b/packages/connect/src/api/binance/api/binanceSignTransaction.ts @@ -1,13 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/BinanceSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import * as helper from '../binanceSignTx'; import { BinanceSignTransaction as BinanceSignTransactionSchema } from '../../../types/api/binance'; -import { AssertWeak } from '@trezor/schema-utils'; - import type { BinancePreparedTransaction } from '../../../types/api/binance'; type Params = { @@ -22,6 +22,7 @@ export default class BinanceSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Binance']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('BNB'), this.firmwareRange); const { payload } = this; diff --git a/packages/connect/src/api/binance/binanceSignTx.ts b/packages/connect/src/api/binance/binanceSignTx.ts index aec5165114b..e38dd384352 100644 --- a/packages/connect/src/api/binance/binanceSignTx.ts +++ b/packages/connect/src/api/binance/binanceSignTx.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/binanceSignTx.js +import { Assert } from '@trezor/schema-utils'; + import { PROTO, ERRORS } from '../../constants'; import { BinanceSDKTransaction, @@ -7,7 +9,6 @@ import { BinancePreparedTransaction, } from '../../types/api/binance'; import type { TypedCall } from '../../device/DeviceCommands'; -import { Assert } from '@trezor/schema-utils'; const processTxRequest = async ( typedCall: TypedCall, diff --git a/packages/connect/src/api/bitcoin/Fees.ts b/packages/connect/src/api/bitcoin/Fees.ts index 9c8b35fff64..1f41d03f33d 100644 --- a/packages/connect/src/api/bitcoin/Fees.ts +++ b/packages/connect/src/api/bitcoin/Fees.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/tx/Fees.js import { BigNumber } from '@trezor/utils/src/bigNumber'; + import { Blockchain } from '../../backend/BlockchainLink'; import type { CoinInfo, FeeLevel } from '../../types'; @@ -94,7 +95,7 @@ export class FeeLevels { Math.max(this.coinInfo.minFee, parseInt(response.feePerUnit, 10)), ).toString(), }; - } catch (error) { + } catch { // silent } @@ -146,7 +147,7 @@ export class FeeLevels { }); this.longTermFeeRate = findLowest(this.blocks); - } catch (error) { + } catch { // do not throw } diff --git a/packages/connect/src/api/bitcoin/TransactionComposer.ts b/packages/connect/src/api/bitcoin/TransactionComposer.ts index 029fa29ff73..31de94c5c57 100644 --- a/packages/connect/src/api/bitcoin/TransactionComposer.ts +++ b/packages/connect/src/api/bitcoin/TransactionComposer.ts @@ -1,7 +1,8 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/tx/TransactionComposer.js import { BigNumber } from '@trezor/utils/src/bigNumber'; -import { composeTx, ComposeOutput } from '@trezor/utxo-lib'; +import { composeTx, ComposeOutput, TransactionInputOutputSortingStrategy } from '@trezor/utxo-lib'; + import { FeeLevels } from './Fees'; import { Blockchain } from '../../backend/BlockchainLink'; import type { BitcoinNetworkInfo, DiscoveryAccount, SelectFeeLevel } from '../../types'; @@ -17,7 +18,7 @@ type Options = { outputs: ComposeOutput[]; coinInfo: BitcoinNetworkInfo; baseFee?: number; - skipPermutation?: boolean; + sortingStrategy: TransactionInputOutputSortingStrategy; }; export class TransactionComposer { @@ -33,7 +34,7 @@ export class TransactionComposer { baseFee: number; - skipPermutation: boolean; + sortingStrategy: TransactionInputOutputSortingStrategy; feeLevels: FeeLevels; @@ -45,7 +46,7 @@ export class TransactionComposer { this.coinInfo = options.coinInfo; this.blockHeight = 0; this.baseFee = options.baseFee || 0; - this.skipPermutation = options.skipPermutation || false; + this.sortingStrategy = options.sortingStrategy; this.feeLevels = new FeeLevels(options.coinInfo); // map to @trezor/utxo-lib/compose format @@ -164,7 +165,7 @@ export class TransactionComposer { outputs: this.outputs, feeRate, longTermFeeRate: this.feeLevels.longTermFeeRate, - skipPermutation: this.skipPermutation, + sortingStrategy: this.sortingStrategy, network: coinInfo.network, changeAddress, dustThreshold: coinInfo.dustLimit, diff --git a/packages/connect/src/api/bitcoin/__fixtures__/refTx.ts b/packages/connect/src/api/bitcoin/__fixtures__/refTx.ts index 0b4bb35c6d5..eae74795ec8 100644 --- a/packages/connect/src/api/bitcoin/__fixtures__/refTx.ts +++ b/packages/connect/src/api/bitcoin/__fixtures__/refTx.ts @@ -1,5 +1,6 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { networks } from '@trezor/utxo-lib'; + // @ts-ignore import tx43d273 from '../../../../e2e/__txcache__/testnet/43d273d3caf41759ad843474f960fbf80ff2ec961135d018b61e9fab3ad1fc06.json'; // @ts-ignore diff --git a/packages/connect/src/api/bitcoin/__tests__/Fees.test.ts b/packages/connect/src/api/bitcoin/__tests__/Fees.test.ts index 695ad337530..401dc0c1156 100644 --- a/packages/connect/src/api/bitcoin/__tests__/Fees.test.ts +++ b/packages/connect/src/api/bitcoin/__tests__/Fees.test.ts @@ -1,6 +1,7 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import coinsJSONEth from '@trezor/connect-common/files/coins-eth.json'; import BlockchainLink from '@trezor/blockchain-link'; + import { parseCoinsJson, getBitcoinNetwork } from '../../../data/coinInfo'; import { initBlockchain } from '../../../backend/BlockchainLink'; import { FeeLevels } from '../Fees'; @@ -110,6 +111,7 @@ describe('api/bitcoin/Fees', () => { // const e2eNetworks = ['BTC', 'TEST', 'BCH', 'BTG', 'DASH', 'DGB', 'DOGE', 'LTC', 'NMC', 'VTC']; // e2eNetworks.forEach(network => { + // eslint-disable-next-line jest/no-commented-out-tests // it.only(`${network} e2e smart FeeLevels`, async () => { // const coinInfo = getBitcoinNetwork(network)!; // if (!coinInfo) throw new Error('coinInfo is missing'); diff --git a/packages/connect/src/api/bitcoin/__tests__/inputs.test.ts b/packages/connect/src/api/bitcoin/__tests__/inputs.test.ts index dce271afdf1..2eaf7207c9f 100644 --- a/packages/connect/src/api/bitcoin/__tests__/inputs.test.ts +++ b/packages/connect/src/api/bitcoin/__tests__/inputs.test.ts @@ -1,4 +1,5 @@ import { networks } from '@trezor/utxo-lib'; + import { validateTrezorInputs } from '../inputs'; import * as fixtures from '../__fixtures__/inputs'; diff --git a/packages/connect/src/api/bitcoin/__tests__/signtxVerify.test.ts b/packages/connect/src/api/bitcoin/__tests__/signtxVerify.test.ts index 07c89977778..1ad323d7136 100644 --- a/packages/connect/src/api/bitcoin/__tests__/signtxVerify.test.ts +++ b/packages/connect/src/api/bitcoin/__tests__/signtxVerify.test.ts @@ -1,4 +1,5 @@ import { networks } from '@trezor/utxo-lib'; + import { verifyTx } from '../signtxVerify'; import fixtures from '../__fixtures__/signtxVerify'; diff --git a/packages/connect/src/api/bitcoin/createPendingTx.ts b/packages/connect/src/api/bitcoin/createPendingTx.ts index 4ea0724950e..d5daedbbb07 100644 --- a/packages/connect/src/api/bitcoin/createPendingTx.ts +++ b/packages/connect/src/api/bitcoin/createPendingTx.ts @@ -1,9 +1,8 @@ import { BigNumber } from '@trezor/utils/src/bigNumber'; - import { Transaction as BitcoinJsTransaction } from '@trezor/utxo-lib'; + import { getSerializedPath } from '../../utils/pathUtils'; import { PROTO } from '../../constants'; - import type { AccountAddresses } from '../../types'; export const createPendingTransaction = ( diff --git a/packages/connect/src/api/bitcoin/inputs.ts b/packages/connect/src/api/bitcoin/inputs.ts index 856cbe01306..062834e8221 100644 --- a/packages/connect/src/api/bitcoin/inputs.ts +++ b/packages/connect/src/api/bitcoin/inputs.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/tx/inputs.js import { Transaction as BitcoinJsTransaction } from '@trezor/utxo-lib'; + import { validatePath, isSegwitPath, diff --git a/packages/connect/src/api/bitcoin/outputs.ts b/packages/connect/src/api/bitcoin/outputs.ts index 99e25ea4145..dd23bee93dc 100644 --- a/packages/connect/src/api/bitcoin/outputs.ts +++ b/packages/connect/src/api/bitcoin/outputs.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/tx/outputs.js import { ComposeOutput as ComposeOutputBase } from '@trezor/utxo-lib'; + import { getOutputScriptType, fixPath, getHDPath } from '../../utils/pathUtils'; import { isValidAddress } from '../../utils/addressUtils'; import { convertMultisigPubKey } from '../../utils/hdnodeUtils'; diff --git a/packages/connect/src/api/bitcoin/refTx.ts b/packages/connect/src/api/bitcoin/refTx.ts index 27cd7bedc46..a2aeca99414 100644 --- a/packages/connect/src/api/bitcoin/refTx.ts +++ b/packages/connect/src/api/bitcoin/refTx.ts @@ -7,12 +7,14 @@ import { Network, } from '@trezor/utxo-lib'; import { bufferUtils } from '@trezor/utils'; -import { getHDPath, getScriptType, getOutputScriptType } from '../../utils/pathUtils'; -import { TypedError } from '../../constants/errors'; import type { TxInput as BitcoinJsInput, TxOutput as BitcoinJsOutput, } from '@trezor/utxo-lib/src/transaction/base'; +import { Assert, Type } from '@trezor/schema-utils'; + +import { getHDPath, getScriptType, getOutputScriptType } from '../../utils/pathUtils'; +import { TypedError } from '../../constants/errors'; import type { CoinInfo, AccountAddresses, @@ -21,7 +23,6 @@ import type { } from '../../types'; import type { RefTransaction, TransactionOptions } from '../../types/api/bitcoin'; import { PROTO } from '../../constants'; -import { Assert, Type } from '@trezor/schema-utils'; // Referenced transactions are not required if: // - all internal inputs script_type === SPENDTAPROOT diff --git a/packages/connect/src/api/bitcoin/signtxVerify.ts b/packages/connect/src/api/bitcoin/signtxVerify.ts index 10c70aa63cc..29887f26ea6 100644 --- a/packages/connect/src/api/bitcoin/signtxVerify.ts +++ b/packages/connect/src/api/bitcoin/signtxVerify.ts @@ -6,8 +6,8 @@ import { payments as BitcoinJsPayments, Transaction as BitcoinJsTransaction, } from '@trezor/utxo-lib'; -import { PROTO, ERRORS } from '../../constants'; +import { PROTO, ERRORS } from '../../constants'; import type { BitcoinNetworkInfo } from '../../types'; import type { DeviceCommands } from '../../device/DeviceCommands'; diff --git a/packages/connect/src/api/blockchainDisconnect.ts b/packages/connect/src/api/blockchainDisconnect.ts index e3fafc4c422..ee21a745969 100644 --- a/packages/connect/src/api/blockchainDisconnect.ts +++ b/packages/connect/src/api/blockchainDisconnect.ts @@ -1,11 +1,12 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/blockchain/BlockchainDisconnect.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { ERRORS } from '../constants'; import { isBackendSupported, findBackend } from '../backend/BlockchainLink'; import { getCoinInfo } from '../data/coinInfo'; import { CoinObj, CoinInfo } from '../types'; -import { Assert } from '@trezor/schema-utils'; type Params = { coinInfo: CoinInfo; diff --git a/packages/connect/src/api/blockchainEstimateFee.ts b/packages/connect/src/api/blockchainEstimateFee.ts index f1cacb9e306..2bd37bd9562 100644 --- a/packages/connect/src/api/blockchainEstimateFee.ts +++ b/packages/connect/src/api/blockchainEstimateFee.ts @@ -73,9 +73,7 @@ export default class BlockchainEstimateFee extends AbstractMethod<'blockchainEst }; if (request && request.feeLevels) { const fees = new FeeLevels(coinInfo); - // TODO: https://github.com/trezor/trezor-suite/issues/5340 - // smart fees for DOGE are not relevant since their fee policy changed, see @trezor/utxo-lib/compose: baseFee - if (request.feeLevels === 'smart' && coinInfo.shortcut !== 'DOGE') { + if (request.feeLevels === 'smart') { const backend = await initBlockchain(coinInfo, this.postMessage, identity); await fees.load(backend); } diff --git a/packages/connect/src/api/blockchainEvmRpcCall.ts b/packages/connect/src/api/blockchainEvmRpcCall.ts new file mode 100644 index 00000000000..d2ebdc93756 --- /dev/null +++ b/packages/connect/src/api/blockchainEvmRpcCall.ts @@ -0,0 +1,63 @@ +import { AbstractMethod, Payload } from '../core/AbstractMethod'; +import { validateParams } from './common/paramsValidator'; +import { ERRORS } from '../constants'; +import { CoinInfo } from '../types'; +import { initBlockchain, isBackendSupported } from '../backend/BlockchainLink'; +import { getCoinInfo } from '../data/coinInfo'; + +type Params = { + coinInfo: CoinInfo; + identity?: string; + request: Omit, 'method' | 'coin'>; +}; + +export default class BlockchainEvmRpcCall extends AbstractMethod<'blockchainEvmRpcCall', Params> { + init() { + this.useDevice = false; + this.useUi = false; + + const { payload } = this; + + // validate incoming parameters + validateParams(payload, [ + { name: 'coin', type: 'string', required: true }, + { name: 'identity', type: 'string' }, + { name: 'from', type: 'string' }, + { name: 'to', type: 'string', required: true }, + { name: 'data', type: 'string', required: true }, + ]); + + const coinInfo = getCoinInfo(payload.coin); + + if (!coinInfo) { + throw ERRORS.TypedError('Method_UnknownCoin'); + } + // validate backend + isBackendSupported(coinInfo); + + this.params = { + coinInfo, + identity: payload.identity, + request: { + from: payload.from, + to: payload.to, + data: payload.data, + }, + }; + } + + get info() { + return 'Blockchain Evm Rpc Call'; + } + + async run() { + const backend = await initBlockchain( + this.params.coinInfo, + postMessage, + this.params.identity, + ); + const response = await backend.rpcCall(this.params.request); + + return response; + } +} diff --git a/packages/connect/src/api/cancelCoinjoinAuthorization.ts b/packages/connect/src/api/cancelCoinjoinAuthorization.ts index 102a7caa0a6..c1f57db01d1 100644 --- a/packages/connect/src/api/cancelCoinjoinAuthorization.ts +++ b/packages/connect/src/api/cancelCoinjoinAuthorization.ts @@ -1,7 +1,8 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; import { CancelCoinjoinAuthorization as CancelCoinjoinAuthorizationSchema } from '../types/api/cancelCoinjoinAuthorization'; export default class CancelCoinjoinAuthorization extends AbstractMethod< diff --git a/packages/connect/src/api/cardano/api/cardanoGetAddress.ts b/packages/connect/src/api/cardano/api/cardanoGetAddress.ts index 07659a4c61d..e78ade2771a 100644 --- a/packages/connect/src/api/cardano/api/cardanoGetAddress.ts +++ b/packages/connect/src/api/cardano/api/cardanoGetAddress.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/CardanoGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; @@ -12,7 +14,6 @@ import { } from '../cardanoAddressParameters'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { CardanoGetAddress as CardanoGetAddressSchema } from '../../../types/api/cardano'; @@ -27,6 +28,7 @@ export default class CardanoGetAddress extends AbstractMethod<'cardanoGetAddress init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Cardano']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Cardano'), diff --git a/packages/connect/src/api/cardano/api/cardanoGetNativeScriptHash.ts b/packages/connect/src/api/cardano/api/cardanoGetNativeScriptHash.ts index ef7b2bcbf3a..57f616642bc 100644 --- a/packages/connect/src/api/cardano/api/cardanoGetNativeScriptHash.ts +++ b/packages/connect/src/api/cardano/api/cardanoGetNativeScriptHash.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/CardanoGetNativeScriptHash.js +import { Assert } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; @@ -9,7 +11,6 @@ import { CardanoGetNativeScriptHash as CardanoGetNativeScriptHashSchema, CardanoNativeScript, } from '../../../types/api/cardano'; -import { Assert } from '@trezor/schema-utils'; export default class CardanoGetNativeScriptHash extends AbstractMethod< 'cardanoGetNativeScriptHash', @@ -17,6 +18,7 @@ export default class CardanoGetNativeScriptHash extends AbstractMethod< > { init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Cardano']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Cardano'), diff --git a/packages/connect/src/api/cardano/api/cardanoGetPublicKey.ts b/packages/connect/src/api/cardano/api/cardanoGetPublicKey.ts index 814a02db15c..78a8fadff90 100644 --- a/packages/connect/src/api/cardano/api/cardanoGetPublicKey.ts +++ b/packages/connect/src/api/cardano/api/cardanoGetPublicKey.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/CardanoGetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { CardanoGetPublicKey as CardanoGetPublicKeySchema } from '../../../types/api/cardano'; @@ -19,6 +20,7 @@ export default class CardanoGetPublicKey extends AbstractMethod<'cardanoGetPubli init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Cardano']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Cardano'), diff --git a/packages/connect/src/api/cardano/api/cardanoSignTransaction.ts b/packages/connect/src/api/cardano/api/cardanoSignTransaction.ts index ef748f83971..c780e77c505 100644 --- a/packages/connect/src/api/cardano/api/cardanoSignTransaction.ts +++ b/packages/connect/src/api/cardano/api/cardanoSignTransaction.ts @@ -4,6 +4,8 @@ import { trezorUtils } from '@fivebinaries/coin-selection'; +import { AssertWeak, Type } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; @@ -35,7 +37,6 @@ import { import { gatherWitnessPaths } from '../cardanoWitnesses'; import type { AssetGroupWithTokens } from '../cardanoTokenBundle'; import { tokenBundleToProto } from '../cardanoTokenBundle'; -import { AssertWeak, Type } from '@trezor/schema-utils'; const CardanoSignTransactionFeatures = Object.freeze({ // FW <2.6.0 is not supported by Connect at all @@ -77,6 +78,7 @@ export default class CardanoSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Cardano']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Cardano'), diff --git a/packages/connect/src/api/cardano/cardanoAddressParameters.ts b/packages/connect/src/api/cardano/cardanoAddressParameters.ts index 7c5e76c71ff..2695f8eac57 100644 --- a/packages/connect/src/api/cardano/cardanoAddressParameters.ts +++ b/packages/connect/src/api/cardano/cardanoAddressParameters.ts @@ -1,9 +1,10 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/cardanoAddressParameters.js +import { Assert } from '@trezor/schema-utils'; + import { validatePath } from '../../utils/pathUtils'; import { PROTO, ERRORS } from '../../constants'; import { CardanoAddressParameters } from '../../types/api/cardano'; -import { Assert } from '@trezor/schema-utils'; export const validateAddressParameters = (addressParameters: CardanoAddressParameters) => { Assert(CardanoAddressParameters, addressParameters); diff --git a/packages/connect/src/api/cardano/cardanoAuxiliaryData.ts b/packages/connect/src/api/cardano/cardanoAuxiliaryData.ts index bb92b3557c3..e625c3c4d9a 100644 --- a/packages/connect/src/api/cardano/cardanoAuxiliaryData.ts +++ b/packages/connect/src/api/cardano/cardanoAuxiliaryData.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/cardanoAuxiliaryData.js +import { Assert } from '@trezor/schema-utils'; + import { addressParametersToProto, modifyAddressParametersForBackwardsCompatibility, @@ -12,7 +14,6 @@ import { CardanoCVoteRegistrationDelegation, CardanoCVoteRegistrationParameters, } from '../../types/api/cardano'; -import { Assert } from '@trezor/schema-utils'; const MAX_DELEGATION_COUNT = 32; diff --git a/packages/connect/src/api/cardano/cardanoCertificate.ts b/packages/connect/src/api/cardano/cardanoCertificate.ts index 92998d7545f..29d39b3db0a 100644 --- a/packages/connect/src/api/cardano/cardanoCertificate.ts +++ b/packages/connect/src/api/cardano/cardanoCertificate.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/cardanoCertificate.js +import { Assert } from '@trezor/schema-utils'; + import { validatePath } from '../../utils/pathUtils'; import { PROTO, ERRORS } from '../../constants'; import { @@ -9,7 +11,6 @@ import { CardanoPoolRelay, CardanoDRep, } from '../../types/api/cardano'; -import { Assert } from '@trezor/schema-utils'; const ipv4AddressToHex = (ipv4Address: string) => Buffer.from(ipv4Address.split('.').map(ipPart => parseInt(ipPart, 10))).toString('hex'); diff --git a/packages/connect/src/api/cardano/cardanoInputs.ts b/packages/connect/src/api/cardano/cardanoInputs.ts index fe24e2940f8..86c80a830a4 100644 --- a/packages/connect/src/api/cardano/cardanoInputs.ts +++ b/packages/connect/src/api/cardano/cardanoInputs.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/cardanoInputs.js +import { Assert, Type, Static } from '@trezor/schema-utils'; + import { validatePath } from '../../utils/pathUtils'; import { PROTO } from '../../constants'; -import { Assert, Type, Static } from '@trezor/schema-utils'; import { DerivationPath } from '../../exports'; export type Path = number[]; diff --git a/packages/connect/src/api/cardano/cardanoOutputs.ts b/packages/connect/src/api/cardano/cardanoOutputs.ts index aa6638936d1..97ece64e515 100644 --- a/packages/connect/src/api/cardano/cardanoOutputs.ts +++ b/packages/connect/src/api/cardano/cardanoOutputs.ts @@ -2,12 +2,13 @@ // allow for...of statements +import { Assert, Type } from '@trezor/schema-utils'; + import { addressParametersToProto, validateAddressParameters } from './cardanoAddressParameters'; import { tokenBundleToProto, AssetGroupWithTokens } from './cardanoTokenBundle'; import { PROTO } from '../../constants'; import { hexStringByteLength, sendChunkedHexString } from './cardanoUtils'; import { CardanoAssetGroup, CardanoAddressParameters } from '../../types/api/cardano'; -import { Assert, Type } from '@trezor/schema-utils'; export type OutputWithData = { output: PROTO.CardanoTxOutput; diff --git a/packages/connect/src/api/cardano/cardanoTokenBundle.ts b/packages/connect/src/api/cardano/cardanoTokenBundle.ts index 9916c5d0f52..ac98219e5e6 100644 --- a/packages/connect/src/api/cardano/cardanoTokenBundle.ts +++ b/packages/connect/src/api/cardano/cardanoTokenBundle.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/cardanoTokenBundle.js +import { Assert, Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { CardanoAssetGroup, CardanoToken } from '../../types/api/cardano'; -import { Assert, Type, Static } from '@trezor/schema-utils'; export type AssetGroupWithTokens = Static; export const AssetGroupWithTokens = Type.Object({ diff --git a/packages/connect/src/api/changeLanguage.ts b/packages/connect/src/api/changeLanguage.ts index 472da0522b3..d4576253e1d 100644 --- a/packages/connect/src/api/changeLanguage.ts +++ b/packages/connect/src/api/changeLanguage.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ChangeLanguage.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { UI } from '../events'; -import { Assert } from '@trezor/schema-utils'; import { ChangeLanguage as ChangeLanguageSchema } from '../types/api/changeLanguage'; export default class ChangeLanguage extends AbstractMethod<'changeLanguage', ChangeLanguageSchema> { diff --git a/packages/connect/src/api/changePin.ts b/packages/connect/src/api/changePin.ts index 932130fdddf..b0d094026f2 100644 --- a/packages/connect/src/api/changePin.ts +++ b/packages/connect/src/api/changePin.ts @@ -1,7 +1,8 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ChangePin.js -import { AbstractMethod } from '../core/AbstractMethod'; import { Assert } from '@trezor/schema-utils'; + +import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; export default class ChangePin extends AbstractMethod<'changePin', PROTO.ChangePin> { diff --git a/packages/connect/src/api/checkFirmwareAuthenticity.ts b/packages/connect/src/api/checkFirmwareAuthenticity.ts deleted file mode 100644 index f2767999d5b..00000000000 --- a/packages/connect/src/api/checkFirmwareAuthenticity.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { randomBytes } from 'crypto'; -import { AbstractMethod } from '../core/AbstractMethod'; -import { stripFwHeaders, calculateFirmwareHash, getBinary } from './firmware'; -import { getReleases } from '../data/firmwareInfo'; -import { ERRORS } from '../constants'; -import { FirmwareType } from '../types'; -import { validateParams } from './common/paramsValidator'; -import { CheckFirmwareAuthenticityParams } from '../types/api/checkFirmwareAuthenticity'; - -export default class CheckFirmwareAuthenticity extends AbstractMethod< - 'checkFirmwareAuthenticity', - CheckFirmwareAuthenticityParams -> { - init() { - this.useEmptyPassphrase = true; - this.requiredPermissions = ['management']; - this.useDeviceState = false; - - const { payload } = this; - validateParams(payload, [{ name: 'baseUrl', type: 'string' }]); - this.params = { baseUrl: payload.baseUrl }; - } - - async run() { - const { device } = this; - - if (!device.firmwareRelease) { - throw ERRORS.TypedError('Runtime', 'device.firmwareRelease is not set'); - } - - const btcOnly = device.firmwareType === FirmwareType.BitcoinOnly; - - try { - const fw = await getBinary({ - releases: getReleases(device.features?.internal_model), - baseUrl: this.params.baseUrl ?? 'https://data.trezor.io', - version: device.getVersion(), - btcOnly, - }); - - if (!fw) { - throw ERRORS.TypedError( - 'Runtime', - 'checkFirmwareAuthenticity: firmware binary not found', - ); - } - - const { hash: expectedFirmwareHash, challenge } = calculateFirmwareHash( - device.features.major_version, - stripFwHeaders(fw), - randomBytes(32), - ); - - const result = await this.device - .getCommands() - .typedCall('GetFirmwareHash', 'FirmwareHash', { - challenge, - }); - - const { message } = result; - const { hash: actualFirmwareHash } = message; - - return { - expectedFirmwareHash, - actualFirmwareHash, - valid: actualFirmwareHash === expectedFirmwareHash, - }; - } catch (e) { - throw ERRORS.TypedError('Runtime', `${e}`); - } - } -} diff --git a/packages/connect/src/api/cipherKeyValue.ts b/packages/connect/src/api/cipherKeyValue.ts index a7efc79921d..4a45b211928 100644 --- a/packages/connect/src/api/cipherKeyValue.ts +++ b/packages/connect/src/api/cipherKeyValue.ts @@ -1,11 +1,12 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/CipherKeyValue.js +import { Assert } from '@trezor/schema-utils'; + import { UI, createUiMessage } from '../events'; import { AbstractMethod } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { validatePath } from '../utils/pathUtils'; import type { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../types/params'; import { CipherKeyValue as CipherKeyValueSchema } from '../types/api/cipherKeyValue'; diff --git a/packages/connect/src/api/common/Discovery.ts b/packages/connect/src/api/common/Discovery.ts index 7d4fafdeb63..d4251abd52e 100644 --- a/packages/connect/src/api/common/Discovery.ts +++ b/packages/connect/src/api/common/Discovery.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/Discovery.js import EventEmitter from 'events'; + import { PROTO, ERRORS } from '../../constants'; import { Blockchain } from '../../backend/BlockchainLink'; import { DeviceCommands } from '../../device/DeviceCommands'; diff --git a/packages/connect/src/api/common/__fixtures__/paramsValidator.ts b/packages/connect/src/api/common/__fixtures__/paramsValidator.ts index 168533e5f7b..d81d982fee1 100644 --- a/packages/connect/src/api/common/__fixtures__/paramsValidator.ts +++ b/packages/connect/src/api/common/__fixtures__/paramsValidator.ts @@ -1,3 +1,5 @@ +import { DeviceModelInternal, FirmwareRange } from '../../../exports'; + export const validateParams = [ { description: 'array', @@ -171,13 +173,21 @@ export const validateParams = [ const DEFAULT_RANGE = { T1B1: { min: '1.0.0', max: '0' }, T2T1: { min: '2.0.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, + T2B1: { min: '2.0.0', max: '0' }, + T3B1: { min: '2.0.0', max: '0' }, + T3T1: { min: '2.0.0', max: '0' }, + T3W1: { min: '2.0.0', max: '0' }, }; const DEFAULT_COIN_INFO = { - support: { T1B1: '1.6.2', T2T1: '2.1.0', T2B1: '2.6.1', T3B1: '2.8.1', T3T1: '2.7.1' }, + support: { + T1B1: '1.6.2', + T2T1: '2.1.0', + T2B1: '2.0.0', + T3B1: '2.0.0', + T3T1: '2.0.0', + T3W1: '2.0.0', + }, shortcut: 'btc', type: 'bitcoin', }; @@ -197,25 +207,30 @@ export const getFirmwareRange = [ description: 'range from coinInfo', config: EMPTY_CONFIG, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], - result: { - T1B1: { min: '1.6.2', max: '0' }, - T2T1: { min: '2.1.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, - }, + result: (Object.keys(DEFAULT_RANGE) as DeviceModelInternal[]).reduce( + (acc, model: DeviceModelInternal) => { + acc[model] = { + min: DEFAULT_COIN_INFO.support[model], + max: '0', + }; + + return acc; + }, + {} as FirmwareRange, + ), }, { description: 'coinInfo without support', config: EMPTY_CONFIG, params: ['signTransaction', { shortcut: 'btc', type: 'bitcoin' }, DEFAULT_RANGE], - result: { - T1B1: { min: '0', max: '0' }, - T2T1: { min: '0', max: '0' }, - T2B1: { min: '0', max: '0' }, - T3B1: { min: '0', max: '0' }, - T3T1: { min: '0', max: '0' }, - }, + result: (Object.keys(DEFAULT_COIN_INFO.support) as DeviceModelInternal[]).reduce( + (acc, model) => { + acc[model] = { min: '0', max: '0' }; + + return acc; + }, + {} as FirmwareRange, + ), }, { description: 'coinInfo without T1B1 support', @@ -223,19 +238,22 @@ export const getFirmwareRange = [ params: [ 'signTransaction', { - support: { T1B1: false, T2T1: '2.1.0', T2B1: '2.6.1', T3T1: '2.7.1' }, + support: { + T1B1: false, + T2T1: '2.1.0', + }, shortcut: 'btc', type: 'bitcoin', }, DEFAULT_RANGE, ], - result: { - T1B1: { min: '0', max: '0' }, - T2T1: { min: '2.1.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, - }, + result: ( + Object.entries(DEFAULT_COIN_INFO.support) as [DeviceModelInternal, string][] + ).reduce((acc, [model, min]) => { + acc[model] = { min: model === 'T1B1' ? '0' : min, max: '0' }; + + return acc; + }, {} as FirmwareRange), }, { description: 'coinInfo without T2 support', @@ -243,19 +261,26 @@ export const getFirmwareRange = [ params: [ 'signTransaction', { - support: { T1B1: '1.6.2', T2T1: false, T2B1: false, T3B1: false, T3T1: false }, + support: { + T1B1: '1.6.2', + T2T1: false, + T2B1: false, + T3B1: false, + T3T1: false, + T3W1: false, + }, shortcut: 'btc', type: 'bitcoin', }, DEFAULT_RANGE, ], - result: { - T1B1: { min: '1.6.2', max: '0' }, - T2T1: { min: '0', max: '0' }, - T2B1: { min: '0', max: '0' }, - T3B1: { min: '0', max: '0' }, - T3T1: { min: '0', max: '0' }, - }, + result: ( + Object.entries(DEFAULT_COIN_INFO.support) as [DeviceModelInternal, string][] + ).reduce((acc, [model, min]) => { + acc[model] = { min: model === 'T1B1' ? min : '0', max: '0' }; + + return acc; + }, {} as FirmwareRange), }, { description: 'coinInfo support is lower than default', @@ -287,11 +312,9 @@ export const getFirmwareRange = [ }, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.11.0', max: '0' }, T2T1: { min: '2.5.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -305,11 +328,9 @@ export const getFirmwareRange = [ }, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.0', max: '0' }, T2T1: { min: '2.4.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -323,11 +344,9 @@ export const getFirmwareRange = [ }, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.0', max: '0' }, T2T1: { min: '2.4.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -355,11 +374,9 @@ export const getFirmwareRange = [ }, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.0', max: '0' }, T2T1: { min: '2.4.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -387,11 +404,9 @@ export const getFirmwareRange = [ }, params: ['decreaseOutput', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.0', max: '0' }, T2T1: { min: '2.4.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -404,18 +419,16 @@ export const getFirmwareRange = [ params: [ 'signTransaction', { - support: { T1B1: '1.10.0', T2T1: '2.4.0', T2B1: '2.6.1' }, + support: { T1B1: '1.10.0', T2T1: '2.4.0' }, shortcut: 'btc', type: 'bitcoin', }, DEFAULT_RANGE, ], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.0', max: '0' }, T2T1: { min: '2.4.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -427,11 +440,9 @@ export const getFirmwareRange = [ }, params: ['signTransaction', DEFAULT_COIN_INFO, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.6.2', max: '1.10.0' }, T2T1: { min: '2.1.0', max: '2.4.0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -457,18 +468,16 @@ export const getFirmwareRange = [ params: [ 'getAccountInfo', { - support: { T1B1: '1.0.1', T2T1: '2.0.1', T2B1: '2.6.1' }, + support: { T1B1: '1.0.1', T2T1: '2.0.1' }, shortcut: 'xrp', type: 'ripple', }, DEFAULT_RANGE, ], result: { + ...DEFAULT_RANGE, T1B1: { min: '0', max: '0' }, T2T1: { min: '2.1.0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { @@ -481,28 +490,25 @@ export const getFirmwareRange = [ params: [ 'eip1559', { - support: { T1B1: '1.6.2', T2T1: '2.1.0', T2B1: '2.6.1' }, + support: { T1B1: '1.6.2', T2T1: '2.1.0' }, shortcut: 'eth', type: 'ethereum', }, DEFAULT_RANGE, ], result: { + ...DEFAULT_RANGE, T1B1: { min: '1.10.4', max: '0' }, T2T1: { min: '2.4.2', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, - T3T1: { min: '2.7.1', max: '0' }, }, }, { description: 'method not available for T1B1 and T2T1, defined by config', params: ['authenticateDevice', undefined, DEFAULT_RANGE], result: { + ...DEFAULT_RANGE, T1B1: { min: '0', max: '0' }, T2T1: { min: '0', max: '0' }, - T2B1: { min: '2.6.1', max: '0' }, - T3B1: { min: '2.8.1', max: '0' }, T3T1: { min: '2.8.0', max: '0' }, }, }, diff --git a/packages/connect/src/api/common/__tests__/paramsValidator.test.ts b/packages/connect/src/api/common/__tests__/paramsValidator.test.ts index 6ac8188ea56..e79e709f562 100644 --- a/packages/connect/src/api/common/__tests__/paramsValidator.test.ts +++ b/packages/connect/src/api/common/__tests__/paramsValidator.test.ts @@ -23,24 +23,26 @@ describe('helpers/paramsValidator', () => { jest.clearAllMocks(); }); fixtures.getFirmwareRange.forEach(f => { - it(f.description, done => { - jest.resetModules(); + it(f.description, () => { + return new Promise(done => { + jest.resetModules(); - const mock = f.config; - jest.mock('../../../data/config', () => { - const actualConfig = jest.requireActual('../../../data/config').config; + const mock = f.config; + jest.mock('../../../data/config', () => { + const actualConfig = jest.requireActual('../../../data/config').config; - return { - __esModule: true, - config: mock || actualConfig, - }; - }); + return { + __esModule: true, + config: mock || actualConfig, + }; + }); - import('../paramsValidator').then(({ getFirmwareRange }) => { - // added new capability - // @ts-expect-error - expect(getFirmwareRange(...f.params)).toEqual(f.result); - done(); + import('../paramsValidator').then(({ getFirmwareRange }) => { + // added new capability + // @ts-expect-error + expect(getFirmwareRange(...f.params)).toEqual(f.result); + done(); + }); }); }); }); diff --git a/packages/connect/src/api/common/paramsValidator.ts b/packages/connect/src/api/common/paramsValidator.ts index 7af8bd6978f..b23a0bc0882 100644 --- a/packages/connect/src/api/common/paramsValidator.ts +++ b/packages/connect/src/api/common/paramsValidator.ts @@ -42,7 +42,7 @@ export function validateParams

>(params: P, schema: validateParams(p, [{ name: field.name, type: t }]); return count + 1; - } catch (e) { + } catch { return count; } }, 0); @@ -166,7 +166,9 @@ export const getFirmwareRange = ( // 0 may be confusing. means: no-support for "min" and unlimited support for "max" if (rule.min) { models.forEach(model => { - const modelMin = rule.min[model]; + const modelMin = (rule.min as Record)[ + model + ]; if (modelMin) { if ( modelMin === '0' || diff --git a/packages/connect/src/api/composeTransaction.ts b/packages/connect/src/api/composeTransaction.ts index f1b7ef42604..f72749c222d 100644 --- a/packages/connect/src/api/composeTransaction.ts +++ b/packages/connect/src/api/composeTransaction.ts @@ -1,6 +1,8 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ComposeTransaction.js import { BigNumber } from '@trezor/utils/src/bigNumber'; +import type { ComposeOutput, TransactionInputOutputSortingStrategy } from '@trezor/utxo-lib'; + import { AbstractMethod } from '../core/AbstractMethod'; import { ERRORS } from '../constants'; import { UI, createUiMessage } from '../events'; @@ -25,7 +27,6 @@ import { verifyTx, parseTransactionHexes, } from './bitcoin'; -import type { ComposeOutput } from '@trezor/utxo-lib'; import type { BitcoinNetworkInfo, DiscoveryAccount, AccountUtxo } from '../types'; import type { SignedTransaction, @@ -34,6 +35,7 @@ import type { PrecomposedResult, } from '../types/api/composeTransaction'; import type { RefTransaction } from '../types/api/bitcoin'; +import { DEFAULT_SORTING_STRATEGY } from '../constants/utxo'; type Params = { outputs: ComposeOutput[]; @@ -45,9 +47,20 @@ type Params = { baseFee?: PrecomposeParams['baseFee']; floorBaseFee?: PrecomposeParams['floorBaseFee']; sequence?: PrecomposeParams['sequence']; - skipPermutation?: PrecomposeParams['skipPermutation']; total: BigNumber; -}; + sortingStrategy: PrecomposeParams['sortingStrategy']; +} & ( + | { + /** @deprecated: use sortingStrategy=none instead */ + skipPermutation?: PrecomposeParams['skipPermutation']; + sortingStrategy?: undefined; + } + | { + /** @deprecated: use sortingStrategy=none instead */ + skipPermutation?: undefined; + sortingStrategy?: TransactionInputOutputSortingStrategy; + } +); export default class ComposeTransaction extends AbstractMethod<'composeTransaction', Params> { discovery?: Discovery; @@ -68,6 +81,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti { name: 'floorBaseFee', type: 'boolean' }, { name: 'sequence', type: 'number' }, { name: 'skipPermutation', type: 'boolean' }, + { name: 'sortingStrategy', type: 'string' }, ]); const coinInfo = getBitcoinNetwork(payload.coin); @@ -115,7 +129,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti baseFee: payload.baseFee, floorBaseFee: payload.floorBaseFee, sequence: payload.sequence, - skipPermutation: payload.skipPermutation, + sortingStrategy: payload.skipPermutation === true ? 'none' : payload.sortingStrategy, push: typeof payload.push === 'boolean' ? payload.push : false, total, }; @@ -143,7 +157,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti account: PrecomposeParams['account'], feeLevels: PrecomposeParams['feeLevels'], ): Promise { - const { coinInfo, outputs, baseFee, skipPermutation } = this.params; + const { coinInfo, outputs, baseFee, sortingStrategy } = this.params; const address_n = pathUtils.validatePath(account.path); const composer = new TransactionComposer({ account: { @@ -157,7 +171,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti coinInfo, outputs, baseFee, - skipPermutation, + sortingStrategy: sortingStrategy ?? DEFAULT_SORTING_STRATEGY, }); // This is mandatory, @trezor/utxo-lib/compose expects current block height @@ -301,7 +315,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti } async selectFee(account: DiscoveryAccount, utxos: AccountUtxo[]) { - const { coinInfo, outputs } = this.params; + const { coinInfo, outputs, sortingStrategy, skipPermutation } = this.params; // get backend instance (it should be initialized before) const blockchain = await this.getBlockchain(); @@ -310,6 +324,8 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti utxos, coinInfo, outputs, + sortingStrategy: + skipPermutation === true ? 'none' : sortingStrategy ?? DEFAULT_SORTING_STRATEGY, }); await composer.init(blockchain); diff --git a/packages/connect/src/api/eos/api/eosGetPublicKey.ts b/packages/connect/src/api/eos/api/eosGetPublicKey.ts index 95196493b53..e4a61d2e140 100644 --- a/packages/connect/src/api/eos/api/eosGetPublicKey.ts +++ b/packages/connect/src/api/eos/api/eosGetPublicKey.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EosGetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; import type { PROTO } from '../../../constants'; -import { Assert } from '@trezor/schema-utils'; import { Bundle, GetPublicKey as GetPublicKeySchema } from '../../../types'; export default class EosGetPublicKey extends AbstractMethod< @@ -17,6 +18,7 @@ export default class EosGetPublicKey extends AbstractMethod< init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_EOS']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('EOS'), this.firmwareRange); // create a bundle with only one batch if bundle doesn't exists diff --git a/packages/connect/src/api/eos/api/eosSignTransaction.ts b/packages/connect/src/api/eos/api/eosSignTransaction.ts index 32b4581608f..67d3653764d 100644 --- a/packages/connect/src/api/eos/api/eosSignTransaction.ts +++ b/packages/connect/src/api/eos/api/eosSignTransaction.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EosSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import * as helper from '../eosSignTx'; import type { PROTO } from '../../../constants'; -import { AssertWeak } from '@trezor/schema-utils'; import { EosSignTransaction as EosSignTransactionSchema } from '../../../types/api/eos'; type Params = { @@ -20,6 +21,7 @@ type Params = { export default class EosSignTransaction extends AbstractMethod<'eosSignTransaction', Params> { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_EOS']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('EOS'), this.firmwareRange); const { payload } = this; diff --git a/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTx.ts b/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTx.ts index dea0ca0011b..b8e26456300 100644 --- a/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTx.ts +++ b/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTx.ts @@ -1,12 +1,21 @@ -export const serializeEthereumTx = [ +import { LegacyTxData } from '@ethereumjs/tx'; + +interface EthereumTxFixture { + description: string; + chainId: number; + tx: LegacyTxData; // + TODO: FeeMarketEIP1559TxData + result: string; +} + +export const serializeEthereumTx: EthereumTxFixture[] = [ { // https://eth1.trezor.io/tx/0xf6652a681b4474132b8b96512eb0bd5311f5ed8414af59e715c9738a3b3673f3 - description: 'ETH regular', + description: 'Legacy tx - ETH regular', + chainId: 1, tx: { // data sent to TrezorConnect.ethereumSignTransaction to: '0x4dC573D5DB497C0bF0674599E81c7dB91151D4e6', value: '0x3905f13a8f0e', - chainId: 1, nonce: '0x12', gasLimit: '0x5208', gasPrice: '0x104c533c00', @@ -20,11 +29,11 @@ export const serializeEthereumTx = [ }, { // https://eth1.trezor.io/tx/0xdcaf3eba690a3cdbad8c2926a8f5a95cd20003c5ba2aace91d8c5fe8048e395b - description: 'Eth with ERC20', + description: 'Legacy tx - ETH with ERC20', + chainId: 1, tx: { to: '0xa74476443119A942dE498590Fe1f2454d7D4aC0d', value: '0x0', - chainId: 1, nonce: '0xb', gasLimit: '0x30d40', gasPrice: '0x12a05f200', @@ -37,11 +46,11 @@ export const serializeEthereumTx = [ }, { // https://etc1.trezor.io/tx/0xebd7ef20c4358a6fdb09a951d6e77b8e88b37ac0f7a8d4e3b68f1666bf4c1d1a - description: 'ETC regular', + description: 'Legacy tx - ETC regular', + chainId: 61, tx: { to: '0xABE894C18832edbe9B7926D729FA950673faD1EC', value: '0x56c212a8e4628', - chainId: 61, nonce: '0x0', gasLimit: '0x5208', gasPrice: '0x5409c6a7b', @@ -53,3 +62,5 @@ export const serializeEthereumTx = [ result: '0xebd7ef20c4358a6fdb09a951d6e77b8e88b37ac0f7a8d4e3b68f1666bf4c1d1a', }, ]; + +// TODO: add EIP1559 tx type diff --git a/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTypedData.ts b/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTypedData.ts index d28b3e5d9ca..caf948a62be 100644 --- a/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTypedData.ts +++ b/packages/connect/src/api/ethereum/__fixtures__/ethereumSignTypedData.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ import { BigNumber } from '@trezor/utils/src/bigNumber'; + import { TrezorError } from '../../../constants/errors'; import { PROTO } from '../../../constants'; @@ -254,7 +254,6 @@ export const encodeData = [ input: { typeName: 'int256', // `1n << 254n` instead of `2n ** 254n` since Babel replaces ** with Math.pow() - // @ts-ignore, BigInt literals are not available when targeting lower than ES2020 data: -(1n << 254n) + 1n, }, // Python (-(2 ** 254) + 1).to_bytes(32, "big", signed=True).hex() @@ -264,7 +263,6 @@ export const encodeData = [ description: `should encode int from string`, input: { typeName: 'int256', - // @ts-ignore, BigInt literals are not available when targeting lower than ES2020 data: `${-(1n << 254n) + 1n}`, }, // Python (-(2 ** 254) + 1).to_bytes(32, "big", signed=True).hex() diff --git a/packages/connect/src/api/ethereum/__tests__/ethereumSignTx.test.ts b/packages/connect/src/api/ethereum/__tests__/ethereumSignTx.test.ts index 1556d0358ab..b218957b39c 100644 --- a/packages/connect/src/api/ethereum/__tests__/ethereumSignTx.test.ts +++ b/packages/connect/src/api/ethereum/__tests__/ethereumSignTx.test.ts @@ -2,22 +2,22 @@ import { TransactionFactory } from '@ethereumjs/tx'; import { keccak256, toHex } from 'web3-utils'; import { serializeEthereumTx } from '../ethereumSignTx'; - import * as fixtures from '../__fixtures__/ethereumSignTx'; describe('helpers/ethereumSignTx', () => { describe('serializeEthereumTx', () => { fixtures.serializeEthereumTx.forEach(f => { it(f.description, () => { - // verify hash using 2 different tools - if (f.tx.chainId !== 61) { - // ETC is not supported + // ETC is not supported + if (f.chainId !== 61) { const tx = TransactionFactory.fromTxData(f.tx); const hash1 = Buffer.from(tx.hash()).toString('hex'); expect(`0x${hash1}`).toEqual(f.result); } - const serialized = serializeEthereumTx({ ...f.tx, type: 0 }, f.tx.chainId); - const hash2 = toHex(keccak256(Buffer.from(serialized.slice(2), 'hex'))); + const serialized = serializeEthereumTx({ ...f.tx, type: 0 }, f.chainId); + const hash2 = toHex( + keccak256(Uint8Array.from(Buffer.from(serialized.slice(2), 'hex'))), + ); expect(hash2).toEqual(f.result); }); }); diff --git a/packages/connect/src/api/ethereum/__tests__/ethereumSignTypedData.test.ts b/packages/connect/src/api/ethereum/__tests__/ethereumSignTypedData.test.ts index 1b5d88f24ea..20e59de5575 100644 --- a/packages/connect/src/api/ethereum/__tests__/ethereumSignTypedData.test.ts +++ b/packages/connect/src/api/ethereum/__tests__/ethereumSignTypedData.test.ts @@ -6,7 +6,7 @@ describe('helpers/ethereumSignTypeData', () => { fixtures.parseArrayType.forEach(f => { it(`${f.description} - ${f.input}`, () => { if (f.error) { - expect(() => parseArrayType(f.input)).toThrowError(...f.error); + expect(() => parseArrayType(f.input)).toThrow(...f.error); } else { expect(parseArrayType(f.input)).toEqual(f.output); } @@ -19,7 +19,7 @@ describe('helpers/ethereumSignTypeData', () => { const { typeName, types } = f.input; it(`${f.description} - ${typeName}`, () => { if (f.error) { - expect(() => getFieldType(typeName, types as any)).toThrowError(...f.error); + expect(() => getFieldType(typeName, types as any)).toThrow(...f.error); } else { expect(getFieldType(typeName, types as any)).toEqual(f.output); } @@ -32,7 +32,7 @@ describe('helpers/ethereumSignTypeData', () => { const { typeName, data } = f.input; it(`${f.description}`, () => { if (f.error) { - expect(() => encodeData(typeName, data)).toThrowError(...f.error); + expect(() => encodeData(typeName, data)).toThrow(...f.error); } else { expect(encodeData(typeName, data)).toEqual(f.output); } diff --git a/packages/connect/src/api/ethereum/api/ethereumGetAddress.ts b/packages/connect/src/api/ethereum/api/ethereumGetAddress.ts index 9fc0bc46144..03c46401a43 100644 --- a/packages/connect/src/api/ethereum/api/ethereumGetAddress.ts +++ b/packages/connect/src/api/ethereum/api/ethereumGetAddress.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { validatePath, getSerializedPath, getSlip44ByPath } from '../../../utils/pathUtils'; @@ -14,7 +16,6 @@ import { decodeEthereumDefinition, ethereumNetworkInfoFromDefinition, } from '../ethereumDefinitions'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -31,6 +32,7 @@ export default class EthereumGetAddress extends AbstractMethod<'ethereumGetAddre init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Ethereum']; // create a bundle with only one batch if bundle doesn't exists this.hasBundle = !!this.payload.bundle; diff --git a/packages/connect/src/api/ethereum/api/ethereumGetPublicKey.ts b/packages/connect/src/api/ethereum/api/ethereumGetPublicKey.ts index c087c861524..f0f7d919bf8 100644 --- a/packages/connect/src/api/ethereum/api/ethereumGetPublicKey.ts +++ b/packages/connect/src/api/ethereum/api/ethereumGetPublicKey.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumGetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { validatePath } from '../../../utils/pathUtils'; @@ -8,7 +10,6 @@ import { getEthereumNetwork, getUniqueNetworks } from '../../../data/coinInfo'; import { UI, createUiMessage } from '../../../events'; import type { PROTO } from '../../../constants'; import type { EthereumNetworkInfo } from '../../../types'; -import { Assert } from '@trezor/schema-utils'; import { Bundle, GetPublicKey as GetPublicKeySchema } from '../../../types'; type Params = PROTO.EthereumGetPublicKey & { @@ -20,6 +21,7 @@ export default class EthereumGetPublicKey extends AbstractMethod<'ethereumGetPub init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Ethereum']; // create a bundle with only one batch if bundle doesn't exists this.hasBundle = !!this.payload.bundle; diff --git a/packages/connect/src/api/ethereum/api/ethereumSignMessage.ts b/packages/connect/src/api/ethereum/api/ethereumSignMessage.ts index c1cf32ecfea..2b5d9a23437 100644 --- a/packages/connect/src/api/ethereum/api/ethereumSignMessage.ts +++ b/packages/connect/src/api/ethereum/api/ethereumSignMessage.ts @@ -1,5 +1,8 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumSignMessage.js +import { MessagesSchema } from '@trezor/protobuf'; +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getSlip44ByPath, validatePath } from '../../../utils/pathUtils'; @@ -12,8 +15,6 @@ import { EthereumNetworkInfo, EthereumSignMessage as EthereumSignMessageSchema, } from '../../../types'; -import { MessagesSchema } from '@trezor/protobuf'; -import { Assert } from '@trezor/schema-utils'; type Params = PROTO.EthereumSignMessage & { network?: EthereumNetworkInfo; @@ -23,6 +24,7 @@ type Params = PROTO.EthereumSignMessage & { export default class EthereumSignMessage extends AbstractMethod<'ethereumSignMessage', Params> { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Ethereum']; const { payload } = this; diff --git a/packages/connect/src/api/ethereum/api/ethereumSignTransaction.ts b/packages/connect/src/api/ethereum/api/ethereumSignTransaction.ts index 2d2f4a26d24..611182bd16e 100644 --- a/packages/connect/src/api/ethereum/api/ethereumSignTransaction.ts +++ b/packages/connect/src/api/ethereum/api/ethereumSignTransaction.ts @@ -1,5 +1,10 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumSignTransaction.js +import { FeeMarketEIP1559TxData, LegacyTxData } from '@ethereumjs/tx'; + +import { MessagesSchema } from '@trezor/protobuf'; +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getSlip44ByPath, validatePath } from '../../../utils/pathUtils'; @@ -17,8 +22,6 @@ import { EthereumNetworkInfo, EthereumSignTransaction as EthereumSignTransactionSchema, } from '../../../types'; -import { MessagesSchema } from '@trezor/protobuf'; -import { Assert } from '@trezor/schema-utils'; type Params = { path: number[]; @@ -52,6 +55,7 @@ export default class EthereumSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Ethereum']; const { payload } = this; // validate incoming parameters @@ -118,46 +122,49 @@ export default class EthereumSignTransaction extends AbstractMethod< async run() { const { type, tx, definitions, chunkify } = this.params; - const signature = - type === 'eip1559' - ? await helper.ethereumSignTxEIP1559( - this.device.getCommands().typedCall.bind(this.device.getCommands()), - this.params.path, - tx.to, - tx.value, - tx.gasLimit, - tx.maxFeePerGas, - tx.maxPriorityFeePerGas, - tx.nonce, - tx.chainId, - chunkify, - tx.data, - tx.accessList, - definitions, - ) - : await helper.ethereumSignTx( - this.device.getCommands().typedCall.bind(this.device.getCommands()), - this.params.path, - tx.to, - tx.value, - tx.gasLimit, - tx.gasPrice, - tx.nonce, - tx.chainId, - chunkify, - tx.data, - tx.txType, - definitions, - ); - - const serializedTx = helper.serializeEthereumTx( - { - ...tx, - ...signature, - type: type === 'legacy' ? 0 : 2, - }, - tx.chainId, - ); + const isLegacy = type === 'legacy'; + + const signature = isLegacy + ? await helper.ethereumSignTx( + this.device.getCommands().typedCall.bind(this.device.getCommands()), + this.params.path, + tx.to, + tx.value, + tx.gasLimit, + tx.gasPrice, + tx.nonce, + tx.chainId, + chunkify, + tx.data, + tx.txType, + definitions, + ) + : await helper.ethereumSignTxEIP1559( + this.device.getCommands().typedCall.bind(this.device.getCommands()), + this.params.path, + tx.to, + tx.value, + tx.gasLimit, + tx.maxFeePerGas, + tx.maxPriorityFeePerGas, + tx.nonce, + tx.chainId, + chunkify, + tx.data, + tx.accessList, + definitions, + ); + + const txData = { + ...tx, + ...signature, + type: isLegacy ? 0 : 2, // 0 for legacy, 2 for EIP-1559 + gasPrice: isLegacy ? tx.gasPrice : null, + maxFeePerGas: isLegacy ? tx.maxFeePerGas : tx.maxPriorityFeePerGas, + maxPriorityFeePerGas: !isLegacy ? tx.maxPriorityFeePerGas : undefined, + } as LegacyTxData | FeeMarketEIP1559TxData; + + const serializedTx = helper.serializeEthereumTx(txData, tx.chainId); return { ...signature, serializedTx }; } diff --git a/packages/connect/src/api/ethereum/api/ethereumSignTypedData.ts b/packages/connect/src/api/ethereum/api/ethereumSignTypedData.ts index 1fdfb042a7d..ab27f21c2e2 100644 --- a/packages/connect/src/api/ethereum/api/ethereumSignTypedData.ts +++ b/packages/connect/src/api/ethereum/api/ethereumSignTypedData.ts @@ -1,5 +1,8 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumSignTypedData.js +import { MessagesSchema } from '@trezor/protobuf'; +import { Assert, Type } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getSlip44ByPath, validatePath } from '../../../utils/pathUtils'; @@ -15,8 +18,6 @@ import { getFieldType, parseArrayType, encodeData } from '../ethereumSignTypedDa import { messageToHex } from '../../../utils/formatUtils'; import { getEthereumDefinitions } from '../ethereumDefinitions'; import { EthereumNetworkInfo, DeviceModelInternal } from '../../../types'; -import { MessagesSchema } from '@trezor/protobuf'; -import { Assert, Type } from '@trezor/schema-utils'; // This type is not inferred, because it internally uses types that are generic type Params = ( @@ -42,6 +43,7 @@ const Params = Type.Intersect([ export default class EthereumSignTypedData extends AbstractMethod<'ethereumSignTypedData', Params> { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Ethereum']; const { payload } = this; diff --git a/packages/connect/src/api/ethereum/api/ethereumVerifyMessage.ts b/packages/connect/src/api/ethereum/api/ethereumVerifyMessage.ts index 9891a50f5ad..ba89fa88e77 100644 --- a/packages/connect/src/api/ethereum/api/ethereumVerifyMessage.ts +++ b/packages/connect/src/api/ethereum/api/ethereumVerifyMessage.ts @@ -1,10 +1,11 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumVerifyMessage.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { stripHexPrefix, messageToHex } from '../../../utils/formatUtils'; import type { PROTO } from '../../../constants'; -import { Assert } from '@trezor/schema-utils'; import { EthereumVerifyMessage as EthereumVerifyMessageSchema } from '../../../types'; export default class EthereumVerifyMessage extends AbstractMethod< @@ -14,6 +15,7 @@ export default class EthereumVerifyMessage extends AbstractMethod< init() { this.requiredPermissions = ['read', 'write']; this.firmwareRange = getFirmwareRange(this.name, null, this.firmwareRange); + this.requiredDeviceCapabilities = ['Capability_Ethereum']; const { payload } = this; diff --git a/packages/connect/src/api/ethereum/ethereumDefinitions.ts b/packages/connect/src/api/ethereum/ethereumDefinitions.ts index 2240b875b1a..3a1aaf98501 100644 --- a/packages/connect/src/api/ethereum/ethereumDefinitions.ts +++ b/packages/connect/src/api/ethereum/ethereumDefinitions.ts @@ -1,12 +1,12 @@ import fetch from 'cross-fetch'; -import { MessagesSchema } from '@trezor/protobuf'; +import { MessagesSchema, parseConfigure, decode as decodeProtobuf } from '@trezor/protobuf'; import { trzd } from '@trezor/protocol'; -import { parseConfigure, decode as decodeProtobuf } from '@trezor/protobuf'; +import { Type, Static, Assert } from '@trezor/schema-utils'; + import { DataManager } from '../../data/DataManager'; import { EthereumNetworkInfo } from '../../types'; import { ethereumNetworkInfoBase } from '../../data/coinInfo'; -import { Type, Static, Assert } from '@trezor/schema-utils'; interface GetEthereumDefinitions { chainId?: number; @@ -152,9 +152,10 @@ export const ethereumNetworkInfoFromDefinition = ( connect: true, T1B1: '1.6.2', T2T1: '2.0.7', - T2B1: '2.6.1', - T3B1: '2.8.1', - T3T1: '2.7.1', + T2B1: '2.0.0', + T3B1: '2.0.0', + T3T1: '2.0.0', + T3W1: '2.0.0', }, blockchainLink: undefined, }); diff --git a/packages/connect/src/api/ethereum/ethereumSignTx.ts b/packages/connect/src/api/ethereum/ethereumSignTx.ts index 82205b30d9b..a844bc1d308 100644 --- a/packages/connect/src/api/ethereum/ethereumSignTx.ts +++ b/packages/connect/src/api/ethereum/ethereumSignTx.ts @@ -4,6 +4,7 @@ import { Common, Chain, Hardfork } from '@ethereumjs/common'; import { FeeMarketEIP1559TxData, TransactionFactory, LegacyTxData } from '@ethereumjs/tx'; import { MessagesSchema } from '@trezor/protobuf'; + import { PROTO, ERRORS } from '../../constants'; import type { TypedCall } from '../../device/DeviceCommands'; import type { EthereumAccessList } from '../../types/api/ethereum'; diff --git a/packages/connect/src/api/ethereum/ethereumSignTypedData.ts b/packages/connect/src/api/ethereum/ethereumSignTypedData.ts index de0979f0a62..b65dbaa1975 100644 --- a/packages/connect/src/api/ethereum/ethereumSignTypedData.ts +++ b/packages/connect/src/api/ethereum/ethereumSignTypedData.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/ethereumSignTypedData.js import { BigNumber } from '@trezor/utils/src/bigNumber'; + import { PROTO, ERRORS } from '../../constants'; import { messageToHex } from '../../utils/formatUtils'; import type { EthereumSignTypedDataTypes } from '../../types/api/ethereum'; diff --git a/packages/connect/src/api/firmware/__tests__/calculateFirmwareHash.test.ts b/packages/connect/src/api/firmware/__tests__/calculateFirmwareHash.test.ts index ebc42d89079..4e1f9f53821 100644 --- a/packages/connect/src/api/firmware/__tests__/calculateFirmwareHash.test.ts +++ b/packages/connect/src/api/firmware/__tests__/calculateFirmwareHash.test.ts @@ -43,8 +43,6 @@ describe('firmware/calculateFirmwareHash', () => { }); it('Firmware too big', () => { - expect(() => calculateFirmwareHash(2, Buffer.alloc(100000000))).toThrowError( - 'Firmware too big', - ); + expect(() => calculateFirmwareHash(2, Buffer.alloc(100000000))).toThrow('Firmware too big'); }); }); diff --git a/packages/connect/src/api/firmware/__tests__/verifyAuthenticityProof.test.ts b/packages/connect/src/api/firmware/__tests__/verifyAuthenticityProof.test.ts index 6f4263a8922..fd3804e275d 100644 --- a/packages/connect/src/api/firmware/__tests__/verifyAuthenticityProof.test.ts +++ b/packages/connect/src/api/firmware/__tests__/verifyAuthenticityProof.test.ts @@ -28,6 +28,10 @@ const CONFIG = { '041b36cc98d5e3d1a20677aaf26254ef3756f27c9d63080c93ad3e7d39d3ad23bf00497b924789bc8e3f87834994e16780ad4eae7e75db1f03835ca64363e980b4', ], }, + T3W1: { + rootPubKeys: ['you shall not pass'], // TODO T3W1 + caPubKeys: ['you shall not pass'], // TODO T3W1 + }, } as DeviceAuthenticityConfig; describe('firmware/verifyAuthenticityProof', () => { diff --git a/packages/connect/src/api/firmware/getBinary.ts b/packages/connect/src/api/firmware/getBinary.ts index a6568610826..50264a7eb43 100644 --- a/packages/connect/src/api/firmware/getBinary.ts +++ b/packages/connect/src/api/firmware/getBinary.ts @@ -1,42 +1,23 @@ -import { versionUtils } from '@trezor/utils'; import { httpRequest } from '../../utils/assets'; -import { FirmwareRelease, IntermediaryVersion } from '../../types'; +import { FirmwareRelease } from '../../types'; interface GetBinaryProps { baseUrl: string; btcOnly?: boolean; - version?: number[]; - intermediaryVersion?: IntermediaryVersion; - releases: FirmwareRelease[]; + release: FirmwareRelease; } -export const getBinary = ({ - releases, - baseUrl, - version, - btcOnly, - intermediaryVersion, -}: GetBinaryProps) => { - if (intermediaryVersion) { - return httpRequest( - `${baseUrl}/firmware/t1b1/trezor-t1b1-inter-v${intermediaryVersion}.bin`, - 'binary', - ); - } - - const releaseByFirmware = releases.find( - r => - version && - versionUtils.isVersionArray(version) && - versionUtils.isEqual(r.version, version), - ); - - if (releaseByFirmware === undefined) { - throw new Error('no firmware found for this device'); - } - - const fwUrl = releaseByFirmware[btcOnly ? 'url_bitcoinonly' : 'url']; +export const getBinary = ({ baseUrl, btcOnly, release }: GetBinaryProps) => { + const fwUrl = release[btcOnly ? 'url_bitcoinonly' : 'url']; const url = `${baseUrl}/${fwUrl}`; return httpRequest(url, 'binary'); }; + +export const getBinaryOptional = async (props: GetBinaryProps) => { + try { + return await getBinary(props); + } catch { + return null; + } +}; diff --git a/packages/connect/src/api/firmware/getBinaryForFirmwareUpgrade.ts b/packages/connect/src/api/firmware/getBinaryForFirmwareUpgrade.ts index 87751c891d2..1217761bf8d 100644 --- a/packages/connect/src/api/firmware/getBinaryForFirmwareUpgrade.ts +++ b/packages/connect/src/api/firmware/getBinaryForFirmwareUpgrade.ts @@ -3,13 +3,13 @@ import { versionUtils } from '@trezor/utils'; import { httpRequest } from '../../utils/assets'; import { isStrictFeatures } from '../../utils/firmwareUtils'; import { getInfo, GetInfoProps } from '../../data/firmwareInfo'; -import { IntermediaryVersion } from '../../types'; +import { IntermediaryVersion, VersionArray } from '../../types'; import { getBinary } from './getBinary'; interface GetBinaryForFirmwareUpgradeProps extends GetInfoProps { baseUrl: string; btcOnly?: boolean; - version?: number[]; + version?: VersionArray; intermediaryVersion?: IntermediaryVersion; } @@ -19,12 +19,12 @@ interface GetBinaryForFirmwareUpgradeProps extends GetInfoProps { * is safe. */ export const getBinaryForFirmwareUpgrade = ({ - features, releases, baseUrl, version, btcOnly, intermediaryVersion, + features, }: GetBinaryForFirmwareUpgradeProps) => { if (!isStrictFeatures(features)) { throw new Error('Features of unexpected shape provided'); @@ -63,5 +63,5 @@ export const getBinaryForFirmwareUpgrade = ({ ); } - return getBinary({ releases, baseUrl, version, btcOnly, intermediaryVersion }); + return getBinary({ release: releaseByFirmware, baseUrl, btcOnly }); }; diff --git a/packages/connect/src/api/firmware/index.ts b/packages/connect/src/api/firmware/index.ts index 47130d817b2..fe8e1d9aab7 100644 --- a/packages/connect/src/api/firmware/index.ts +++ b/packages/connect/src/api/firmware/index.ts @@ -1,5 +1,5 @@ export { getBinaryForFirmwareUpgrade } from './getBinaryForFirmwareUpgrade'; -export { getBinary } from './getBinary'; +export { getBinary, getBinaryOptional } from './getBinary'; export { getLanguage } from '../../data/getLanguage'; export { shouldStripFwHeaders, stripFwHeaders } from './modifyFirmware'; export { uploadFirmware } from './uploadFirmware'; diff --git a/packages/connect/src/api/firmware/verifyAuthenticityProof.ts b/packages/connect/src/api/firmware/verifyAuthenticityProof.ts index 33e28ea740e..f86cd6d2a4b 100644 --- a/packages/connect/src/api/firmware/verifyAuthenticityProof.ts +++ b/packages/connect/src/api/firmware/verifyAuthenticityProof.ts @@ -1,4 +1,5 @@ import * as crypto from 'crypto'; + import { bufferUtils } from '@trezor/utils'; import { PROTO } from '../../constants'; diff --git a/packages/connect/src/api/firmware/x509certificate.ts b/packages/connect/src/api/firmware/x509certificate.ts index 6f93bb86891..c01e48170e7 100644 --- a/packages/connect/src/api/firmware/x509certificate.ts +++ b/packages/connect/src/api/firmware/x509certificate.ts @@ -81,12 +81,9 @@ const derToAsn1 = (byteArray: Uint8Array): Asn1 => { throw new Error('Unsupported length encoding'); } - let length = getLength(); // As encoded, which may be special value 0 - let byteLength; - let contents; - - byteLength = position + length; - contents = byteArray.subarray(position, byteLength); + const length = getLength(); // As encoded, which may be special value 0 + const byteLength = position + length; + const contents = byteArray.subarray(position, byteLength); const raw = byteArray.subarray(0, byteLength); // May not be the whole input array diff --git a/packages/connect/src/api/getAccountDescriptor.ts b/packages/connect/src/api/getAccountDescriptor.ts index a78ba51ea53..1e5417a4916 100644 --- a/packages/connect/src/api/getAccountDescriptor.ts +++ b/packages/connect/src/api/getAccountDescriptor.ts @@ -1,3 +1,5 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType, DEFAULT_FIRMWARE_RANGE } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { validatePath, getSerializedPath } from '../utils/pathUtils'; @@ -10,7 +12,6 @@ import { GetAccountDescriptorParams, GetAccountDescriptorResponse, } from '../types/api/getAccountDescriptor'; -import { Assert } from '@trezor/schema-utils'; type Request = GetAccountDescriptorParams & { address_n: number[]; coinInfo: CoinInfo }; diff --git a/packages/connect/src/api/getAddress.ts b/packages/connect/src/api/getAddress.ts index d759a9ad7ac..bdf7140ecb3 100644 --- a/packages/connect/src/api/getAddress.ts +++ b/packages/connect/src/api/getAddress.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/GetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../core/AbstractMethod'; import { validateCoinPath, getFirmwareRange } from './common/paramsValidator'; import { validatePath, getLabel, getSerializedPath } from '../utils/pathUtils'; @@ -7,7 +9,6 @@ import { getBitcoinNetwork, fixCoinInfoNetwork, getUniqueNetworks } from '../dat import { PROTO, ERRORS } from '../constants'; import { UI, createUiMessage } from '../events'; import { Bundle, type BitcoinNetworkInfo } from '../types'; -import { Assert } from '@trezor/schema-utils'; import { GetAddress as GetAddressSchema } from '../types/api/getAddress'; type Params = PROTO.GetAddress & { diff --git a/packages/connect/src/api/getCoinInfo.ts b/packages/connect/src/api/getCoinInfo.ts index a948e487062..2b32220bb92 100644 --- a/packages/connect/src/api/getCoinInfo.ts +++ b/packages/connect/src/api/getCoinInfo.ts @@ -1,10 +1,11 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/GetCoinInfo.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { ERRORS } from '../constants'; import { getCoinInfo } from '../data/coinInfo'; import { CoinObj, CoinInfo } from '../types'; -import { Assert } from '@trezor/schema-utils'; type Params = { coinInfo: CoinInfo; diff --git a/packages/connect/src/api/getFirmwareHash.ts b/packages/connect/src/api/getFirmwareHash.ts index e44ef009d6f..933ba5c4384 100644 --- a/packages/connect/src/api/getFirmwareHash.ts +++ b/packages/connect/src/api/getFirmwareHash.ts @@ -1,8 +1,9 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; import { UI } from '../events'; import { getFirmwareRange } from './common/paramsValidator'; -import { Assert } from '@trezor/schema-utils'; export default class GetFirmwareHash extends AbstractMethod< 'getFirmwareHash', diff --git a/packages/connect/src/api/getOwnershipId.ts b/packages/connect/src/api/getOwnershipId.ts index f36d2615d1e..440fbea6423 100644 --- a/packages/connect/src/api/getOwnershipId.ts +++ b/packages/connect/src/api/getOwnershipId.ts @@ -1,10 +1,11 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { validatePath, getScriptType, getSerializedPath } from '../utils/pathUtils'; import { getBitcoinNetwork } from '../data/coinInfo'; import { PROTO } from '../constants'; import { UI, createUiMessage } from '../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../exports'; import { GetOwnershipId as GetOwnershipIdSchema } from '../types/api/getOwnershipId'; diff --git a/packages/connect/src/api/getOwnershipProof.ts b/packages/connect/src/api/getOwnershipProof.ts index 6e923cb69f5..eb8c4c7a195 100644 --- a/packages/connect/src/api/getOwnershipProof.ts +++ b/packages/connect/src/api/getOwnershipProof.ts @@ -1,10 +1,11 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { validatePath, getScriptType, getSerializedPath } from '../utils/pathUtils'; import { getBitcoinNetwork } from '../data/coinInfo'; import { PROTO } from '../constants'; import { UI, createUiMessage } from '../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../exports'; import { GetOwnershipProof as GetOwnershipProofSchema } from '../types/api/getOwnershipProof'; diff --git a/packages/connect/src/api/getPublicKey.ts b/packages/connect/src/api/getPublicKey.ts index d04b2aed617..c21b4a0455e 100644 --- a/packages/connect/src/api/getPublicKey.ts +++ b/packages/connect/src/api/getPublicKey.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/GetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../core/AbstractMethod'; import { validateCoinPath, getFirmwareRange } from './common/paramsValidator'; import { validatePath } from '../utils/pathUtils'; @@ -8,7 +10,6 @@ import { getBitcoinNetwork } from '../data/coinInfo'; import { getPublicKeyLabel } from '../utils/accountUtils'; import type { BitcoinNetworkInfo } from '../types'; import type { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../types'; import { GetPublicKey as GetPublicKeySchema } from '../types/api/getPublicKey'; diff --git a/packages/connect/src/api/index.ts b/packages/connect/src/api/index.ts index faa7c69d647..561e4bcde8f 100644 --- a/packages/connect/src/api/index.ts +++ b/packages/connect/src/api/index.ts @@ -9,6 +9,7 @@ export { default as blockchainDisconnect } from './blockchainDisconnect'; export { default as blockchainEstimateFee } from './blockchainEstimateFee'; export { default as blockchainGetAccountBalanceHistory } from './blockchainGetAccountBalanceHistory'; export { default as blockchainGetCurrentFiatRates } from './blockchainGetCurrentFiatRates'; +export { default as blockchainEvmRpcCall } from './blockchainEvmRpcCall'; export { default as blockchainGetFiatRatesForTimestamps } from './blockchainGetFiatRatesForTimestamps'; export { default as blockchainGetTransactions } from './blockchainGetTransactions'; export { default as blockchainSetCustomBackend } from './blockchainSetCustomBackend'; @@ -40,12 +41,12 @@ export { default as getSettings } from './getSettings'; // export { default as off } from './off'; // export { default as on } from './on'; export { default as pushTransaction } from './pushTransaction'; -export { default as rebootToBootloader } from './rebootToBootloader'; export { default as recoveryDevice } from './recoveryDevice'; // export { default as removeAllListeners } from './composeTransaction'; // export { default as renderWebUSBButton } from './composeTransaction'; export { default as requestLogin } from './requestLogin'; export { default as resetDevice } from './resetDevice'; +export { default as loadDevice } from './loadDevice'; export { default as setBrightness } from './setBrightness'; export { default as setBusy } from './setBusy'; export { default as setProxy } from './setProxy'; @@ -55,4 +56,3 @@ export { default as unlockPath } from './unlockPath'; // export { default as uiResponse } from './uiResponse'; export { default as verifyMessage } from './verifyMessage'; export { default as wipeDevice } from './wipeDevice'; -export { default as checkFirmwareAuthenticity } from './checkFirmwareAuthenticity'; diff --git a/packages/connect/src/api/loadDevice.ts b/packages/connect/src/api/loadDevice.ts new file mode 100644 index 00000000000..53827718a22 --- /dev/null +++ b/packages/connect/src/api/loadDevice.ts @@ -0,0 +1,49 @@ +import { Assert } from '@trezor/schema-utils'; + +import { AbstractMethod } from '../core/AbstractMethod'; +import { UI } from '../events'; +import { getFirmwareRange } from './common/paramsValidator'; +import { PROTO } from '../constants'; + +export default class LoadDevice extends AbstractMethod<'loadDevice', PROTO.LoadDevice> { + init() { + this.allowDeviceMode = [UI.INITIALIZE]; + this.useDeviceState = false; + this.requiredPermissions = ['management']; + this.firmwareRange = getFirmwareRange(this.name, null, this.firmwareRange); + + const { payload } = this; + // validate bundle type + Assert(PROTO.LoadDevice, payload); + + this.params = { + mnemonics: payload.mnemonics, + pin: payload.pin, + passphrase_protection: payload.passphrase_protection, + language: payload.language, + label: payload.label, + skip_checksum: payload.skip_checksum, + u2f_counter: payload.u2f_counter, + needs_backup: payload.needs_backup, + no_backup: payload.no_backup, + }; + } + + get info() { + return 'Load seed and related internal settings.'; + } + + get confirmation() { + return { + view: 'device-management' as const, + label: 'Do you really you want to load device?', + }; + } + + async run() { + const cmd = this.device.getCommands(); + const response = await cmd.typedCall('LoadDevice', 'Success', this.params); + + return response.message; + } +} diff --git a/packages/connect/src/api/nem/api/nemGetAddress.ts b/packages/connect/src/api/nem/api/nemGetAddress.ts index baef0a318ef..931aea0c0be 100644 --- a/packages/connect/src/api/nem/api/nemGetAddress.ts +++ b/packages/connect/src/api/nem/api/nemGetAddress.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/NEMGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -25,6 +26,7 @@ export default class NEMGetAddress extends AbstractMethod<'nemGetAddress', Param init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_NEM']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('NEM'), this.firmwareRange); // create a bundle with only one batch if bundle doesn't exists diff --git a/packages/connect/src/api/nem/api/nemSignTransaction.ts b/packages/connect/src/api/nem/api/nemSignTransaction.ts index 0d962218d7d..086371e2b02 100644 --- a/packages/connect/src/api/nem/api/nemSignTransaction.ts +++ b/packages/connect/src/api/nem/api/nemSignTransaction.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/NEMSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import * as helper from '../nemSignTx'; import type { PROTO } from '../../../constants'; -import { AssertWeak } from '@trezor/schema-utils'; import { NEMSignTransaction as NEMSignTransactionSchema } from '../../../types/api/nem'; export default class NEMSignTransaction extends AbstractMethod< @@ -15,6 +16,7 @@ export default class NEMSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_NEM']; this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('NEM'), this.firmwareRange); const { payload } = this; diff --git a/packages/connect/src/api/pushTransaction.ts b/packages/connect/src/api/pushTransaction.ts index 14369bcdf2d..277624e9b3d 100644 --- a/packages/connect/src/api/pushTransaction.ts +++ b/packages/connect/src/api/pushTransaction.ts @@ -1,11 +1,12 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/PushTransaction.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { getCoinInfo } from '../data/coinInfo'; import { ERRORS } from '../constants'; import { isBackendSupported, initBlockchain } from '../backend/BlockchainLink'; import type { CoinInfo } from '../types'; -import { Assert } from '@trezor/schema-utils'; import { PushTransaction as PushTransactionSchema } from '../types/api/pushTransaction'; type Params = { diff --git a/packages/connect/src/api/rebootToBootloader.ts b/packages/connect/src/api/rebootToBootloader.ts deleted file mode 100644 index e8036de919d..00000000000 --- a/packages/connect/src/api/rebootToBootloader.ts +++ /dev/null @@ -1,52 +0,0 @@ -// origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/RebootToBootloader.js - -import { AbstractMethod } from '../core/AbstractMethod'; -import { getFirmwareRange } from './common/paramsValidator'; -import { UI } from '../events'; -import { Assert } from '@trezor/schema-utils'; -import { PROTO } from '../constants'; - -export default class RebootToBootloader extends AbstractMethod< - 'rebootToBootloader', - PROTO.RebootToBootloader -> { - init() { - this.allowDeviceMode = [UI.INITIALIZE, UI.SEEDLESS]; - this.skipFinalReload = true; - this.keepSession = false; - this.requiredPermissions = ['management']; - this.useDeviceState = false; - this.firmwareRange = getFirmwareRange(this.name, null, this.firmwareRange); - - const { payload } = this; - Assert(PROTO.RebootToBootloader, payload); - - this.params = { - boot_command: payload.boot_command, - firmware_header: payload.firmware_header, - language_data_length: payload.language_data_length, - }; - } - - get info() { - return 'Reboot to bootloader'; - } - - get confirmation() { - return { - view: 'device-management' as const, - customConfirmButton: { - className: 'confirm', - label: `Reboot`, - }, - label: 'Are you sure you want to reboot to bootloader?', - }; - } - - async run() { - const cmd = this.device.getCommands(); - const response = await cmd.typedCall('RebootToBootloader', 'Success', this.params); - - return response.message; - } -} diff --git a/packages/connect/src/api/recoveryDevice.ts b/packages/connect/src/api/recoveryDevice.ts index 337557d7a70..559d7d9abe2 100644 --- a/packages/connect/src/api/recoveryDevice.ts +++ b/packages/connect/src/api/recoveryDevice.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/RecoveryDevice.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { UI } from '../events'; -import { Assert } from '@trezor/schema-utils'; import type { PROTO } from '../constants'; import { RecoveryDevice as RecoveryDeviceSchema } from '../types/api/recoveryDevice'; diff --git a/packages/connect/src/api/requestLogin.ts b/packages/connect/src/api/requestLogin.ts index b835cf6d1dd..0dbf2e8d719 100644 --- a/packages/connect/src/api/requestLogin.ts +++ b/packages/connect/src/api/requestLogin.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/RequestLogin.js +import { Assert, Type } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { ERRORS } from '../constants'; @@ -7,7 +9,6 @@ import { UI, createUiMessage } from '../events'; import { DataManager } from '../data/DataManager'; import type { ConnectSettings } from '../types'; import type { PROTO } from '../constants'; -import { Assert, Type } from '@trezor/schema-utils'; import { RequestLoginSchema } from '../types/api/requestLogin'; export default class RequestLogin extends AbstractMethod<'requestLogin', PROTO.SignIdentity> { diff --git a/packages/connect/src/api/resetDevice.ts b/packages/connect/src/api/resetDevice.ts index 84d16c8e073..d19f695c271 100644 --- a/packages/connect/src/api/resetDevice.ts +++ b/packages/connect/src/api/resetDevice.ts @@ -1,10 +1,11 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ResetDevice.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { UI } from '../events'; import { getFirmwareRange } from './common/paramsValidator'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; export default class ResetDevice extends AbstractMethod<'resetDevice', PROTO.ResetDevice> { init() { @@ -18,7 +19,6 @@ export default class ResetDevice extends AbstractMethod<'resetDevice', PROTO.Res Assert(PROTO.ResetDevice, payload); this.params = { - display_random: payload.display_random, strength: payload.strength || 256, passphrase_protection: payload.passphrase_protection, pin_protection: payload.pin_protection, diff --git a/packages/connect/src/api/ripple/api/rippleGetAddress.ts b/packages/connect/src/api/ripple/api/rippleGetAddress.ts index a2e8b5bc5c2..d8ca192d78e 100644 --- a/packages/connect/src/api/ripple/api/rippleGetAddress.ts +++ b/packages/connect/src/api/ripple/api/rippleGetAddress.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/RippleGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -21,6 +22,7 @@ export default class RippleGetAddress extends AbstractMethod<'rippleGetAddress', init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Ripple']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Ripple'), diff --git a/packages/connect/src/api/ripple/api/rippleSignTransaction.ts b/packages/connect/src/api/ripple/api/rippleSignTransaction.ts index 241f0475935..a53e64a3971 100644 --- a/packages/connect/src/api/ripple/api/rippleSignTransaction.ts +++ b/packages/connect/src/api/ripple/api/rippleSignTransaction.ts @@ -1,11 +1,12 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/RippleSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import type { PROTO } from '../../../constants'; -import { AssertWeak } from '@trezor/schema-utils'; import { RippleSignTransaction as RippleSignTransactionSchema } from '../../../types/api/ripple'; export default class RippleSignTransaction extends AbstractMethod< @@ -14,6 +15,7 @@ export default class RippleSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Ripple']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Ripple'), diff --git a/packages/connect/src/api/setBrightness.ts b/packages/connect/src/api/setBrightness.ts index 97a8fb89f71..ee0665c7eec 100644 --- a/packages/connect/src/api/setBrightness.ts +++ b/packages/connect/src/api/setBrightness.ts @@ -1,8 +1,9 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/SetBrightness.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; -import { Assert } from '@trezor/schema-utils'; export default class SetBrightness extends AbstractMethod<'setBrightness', PROTO.SetBrightness> { init() { diff --git a/packages/connect/src/api/setBusy.ts b/packages/connect/src/api/setBusy.ts index 576aac81b1a..67940bbfedb 100644 --- a/packages/connect/src/api/setBusy.ts +++ b/packages/connect/src/api/setBusy.ts @@ -1,8 +1,9 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { DEVICE, createDeviceMessage } from '../events'; import { PROTO } from '../constants'; import { getFirmwareRange } from './common/paramsValidator'; -import { Assert } from '@trezor/schema-utils'; export default class SetBusy extends AbstractMethod<'setBusy', PROTO.SetBusy> { init() { diff --git a/packages/connect/src/api/signMessage.ts b/packages/connect/src/api/signMessage.ts index 3bf9e74b80a..d8c9b2eefa7 100644 --- a/packages/connect/src/api/signMessage.ts +++ b/packages/connect/src/api/signMessage.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/SignMessage.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { validateCoinPath, getFirmwareRange } from './common/paramsValidator'; import { validatePath, getLabel, getScriptType } from '../utils/pathUtils'; @@ -8,7 +10,6 @@ import { messageToHex } from '../utils/formatUtils'; import type { BitcoinNetworkInfo } from '../types'; import type { PROTO } from '../constants'; import { SignMessage as SignMessageSchema } from '../types'; -import { Assert } from '@trezor/schema-utils'; export default class SignMessage extends AbstractMethod<'signMessage', PROTO.SignMessage> { init() { diff --git a/packages/connect/src/api/solana/additionalInfo.ts b/packages/connect/src/api/solana/additionalInfo.ts index c49490af41d..5a86efabeb9 100644 --- a/packages/connect/src/api/solana/additionalInfo.ts +++ b/packages/connect/src/api/solana/additionalInfo.ts @@ -1,4 +1,5 @@ import { Assert } from '@trezor/schema-utils'; + import { SolanaTxAdditionalInfo } from '../../types/api/solana'; export const transformAdditionalInfo = (additionalInfo?: SolanaTxAdditionalInfo) => { diff --git a/packages/connect/src/api/solana/api/solanaGetAddress.ts b/packages/connect/src/api/solana/api/solanaGetAddress.ts index c874c5038b9..793954c9cca 100644 --- a/packages/connect/src/api/solana/api/solanaGetAddress.ts +++ b/packages/connect/src/api/solana/api/solanaGetAddress.ts @@ -1,10 +1,11 @@ +import { Assert } from '@trezor/schema-utils'; + import { ERRORS, PROTO } from '../../../constants'; import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -19,6 +20,7 @@ export default class SolanaGetAddress extends AbstractMethod<'solanaGetAddress', init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Solana']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Solana'), diff --git a/packages/connect/src/api/solana/api/solanaGetPublicKey.ts b/packages/connect/src/api/solana/api/solanaGetPublicKey.ts index 411c38901b0..de1fcc753ec 100644 --- a/packages/connect/src/api/solana/api/solanaGetPublicKey.ts +++ b/packages/connect/src/api/solana/api/solanaGetPublicKey.ts @@ -1,10 +1,11 @@ +import { Assert } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle, GetPublicKey as GetPublicKeySchema } from '../../../types'; export default class SolanaGetPublicKey extends AbstractMethod< @@ -16,6 +17,7 @@ export default class SolanaGetPublicKey extends AbstractMethod< init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Solana']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Solana'), diff --git a/packages/connect/src/api/solana/api/solanaSignTransaction.ts b/packages/connect/src/api/solana/api/solanaSignTransaction.ts index e17f8dacd26..26a64d3fb5f 100644 --- a/packages/connect/src/api/solana/api/solanaSignTransaction.ts +++ b/packages/connect/src/api/solana/api/solanaSignTransaction.ts @@ -1,10 +1,11 @@ +import { AssertWeak } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import { transformAdditionalInfo } from '../additionalInfo'; -import { AssertWeak } from '@trezor/schema-utils'; import { SolanaSignTransaction as SolanaSignTransactionSchema } from '../../../types/api/solana'; export default class SolanaSignTransaction extends AbstractMethod< @@ -13,6 +14,7 @@ export default class SolanaSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Solana']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Solana'), diff --git a/packages/connect/src/api/stellar/api/stellarGetAddress.ts b/packages/connect/src/api/stellar/api/stellarGetAddress.ts index 064485f2488..4cf73a86675 100644 --- a/packages/connect/src/api/stellar/api/stellarGetAddress.ts +++ b/packages/connect/src/api/stellar/api/stellarGetAddress.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/StellarGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -21,6 +22,7 @@ export default class StellarGetAddress extends AbstractMethod<'stellarGetAddress init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Stellar']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Stellar'), diff --git a/packages/connect/src/api/stellar/api/stellarSignTransaction.ts b/packages/connect/src/api/stellar/api/stellarSignTransaction.ts index 85264da1b82..1bd087a8347 100644 --- a/packages/connect/src/api/stellar/api/stellarSignTransaction.ts +++ b/packages/connect/src/api/stellar/api/stellarSignTransaction.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/StellarSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; @@ -10,7 +12,6 @@ import { StellarTransaction, StellarSignTransaction as StellarSignTransactionSchema, } from '../../../types/api/stellar'; -import { AssertWeak } from '@trezor/schema-utils'; type Params = { path: number[]; @@ -29,6 +30,7 @@ export default class StellarSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Stellar']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Stellar'), diff --git a/packages/connect/src/api/stellar/stellarSignTx.ts b/packages/connect/src/api/stellar/stellarSignTx.ts index c7b8197437c..9cf84b881dc 100644 --- a/packages/connect/src/api/stellar/stellarSignTx.ts +++ b/packages/connect/src/api/stellar/stellarSignTx.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/helpers/stellarSignTx.js +import { Assert } from '@trezor/schema-utils'; + import { PROTO, ERRORS } from '../../constants'; import type { TypedCall } from '../../device/DeviceCommands'; import { @@ -7,7 +9,6 @@ import { StellarOperation, StellarOperationMessage, } from '../../types/api/stellar'; -import { Assert } from '@trezor/schema-utils'; const processTxRequest = async ( typedCall: TypedCall, diff --git a/packages/connect/src/api/tezos/api/tezosGetAddress.ts b/packages/connect/src/api/tezos/api/tezosGetAddress.ts index dc669fbb3b1..07862cfa696 100644 --- a/packages/connect/src/api/tezos/api/tezosGetAddress.ts +++ b/packages/connect/src/api/tezos/api/tezosGetAddress.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/TezosGetAddress.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { PROTO, ERRORS } from '../../../constants'; import { UI, createUiMessage } from '../../../events'; -import { Assert } from '@trezor/schema-utils'; import { Bundle } from '../../../types'; import { GetAddress as GetAddressSchema } from '../../../types/api/getAddress'; @@ -21,6 +22,7 @@ export default class TezosGetAddress extends AbstractMethod<'tezosGetAddress', P init() { this.noBackupConfirmationMode = 'always'; this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Tezos']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Tezos'), diff --git a/packages/connect/src/api/tezos/api/tezosGetPublicKey.ts b/packages/connect/src/api/tezos/api/tezosGetPublicKey.ts index c9a7400010c..beb8a5a1700 100644 --- a/packages/connect/src/api/tezos/api/tezosGetPublicKey.ts +++ b/packages/connect/src/api/tezos/api/tezosGetPublicKey.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/TezosGetPublicKey.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod, MethodReturnType } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath, fromHardened, getSerializedPath } from '../../../utils/pathUtils'; import { UI, createUiMessage } from '../../../events'; import type { PROTO } from '../../../constants'; -import { Assert } from '@trezor/schema-utils'; import { Bundle, GetPublicKey as GetPublicKeySchema } from '../../../types'; export default class TezosGetPublicKey extends AbstractMethod< @@ -17,6 +18,7 @@ export default class TezosGetPublicKey extends AbstractMethod< init() { this.requiredPermissions = ['read']; + this.requiredDeviceCapabilities = ['Capability_Tezos']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Tezos'), diff --git a/packages/connect/src/api/tezos/api/tezosSignTransaction.ts b/packages/connect/src/api/tezos/api/tezosSignTransaction.ts index e307d2e8560..f2200be4f1a 100644 --- a/packages/connect/src/api/tezos/api/tezosSignTransaction.ts +++ b/packages/connect/src/api/tezos/api/tezosSignTransaction.ts @@ -1,12 +1,13 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/TezosSignTransaction.js +import { AssertWeak } from '@trezor/schema-utils'; + import { AbstractMethod } from '../../../core/AbstractMethod'; import { getFirmwareRange } from '../../common/paramsValidator'; import { getMiscNetwork } from '../../../data/coinInfo'; import { validatePath } from '../../../utils/pathUtils'; import * as helper from '../tezosSignTx'; import type { PROTO } from '../../../constants'; -import { AssertWeak } from '@trezor/schema-utils'; import { TezosSignTransaction as TezosSignTransactionSchema } from '../../../types/api/tezos'; export default class TezosSignTransaction extends AbstractMethod< @@ -15,6 +16,7 @@ export default class TezosSignTransaction extends AbstractMethod< > { init() { this.requiredPermissions = ['read', 'write']; + this.requiredDeviceCapabilities = ['Capability_Tezos']; this.firmwareRange = getFirmwareRange( this.name, getMiscNetwork('Tezos'), diff --git a/packages/connect/src/api/tezos/tezosSignTx.ts b/packages/connect/src/api/tezos/tezosSignTx.ts index 268f2198c00..68951bb79b7 100644 --- a/packages/connect/src/api/tezos/tezosSignTx.ts +++ b/packages/connect/src/api/tezos/tezosSignTx.ts @@ -1,7 +1,9 @@ import bs58check from 'bs58check'; + +import { Assert } from '@trezor/schema-utils'; + import { PROTO, ERRORS } from '../../constants'; import { TezosOperation } from '../../types/api/tezos'; -import { Assert } from '@trezor/schema-utils'; const PREFIX = { B: new Uint8Array([1, 52]), diff --git a/packages/connect/src/api/unlockPath.ts b/packages/connect/src/api/unlockPath.ts index a445a67807b..89e852f3037 100644 --- a/packages/connect/src/api/unlockPath.ts +++ b/packages/connect/src/api/unlockPath.ts @@ -1,8 +1,9 @@ +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { PROTO } from '../constants'; import { validatePath } from '../utils/pathUtils'; import { getFirmwareRange } from './common/paramsValidator'; -import { Assert } from '@trezor/schema-utils'; import { UnlockPathParams } from '../types/api/unlockPath'; export default class UnlockPath extends AbstractMethod<'unlockPath', PROTO.UnlockPath> { diff --git a/packages/connect/src/api/verifyMessage.ts b/packages/connect/src/api/verifyMessage.ts index 08a90a81a6f..9c6be6ff739 100644 --- a/packages/connect/src/api/verifyMessage.ts +++ b/packages/connect/src/api/verifyMessage.ts @@ -1,5 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/VerifyMessage.js +import { Assert } from '@trezor/schema-utils'; + import { AbstractMethod } from '../core/AbstractMethod'; import { getFirmwareRange } from './common/paramsValidator'; import { getBitcoinNetwork } from '../data/coinInfo'; @@ -7,7 +9,6 @@ import { getLabel } from '../utils/pathUtils'; import { messageToHex } from '../utils/formatUtils'; import { PROTO, ERRORS } from '../constants'; import { VerifyMessage as VerifyMessageSchema } from '../types'; -import { Assert } from '@trezor/schema-utils'; export default class VerifyMessage extends AbstractMethod<'verifyMessage', PROTO.VerifyMessage> { init() { diff --git a/packages/connect/src/backend/BackendManager.ts b/packages/connect/src/backend/BackendManager.ts index d2457e10358..fbe4767963f 100644 --- a/packages/connect/src/backend/BackendManager.ts +++ b/packages/connect/src/backend/BackendManager.ts @@ -2,7 +2,6 @@ import { DataManager } from '../data/DataManager'; import { ERRORS } from '../constants'; import { Blockchain, BlockchainOptions } from './Blockchain'; import { createBlockchainMessage, BLOCKCHAIN } from '../events'; - import type { CoinInfo, BlockchainLink } from '../types'; type CoinShortcut = CoinInfo['shortcut']; diff --git a/packages/connect/src/backend/Blockchain.ts b/packages/connect/src/backend/Blockchain.ts index dc373202d33..a20ed76536c 100644 --- a/packages/connect/src/backend/Blockchain.ts +++ b/packages/connect/src/backend/Blockchain.ts @@ -4,6 +4,7 @@ import BlockchainLink, { BlockchainLinkParams, BlockchainLinkResponse, } from '@trezor/blockchain-link'; + import { createBlockchainMessage, BLOCKCHAIN, CoreEventMessage } from '../events'; import { ERRORS } from '../constants'; import { @@ -13,7 +14,6 @@ import { ElectrumWorker, SolanaWorker, } from '../workers/workers'; - import type { CoinInfo, Proxy } from '../types'; const getWorker = (type: string) => { @@ -40,13 +40,8 @@ const getNormalizedTrezorShortcut = (shortcut: string) => { return 'XRP'; } - return shortcut; -}; - -const getNormalizedBackendShortcut = (shortcut: string) => { - // Can be safely removed when both Polygon PoS Blockbooks return POL as shortcut - if (shortcut.toLowerCase() === 'matic') { - return 'pol'; + if (shortcut === 'OP') { + return 'ETH'; } return shortcut; @@ -138,7 +133,7 @@ export class Blockchain { this.serverInfo = info; const trezorShortcut = getNormalizedTrezorShortcut(this.coinInfo.shortcut); - const backendShortcut = getNormalizedBackendShortcut(this.serverInfo.shortcut); + const backendShortcut = this.serverInfo.shortcut; if (trezorShortcut.toLowerCase() !== backendShortcut.toLowerCase()) { throw ERRORS.TypedError('Backend_Invalid'); @@ -222,6 +217,10 @@ export class Blockchain { return this.link.getAccountUtxo(descriptor); } + rpcCall(params: BlockchainLinkParams<'rpcCall'>) { + return this.link.rpcCall(params); + } + async estimateFee(request: Parameters[0]) { const { blocks } = request; // cache should be used if there is no specific data (ethereum case) and requested blocks are already cached/downloaded diff --git a/packages/connect/src/backend/BlockchainLink.ts b/packages/connect/src/backend/BlockchainLink.ts index 7efc4df87ab..3b57ed5e2c8 100644 --- a/packages/connect/src/backend/BlockchainLink.ts +++ b/packages/connect/src/backend/BlockchainLink.ts @@ -1,5 +1,4 @@ import { BackendManager } from './BackendManager'; - import type { BlockchainOptions as Options } from './Blockchain'; import type { CoinInfo } from '../types'; diff --git a/packages/connect/src/backend/__tests__/Blockchain.test.ts b/packages/connect/src/backend/__tests__/Blockchain.test.ts index 363307a7a78..3d8e4086bd0 100644 --- a/packages/connect/src/backend/__tests__/Blockchain.test.ts +++ b/packages/connect/src/backend/__tests__/Blockchain.test.ts @@ -1,6 +1,7 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import coinsJSONEth from '@trezor/connect-common/files/coins-eth.json'; import BlockchainLink from '@trezor/blockchain-link'; + import { parseCoinsJson, getBitcoinNetwork, getEthereumNetwork } from '../../data/coinInfo'; import { initBlockchain } from '../BlockchainLink'; diff --git a/packages/connect/src/constants/errors.ts b/packages/connect/src/constants/errors.ts index eccb6436d4d..95ecf1fc038 100644 --- a/packages/connect/src/constants/errors.ts +++ b/packages/connect/src/constants/errors.ts @@ -42,13 +42,18 @@ export const ERROR_CODES = { Device_InvalidState: 'Passphrase is incorrect', // authorization error (device state comparison) Device_CallInProgress: 'Device call in progress', // thrown when trying to make another call while current is still running Device_MultipleNotSupported: 'Multiple devices are not supported', // thrown by methods which require single device + Device_MissingCapability: 'Device is missing capability', // thrown by methods which require specific capability + Device_MissingCapabilityBtcOnly: 'Device is missing capability (BTC only)', // thrown by methods which require specific capability when using BTC only firmware Failure_ActionCancelled: 'Action cancelled by user', Failure_FirmwareError: 'Firmware installation failed', Failure_UnknownCode: 'Unknown error', Failure_PinCancelled: 'PIN cancelled', + Failure_PinInvalid: 'PIN invalid', Failure_PinMismatch: 'PIN mismatch', Failure_WipeCodeMismatch: 'Wipe code mismatch', + + Deeplink_VersionMismatch: 'Not compatible with current version of the app', } as const; export type ErrorCode = keyof typeof ERROR_CODES; diff --git a/packages/connect/src/constants/firmware.ts b/packages/connect/src/constants/firmware.ts new file mode 100644 index 00000000000..d6473fe1e80 --- /dev/null +++ b/packages/connect/src/constants/firmware.ts @@ -0,0 +1,3 @@ +// given that firmware hash is a security feature, we prefer to hardcode the version rather than to query the device capabilities +// (a counterfeit device could simply declare it's incapable of hash check) +export const FW_HASH_SUPPORTED_VERSIONS = ['1.11.1', '2.5.1']; diff --git a/packages/connect/src/constants/index.ts b/packages/connect/src/constants/index.ts index b8003479228..ffaeb61b37f 100644 --- a/packages/connect/src/constants/index.ts +++ b/packages/connect/src/constants/index.ts @@ -2,4 +2,6 @@ export * as ERRORS from './errors'; export * as NETWORK from './network'; export * as CARDANO from './cardano'; export * as NEM from './nem'; +export * as FIRMWARE from './firmware'; +export { DEFAULT_SORTING_STRATEGY } from './utxo'; export { MessagesSchema as PROTO } from '@trezor/protobuf'; diff --git a/packages/connect/src/constants/utxo.ts b/packages/connect/src/constants/utxo.ts new file mode 100644 index 00000000000..f0f86f46db2 --- /dev/null +++ b/packages/connect/src/constants/utxo.ts @@ -0,0 +1,3 @@ +import type { TransactionInputOutputSortingStrategy } from '@trezor/utxo-lib'; + +export const DEFAULT_SORTING_STRATEGY: TransactionInputOutputSortingStrategy = 'random'; diff --git a/packages/connect/src/core/AbstractMethod.ts b/packages/connect/src/core/AbstractMethod.ts index 2976cfe8271..95713119a46 100644 --- a/packages/connect/src/core/AbstractMethod.ts +++ b/packages/connect/src/core/AbstractMethod.ts @@ -1,7 +1,8 @@ import { storage } from '@trezor/connect-common'; import { versionUtils } from '@trezor/utils'; -import { DataManager } from '../data/DataManager'; -import { NETWORK } from '../constants'; +import { Capability } from '@trezor/protobuf/src/messages'; + +import { NETWORK, ERRORS } from '../constants'; import { UI, DEVICE, @@ -15,8 +16,14 @@ import { } from '../events'; import { getHost } from '../utils/urlUtils'; import type { Device } from '../device/Device'; -import type { FirmwareRange, DeviceState, StaticSessionId } from '../types'; -import { ERRORS } from '../constants'; +import type { + FirmwareRange, + DeviceState, + StaticSessionId, + DeviceUniquePath, + ConnectSettings, +} from '../types'; +import { config } from '../data/config'; export type Payload = Extract & { override?: boolean }; export type MethodReturnType = CallMethodResponse; @@ -30,6 +37,7 @@ export const DEFAULT_FIRMWARE_RANGE: FirmwareRange = { T2B1: { min: '2.6.1', max: '0' }, T3B1: { min: '2.8.1', max: '0' }, T3T1: { min: '2.7.1', max: '0' }, + T3W1: { min: '2.7.1', max: '0' }, // TODO T3W1 }; function validateStaticSessionId(input: unknown): StaticSessionId { @@ -53,6 +61,30 @@ function validateStaticSessionId(input: unknown): StaticSessionId { 'DeviceState: invalid staticSessionId: ' + input, ); } +// validate expected state from method parameter. +// it could be undefined +function validateDeviceState(input: unknown): DeviceState | undefined { + if (typeof input === 'string') { + return { staticSessionId: validateStaticSessionId(input) }; + } + if (input && typeof input === 'object') { + const state: DeviceState = {}; + if ('staticSessionId' in input) { + state.staticSessionId = validateStaticSessionId(input.staticSessionId); + } + if ('sessionId' in input && typeof input.sessionId === 'string') { + state.sessionId = input.sessionId; + } + if ('deriveCardano' in input && typeof input.deriveCardano === 'boolean') { + state.deriveCardano = input.deriveCardano; + } + + return state; + } + + return undefined; +} + export abstract class AbstractMethod { responseID: number; @@ -61,7 +93,7 @@ export abstract class AbstractMethod originalFn(t, d || device); } - private getOriginPermissions() { - const origin = DataManager.getSettings('origin'); + private getOriginPermissions({ origin }: Pick) { if (!origin) { return []; } @@ -202,8 +222,8 @@ export abstract class AbstractMethod) { + const originPermissions = this.getOriginPermissions({ origin }); let notPermitted = [...this.requiredPermissions]; if (originPermissions.length > 0) { // check if permission was granted @@ -218,8 +238,8 @@ export abstract class AbstractMethod) { + const originPermissions = this.getOriginPermissions({ origin }); let permissionsToSave = this.requiredPermissions.map(p => ({ type: p, @@ -248,13 +268,12 @@ export abstract class AbstractMethod ({ ...state, permissions: [...(state.permissions || []), ...permissionsToSave], }), - origin, + origin!, temporary, ); @@ -299,11 +318,10 @@ export abstract class AbstractMethod) { if (popup && this.requiredPermissions.includes('management')) { const host = getHost(origin); - const allowed = DataManager.getConfig().management.find( + const allowed = config.management.find( item => item.origin === host || item.origin === origin, ); @@ -313,6 +331,33 @@ export abstract class AbstractMethod this.device.features.capabilities.includes(capability), + ); + if (!deviceHasAllRequiredCapabilities) { + if (this.device.firmwareType === 'bitcoin-only') { + throw ERRORS.TypedError( + 'Device_MissingCapabilityBtcOnly', + `Trezor has Bitcoin-only firmware installed, which does not support this operation. Please install Universal firmware through Trezor Suite.`, + ); + } + throw ERRORS.TypedError( + 'Device_MissingCapability', + 'Device does not have capability to call this method. Make sure you have the latest firmware installed.', + ); + } + } + abstract run(): Promise>; dispose() {} diff --git a/packages/connect/src/core/__tests__/Core.test.ts b/packages/connect/src/core/__tests__/Core.test.ts index 68b80aaa1b4..bcd9e4dbcd8 100644 --- a/packages/connect/src/core/__tests__/Core.test.ts +++ b/packages/connect/src/core/__tests__/Core.test.ts @@ -7,7 +7,11 @@ import { initCoreState } from '../index'; const { createTestTransport } = global.JestMocks; const getSettings = (partial: Partial = {}) => - parseConnectSettings({ transports: [createTestTransport()], ...partial }); + parseConnectSettings({ + transports: [createTestTransport()], + transportReconnect: false, + ...partial, + }); describe('Core', () => { beforeAll(async () => {}); diff --git a/packages/connect/src/core/index.ts b/packages/connect/src/core/index.ts index 561767f337c..7230c1d4e26 100644 --- a/packages/connect/src/core/index.ts +++ b/packages/connect/src/core/index.ts @@ -2,8 +2,7 @@ import EventEmitter from 'events'; import { TRANSPORT, TRANSPORT_ERROR } from '@trezor/transport'; -import { createLazy, createDeferred, throwError } from '@trezor/utils'; -import { getSynchronize } from '@trezor/utils'; +import { createLazy, createDeferred, throwError, getSynchronize } from '@trezor/utils'; import { storage } from '@trezor/connect-common'; import { DataManager } from '../data/DataManager'; @@ -29,14 +28,18 @@ import { } from '../events'; import { getMethod } from './method'; import { AbstractMethod } from './AbstractMethod'; -import { resolveAfter } from '../utils/promiseUtils'; import { createUiPromiseManager } from '../utils/uiPromiseManager'; import { createPopupPromiseManager } from '../utils/popupPromiseManager'; import { initLog, enableLog, setLogWriter, LogWriter } from '../utils/debug'; import { dispose as disposeBackend } from '../backend/BlockchainLink'; import { InteractionTimeout } from '../utils/interactionTimeout'; import type { DeviceEvents, Device } from '../device/Device'; -import type { ConnectSettings, Device as DeviceTyped, StaticSessionId } from '../types'; +import type { + ConnectSettings, + Device as DeviceTyped, + DeviceUniquePath, + StaticSessionId, +} from '../types'; import { onCallFirmwareUpdate } from './onCallFirmwareUpdate'; import { WebextensionStateStorage } from '../device/StateStorage'; @@ -65,7 +68,7 @@ const startInteractionTimeout = (context: CoreContext) => * @returns {Promise} * @memberof Core */ -const initDevice = async (context: CoreContext, devicePath?: string) => { +const initDevice = async (context: CoreContext, devicePath?: DeviceUniquePath) => { const { uiPromises, deviceList, sendCoreMessage } = context; assertDeviceListConnected(deviceList); @@ -77,7 +80,8 @@ const initDevice = async (context: CoreContext, devicePath?: string) => { const origin = DataManager.getSettings('origin')!; const useCoreInPopup = DataManager.getSettings('useCoreInPopup'); const { preferredDevice } = storage.load().origin[origin] || {}; - const preferredDeviceInList = preferredDevice && deviceList.getDevice(preferredDevice.path); + const preferredDeviceInList = + preferredDevice && deviceList.getDeviceByPath(preferredDevice.path); // we detected that there is a preferred device (user stored previously) but it's not in the list anymore (disconnected now) // we treat this situation as implicit forget @@ -90,18 +94,18 @@ const initDevice = async (context: CoreContext, devicePath?: string) => { } if (devicePath) { - device = deviceList.getDevice(devicePath); + device = deviceList.getDeviceByPath(devicePath); showDeviceSelection = - !device || !!device?.unreadableError || (device.isUnacquired() && !!isUsingPopup); + !device || device.isUnreadable() || (device.isUnacquired() && !!isUsingPopup); } else { - const devices = deviceList.asArray(); - if (devices.length === 1 && (!isWebUsb || !isUsingPopup)) { + const onlyDevice = deviceList.getOnlyDevice(); + if (onlyDevice && (!isWebUsb || !isUsingPopup)) { // there is only one device available. use it - device = deviceList.getDevice(devices[0].path); + device = onlyDevice; // Show device selection if device is unreadable or unacquired // Also in case of core in popup, so user can press "Remember device" showDeviceSelection = - !!device?.unreadableError || device.isUnacquired() || !!useCoreInPopup; + device.isUnreadable() || device.isUnacquired() || !!useCoreInPopup; } else { showDeviceSelection = true; } @@ -124,22 +128,22 @@ const initDevice = async (context: CoreContext, devicePath?: string) => { // check again for available devices // there is a possible race condition before popup open - const devices = deviceList.asArray(); + const onlyDevice = deviceList.getOnlyDevice(); if ( - devices.length === 1 && - devices[0].type !== 'unreadable' && - devices[0].features && + onlyDevice && + !onlyDevice.isUnreadable() && + !onlyDevice.isUnacquired() && !isWebUsb && !useCoreInPopup ) { // there is one device available. use it - device = deviceList.getDevice(devices[0].path); + device = onlyDevice; } else { // request select device view sendCoreMessage( createUiMessage(UI.SELECT_DEVICE, { webusb: isWebUsb, - devices: deviceList.asArray(), + devices: deviceList.getAllDevices().map(d => d.toMessageObject()), }), ); @@ -157,7 +161,7 @@ const initDevice = async (context: CoreContext, devicePath?: string) => { return store; }); } - device = deviceList.getDevice(payload.device.path); + device = deviceList.getDeviceByPath(payload.device.path); } } } else if (uiPromises.exists(UI.RECEIVE_DEVICE)) { @@ -246,7 +250,7 @@ const inner = async (context: CoreContext, method: AbstractMethod, device: method.requireDeviceMode, ); if (unexpectedMode) { - device.keepTransportSession = false; + device.releaseTransportSession(); if (isUsingPopup) { // wait for popup handshake await waitForPopup(context); @@ -264,8 +268,10 @@ const inner = async (context: CoreContext, method: AbstractMethod, device: return Promise.reject(ERRORS.TypedError('Device_ModeException', unexpectedMode)); } + method.checkDeviceCapability(); + // check and request permissions [read, write...] - method.checkPermissions(); + method.checkPermissions({ origin: DataManager.getSettings('origin') }); if (!trustedHost && method.requiredPermissions.length > 0) { // wait for popup window await waitForPopup(context); @@ -281,7 +287,7 @@ const inner = async (context: CoreContext, method: AbstractMethod, device: const { granted, remember } = await uiPromise.promise.then(({ payload }) => payload); if (granted) { - method.savePermissions(!remember); + method.savePermissions(!remember, { origin: DataManager.getSettings('origin') }); } else { // interrupt process and go to "final" block return Promise.reject(ERRORS.TypedError('Method_PermissionsNotGranted')); @@ -468,16 +474,16 @@ const onCall = async (context: CoreContext, message: IFrameCallMessage) => { try { method = await methodSynchronize(async () => { _log.debug('loading method...'); - const method = await getMethod(message); - _log.debug('method selected', method.name); + const method2 = await getMethod(message); + _log.debug('method selected', method2.name); // bind callbacks - method.postMessage = sendCoreMessage; - method.createUiPromise = uiPromises.create; + method2.postMessage = sendCoreMessage; + method2.createUiPromise = uiPromises.create; // start validation process - method.init(); - await method.initAsync?.(); + method2.init(); + await method2.initAsync?.(); - return method; + return method2; }); resolveWaitForFirstMethod(); callMethods.push(method); @@ -488,6 +494,13 @@ const onCall = async (context: CoreContext, message: IFrameCallMessage) => { return Promise.resolve(); } + if (method.payload.__info) { + const response = method.getMethodInfo(); + sendCoreMessage(createResponseMessage(method.responseID, true, response)); + + return Promise.resolve(); + } + // this method is not using the device, there is no need to acquire if (!method.useDevice) { try { @@ -498,6 +511,7 @@ const onCall = async (context: CoreContext, message: IFrameCallMessage) => { // cancel popup request sendCoreMessage(createPopupMessage(POPUP.CANCEL_POPUP_REQUEST)); } + const response = await method.run(); sendCoreMessage(createResponseMessage(method.responseID, true, response)); } catch (error) { @@ -507,7 +521,7 @@ const onCall = async (context: CoreContext, message: IFrameCallMessage) => { return Promise.resolve(); } - if (method.isManagementRestricted()) { + if (method.isManagementRestricted({ origin: DataManager.getSettings('origin') })) { sendCoreMessage(createPopupMessage(POPUP.CANCEL_POPUP_REQUEST)); sendCoreMessage( createResponseMessage(responseID, false, { @@ -681,25 +695,20 @@ const onCallDevice = async ( } // Work done + if ( + method.keepSession && + method.deviceState && + method.deviceState.sessionId !== device.getState()?.sessionId + ) { + // if session was changed from the one that was sent, send a device changed event + sendCoreMessage(createDeviceMessage(DEVICE.CHANGED, device.toMessageObject())); + } + // TODO: This requires a massive refactoring https://github.com/trezor/trezor-suite/issues/5323 // @ts-expect-error TODO: messageResponse should be assigned from the response of "inner" function const response = messageResponse; if (response) { - if (method.name === 'rebootToBootloader' && response.success) { - // Wait for device to switch to bootloader - // This delay is crucial see https://github.com/trezor/trezor-firmware/issues/1983 - await resolveAfter(1000).promise; - // call Device.run with empty function to fetch new Features - // (acquire > Initialize > nothing > release) - try { - await device.run(() => Promise.resolve(), { skipFinalReload: true }); - } catch (err) { - // ignore. on model T, this block of code is probably not needed at all. but I am keeping it here for - // backwards compatibility - } - } - await device.cleanup(); if (useCoreInPopup) { @@ -896,9 +905,9 @@ const onPopupClosed = (context: CoreContext, customErrorMessage?: string) => { ? ERRORS.TypedError('Method_Cancel', customErrorMessage) : ERRORS.TypedError('Method_Interrupted'); // Device was already acquired. Try to interrupt running action which will throw error from onCall try/catch block - if (deviceList.isConnected() && deviceList.asArray().length > 0) { - deviceList.allDevices().forEach(d => { - d.keepTransportSession = false; // clear transportSession on release + if (deviceList.isConnected() && deviceList.getDeviceCount() > 0) { + deviceList.getAllDevices().forEach(d => { + d.releaseTransportSession(); // clear transportSession on release if (d.isUsedHere()) { setOverridePromise(d.interruptionFromUser(error)); } else { @@ -933,22 +942,22 @@ const handleDeviceSelectionChanges = (context: CoreContext, interruptDevice?: De // update list of devices in popup const promiseExists = uiPromises.exists(UI.RECEIVE_DEVICE); if (promiseExists && deviceList.isConnected()) { - const list = deviceList.asArray(); + const onlyDevice = deviceList.getOnlyDevice(); const isWebUsb = deviceList.transportType() === 'WebUsbTransport'; - if (list.length === 1 && !isWebUsb) { + if (onlyDevice && !isWebUsb) { // there is only one device. use it // resolve uiPromise to looks like it's a user choice (see: handleMessage function) uiPromises.resolve({ type: UI.RECEIVE_DEVICE, - payload: { device: list[0] }, + payload: { device: onlyDevice.toMessageObject() }, }); } else { // update device selection list view sendCoreMessage( createUiMessage(UI.SELECT_DEVICE, { webusb: isWebUsb, - devices: list, + devices: deviceList.getAllDevices().map(d => d.toMessageObject()), }), ); } @@ -1192,7 +1201,7 @@ export class Core extends EventEmitter { try { await DataManager.load(settings); - const { debug, priority, _sessionsBackgroundUrl } = DataManager.getSettings(); + const { debug, priority, _sessionsBackgroundUrl, manifest } = DataManager.getSettings(); const messages = DataManager.getProtobufMessages(); enableLog(debug); @@ -1207,6 +1216,7 @@ export class Core extends EventEmitter { messages, priority, _sessionsBackgroundUrl, + manifest, }); initDeviceList(this.getCoreContext()); diff --git a/packages/connect/src/core/onCallFirmwareUpdate.ts b/packages/connect/src/core/onCallFirmwareUpdate.ts index 0abdc690c2b..cc9531ea04b 100644 --- a/packages/connect/src/core/onCallFirmwareUpdate.ts +++ b/packages/connect/src/core/onCallFirmwareUpdate.ts @@ -1,6 +1,7 @@ import { randomBytes } from 'crypto'; import { createTimeoutPromise } from '@trezor/utils'; +import { isNewer } from '@trezor/utils/src/versionUtils'; import { DeviceList } from '../device/DeviceList'; import { UI, DEVICE, createUiMessage, createDeviceMessage, CoreEventMessage } from '../events'; @@ -14,11 +15,10 @@ import { stripFwHeaders, } from '../api/firmware'; import { getReleases } from '../data/firmwareInfo'; -import { CommonParams, IntermediaryVersion } from '../types'; -import { PROTO, ERRORS } from '../constants'; +import { CommonParams, DeviceUniquePath, IntermediaryVersion } from '../types'; +import { FIRMWARE, PROTO, ERRORS } from '../constants'; import type { Log } from '../utils/debug'; import type { Device } from '../device/Device'; -import { isNewer } from '@trezor/utils/src/versionUtils'; type PostMessage = (message: CoreEventMessage) => void; @@ -91,9 +91,10 @@ const waitForReconnectedDevice = async ( await createTimeoutPromise(2000); try { - const path = deviceList.getFirstDevicePath(); - reconnectedDevice = deviceList.getDevice(path); - } catch {} + reconnectedDevice = deviceList.getOnlyDevice(); + } catch { + /* empty */ + } i++; log.debug('onCallFirmwareUpdate', 'waiting for device to reconnect', i); } while ( @@ -287,7 +288,7 @@ export type Params = { type Context = { deviceList: DeviceList; postMessage: PostMessage; - initDevice: (path?: string) => Promise; + initDevice: (path?: DeviceUniquePath) => Promise; log: Log; abortSignal: AbortSignal; }; @@ -306,7 +307,7 @@ export const onCallFirmwareUpdate = async ({ throw ERRORS.TypedError('Runtime', 'device.firmwareRelease is not set'); } - if (deviceList.allDevices().length > 1) { + if (deviceList.getDeviceCount() > 1) { throw ERRORS.TypedError( 'Device_MultipleNotSupported', 'Firmware update allowed with only 1 device connected', @@ -493,7 +494,8 @@ export const onCallFirmwareUpdate = async ({ } } - const checkSupported = reconnectedDevice.atLeast(['1.11.1', '2.5.1']) && !params.binary; + const checkSupported = + reconnectedDevice.atLeast(FIRMWARE.FW_HASH_SUPPORTED_VERSIONS) && !params.binary; if (checkSupported) { try { @@ -513,8 +515,7 @@ export const onCallFirmwareUpdate = async ({ return { check: 'mismatch' as const }; } } catch (err) { - // TrezorConnect error. Only 'softly' inform user that we were not able to - // validate firmware hash + // device failed to respond to the hash check, consider the firmware counterfeit return { check: 'other-error' as const, checkError: err.message }; } } else { diff --git a/packages/connect/src/data/DataManager.ts b/packages/connect/src/data/DataManager.ts index cc17c9ecf1e..2413c2037e7 100644 --- a/packages/connect/src/data/DataManager.ts +++ b/packages/connect/src/data/DataManager.ts @@ -4,12 +4,49 @@ import { httpRequest } from '../utils/assets'; import { parseCoinsJson } from './coinInfo'; import { parseFirmware } from './firmwareInfo'; import { parseBridgeJSON } from './transportInfo'; -import { config } from './config'; - import { ConnectSettings, DeviceModelInternal } from '../types'; type AssetCollection = { [key: string]: Record }; +const assets = [ + { + name: 'coins', + url: './data/coins.json', + }, + { + name: 'coinsEth', + url: './data/coins-eth.json', + }, + { + name: 'bridge', + url: './data/bridge/releases.json', + }, + { + name: 'firmware-t1b1', + url: './data/firmware/t1b1/releases.json', + }, + { + name: 'firmware-t2t1', + url: './data/firmware/t2t1/releases.json', + }, + { + name: 'firmware-t2b1', + url: './data/firmware/t2b1/releases.json', + }, + { + name: 'firmware-t3b1', + url: './data/firmware/t3b1/releases.json', + }, + { + name: 'firmware-t3t1', + url: './data/firmware/t3t1/releases.json', + }, + { + name: 'firmware-t3tw1', + url: './data/firmware/t3w1/releases.json', + }, +]; + export class DataManager { static assets: AssetCollection = {}; @@ -22,13 +59,13 @@ export class DataManager { if (!withAssets) return; - const assetPromises = config.assets.map(async asset => { + const assetPromises = assets.map(async asset => { const json = await httpRequest(`${asset.url}${ts}`, 'json'); this.assets[asset.name] = json; }); await Promise.all(assetPromises); - this.messages = await httpRequest(`${config.messages}${ts}`, 'json'); + this.messages = await httpRequest('./data/messages/messages.json', 'json'); // parse bridge JSON parseBridgeJSON(this.assets.bridge); @@ -64,8 +101,4 @@ export class DataManager { return this.settings; } - - static getConfig() { - return config; - } } diff --git a/packages/connect/src/data/__tests__/DataManager.test.ts b/packages/connect/src/data/__tests__/DataManager.test.ts index a06976cc4bb..7265de88317 100644 --- a/packages/connect/src/data/__tests__/DataManager.test.ts +++ b/packages/connect/src/data/__tests__/DataManager.test.ts @@ -17,6 +17,7 @@ const settings = { iframeSrc: '', popupSrc: '', webusbSrc: '', + deeplinkUrl: '', version: '9.0.0', priority: 1, trustedHost: true, @@ -31,6 +32,7 @@ describe('data/DataManager', () => { try { await DataManager.load(settings, false); } catch (err) { + // eslint-disable-next-line jest/no-standalone-expect expect(err).toBe(undefined); } }); diff --git a/packages/connect/src/data/__tests__/coinInfo.test.ts b/packages/connect/src/data/__tests__/coinInfo.test.ts index ec23950cd45..da5b40e139b 100644 --- a/packages/connect/src/data/__tests__/coinInfo.test.ts +++ b/packages/connect/src/data/__tests__/coinInfo.test.ts @@ -1,4 +1,5 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; + import { parseCoinsJson, getCoinInfo, getUniqueNetworks, getAllNetworks } from '../coinInfo'; describe('data/coinInfo', () => { diff --git a/packages/connect/src/data/__tests__/firmwareInfo.test.ts b/packages/connect/src/data/__tests__/firmwareInfo.test.ts index 469b77fb26b..d345601e503 100644 --- a/packages/connect/src/data/__tests__/firmwareInfo.test.ts +++ b/packages/connect/src/data/__tests__/firmwareInfo.test.ts @@ -1,5 +1,6 @@ -import { getReleases, parseFirmware, getFirmwareStatus } from '../firmwareInfo'; import * as releases2 from '@trezor/connect-common/files/firmware/t2t1/releases.json'; + +import { getReleases, parseFirmware, getFirmwareStatus } from '../firmwareInfo'; import { DeviceModelInternal } from '../../types'; describe('data/firmwareInfo', () => { diff --git a/packages/connect/src/data/__tests__/transportInfo.test.ts b/packages/connect/src/data/__tests__/transportInfo.test.ts index c70d4348667..83add78dfca 100644 --- a/packages/connect/src/data/__tests__/transportInfo.test.ts +++ b/packages/connect/src/data/__tests__/transportInfo.test.ts @@ -1,6 +1,7 @@ -import { parseBridgeJSON } from '../transportInfo'; import * as releases from '@trezor/connect-common/files/bridge/releases.json'; +import { parseBridgeJSON } from '../transportInfo'; + describe('data/transportInfo', () => { test('parseBridgeJSON', () => { expect(parseBridgeJSON(releases)).toEqual({ diff --git a/packages/connect/src/data/analyticsInfo.ts b/packages/connect/src/data/analyticsInfo.ts index 928a277bd0d..b6cdf34b55b 100644 --- a/packages/connect/src/data/analyticsInfo.ts +++ b/packages/connect/src/data/analyticsInfo.ts @@ -1,28 +1,22 @@ -// import { EventType } from '@trezor/connect-analytics'; +import { EventType } from '@trezor/connect-analytics'; + import { CoreEventMessage, UI_REQUEST } from '../events'; import type { Device } from '../types'; -// TODO: imho this belongs somewhere to packages/connect-iframe package. -// There I can freely import from anyplace within monorepo without needing -// to release new packages. Having it here means that I would need to -// release 2 new packages to npm which is unjustifiable burden export const enhanceMessageWithAnalytics = ( message: CoreEventMessage, data: { device?: Device }, ): CoreEventMessage => { switch (message.type) { - case UI_REQUEST.REQUEST_CONFIRMATION: + case UI_REQUEST.REQUEST_CONFIRMATION: { const { device } = data; return { ...message, - // @ts-expect-error (EventType.DeviceSelected is inlined here) payload: { ...message.payload, analytics: { - // todo: type inlined temporarily - // type: EventType.DeviceSelected, - type: 'device/selected', + type: EventType.DeviceSelected, payload: { mode: device?.mode || '', pinProtection: device?.features?.pin_protection || '', @@ -44,6 +38,7 @@ export const enhanceMessageWithAnalytics = ( }, }, }; + } default: return message; diff --git a/packages/connect/src/data/coinInfo.ts b/packages/connect/src/data/coinInfo.ts index e937b9c08fc..92216cdf56c 100644 --- a/packages/connect/src/data/coinInfo.ts +++ b/packages/connect/src/data/coinInfo.ts @@ -1,5 +1,6 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/data/CoinInfo.js import { cloneObject } from '@trezor/utils'; + import { getBitcoinFeeLevels, getEthereumFeeLevels, getMiscFeeLevels } from './defaultFeeLevels'; import { ERRORS } from '../constants'; import { toHardened, fromHardened } from '../utils/pathUtils'; diff --git a/packages/connect/src/data/config.ts b/packages/connect/src/data/config.ts index c4adf818de4..93d17614379 100644 --- a/packages/connect/src/data/config.ts +++ b/packages/connect/src/data/config.ts @@ -66,41 +66,6 @@ export const config = { onionDomains: { 'trezor.io': 'trezoriovpjcahpzkrewelclulmszwbqpzmzgub37gbcjlvluxtruqad.onion', }, - assets: [ - { - name: 'coins', - url: './data/coins.json', - }, - { - name: 'coinsEth', - url: './data/coins-eth.json', - }, - { - name: 'bridge', - url: './data/bridge/releases.json', - }, - { - name: 'firmware-t1b1', - url: './data/firmware/t1b1/releases.json', - }, - { - name: 'firmware-t2t1', - url: './data/firmware/t2t1/releases.json', - }, - { - name: 'firmware-t2b1', - url: './data/firmware/t2b1/releases.json', - }, - { - name: 'firmware-t3b1', - url: './data/firmware/t3b1/releases.json', - }, - { - name: 'firmware-t3t1', - url: './data/firmware/t3t1/releases.json', - }, - ], - messages: './data/messages/messages.json', supportedBrowsers: { chrome: { version: 59, @@ -257,11 +222,11 @@ export const config = { }, { methods: ['showDeviceTutorial', 'authenticateDevice'], - min: { T1B1: '0', T2T1: '0', T2B1: '2.6.1', T3B1: '2.8.1', T3T1: '2.8.0' }, - }, - { - methods: ['rebootToBootloader'], - min: { T1B1: '1.10.0', T2T1: '2.6.0' }, + min: { + T1B1: '0', + T2T1: '0', + T3T1: '2.8.0', + }, }, { methods: ['getFirmwareHash'], @@ -269,11 +234,19 @@ export const config = { }, { methods: ['solanaGetPublicKey', 'solanaGetAddress', 'solanaSignTransaction'], - min: { T1B1: '0', T2T1: '2.6.4', T2B1: '2.6.4', T3B1: '2.8.1', T3T1: '2.7.2' }, + min: { + T1B1: '0', + T2T1: '2.6.4', + T2B1: '2.6.4', + }, }, { capabilities: ['chunkify'], - min: { T1B1: '0', T2T1: '2.6.3', T2B1: '2.6.3', T3B1: '2.8.1', T3T1: '2.7.2' }, + min: { + T1B1: '0', + T2T1: '2.6.3', + T2B1: '2.6.3', + }, comment: [ "Since firmware 2.6.3 there is a new protobuf field 'chunkify' in almost all getAddress and signTx methods", ], @@ -284,8 +257,6 @@ export const config = { T1B1: '0', T2T1: '2.7.0', T2B1: '2.7.0', - T3B1: '2.8.1', // adding T3B1 to the list so that it gets inferred as type - T3T1: '2.7.2', }, }, ], diff --git a/packages/connect/src/data/connectSettings.ts b/packages/connect/src/data/connectSettings.ts index f278b0eb778..1886127aa15 100644 --- a/packages/connect/src/data/connectSettings.ts +++ b/packages/connect/src/data/connectSettings.ts @@ -1,7 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/data/ConnectSettings.js import type { Manifest, ConnectSettings } from '../types'; -import { VERSION, DEFAULT_DOMAIN } from './version'; +import { VERSION, DEFAULT_DOMAIN, DEEPLINK_VERSION } from './version'; /* * Initial settings for connect. @@ -28,6 +28,8 @@ const initialSettings: ConnectSettings = { timestamp: new Date().getTime(), interactionTimeout: 600, // 5 minutes sharedLogger: true, + deeplinkUrl: `${DEFAULT_DOMAIN}deeplink/${DEEPLINK_VERSION}/`, + transportReconnect: true, }; const parseManifest = (manifest?: Manifest) => { @@ -83,16 +85,12 @@ export const parseConnectSettings = (input: Partial = {}) => { settings.iframeSrc = `${src}iframe.html`; settings.popupSrc = `${src}popup.html`; settings.webusbSrc = `${src}webusb.html`; + settings.deeplinkUrl = `${src}deeplink/${DEEPLINK_VERSION}/`; if (typeof input.transportReconnect === 'boolean') { settings.transportReconnect = input.transportReconnect; } - // deprecated, settings.transport should be used instead - if (typeof input.webusb === 'boolean') { - settings.webusb = input.webusb; - } - if (Array.isArray(input.transports)) { settings.transports = input.transports; } @@ -144,9 +142,17 @@ export const parseConnectSettings = (input: Partial = {}) => { settings._extendWebextensionLifetime = input._extendWebextensionLifetime; } - if (typeof input._sessionsBackgroundUrl === 'string') { + if (typeof input._sessionsBackgroundUrl === 'string' || input._sessionsBackgroundUrl === null) { settings._sessionsBackgroundUrl = input._sessionsBackgroundUrl; } + if (typeof input.binFilesBaseUrl === 'string') { + settings.binFilesBaseUrl = input.binFilesBaseUrl; + } + + if (typeof input.enableFirmwareHashCheck === 'boolean') { + settings.enableFirmwareHashCheck = Boolean(input.enableFirmwareHashCheck); + } + return settings; }; diff --git a/packages/connect/src/data/deviceAuthenticityConfig.ts b/packages/connect/src/data/deviceAuthenticityConfig.ts index ba0c1ce6580..606a0c13d91 100644 --- a/packages/connect/src/data/deviceAuthenticityConfig.ts +++ b/packages/connect/src/data/deviceAuthenticityConfig.ts @@ -52,6 +52,12 @@ export const deviceAuthenticityConfig: DeviceAuthenticityConfig = { '04c243743ba6326a443c34d503ea5d2d3036e1fbc8dbb072d4ae0f9b8016910db38e234641b87557fb44bcddee612722a67831bbac9758982a5843d9077be9434d', '04f1a32b84249a4b909974e32b33cc78aeef8144ef57744e792e6203fb8acce348f0d5d5535ed9a65ac741744a264de2073e49dbcd989819cddc90285b5e97a28a', '04894fa385d11809244da4f3d7de8ddd70c25abd5d4d3054b438d09016f2fd0a98729e15ea1483fbde83489fec6c64f8be014db8dc22227babcecdb5fd996ea671', + '0488c8f35649583d68c01c3b34223f11cc61389bcc71707960a6b1c30854ac1a0b5da916c47ee4cb1a78a9bac3d602aec67a5cd49b34732b6d1cc4fb2cb6b04361', + '04e4be5f15b528c594bfe732e4fcae4e65d63580051bb0235e6c1ca91c2663d54afa4b873dfeacbc4df98c57a923f7b308277f9d6bd064e81ed8e6890a1c582532', + '041c6ff76562b8042cf0e678d4bf977da09947d531e628450acbda0f0609c242c63db0be7fa1c601940af499b455e207c252706b37487c41fa9086621c3bd5c131', + '041b31c4fc213b27a34106e5ee8c7516941fdf8b15e3410987592ee488081c8dde5f42d397901c6d8b1d029d89bd2286b9183091a1edf12914be29aea40968d039', + '045de24c0d64b611bee13f34d74a3df7b24a658e6fea1bf1b9b3b530c23177759ba757e86494ff51c688bd63c1c85e93148bd81f39c5db80d3b5a0c05a6da4f380', + '049fb194cd0f7d79b3aae0b681d9f324c3dd086bb3f568067070bed195e1d7f0c722f86f7f9476da68b4c57e42536e5ab8c7fc74ee4726208814897849cac77b47', ], debug: { rootPubKeys: [ @@ -99,6 +105,9 @@ export const deviceAuthenticityConfig: DeviceAuthenticityConfig = { '04b427eb0e1484b7127f82820edddbbb538c11d3c330b5f68acef96c8cf4601b30390311f2d48090f3f5c5e4bf56ca0396984797cd92ab0d12164600c9f43d2ba3', '0438e670e9eb3cd19f8579b3202caf9aa766c8d5735ab97bd3682155437499e062587bded86c732789345538581c83e9e42af0a15a4ad81bc42e1f28a7859ebce0', '04ac2a7e88ffc08186b3cb461966a96cacdb2c07b383018d708a6f0ce0b74f59dca97a6f31fc8d243a781c7fe5006dc6b8ef05347972082b32fb29c636c595ffbc', + '047c32e92dc23894b60b6c182f589a12e4e57f82e48176936f56646c14dd8f0d300fd4737e17501d71deb84127c272c5b797ccf30a7a4531846b9c79866f2892c6', + '046482d01c4dbdac60327352d150b5e6d5772cd68732616a0abc5649c49111bc3ba0eb28aa424100efa27cdb98675395df4b9097097e92adecc39f5587306a9fac', + '04f96a9fdecc4247c67e083a69037d1794230441270e4bda6d4151eb2e385d576d046d36734dd262e45c8cf3bbe24bdd11c1dc0e58437107d55cea3942f14b7d61', ], debug: { rootPubKeys: [ @@ -109,4 +118,12 @@ export const deviceAuthenticityConfig: DeviceAuthenticityConfig = { ], }, }, + T3W1: { + rootPubKeys: ['you shall not pass'], // TODO T3W1 + caPubKeys: ['you shall not pass'], // TODO T3W1 + debug: { + rootPubKeys: ['you shall not pass'], // TODO T3W1 + caPubKeys: ['you shall not pass'], // TODO T3W1 + }, + }, }; diff --git a/packages/connect/src/data/deviceAuthenticityConfigTypes.ts b/packages/connect/src/data/deviceAuthenticityConfigTypes.ts index 7f61ba02d1a..004b30612c8 100644 --- a/packages/connect/src/data/deviceAuthenticityConfigTypes.ts +++ b/packages/connect/src/data/deviceAuthenticityConfigTypes.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../constants'; type CertPubKeys = Static; diff --git a/packages/connect/src/data/firmwareInfo.ts b/packages/connect/src/data/firmwareInfo.ts index de9b904a450..a852e878a00 100644 --- a/packages/connect/src/data/firmwareInfo.ts +++ b/packages/connect/src/data/firmwareInfo.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/data/FirmwareInfo.js import { versionUtils } from '@trezor/utils'; + import { filterSafeListByFirmware, filterSafeListByBootloader, @@ -34,7 +35,7 @@ export const parseFirmware = (json: any, deviceModel: DeviceModelInternal) => { export const getReleases = (deviceModel: DeviceModelInternal) => releases[deviceModel] || []; -const getChangelog = (releases: FirmwareRelease[], features: StrictFeatures) => { +const getChangelog = (releases2: FirmwareRelease[], features: StrictFeatures) => { // releases are already filtered, so they can be considered "safe". // so lets build changelog! It should include only those firmwares, that are // newer than currently installed firmware. @@ -51,7 +52,7 @@ const getChangelog = (releases: FirmwareRelease[], features: StrictFeatures) => if (features.firmware_present && features.major_version === 2) { // little different situation is with model 2, where in bootloader (and with some fw installed) // we actually know the firmware version - return releases.filter(r => + return releases2.filter(r => versionUtils.isNewer(r.version, [ features.fw_major, features.fw_minor, @@ -61,13 +62,13 @@ const getChangelog = (releases: FirmwareRelease[], features: StrictFeatures) => } // for fresh devices, we can assume that all releases are actually "new" - return releases; + return releases2; } // otherwise we are in firmware mode and because each release in releases list has // version higher than the previous one, we can filter out the version that is already // installed and show only what's new! - return releases.filter(r => + return releases2.filter(r => versionUtils.isNewer(r.version, [ features.major_version, features.minor_version, @@ -98,7 +99,7 @@ const isEqual = (release: FirmwareRelease, latest: FirmwareRelease) => versionUtils.isEqual(release.version, latest.version); const getT1BootloaderVersion = ( - releases: FirmwareRelease[], + releases2: FirmwareRelease[], features: StrictFeatures, ): VersionArray => { const { bootloader_mode, major_version, minor_version, patch_version } = features; @@ -108,7 +109,7 @@ const getT1BootloaderVersion = ( return versionArray; } - const release = releases.find(({ version }) => versionUtils.isEqual(version, versionArray)); + const release = releases2.find(({ version }) => versionUtils.isEqual(version, versionArray)); /** * FW version 1.6.0 and below don't have bootloader_version listed, so default to 1.0.0, @@ -123,7 +124,7 @@ const getT1BootloaderVersion = ( * v3 - bootloader >= 1.12.0 */ const getIntermediaryVersion = ( - releases: FirmwareRelease[], + releases2: FirmwareRelease[], features: StrictFeatures, offerLatest: boolean, ): IntermediaryVersion | undefined => { @@ -132,7 +133,7 @@ const getIntermediaryVersion = ( return; } - const bootloaderVersion = getT1BootloaderVersion(releases, features); + const bootloaderVersion = getT1BootloaderVersion(releases2, features); if (versionUtils.isNewerOrEqual(bootloaderVersion, [1, 12, 0])) { return 3; @@ -150,6 +151,7 @@ export interface GetInfoProps { releases: FirmwareRelease[]; } +// eslint-disable-next-line @typescript-eslint/no-shadow const getSafeReleases = ({ features, releases }: GetInfoProps) => { const { bootloader_mode, @@ -193,6 +195,7 @@ const getSafeReleases = ({ features, releases }: GetInfoProps) => { * @param features * @param releases */ +// eslint-disable-next-line @typescript-eslint/no-shadow export const getInfo = ({ features, releases }: GetInfoProps): ReleaseInfo | null => { if (!Array.isArray(releases) || releases.length < 1) { // no available releases - should never happen for official firmware, only custom diff --git a/packages/connect/src/data/models.ts b/packages/connect/src/data/models.ts index c4cfe96d8e3..9882b244b46 100644 --- a/packages/connect/src/data/models.ts +++ b/packages/connect/src/data/models.ts @@ -29,4 +29,13 @@ export const models = { '4': 'Bitcoin Orange', }, }, + T3W1: { + name: 'Trezor Safe 7', + colors: { + '1': 'Fantastic Ethereum', // TODO T3W1 + '2': 'Lunatic Dogecoin', // TODO T3W1 + '3': 'Galactic Litecoin', // TODO T3W1 + '4': 'Majestic Bitcoin', // TODO T3W1 + }, + }, }; diff --git a/packages/connect/src/data/transportInfo.ts b/packages/connect/src/data/transportInfo.ts index 7bae65e314d..ea1b1be5930 100644 --- a/packages/connect/src/data/transportInfo.ts +++ b/packages/connect/src/data/transportInfo.ts @@ -32,17 +32,17 @@ export const parseBridgeJSON = (json: any) => { export const getBridgeInfo = (): BridgeInfo => info; export const suggestBridgeInstaller = (platform?: string) => { - const info = getBridgeInfo(); + const info2 = getBridgeInfo(); // check if preferred field was already added - if (!info.packages.find(p => p.preferred)) { + if (!info2.packages.find(p => p.preferred)) { if (platform) { // override BridgeInfo packages, add preferred field - info.packages = info.packages.map(p => ({ + info2.packages = info2.packages.map(p => ({ ...p, preferred: p.platform.indexOf(platform) >= 0, })); } } - return info; + return info2; }; diff --git a/packages/connect/src/data/udevInfo.ts b/packages/connect/src/data/udevInfo.ts index f5147e45884..75cd9f2fc87 100644 --- a/packages/connect/src/data/udevInfo.ts +++ b/packages/connect/src/data/udevInfo.ts @@ -24,17 +24,17 @@ const info: UdevInfo = { export const getUdevInfo = () => info; export const suggestUdevInstaller = (platform?: string) => { - const info = getUdevInfo(); + const info2 = getUdevInfo(); // check if preferred field was already added - if (!info.packages.find(p => p.preferred)) { + if (!info2.packages.find(p => p.preferred)) { if (platform) { // override UdevInfo packages, add preferred field - info.packages = info.packages.map(p => ({ + info2.packages = info2.packages.map(p => ({ ...p, preferred: p.platform.indexOf(platform) >= 0, })); } } - return info; + return info2; }; diff --git a/packages/connect/src/data/version.ts b/packages/connect/src/data/version.ts index 2f5e69f32b4..9e247ebce7b 100644 --- a/packages/connect/src/data/version.ts +++ b/packages/connect/src/data/version.ts @@ -1,4 +1,4 @@ -export const VERSION = '9.4.2'; +export const VERSION = '9.4.3-beta.2'; const versionN = VERSION.split('.').map(s => parseInt(s, 10)); @@ -10,3 +10,5 @@ export const DEFAULT_DOMAIN = isBeta // Increment with content script changes export const CONTENT_SCRIPT_VERSION = 1; +// Increment with deeplink protocol changes +export const DEEPLINK_VERSION = 1; diff --git a/packages/connect/src/device/Device.ts b/packages/connect/src/device/Device.ts index 19a338d616d..60bc96b2b89 100644 --- a/packages/connect/src/device/Device.ts +++ b/packages/connect/src/device/Device.ts @@ -1,9 +1,19 @@ // original file https://github.com/trezor/connect/blob/develop/src/js/device/Device.js -import { versionUtils, createDeferred, Deferred, TypedEmitter } from '@trezor/utils'; +import { randomBytes } from 'crypto'; + +import { + versionUtils, + createDeferred, + Deferred, + TypedEmitter, + createTimeoutPromise, +} from '@trezor/utils'; import { Session } from '@trezor/transport'; import { TransportProtocol, v1 as v1Protocol } from '@trezor/protocol'; +import { type Transport, type Descriptor, TRANSPORT_ERROR } from '@trezor/transport'; + import { DeviceCommands } from './DeviceCommands'; -import { PROTO, ERRORS, NETWORK } from '../constants'; +import { PROTO, ERRORS, FIRMWARE } from '../constants'; import { DEVICE, DeviceButtonRequestPayload, @@ -22,7 +32,6 @@ import { ensureInternalModelFeature, } from '../utils/deviceFeaturesUtils'; import { initLog } from '../utils/debug'; -import { type Transport, type Descriptor, TRANSPORT_ERROR } from '@trezor/transport'; import { Device as DeviceTyped, DeviceFirmwareStatus, @@ -35,12 +44,16 @@ import { VersionArray, KnownDevice, StaticSessionId, + FirmwareHashCheckResult, + FirmwareHashCheckError, + DeviceUniquePath, } from '../types'; import { models } from '../data/models'; import { getLanguage } from '../data/getLanguage'; import { checkFirmwareRevision } from './checkFirmwareRevision'; import { IStateStorage } from './StateStorage'; import type { PromptCallback } from './prompts'; +import { calculateFirmwareHash, getBinaryOptional, stripFwHeaders } from '../api/firmware'; // custom log const _log = initLog('Device'); @@ -91,182 +104,318 @@ export interface DeviceEvents { ) => void; [DEVICE.PASSPHRASE_ON_DEVICE]: () => void; [DEVICE.BUTTON]: (device: Device, payload: DeviceButtonRequestPayload) => void; - [DEVICE.ACQUIRED]: () => void; } +type DeviceLifecycle = + | typeof DEVICE.CONNECT + | typeof DEVICE.CONNECT_UNACQUIRED + | typeof DEVICE.DISCONNECT + | typeof DEVICE.CHANGED; + +type DeviceLifecycleListener = (lifecycle: DeviceLifecycle) => void; + +type DeviceParams = { + id: DeviceUniquePath; + transport: Transport; + descriptor: Descriptor; + listener: DeviceLifecycleListener; +}; + /** * @export * @class Device * @extends {EventEmitter} */ export class Device extends TypedEmitter { - transport: Transport; - protocol: TransportProtocol; - - originalDescriptor: Descriptor; + public readonly transport: Transport; + public readonly protocol: TransportProtocol; + private readonly transportPath; + private readonly transportSessionOwner; + private readonly transportDescriptorType; + private session; + private lastAcquiredHere; /** * descriptor was detected on transport layer but sending any messages (such as GetFeatures) to it failed either * with some expected error, for example HID device, LIBUSB_ERROR, or it simply timeout out. such device can't be worked * with and user needs to take some action. for example reconnect the device, update firmware or change transport type */ - unreadableError?: string; + private unreadableError?: string; // @ts-expect-error: strictPropertyInitialization - firmwareStatus: DeviceFirmwareStatus; + private _firmwareStatus: DeviceFirmwareStatus; + public get firmwareStatus() { + return this._firmwareStatus; + } - firmwareRelease?: ReleaseInfo | null; + private _firmwareRelease?: ReleaseInfo | null; + public get firmwareRelease() { + return this._firmwareRelease; + } // @ts-expect-error: strictPropertyInitialization - features: Features; + private _features: Features; + public get features() { + return this._features; + } - featuresNeedsReload = false; + private _featuresNeedsReload = false; // variables used in one workflow: acquire -> transportSession -> commands -> run -> keepTransportSession -> release private acquirePromise?: ReturnType; private releasePromise?: ReturnType; private runPromise?: Deferred; - transportSession?: Session | null; - keepTransportSession = false; + + private keepTransportSession = false; public commands?: DeviceCommands; private cancelableAction?: (err?: Error) => Promise; - loaded = false; + private loaded = false; - inconsistent = false; + private inconsistent = false; - firstRunPromise: Deferred; - instance = 0; + private firstRunPromise: Deferred; + private instance = 0; // DeviceState list [this.instance]: DeviceState | undefined private state: DeviceState[] = []; private stateStorage?: IStateStorage = undefined; - unavailableCapabilities: UnavailableCapabilities = {}; - - networkTypeState: NETWORK.NetworkType[] = []; + private _unavailableCapabilities: UnavailableCapabilities = {}; + public get unavailableCapabilities(): Readonly { + return this._unavailableCapabilities; + } - firmwareType?: FirmwareType; + private _firmwareType?: FirmwareType; + public get firmwareType() { + return this._firmwareType; + } - name = 'Trezor'; + private name = 'Trezor'; - color?: string; + private color?: string; - availableTranslations: string[] = []; + private availableTranslations: string[] = []; - authenticityChecks: NonNullable = { + private authenticityChecks: NonNullable = { firmwareRevision: null, + firmwareHash: null, }; - private useCardanoDerivation = false; + private readonly uniquePath; + + private readonly emitLifecycle; - constructor(transport: Transport, descriptor: Descriptor) { + private sessionDfd?: Deferred; + + constructor({ id, transport, descriptor, listener }: DeviceParams) { super(); + this.emitLifecycle = listener; this.protocol = v1Protocol; // === immutable properties + this.uniquePath = id; this.transport = transport; - this.originalDescriptor = descriptor; + this.transportPath = descriptor.path; + this.transportSessionOwner = descriptor.sessionOwner; + this.transportDescriptorType = descriptor.type; + + this.session = descriptor.session; + this.lastAcquiredHere = false; // this will be released after first run this.firstRunPromise = createDeferred(); } - static fromDescriptor(transport: Transport, originalDescriptor: Descriptor) { - const descriptor = { ...originalDescriptor, session: null }; - try { - const device: Device = new Device(transport, descriptor); - - return device; - } catch (error) { - _log.error('Device.fromDescriptor', error); - throw error; + private getSessionChangePromise() { + if (!this.sessionDfd) { + this.sessionDfd = createDeferred(); + this.sessionDfd.promise.finally(() => { + this.sessionDfd = undefined; + }); } - } - - static createUnacquired( - transport: Transport, - descriptor: Descriptor, - unreadableError?: string, - ) { - const device = new Device(transport, descriptor); - device.unreadableError = unreadableError; - return device; + return this.sessionDfd.promise; } - async acquire() { - this.acquirePromise = this.transport.acquire({ - input: { - path: this.originalDescriptor.path, - previous: this.originalDescriptor.session, - }, - }); - const acquireResult = await this.acquirePromise; - this.acquirePromise = undefined; - if (!acquireResult.success) { - if (this.runPromise) { - this.runPromise.reject(new Error(acquireResult.error)); - delete this.runPromise; + private async waitAndCompareSession< + T extends { success: true; payload: Session | null } | { success: false }, + >(response: T, sessionPromise: Promise) { + if (response.success) { + try { + if ((await sessionPromise) !== response.payload) { + return { + success: false, + error: TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS, + } as const; + } + } catch { + return { + success: false, + error: TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION, + } as const; } - throw acquireResult.error; } - const transportSession = acquireResult.payload; + return response; + } + + acquire() { + const sessionPromise = this.getSessionChangePromise(); - _log.debug('Expected workflow id:', transportSession); - this.transportSession = transportSession; - // note: this.originalDescriptor is updated here and also in TRANSPORT.UPDATE listener. - // I would like to update it only in one place (listener) but it some cases (unchained test), - // listen response is not triggered by device acquire. not sure why. - this.originalDescriptor.session = transportSession; + this.acquirePromise = this.transport + .acquire({ input: { path: this.transportPath, previous: this.session } }) + .then(result => this.waitAndCompareSession(result, sessionPromise)) + .then(result => { + if (result.success) { + this.session = result.payload; + this.lastAcquiredHere = true; + + this.commands?.dispose(); + this.commands = new DeviceCommands(this, this.transport, this.session); + + return result; + } else { + if (this.runPromise) { + this.runPromise.reject(new Error(result.error)); + delete this.runPromise; + } + throw result.error; + } + }) + .finally(() => { + this.acquirePromise = undefined; + }); + + return this.acquirePromise; + } + + async release() { + const localSession = this.getLocalSession(); + if (!localSession || this.keepTransportSession || this.releasePromise) { + return; + } if (this.commands) { this.commands.dispose(); + if (this.commands.callPromise) { + await this.commands.callPromise; + } } - this.commands = new DeviceCommands(this, this.transport, transportSession); - } - async release() { - if ( - this.isUsedHere() && - this.transportSession && - !this.keepTransportSession && - !this.releasePromise - ) { - if (this.commands) { - this.commands.dispose(); - if (this.commands.callPromise) { - await this.commands.callPromise; + const sessionPromise = this.getSessionChangePromise(); + + this.releasePromise = this.transport + .release({ session: localSession, path: this.transportPath }) + .then(result => this.waitAndCompareSession(result, sessionPromise)) + .then(result => { + if (result.success) { + this.session = null; } - } - this.releasePromise = this.transport.release({ - session: this.transportSession, - path: this.originalDescriptor.path, + return result; + }) + .finally(() => { + this.releasePromise = undefined; }); - const releaseResponse = await this.releasePromise; - this.releasePromise = undefined; - if (releaseResponse.success) { - this.transportSession = null; - this.originalDescriptor.session = null; - } - } + return this.releasePromise; + } + + releaseTransportSession() { + this.keepTransportSession = false; } async cleanup() { - // remove all listeners **except** DEVICE.ACQUIRED - waiting for acquired Device in DeviceList - const acquiredListeners = this.listeners(DEVICE.ACQUIRED); - this.removeAllListeners(); + // remove all listeners + this.eventNames().forEach(e => this.removeAllListeners(e as keyof DeviceEvents)); + // make sure that Device_CallInProgress will not be thrown delete this.runPromise; await this.release(); - // restore DEVICE.ACQUIRED listeners - acquiredListeners.forEach(l => this.once(DEVICE.ACQUIRED, l)); } + // call only once, right after device creation + async handshake(delay?: number) { + if (delay) { + await createTimeoutPromise(501 + delay); + } + + while (true) { + if (this.isUsedElsewhere()) { + this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); + } else { + try { + await this.run(); + } catch (error) { + if ( + error.code === 'Device_NotFound' || + error.message === TRANSPORT_ERROR.DEVICE_NOT_FOUND || + error.message === TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION || + error.message === TRANSPORT_ERROR.UNEXPECTED_ERROR || + error.message === TRANSPORT_ERROR.DESCRIPTOR_NOT_FOUND || + error.message === TRANSPORT_ERROR.HTTP_ERROR // bridge died during device initialization + ) { + // disconnected, do nothing + } else if ( + error.message === TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS || + error.code === 'Device_UsedElsewhere' // most common error - someone else took the device at the same time + ) { + // TODO needed only for TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS + // this.enumerate(transport); + this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); + } else if ( + // device was claimed by another application on transport api layer (claimInterface in usb nomenclature) but never released (releaseInterface in usb nomenclature) + // the only remedy for this is to reconnect device manually + // or possibly there are 2 applications without common sessions background + error.message === TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE || + // catch one of trezord LIBUSB_ERRORs + error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0 || + // we tried to initialize device (either automatically after enumeration or after user click) + // but it did not work out. this device is effectively unreadable and user should do something about it + error.code === 'Device_InitializeFailed' + ) { + this.unreadableError = error?.message; + this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); + } else { + await createTimeoutPromise(501); + continue; + } + } + } + + return; + } + } + + async updateDescriptor(descriptor: Descriptor) { + this.sessionDfd?.resolve(descriptor.session); + + await Promise.all([this.acquirePromise, this.releasePromise]); + + // TODO improve these conditions + + // Session changed to different than the current one + // -> acquired by someone else + if (descriptor.session && descriptor.session !== this.session) { + this.usedElsewhere(); + } + + // Session changed to null + // -> released + if (!descriptor.session) { + const methodStillRunning = !this.commands?.isDisposed(); + if (methodStillRunning) { + this.releaseTransportSession(); + } + } + + this.session = descriptor.session; + this.emitLifecycle(DEVICE.CHANGED); + } + + // TODO empty fn variant can be split/removed run(fn?: () => Promise, options?: RunOptions) { if (this.runPromise) { _log.warn('Previous call is still running'); @@ -275,12 +424,19 @@ export class Device extends TypedEmitter { options = parseRunOptions(options); + const wasUnacquired = this.isUnacquired(); const runPromise = createDeferred(); this.runPromise = runPromise; - this._runInner(fn, options).catch(err => { - runPromise.reject(err); - }); + this._runInner(fn, options) + .then(() => { + if (wasUnacquired && !this.isUnacquired()) { + this.emitLifecycle(DEVICE.CONNECT); + } + }) + .catch(err => { + runPromise.reject(err); + }); return runPromise.promise; } @@ -301,8 +457,8 @@ export class Device extends TypedEmitter { setCancelableAction(callback: NonNullable) { this.cancelableAction = (e?: Error) => callback(e) - .catch(e => { - _log.debug('cancelableAction error', e); + .catch(e2 => { + _log.debug('cancelableAction error', e2); }) .finally(() => { this.clearCancelableAction(); @@ -326,14 +482,14 @@ export class Device extends TypedEmitter { } } - /** - * TODO: this does not work properly (even before transport-refactor) - * one of the problem here is, that this.runPromise.reject is caught in src/core finally block that triggers - * device release. This is not right because we know that somebody else has already taken control of device - * which means that session management does not make sense anymore. releasing device, on the other hand - * makes sense, because this instance of connect might be the only one who has the right to do it. - */ - interruptionFromOutside() { + public usedElsewhere() { + // only makes sense to continue when device held by this instance + if (!this.lastAcquiredHere) { + return; + } + this.lastAcquiredHere = false; + this._featuresNeedsReload = true; + _log.debug('interruptionFromOutside'); if (this.commands) { @@ -347,8 +503,8 @@ export class Device extends TypedEmitter { // session was acquired by another instance. but another might not have power to release interface // so it only notified about its session acquiral and the interrupted instance should cooperate // and release device too. - if (this.originalDescriptor.session) { - this.transport.releaseDevice(this.originalDescriptor.session); + if (this.session) { + this.transport.releaseDevice(this.session); } } @@ -360,15 +516,14 @@ export class Device extends TypedEmitter { await this.releasePromise; } - if ( - !this.isUsedHere() || - this.commands?.disposed || - !this.getState()?.staticSessionId || - this.useCardanoDerivation != !!options.useCardanoDerivation - ) { + const acquireNeeded = !this.isUsedHere() || this.commands?.disposed; + if (acquireNeeded) { // acquire session await this.acquire(); + } + const { staticSessionId, deriveCardano } = this.getState() || {}; + if (acquireNeeded || !staticSessionId || (!deriveCardano && options.useCardanoDerivation)) { // update features try { if (fn) { @@ -434,12 +589,6 @@ export class Device extends TypedEmitter { this.keepTransportSession = true; } - // if we were waiting for device to be acquired, it should be guaranteed here that it had already happened - // (features are reloaded too) - if (this.listeners(DEVICE.ACQUIRED).length > 0) { - this.emit(DEVICE.ACQUIRED); - } - // call inner function if (fn) { await fn(); @@ -484,7 +633,7 @@ export class Device extends TypedEmitter { // and device wasn't released in previous call (example: interrupted discovery which set "keepSession" to true but never released) // clear "keepTransportSession" and reset "transportSession" to ensure that "initialize" will be called if (this.keepTransportSession) { - this.transportSession = null; + this.lastAcquiredHere = false; this.keepTransportSession = false; } } @@ -543,12 +692,12 @@ export class Device extends TypedEmitter { async initialize(useCardanoDerivation: boolean) { let payload: PROTO.Initialize | undefined; if (this.features) { - const sessionId = this.getState()?.sessionId; - payload = {}; + const { sessionId, deriveCardano } = this.getState() || {}; // If the user has BIP-39 seed, and Initialize(derive_cardano=True) is not sent, // all Cardano calls will fail because the root secret will not be available. - payload.derive_cardano = useCardanoDerivation; - this.useCardanoDerivation = useCardanoDerivation; + payload = { + derive_cardano: deriveCardano || useCardanoDerivation, + }; if (sessionId) { payload.session_id = sessionId; } @@ -556,6 +705,7 @@ export class Device extends TypedEmitter { const { message } = await this.getCommands().typedCall('Initialize', 'Features', payload); this._updateFeatures(message); + this.setState({ deriveCardano: payload?.derive_cardano }); } initStorage(storage: IStateStorage) { @@ -567,6 +717,10 @@ export class Device extends TypedEmitter { const { message } = await this.getCommands().typedCall('GetFeatures', 'Features', {}); this._updateFeatures(message); + if (this.authenticityChecks.firmwareHash === null) { + this.authenticityChecks.firmwareHash = await this.checkFirmwareHash(); + } + if ( // The check was not yet performed this.authenticityChecks.firmwareRevision === null || @@ -595,6 +749,68 @@ export class Device extends TypedEmitter { } } + async checkFirmwareHash(): Promise { + const createFailResult = (error: FirmwareHashCheckError) => ({ success: false, error }); + + const baseUrl = DataManager.getSettings('binFilesBaseUrl'); + const enabled = DataManager.getSettings('enableFirmwareHashCheck'); + if (!enabled || baseUrl === undefined) return createFailResult('check-skipped'); + + const firmwareVersion = this.getVersion(); + // device has no features (not yet connected) or no firmware + if (firmwareVersion === undefined || !this.features || this.features.bootloader_mode) { + return null; + } + + const checkSupported = this.atLeast(FIRMWARE.FW_HASH_SUPPORTED_VERSIONS); + if (!checkSupported) return createFailResult('check-unsupported'); + + const release = getReleases(this.features.internal_model).find(r => + versionUtils.isEqual(r.version, firmwareVersion), + ); + // if version is expected to support hash check, but the release is unknown, then firmware is considered unofficial + if (release === undefined) return createFailResult('unknown-release'); + + const btcOnly = this.firmwareType === FirmwareType.BitcoinOnly; + const binary = await getBinaryOptional({ baseUrl, btcOnly, release }); + // release was found, but not its binary - happens on desktop, where only local files are searched + if (binary === null) { + return createFailResult('check-unsupported'); + } + // binary was found, but it's likely a git LFS pointer (can happen on dev) - see onCallFirmwareUpdate.ts + if (binary.byteLength < 200) { + _log.warn(`Firmware binary for hash check suspiciously small (< 200 b)`); + + return createFailResult('check-unsupported'); + } + + const strippedBinary = stripFwHeaders(binary); + const { hash: expectedHash, challenge } = calculateFirmwareHash( + this.features.major_version, + strippedBinary, + randomBytes(32), + ); + + // handle rejection of call by a counterfeit device. If unhandled, it crashes device initialization, + // so device can't be used, but it's preferable to display proper message about counterfeit device + const getFirmwareHashOptional = async () => { + try { + return await this.getCommands().typedCall('GetFirmwareHash', 'FirmwareHash', { + challenge, + }); + } catch { + return null; + } + }; + const deviceResponse = await getFirmwareHashOptional(); + + if (!deviceResponse?.message?.hash) return createFailResult('other-error'); + + if (deviceResponse.message.hash !== expectedHash) return createFailResult('hash-mismatch'); + + return { success: true }; + } + async checkFirmwareRevision() { const firmwareVersion = this.getVersion(); @@ -724,24 +940,24 @@ export class Device extends TypedEmitter { // check if FW version or capabilities did change if (!version || !versionUtils.isEqual(version, newVersion)) { - this.unavailableCapabilities = getUnavailableCapabilities(feat, getAllNetworks()); - this.firmwareStatus = getFirmwareStatus(feat); - this.firmwareRelease = getRelease(feat); + this._unavailableCapabilities = getUnavailableCapabilities(feat, getAllNetworks()); + this._firmwareStatus = getFirmwareStatus(feat); + this._firmwareRelease = getRelease(feat); this.availableTranslations = this.firmwareRelease?.translations ?? []; } - this.features = feat; - this.featuresNeedsReload = false; + this._features = feat; + this._featuresNeedsReload = false; // Vendor headers have been changed in 2.6.3. if (feat.fw_vendor === 'Trezor Bitcoin-only') { - this.firmwareType = FirmwareType.BitcoinOnly; + this._firmwareType = FirmwareType.BitcoinOnly; } else if (feat.fw_vendor === 'Trezor') { - this.firmwareType = FirmwareType.Regular; + this._firmwareType = FirmwareType.Regular; } else if (this.getMode() !== 'bootloader') { // Relevant for T1B1, T2T1 and custom firmware with a different vendor header. Capabilities do not work in bootloader mode. - this.firmwareType = + this._firmwareType = feat.capabilities && feat.capabilities.length > 0 && !feat.capabilities.includes('Capability_Bitcoin_like') @@ -770,11 +986,19 @@ export class Device extends TypedEmitter { return this.features === undefined; } + isUnreadable() { + return !!this.unreadableError; + } + disconnect() { // TODO: cleanup everything _log.debug('Disconnect cleanup'); - this.transportSession = null; // set to null to prevent transport.release and cancelableAction + this.sessionDfd?.reject(new Error()); + + this.lastAcquiredHere = false; // set to null to prevent transport.release and cancelableAction + + this.emitLifecycle(DEVICE.DISCONNECT); return this.interruptionFromUser(ERRORS.TypedError('Device_Disconnected')); } @@ -815,15 +1039,15 @@ export class Device extends TypedEmitter { } isUsed() { - return typeof this.originalDescriptor.session === 'string'; + return typeof this.session === 'string'; } isUsedHere() { - return this.isUsed() && this.originalDescriptor.session === this.transportSession; + return this.isUsed() && this.lastAcquiredHere; } isUsedElsewhere() { - return this.isUsed() && !this.isUsedHere(); + return this.isUsed() && !this.lastAcquiredHere; } isRunning() { @@ -838,8 +1062,12 @@ export class Device extends TypedEmitter { return this.firstRunPromise.promise; } - getDevicePath() { - return this.originalDescriptor.path; + getLocalSession() { + return this.lastAcquiredHere ? this.session : null; + } + + getUniquePath() { + return this.uniquePath; } isT1() { @@ -869,28 +1097,19 @@ export class Device extends TypedEmitter { return null; } - updateDescriptor(descriptor: Descriptor) { - this.originalDescriptor = { - session: descriptor.session, - path: descriptor.path, - product: descriptor.product, - type: descriptor.type, - }; - } - async dispose() { this.removeAllListeners(); - if (this.isUsedHere() && this.transportSession) { + if (this.session && this.lastAcquiredHere) { try { await this.cancelableAction?.(); await this.commands?.cancel(); return this.transport.release({ - session: this.transportSession, - path: this.originalDescriptor.path, + session: this.session, + path: this.transportPath, onClose: true, }); - } catch (err) { + } catch { // empty } } @@ -906,39 +1125,42 @@ export class Device extends TypedEmitter { // simplified object to pass via postMessage toMessageObject(): DeviceTyped { + const { name, uniquePath: path } = this; + const base = { path, name }; + if (this.unreadableError) { return { + ...base, type: 'unreadable', - path: this.originalDescriptor.path, error: this.unreadableError, // provide error details label: 'Unreadable device', - name: this.name, + transportDescriptorType: this.transportDescriptorType, }; } if (this.isUnacquired()) { return { + ...base, type: 'unacquired', - path: this.originalDescriptor.path, label: 'Unacquired device', name: this.name, + transportSessionOwner: this.transportSessionOwner, }; } const defaultLabel = 'My Trezor'; const label = this.features.label === '' || !this.features.label ? defaultLabel : this.features.label; let status: DeviceStatus = this.isUsedElsewhere() ? 'occupied' : 'available'; - if (this.featuresNeedsReload) status = 'used'; + if (this._featuresNeedsReload) status = 'used'; return { + ...base, type: 'acquired', id: this.features.device_id, - path: this.originalDescriptor.path, label, _state: this.getState(), state: this.getState()?.staticSessionId, status, mode: this.getMode(), - name: this.name, color: this.color, firmware: this.firmwareStatus, firmwareRelease: this.firmwareRelease, diff --git a/packages/connect/src/device/DeviceCommands.ts b/packages/connect/src/device/DeviceCommands.ts index 64a125c1b5f..ae99de4c7e3 100644 --- a/packages/connect/src/device/DeviceCommands.ts +++ b/packages/connect/src/device/DeviceCommands.ts @@ -1,9 +1,12 @@ // original file https://github.com/trezor/connect/blob/develop/src/js/device/DeviceCommands.js import { randomBytes } from 'crypto'; + import { Transport, Session } from '@trezor/transport'; import { MessagesSchema as Messages } from '@trezor/protobuf'; import { createTimeoutPromise, versionUtils } from '@trezor/utils'; +import { Assert } from '@trezor/schema-utils'; + import { ERRORS } from '../constants'; import { DEVICE } from '../events'; import * as hdnodeUtils from '../utils/hdnodeUtils'; @@ -11,11 +14,9 @@ import { isTaprootPath, getSerializedPath, getScriptType, toHardened } from '../ import { getAccountAddressN } from '../utils/accountUtils'; import { getSegwitNetwork, getBech32Network } from '../data/coinInfo'; import { initLog } from '../utils/debug'; - import { Device } from './Device'; import type { CoinInfo, BitcoinNetworkInfo, Network } from '../types'; import type { HDNodeResponse } from '../types/api/getPublicKey'; -import { Assert } from '@trezor/schema-utils'; import { resolveDescriptorForTaproot } from './resolveDescriptorForTaproot'; import { promptPin, promptPassphrase, promptWord, cancelPrompt } from './prompts'; @@ -45,7 +46,7 @@ const assertType = (res: DefaultPayloadMessage, resType: MessageKey | MessageKey const generateEntropy = (len: number) => { try { return randomBytes(len); - } catch (err) { + } catch { throw ERRORS.TypedError( 'Runtime', 'generateEntropy: Environment does not support crypto random', @@ -373,10 +374,10 @@ export class DeviceCommands { } async _commonCall(type: MessageKey, msg?: DefaultPayloadMessage['message']) { - const resp = await this.call(type, msg); if (this.disposed) { throw ERRORS.TypedError('Runtime', 'typedCall: DeviceCommands already disposed'); } + const resp = await this.call(type, msg); return this._filterCommonTypes(resp); } @@ -563,7 +564,7 @@ export class DeviceCommands { await createTimeoutPromise(1); await this.device.acquire(); await cancelPrompt(this.device, false); - } catch (err) { + } catch { // ignore whatever happens } } else { diff --git a/packages/connect/src/device/DeviceList.ts b/packages/connect/src/device/DeviceList.ts index 86277a773dc..5160eece0ab 100644 --- a/packages/connect/src/device/DeviceList.ts +++ b/packages/connect/src/device/DeviceList.ts @@ -1,6 +1,6 @@ // original file https://github.com/trezor/connect/blob/develop/src/js/device/DeviceList.js -import { TypedEmitter, Deferred, createDeferred, promiseAllSequence } from '@trezor/utils'; +import { TypedEmitter, createDeferred, getSynchronize } from '@trezor/utils'; import { BridgeTransport, WebUsbTransport, @@ -8,19 +8,18 @@ import { UdpTransport, Transport, TRANSPORT, - Descriptor, - TRANSPORT_ERROR, isTransportInstance, - DeviceDescriptorDiff, } from '@trezor/transport'; +import { Descriptor, PathPublic } from '@trezor/transport/src/types'; + import { ERRORS } from '../constants'; import { DEVICE, TransportInfo } from '../events'; import { Device } from './Device'; -import type { ConnectSettings, Device as DeviceTyped } from '../types'; - +import { ConnectSettings, DeviceUniquePath, Device as DeviceTyped } from '../types'; import { getBridgeInfo } from '../data/transportInfo'; import { initLog } from '../utils/debug'; import { resolveAfter } from '../utils/promiseUtils'; +import { typedObjectKeys } from '../types/utils'; // custom log const _log = initLog('DeviceList'); @@ -60,8 +59,6 @@ interface DeviceListEvents { [DEVICE.CONNECT_UNACQUIRED]: DeviceTyped; [DEVICE.DISCONNECT]: DeviceTyped; [DEVICE.CHANGED]: DeviceTyped; - [DEVICE.RELEASED]: DeviceTyped; - [DEVICE.ACQUIRED]: DeviceTyped; } export interface IDeviceList { @@ -82,7 +79,10 @@ export const assertDeviceListConnected: ( if (!deviceList.isConnected()) throw ERRORS.TypedError('Transport_Missing'); }; -type ConstructorParams = Pick & { +type ConstructorParams = Pick< + ConnectSettings, + 'priority' | 'debug' | '_sessionsBackgroundUrl' | 'manifest' +> & { messages: Record; }; type InitParams = Pick; @@ -94,11 +94,10 @@ export class DeviceList extends TypedEmitter implements IDevic // array of transport that might be used in this environment private transports: Transport[]; - private devices: { [path: string]: Device } = {}; - - private creatingDevicesDescriptors: { [k: string]: Descriptor } = {}; - private createDevicesQueue: Deferred[] = []; + private devices: Record = {}; + private deviceCounter = Date.now(); + private readonly handshakeLock; private readonly authPenaltyManager; private initPromise?: Promise; @@ -115,16 +114,24 @@ export class DeviceList extends TypedEmitter implements IDevic return this.initPromise; } - constructor({ messages, priority, debug, _sessionsBackgroundUrl }: ConstructorParams) { + constructor({ + messages, + priority, + debug, + _sessionsBackgroundUrl, + manifest, + }: ConstructorParams) { super(); const transportLogger = initLog('@trezor/transport', debug); + this.handshakeLock = getSynchronize(); this.authPenaltyManager = createAuthPenaltyManager(priority); this.transportCommonArgs = { messages, logger: transportLogger, sessionsBackgroundUrl: _sessionsBackgroundUrl, + id: manifest?.appUrl || 'unknown app', }; this.transports = [ @@ -182,120 +189,46 @@ export class DeviceList extends TypedEmitter implements IDevic this.transports = transportTypes.map(this.createTransport.bind(this)); } - private onTransportUpdate(diff: DeviceDescriptorDiff, transport: Transport) { - diff.forEach(async ({ descriptor, ...category }) => { - // whenever descriptors change we need to update them so that we can use them - // in subsequent transport.acquire calls - const path = descriptor.path.toString(); - const device = this.devices[path] as Device | undefined; - - // creatingDevicesDescriptors is needed, so that if *during* creating of Device, - // other application acquires the device and changes the descriptor, - // the new unacquired device has correct descriptor - this.creatingDevicesDescriptors[path] = descriptor; - - switch (category.type) { - case 'disconnected': - this.removeFromCreateDevicesQueue(path); - - if (!device) break; - - device.disconnect(); - delete this.devices[path]; - this.emit(DEVICE.DISCONNECT, device.toMessageObject()); - break; - - case 'connected': - if (!(await this.waitForCreateDevicesQueue(path))) { - break; - } - - const penalty = this.authPenaltyManager.get(); - - if (penalty) { - await resolveAfter(501 + penalty, null).promise; - } - if (this.creatingDevicesDescriptors[path].session == null) { - await this._createAndSaveDevice(descriptor, transport); - } else { - const device = this._createUnacquiredDevice(descriptor, transport); - this.devices[path] = device; - this.emit(DEVICE.CONNECT_UNACQUIRED, device.toMessageObject()); - } - - this.removeFromCreateDevicesQueue(path); - break; - - case 'acquired': - if (category.subtype === 'elsewhere') { - this.removeFromCreateDevicesQueue(path); - if (device) { - device.featuresNeedsReload = true; - device.interruptionFromOutside(); - } - } - - if (!device) break; - - _log.debug('Event', DEVICE.CHANGED, device.toMessageObject()); - this.emit(DEVICE.CHANGED, device.toMessageObject()); - - _log.debug('Event', DEVICE.ACQUIRED, device.toMessageObject()); - this.emit(DEVICE.ACQUIRED, device.toMessageObject()); - - break; - - case 'released': - if (!device) break; - - const methodStillRunning = !device.commands?.isDisposed(); - if (methodStillRunning) { - device.keepTransportSession = false; - } - - _log.debug('Event', DEVICE.CHANGED, device.toMessageObject()); - this.emit(DEVICE.CHANGED, device.toMessageObject()); - - _log.debug('Event', DEVICE.RELEASED, device.toMessageObject()); - this.emit(DEVICE.RELEASED, device.toMessageObject()); - - if (category.subtype === 'elsewhere') { - await resolveAfter(1000, null).promise; - // after device was released in another window wait for a while (the other window might - // have the intention of acquiring it again) - // and if the device is still released and has never been acquired before, acquire it here. - if (!device.isUsed() && device.isUnacquired() && !device.isInconsistent()) { - _log.debug('Create device from unacquired', device.toMessageObject()); - await this._createAndSaveDevice(descriptor, transport); - } - } - break; - } + private onDeviceConnected(descriptor: Descriptor, transport: Transport) { + const { path } = descriptor; + const id = (this.deviceCounter++).toString(16).slice(-8); + const device = new Device({ + id: DeviceUniquePath(id), + transport, + descriptor, + listener: lifecycle => this.emit(lifecycle, device.toMessageObject()), + }); + this.devices[path] = device; - device?.updateDescriptor(descriptor); + const penalty = this.authPenaltyManager.get(); + this.handshakeLock(async () => { + if (this.devices[path]) { + // device wasn't removed while waiting for lock + await device.handshake(penalty); + } }); } - private async waitForCreateDevicesQueue(path: string) { - const dfd = createDeferred(path); - const prevQueue = this.createDevicesQueue.slice(); - this.createDevicesQueue.push(dfd); - - await promiseAllSequence(prevQueue.map(pr => () => pr.promise)); - - // Return whether current pending action still in queue or it was - // removed by disconnected/acquiredElsewhere events or dispose - return this.createDevicesQueue.includes(dfd); + private onDeviceDisconnected(descriptor: Descriptor) { + const { path } = descriptor; + const device = this.devices[path]; + if (device) { + device.disconnect(); + delete this.devices[path]; + } } - private removeFromCreateDevicesQueue(path: string) { - const index = this.createDevicesQueue.findIndex(dfd => dfd.id === path); - if (index >= 0) { - const [dfd] = this.createDevicesQueue.splice(index, 1); - dfd.resolve(); + private onDeviceSessionChanged(descriptor: Descriptor) { + const device = this.devices[descriptor.path]; + if (device) { + device.updateDescriptor(descriptor); } } + private onDeviceRequestRelease(descriptor: Descriptor) { + this.devices[descriptor.path]?.usedElsewhere(); + } + /** * Init @trezor/transport and do something with its results */ @@ -330,7 +263,9 @@ export class DeviceList extends TypedEmitter implements IDevic const { promise, reject } = resolveAfter(1000, initParams); this.rejectPending = reject; - return promise.then(this.createInitPromise.bind(this)); + return promise.then(this.createInitPromise.bind(this)).finally(() => { + this.rejectPending = undefined; + }); } private async selectTransport([transport, ...rest]: Transport[]): Promise { @@ -349,7 +284,10 @@ export class DeviceList extends TypedEmitter implements IDevic * releasing/acquiring device by this application is not solved here but directly * where transport.acquire, transport.release is called */ - transport.on(TRANSPORT.UPDATE, diff => this.onTransportUpdate(diff, transport)); + transport.on(TRANSPORT.DEVICE_CONNECTED, d => this.onDeviceConnected(d, transport)); + transport.on(TRANSPORT.DEVICE_DISCONNECTED, this.onDeviceDisconnected.bind(this)); + transport.on(TRANSPORT.DEVICE_SESSION_CHANGED, this.onDeviceSessionChanged.bind(this)); + transport.on(TRANSPORT.DEVICE_REQUEST_RELEASE, this.onDeviceRequestRelease.bind(this)); // just like transport emits updates, it may also start producing errors, for example bridge process crashes. transport.on(TRANSPORT.ERROR, error => { @@ -375,7 +313,6 @@ export class DeviceList extends TypedEmitter implements IDevic ? this.waitForDevices(descriptors.length, 10000) : Promise.resolve(); - // TODO handleDescriptorChange can emit TRANSPORT.UPDATE before TRANSPORT.START is emitted, check whether acceptable transport.handleDescriptorsChange(descriptors); transport.listen(); @@ -417,50 +354,20 @@ export class DeviceList extends TypedEmitter implements IDevic }); } - private async _createAndSaveDevice(descriptor: Descriptor, transport: Transport) { - _log.debug('Creating Device', descriptor); - await this.handle(descriptor, transport); - } - - private _createUnacquiredDevice(descriptor: Descriptor, transport: Transport) { - _log.debug('Creating Unacquired Device', descriptor); - const device = Device.createUnacquired(transport, descriptor); - device.once(DEVICE.ACQUIRED, () => { - // emit connect event once device becomes acquired - this.emit(DEVICE.CONNECT, device.toMessageObject()); - }); - - return device; - } - - private _createUnreadableDevice( - descriptor: Descriptor, - transport: Transport, - unreadableError: string, - ) { - _log.debug('Creating Unreadable Device', descriptor, unreadableError); - - return Device.createUnacquired(transport, descriptor, unreadableError); - } - - getDevice(path: string) { - return this.devices[path]; + getDeviceCount() { + return Object.keys(this.devices).length; } - getFirstDevicePath() { - return this.asArray()[0].path; + getAllDevices(): Device[] { + return typedObjectKeys(this.devices).map(key => this.devices[key]); } - asArray(): DeviceTyped[] { - return this.allDevices().map(device => device.toMessageObject()); + getOnlyDevice(): Device | undefined { + return this.getDeviceCount() === 1 ? Object.values(this.devices)[0] : undefined; } - allDevices(): Device[] { - return Object.keys(this.devices).map(key => this.devices[key]); - } - - length() { - return this.asArray().length; + getDeviceByPath(path: DeviceUniquePath): Device | undefined { + return this.getAllDevices().find(d => d.getUniquePath() === path); } transportType() { @@ -483,12 +390,12 @@ export class DeviceList extends TypedEmitter implements IDevic async cleanup() { const { transport } = this; - const devices = this.allDevices(); + const devices = this.getAllDevices(); // @ts-expect-error will be fixed later this.transport = undefined; this.authPenaltyManager.clear(); - Object.keys(this.devices).forEach(key => delete this.devices[key]); + typedObjectKeys(this.devices).forEach(key => delete this.devices[key]); this.rejectPending?.(new Error('Disposed')); @@ -498,9 +405,6 @@ export class DeviceList extends TypedEmitter implements IDevic this.emit(DEVICE.DISCONNECT, device.toMessageObject()); }); - this.createDevicesQueue.forEach(dfd => dfd.resolve()); - this.createDevicesQueue = []; - // release all devices await Promise.all(devices.map(device => device.dispose())); @@ -518,11 +422,7 @@ export class DeviceList extends TypedEmitter implements IDevic } res.payload.forEach(d => { - if (this.devices[d.path]) { - this.devices[d.path].updateDescriptor(d); - // TODO: is this ok? transportSession should be set only as result of acquire/release - // this.devices[d.path].transportSession = d.session; - } + this.devices[d.path]?.updateDescriptor(d); }); } @@ -533,80 +433,4 @@ export class DeviceList extends TypedEmitter implements IDevic removeAuthPenalty(device: Device) { return this.authPenaltyManager.remove(device); } - - // main logic - private async handle(descriptor: Descriptor, transport: Transport) { - const path = descriptor.path.toString(); - try { - // "regular" device creation - await this._takeAndCreateDevice(descriptor, transport); - } catch (error) { - _log.debug('Cannot create device', error); - if ( - error.code === 'Device_NotFound' || - error.message === TRANSPORT_ERROR.DEVICE_NOT_FOUND || - error.message === TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION || - error.message === TRANSPORT_ERROR.UNEXPECTED_ERROR || - error.message === TRANSPORT_ERROR.DESCRIPTOR_NOT_FOUND || - // bridge died during device initialization - error.message === TRANSPORT_ERROR.HTTP_ERROR - ) { - // do nothing - // For example: - // 1. connect device - // 2. _createAndSaveDevice => handle => _takeAndCreateDevice => device.run() - // 3. disconnect device - // 4. some of the above mentioned errors is returned. - delete this.devices[path]; - } else if (error.message === TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS) { - this.enumerate(transport); - this._handleUsedElsewhere(descriptor, transport); - } else if ( - // device was claimed by another application on transport api layer (claimInterface in usb nomenclature) but never released (releaseInterface in usb nomenclature) - // the only remedy for this is to reconnect device manually - // or possibly there are 2 applications without common sessions background - error.message === TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE || - // catch one of trezord LIBUSB_ERRORs - error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0 || - // we tried to initialize device (either automatically after enumeration or after user click) - // but it did not work out. this device is effectively unreadable and user should do something about it - error.code === 'Device_InitializeFailed' - ) { - const device = this._createUnreadableDevice( - this.creatingDevicesDescriptors[path], - transport, - error.message, - ); - this.devices[path] = device; - this.emit(DEVICE.CONNECT_UNACQUIRED, device.toMessageObject()); - } else if (error.code === 'Device_UsedElsewhere') { - // most common error - someone else took the device at the same time - this._handleUsedElsewhere(descriptor, transport); - } else { - await resolveAfter(501, null).promise; - await this.handle(descriptor, transport); - } - } - } - - private async _takeAndCreateDevice(descriptor: Descriptor, transport: Transport) { - const device = Device.fromDescriptor(transport, descriptor); - const path = descriptor.path.toString(); - this.devices[path] = device; - const promise = device.run(); - await promise; - - this.emit(DEVICE.CONNECT, device.toMessageObject()); - } - - private _handleUsedElsewhere(descriptor: Descriptor, transport: Transport) { - const path = descriptor.path.toString(); - - const device = this._createUnacquiredDevice( - this.creatingDevicesDescriptors[path], - transport, - ); - this.devices[path] = device; - this.emit(DEVICE.CONNECT_UNACQUIRED, device.toMessageObject()); - } } diff --git a/packages/connect/src/device/StateStorage.ts b/packages/connect/src/device/StateStorage.ts index f030c0c0007..7b13cd5a794 100644 --- a/packages/connect/src/device/StateStorage.ts +++ b/packages/connect/src/device/StateStorage.ts @@ -1,6 +1,7 @@ +import { storage } from '@trezor/connect-common'; + import { DeviceState } from '../types'; import { Device } from './Device'; -import { storage } from '@trezor/connect-common'; export interface IStateStorage { saveState(device: Device, state: DeviceState): void; diff --git a/packages/connect/src/device/__tests__/DeviceList.test.ts b/packages/connect/src/device/__tests__/DeviceList.test.ts index 696367e7f47..9c90023a4d0 100644 --- a/packages/connect/src/device/__tests__/DeviceList.test.ts +++ b/packages/connect/src/device/__tests__/DeviceList.test.ts @@ -21,13 +21,7 @@ const waitForNthEventOfType = ( }); }; -const DEVICE_CONNECTION_SEQUENCE = [ - 'device-changed', - 'device-acquired', - 'device-changed', - 'device-released', - 'device-connect', -]; +const DEVICE_CONNECTION_SEQUENCE = ['device-changed', 'device-changed', 'device-connect']; describe('DeviceList', () => { beforeAll(async () => { @@ -56,11 +50,9 @@ describe('DeviceList', () => { [ 'transport-start', 'transport-error', - 'device-changed', 'device-connect', 'device-connect_unacquired', - 'device-acquired', - 'device-released', + 'device-changed', 'device-disconnect', ] as const ).forEach(event => { @@ -186,12 +178,7 @@ describe('DeviceList', () => { await list.pendingConnection(); const events = eventsSpy.mock.calls.map(call => call[0]); - expect(events).toEqual([ - 'device-changed', - 'device-acquired', - 'device-connect_unacquired', - 'transport-start', - ]); + expect(events).toEqual(['device-changed', 'device-connect_unacquired', 'transport-start']); }); it('.init() with pendingTransportEvent (multiple acquired devices)', async () => { @@ -210,9 +197,9 @@ describe('DeviceList', () => { // note: acquire - release - connect should be ok. // acquire - deviceList._takeAndCreateDevice start (run -> rurInner -> getFeatures -> release) -> deviceList._takeAndCreateDevice end => emit DEVICE.CONNECT expect(events).toEqual([ - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '1']), // path 1 - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '2']), // path 2 - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '3']), // path 3 + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[0][1]]), // path 1 + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[3][1]]), // path 2 + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[6][1]]), // path 3 ['transport-start', undefined], ]); }); @@ -240,15 +227,11 @@ describe('DeviceList', () => { await transportFirstEvent; jest.useRealTimers(); - expect(eventsSpy).toHaveBeenCalledTimes(8); - const events = eventsSpy.mock.calls.map(call => call[0]); + // expect(eventsSpy).toHaveBeenCalledTimes(5); + const events = eventsSpy.mock.calls.map(([event]) => event); expect(events).toEqual([ 'device-changed', - 'device-acquired', - 'device-changed', - 'device-acquired', 'device-changed', - 'device-released', 'device-connect', 'transport-start', ]); @@ -299,9 +282,10 @@ describe('DeviceList', () => { expect(events).toEqual([ ['transport-start', undefined], - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '1']), // path 1 - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '3']), // path 3 - ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, '4']), // path 4 + ['device-disconnect', events[1][1]], + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[2][1]]), // path 1 + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[5][1]]), // path 3 + ...DEVICE_CONNECTION_SEQUENCE.map(e => [e, events[8][1]]), // path 4 ]); }); }); diff --git a/packages/connect/src/device/__tests__/checkFirmwareRevision.test.ts b/packages/connect/src/device/__tests__/checkFirmwareRevision.test.ts index d5ad53d5c91..e54f5350669 100644 --- a/packages/connect/src/device/__tests__/checkFirmwareRevision.test.ts +++ b/packages/connect/src/device/__tests__/checkFirmwareRevision.test.ts @@ -1,6 +1,7 @@ import { FetchError } from 'node-fetch'; import { DeviceModelInternal } from '@trezor/protobuf'; + import { checkFirmwareRevision, CheckFirmwareRevisionParams } from '../checkFirmwareRevision'; import { FirmwareRelease, FirmwareRevisionCheckResult } from '../../exports'; import * as utilsAssets from '../../utils/assets'; diff --git a/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts b/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts index 4d2fe5ccb67..b7e2a936a0a 100644 --- a/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts +++ b/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts @@ -1,6 +1,7 @@ +import { MessagesSchema as Messages } from '@trezor/protobuf'; + import { resolveDescriptorForTaproot } from '../resolveDescriptorForTaproot'; import { HDNodeResponse } from '../../types/api/getPublicKey'; -import { MessagesSchema as Messages } from '@trezor/protobuf'; const originalResponse: HDNodeResponse = { path: [2147483734, 2147483648, 2147483648], diff --git a/packages/connect/src/device/calculateRevisionForDevice.ts b/packages/connect/src/device/calculateRevisionForDevice.ts index a8d46948dae..93cfd5283b0 100644 --- a/packages/connect/src/device/calculateRevisionForDevice.ts +++ b/packages/connect/src/device/calculateRevisionForDevice.ts @@ -1,4 +1,5 @@ import { isNewer } from '@trezor/utils/src/versionUtils'; + import { VersionArray } from '../exports'; type calculateRevisionForDeviceParams = { diff --git a/packages/connect/src/device/checkFirmwareRevision.ts b/packages/connect/src/device/checkFirmwareRevision.ts index ade88aa48f1..e7da0dcdfc4 100644 --- a/packages/connect/src/device/checkFirmwareRevision.ts +++ b/packages/connect/src/device/checkFirmwareRevision.ts @@ -1,4 +1,5 @@ import { isEqual } from '@trezor/utils/src/versionUtils'; + import { PROTO } from '../constants'; import { downloadReleasesMetadata } from '../data/downloadReleasesMetadata'; import { FirmwareRelease, VersionArray } from '../types'; diff --git a/packages/connect/src/device/prompts.ts b/packages/connect/src/device/prompts.ts index cd163d78d86..1205969c223 100644 --- a/packages/connect/src/device/prompts.ts +++ b/packages/connect/src/device/prompts.ts @@ -20,7 +20,9 @@ type DeviceEventCallback = DeviceEvents[K] extends : never; export const cancelPrompt = (device: Device, expectResponse = true) => { - if (!device.transportSession) { + const session = device.getLocalSession(); + + if (!session) { // device disconnected or acquired by someone else return Promise.resolve({ success: false, @@ -29,7 +31,7 @@ export const cancelPrompt = (device: Device, expectResponse = true) => { } const cancelArgs = { - session: device.transportSession, + session, name: 'Cancel', data: {}, protocol: device.protocol, diff --git a/packages/connect/src/device/resolveDescriptorForTaproot.ts b/packages/connect/src/device/resolveDescriptorForTaproot.ts index 275212c437d..28bb8ec2e74 100644 --- a/packages/connect/src/device/resolveDescriptorForTaproot.ts +++ b/packages/connect/src/device/resolveDescriptorForTaproot.ts @@ -1,6 +1,7 @@ -import { HDNodeResponse } from '../types/api/getPublicKey'; import { MessagesSchema as Messages } from '@trezor/protobuf'; +import { HDNodeResponse } from '../types/api/getPublicKey'; + interface Params { response: HDNodeResponse; publicKey: Messages.PublicKey; diff --git a/packages/connect/src/events/blockchain.ts b/packages/connect/src/events/blockchain.ts index a7a3cecb20b..624c7a06aed 100644 --- a/packages/connect/src/events/blockchain.ts +++ b/packages/connect/src/events/blockchain.ts @@ -4,6 +4,7 @@ import type { FiatRatesBySymbol, NotificationEvent, } from '@trezor/blockchain-link'; + import type { CoinInfo } from '../types/coinInfo'; import type { MessageFactoryFn } from '../types/utils'; diff --git a/packages/connect/src/events/call.ts b/packages/connect/src/events/call.ts index 70471b8e1cc..36b0072bd06 100644 --- a/packages/connect/src/events/call.ts +++ b/packages/connect/src/events/call.ts @@ -5,24 +5,28 @@ import type { CommonParams, DeviceIdentity } from '../types/params'; import { Device } from '../device/Device'; // conditionally unwrap TrezorConnect api method Success response -type UnwrappedResponse = - T extends Promise ? (R extends { success: true; payload: infer P } ? P : never) : void; +type UnwrappedResponse = + Response extends Promise + ? R extends { success: true; payload: infer P } + ? P + : never + : void; // https://github.com/microsoft/TypeScript/issues/32164 // there is no native way how to get Parameters for overloaded function // current TrezorConnect api methods have exactly 2 overloads (if any) -type OverloadedMethod> = T extends { +type OverloadedMethod> = Method extends { (params: infer P1): infer R1; (params: infer P2): infer R2; } - ? ((params: P1 & E) => R1) | ((params: P2 & E) => R2) // - method IS overloaded, result depends on params (example: getAddress) - : T extends (...args: infer P) => infer R - ? (params: E & P[0]) => R // - method in NOT overloaded, one set of params and one set of result (example: signTransaction) + ? ((params: P1 & Params) => R1) | ((params: P2 & Params) => R2) // - method IS overloaded, result depends on params (example: getAddress) + : Method extends (...args: infer P) => infer R + ? (params: Params & P[0]) => R // - method in NOT overloaded, one set of params and one set of result (example: signTransaction) : never; -type UnwrappedMethod> = T extends () => infer R - ? (params: M & CommonParams) => R // - method doesn't have params (example: dispose, disableWebUSB) - : OverloadedMethod; +type UnwrappedMethod> = Method extends () => infer R + ? (params: Params & CommonParams) => R // - method doesn't have params (example: dispose, disableWebUSB) + : OverloadedMethod; type IsMethodCallable = T extends (...args: any[]) => infer R ? R extends Promise<{ success: boolean }> @@ -76,9 +80,9 @@ export const createResponseMessage = ( payload: success ? payload : serializeError(payload), device: device ? { - path: device?.originalDescriptor.path, + path: device?.getUniquePath(), state: device?.getState(), - instance: device?.instance, + instance: device?.getInstance(), } : undefined, }); diff --git a/packages/connect/src/events/device.ts b/packages/connect/src/events/device.ts index 1cec7002019..aa94479a514 100644 --- a/packages/connect/src/events/device.ts +++ b/packages/connect/src/events/device.ts @@ -9,13 +9,6 @@ export const DEVICE = { CONNECT_UNACQUIRED: 'device-connect_unacquired', DISCONNECT: 'device-disconnect', CHANGED: 'device-changed', - ACQUIRE: 'device-acquire', - RELEASE: 'device-release', - ACQUIRED: 'device-acquired', - RELEASED: 'device-released', - USED_ELSEWHERE: 'device-used_elsewhere', - - LOADING: 'device-loading', // trezor-link events in protobuf format BUTTON: 'button', diff --git a/packages/connect/src/events/transport.ts b/packages/connect/src/events/transport.ts index 38cb887e3be..f0d83f8442f 100644 --- a/packages/connect/src/events/transport.ts +++ b/packages/connect/src/events/transport.ts @@ -1,9 +1,9 @@ -import { serializeError } from '../constants/errors'; -import type { MessageFactoryFn } from '../types/utils'; import type { Transport } from '@trezor/transport'; - import { TRANSPORT } from '@trezor/transport/src/constants'; +import { serializeError } from '../constants/errors'; +import type { MessageFactoryFn } from '../types/utils'; + export { TRANSPORT } from '@trezor/transport/src/constants'; export const TRANSPORT_EVENT = 'TRANSPORT_EVENT'; diff --git a/packages/connect/src/events/ui-promise.ts b/packages/connect/src/events/ui-promise.ts index 6604a6b7e2e..af125d0d9c7 100644 --- a/packages/connect/src/events/ui-promise.ts +++ b/packages/connect/src/events/ui-promise.ts @@ -1,4 +1,5 @@ import type { Deferred } from '@trezor/utils'; + import type { DEVICE } from './device'; import type { Device } from '../device/Device'; import type { UiResponseEvent } from './ui-response'; diff --git a/packages/connect/src/factory.ts b/packages/connect/src/factory.ts index 02c8f550a82..c3b8ed7bd4a 100644 --- a/packages/connect/src/factory.ts +++ b/packages/connect/src/factory.ts @@ -1,274 +1,264 @@ -import { UI } from './events'; import type { EventEmitter } from 'events'; + +import { UI } from './events'; import type { TrezorConnect } from './types'; import type { CallMethod } from './events/call'; +import type { InitType } from './types/api/init'; -export interface ConnectFactoryDependencies { +export interface ConnectFactoryDependencies> { + init: InitType; call: CallMethod; eventEmitter: EventEmitter; manifest: TrezorConnect['manifest']; - init: TrezorConnect['init']; requestLogin: TrezorConnect['requestLogin']; uiResponse: TrezorConnect['uiResponse']; - renderWebUSBButton: TrezorConnect['renderWebUSBButton']; - disableWebUSB: TrezorConnect['disableWebUSB']; - requestWebUSBDevice: TrezorConnect['requestWebUSBDevice']; cancel: TrezorConnect['cancel']; dispose: TrezorConnect['dispose']; } -export const factory = ({ - eventEmitter, - manifest, - init, - call, - requestLogin, - uiResponse, - renderWebUSBButton, - disableWebUSB, - requestWebUSBDevice, - cancel, - dispose, -}: ConnectFactoryDependencies): TrezorConnect => { - const api: TrezorConnect = { +export const factory = < + SettingsType extends Record, + ExtraMethodsType extends Record, +>( + { + eventEmitter, manifest, init, - getSettings: () => call({ method: 'getSettings' }), - - on: any>(type: T, fn: P) => { - eventEmitter.on(type, fn); - }, - - off: (type, fn) => { - eventEmitter.removeListener(type, fn); - }, + call, + requestLogin, + uiResponse, + cancel, + dispose, + }: ConnectFactoryDependencies, + extraMethods: ExtraMethodsType = {} as ExtraMethodsType, +): Omit & { init: InitType } & ExtraMethodsType => ({ + manifest, + init, - removeAllListeners: type => { - if (typeof type === 'string') { - eventEmitter.removeAllListeners(type); - } else { - eventEmitter.removeAllListeners(); - } - }, + on: any>(type: T, fn: P) => { + eventEmitter.on(type, fn); + }, - uiResponse, + off: (type, fn) => { + eventEmitter.removeListener(type, fn); + }, - // methods + removeAllListeners: type => { + if (typeof type === 'string') { + eventEmitter.removeAllListeners(type); + } else { + eventEmitter.removeAllListeners(); + } + }, - blockchainGetAccountBalanceHistory: params => - call({ ...params, method: 'blockchainGetAccountBalanceHistory' }), + uiResponse, - blockchainGetCurrentFiatRates: params => - call({ ...params, method: 'blockchainGetCurrentFiatRates' }), + // methods - blockchainGetFiatRatesForTimestamps: params => - call({ ...params, method: 'blockchainGetFiatRatesForTimestamps' }), + blockchainGetAccountBalanceHistory: params => + call({ ...params, method: 'blockchainGetAccountBalanceHistory' }), - blockchainDisconnect: params => call({ ...params, method: 'blockchainDisconnect' }), + blockchainGetCurrentFiatRates: params => + call({ ...params, method: 'blockchainGetCurrentFiatRates' }), - blockchainEstimateFee: params => call({ ...params, method: 'blockchainEstimateFee' }), + blockchainGetFiatRatesForTimestamps: params => + call({ ...params, method: 'blockchainGetFiatRatesForTimestamps' }), - blockchainGetTransactions: params => - call({ ...params, method: 'blockchainGetTransactions' }), + blockchainEvmRpcCall: params => call({ ...params, method: 'blockchainEvmRpcCall' }), - blockchainSetCustomBackend: params => - call({ ...params, method: 'blockchainSetCustomBackend' }), + blockchainDisconnect: params => call({ ...params, method: 'blockchainDisconnect' }), - blockchainSubscribe: params => call({ ...params, method: 'blockchainSubscribe' }), + blockchainEstimateFee: params => call({ ...params, method: 'blockchainEstimateFee' }), - blockchainSubscribeFiatRates: params => - call({ ...params, method: 'blockchainSubscribeFiatRates' }), + blockchainGetTransactions: params => call({ ...params, method: 'blockchainGetTransactions' }), - blockchainUnsubscribe: params => call({ ...params, method: 'blockchainUnsubscribe' }), + blockchainSetCustomBackend: params => call({ ...params, method: 'blockchainSetCustomBackend' }), - blockchainUnsubscribeFiatRates: params => - call({ ...params, method: 'blockchainUnsubscribeFiatRates' }), + blockchainSubscribe: params => call({ ...params, method: 'blockchainSubscribe' }), - requestLogin: params => requestLogin(params), + blockchainSubscribeFiatRates: params => + call({ ...params, method: 'blockchainSubscribeFiatRates' }), - cardanoGetAddress: params => - call({ - ...params, - method: 'cardanoGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + blockchainUnsubscribe: params => call({ ...params, method: 'blockchainUnsubscribe' }), - cardanoGetNativeScriptHash: params => - call({ ...params, method: 'cardanoGetNativeScriptHash' }), + blockchainUnsubscribeFiatRates: params => + call({ ...params, method: 'blockchainUnsubscribeFiatRates' }), - cardanoGetPublicKey: params => call({ ...params, method: 'cardanoGetPublicKey' }), + requestLogin: params => requestLogin(params), - cardanoSignTransaction: params => call({ ...params, method: 'cardanoSignTransaction' }), + cardanoGetAddress: params => + call({ + ...params, + method: 'cardanoGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - cardanoComposeTransaction: params => - call({ ...params, method: 'cardanoComposeTransaction' }), + cardanoGetNativeScriptHash: params => call({ ...params, method: 'cardanoGetNativeScriptHash' }), - cipherKeyValue: params => call({ ...params, method: 'cipherKeyValue' }), + cardanoGetPublicKey: params => call({ ...params, method: 'cardanoGetPublicKey' }), - composeTransaction: params => call({ ...params, method: 'composeTransaction' }), + cardanoSignTransaction: params => call({ ...params, method: 'cardanoSignTransaction' }), - ethereumGetAddress: params => - call({ - ...params, - method: 'ethereumGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + cardanoComposeTransaction: params => call({ ...params, method: 'cardanoComposeTransaction' }), - ethereumGetPublicKey: params => call({ ...params, method: 'ethereumGetPublicKey' }), + cipherKeyValue: params => call({ ...params, method: 'cipherKeyValue' }), - ethereumSignMessage: params => call({ ...params, method: 'ethereumSignMessage' }), + composeTransaction: params => call({ ...params, method: 'composeTransaction' }), - ethereumSignTransaction: params => call({ ...params, method: 'ethereumSignTransaction' }), + ethereumGetAddress: params => + call({ + ...params, + method: 'ethereumGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - // @ts-expect-error generic param - ethereumSignTypedData: params => call({ ...params, method: 'ethereumSignTypedData' }), + ethereumGetPublicKey: params => call({ ...params, method: 'ethereumGetPublicKey' }), - ethereumVerifyMessage: params => call({ ...params, method: 'ethereumVerifyMessage' }), + ethereumSignMessage: params => call({ ...params, method: 'ethereumSignMessage' }), - getAccountDescriptor: params => call({ ...params, method: 'getAccountDescriptor' }), + ethereumSignTransaction: params => call({ ...params, method: 'ethereumSignTransaction' }), - getAccountInfo: params => call({ ...params, method: 'getAccountInfo' }), + // @ts-expect-error generic param + ethereumSignTypedData: params => call({ ...params, method: 'ethereumSignTypedData' }), - getAddress: params => - call({ - ...params, - method: 'getAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + ethereumVerifyMessage: params => call({ ...params, method: 'ethereumVerifyMessage' }), - getDeviceState: params => call({ ...params, method: 'getDeviceState' }), + getAccountDescriptor: params => call({ ...params, method: 'getAccountDescriptor' }), - getFeatures: params => call({ ...params, method: 'getFeatures' }), + getAccountInfo: params => call({ ...params, method: 'getAccountInfo' }), - getFirmwareHash: params => call({ ...params, method: 'getFirmwareHash' }), + getAddress: params => + call({ + ...params, + method: 'getAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - getOwnershipId: params => call({ ...params, method: 'getOwnershipId' }), + getDeviceState: params => call({ ...params, method: 'getDeviceState' }), - getOwnershipProof: params => call({ ...params, method: 'getOwnershipProof' }), + getFeatures: params => call({ ...params, method: 'getFeatures' }), - getPublicKey: params => call({ ...params, method: 'getPublicKey' }), + getFirmwareHash: params => call({ ...params, method: 'getFirmwareHash' }), - nemGetAddress: params => - call({ - ...params, - method: 'nemGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + getOwnershipId: params => call({ ...params, method: 'getOwnershipId' }), - nemSignTransaction: params => call({ ...params, method: 'nemSignTransaction' }), + getOwnershipProof: params => call({ ...params, method: 'getOwnershipProof' }), - pushTransaction: params => call({ ...params, method: 'pushTransaction' }), + getPublicKey: params => call({ ...params, method: 'getPublicKey' }), - rippleGetAddress: params => - call({ - ...params, - method: 'rippleGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + nemGetAddress: params => + call({ + ...params, + method: 'nemGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - rippleSignTransaction: params => call({ ...params, method: 'rippleSignTransaction' }), + nemSignTransaction: params => call({ ...params, method: 'nemSignTransaction' }), - signMessage: params => call({ ...params, method: 'signMessage' }), + pushTransaction: params => call({ ...params, method: 'pushTransaction' }), - signTransaction: params => call({ ...params, method: 'signTransaction' }), + rippleGetAddress: params => + call({ + ...params, + method: 'rippleGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - solanaGetPublicKey: params => call({ ...params, method: 'solanaGetPublicKey' }), + rippleSignTransaction: params => call({ ...params, method: 'rippleSignTransaction' }), - solanaGetAddress: params => call({ ...params, method: 'solanaGetAddress' }), + signMessage: params => call({ ...params, method: 'signMessage' }), - solanaSignTransaction: params => call({ ...params, method: 'solanaSignTransaction' }), + signTransaction: params => call({ ...params, method: 'signTransaction' }), - stellarGetAddress: params => - call({ - ...params, - method: 'stellarGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + solanaGetPublicKey: params => call({ ...params, method: 'solanaGetPublicKey' }), - stellarSignTransaction: params => call({ ...params, method: 'stellarSignTransaction' }), + solanaGetAddress: params => call({ ...params, method: 'solanaGetAddress' }), - tezosGetAddress: params => - call({ - ...params, - method: 'tezosGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + solanaSignTransaction: params => call({ ...params, method: 'solanaSignTransaction' }), - tezosGetPublicKey: params => call({ ...params, method: 'tezosGetPublicKey' }), + stellarGetAddress: params => + call({ + ...params, + method: 'stellarGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - tezosSignTransaction: params => call({ ...params, method: 'tezosSignTransaction' }), + stellarSignTransaction: params => call({ ...params, method: 'stellarSignTransaction' }), - unlockPath: params => call({ ...params, method: 'unlockPath' }), + tezosGetAddress: params => + call({ + ...params, + method: 'tezosGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - eosGetPublicKey: params => call({ ...params, method: 'eosGetPublicKey' }), + tezosGetPublicKey: params => call({ ...params, method: 'tezosGetPublicKey' }), - eosSignTransaction: params => call({ ...params, method: 'eosSignTransaction' }), + tezosSignTransaction: params => call({ ...params, method: 'tezosSignTransaction' }), - binanceGetAddress: params => - call({ - ...params, - method: 'binanceGetAddress', - useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, - }), + unlockPath: params => call({ ...params, method: 'unlockPath' }), - binanceGetPublicKey: params => call({ ...params, method: 'binanceGetPublicKey' }), + eosGetPublicKey: params => call({ ...params, method: 'eosGetPublicKey' }), - binanceSignTransaction: params => call({ ...params, method: 'binanceSignTransaction' }), + eosSignTransaction: params => call({ ...params, method: 'eosSignTransaction' }), - verifyMessage: params => call({ ...params, method: 'verifyMessage' }), + binanceGetAddress: params => + call({ + ...params, + method: 'binanceGetAddress', + useEventListener: eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0, + }), - resetDevice: params => call({ ...params, method: 'resetDevice' }), + binanceGetPublicKey: params => call({ ...params, method: 'binanceGetPublicKey' }), - wipeDevice: params => call({ ...params, method: 'wipeDevice' }), + binanceSignTransaction: params => call({ ...params, method: 'binanceSignTransaction' }), - checkFirmwareAuthenticity: params => - call({ ...params, method: 'checkFirmwareAuthenticity' }), + verifyMessage: params => call({ ...params, method: 'verifyMessage' }), - applyFlags: params => call({ ...params, method: 'applyFlags' }), + resetDevice: params => call({ ...params, method: 'resetDevice' }), - applySettings: params => call({ ...params, method: 'applySettings' }), + loadDevice: params => call({ ...params, method: 'loadDevice' }), - authenticateDevice: params => call({ ...params, method: 'authenticateDevice' }), + wipeDevice: params => call({ ...params, method: 'wipeDevice' }), - authorizeCoinjoin: params => call({ ...params, method: 'authorizeCoinjoin' }), + applyFlags: params => call({ ...params, method: 'applyFlags' }), - cancelCoinjoinAuthorization: params => - call({ ...params, method: 'cancelCoinjoinAuthorization' }), + applySettings: params => call({ ...params, method: 'applySettings' }), - showDeviceTutorial: params => call({ ...params, method: 'showDeviceTutorial' }), + getSettings: () => call({ method: 'getSettings' }), - backupDevice: params => call({ ...params, method: 'backupDevice' }), + authenticateDevice: params => call({ ...params, method: 'authenticateDevice' }), - changeLanguage: params => call({ ...params, method: 'changeLanguage' }), + authorizeCoinjoin: params => call({ ...params, method: 'authorizeCoinjoin' }), - changePin: params => call({ ...params, method: 'changePin' }), + cancelCoinjoinAuthorization: params => + call({ ...params, method: 'cancelCoinjoinAuthorization' }), - changeWipeCode: params => call({ ...params, method: 'changeWipeCode' }), + showDeviceTutorial: params => call({ ...params, method: 'showDeviceTutorial' }), - firmwareUpdate: params => call({ ...params, method: 'firmwareUpdate' }), + backupDevice: params => call({ ...params, method: 'backupDevice' }), - recoveryDevice: params => call({ ...params, method: 'recoveryDevice' }), + changeLanguage: params => call({ ...params, method: 'changeLanguage' }), - getCoinInfo: params => call({ ...params, method: 'getCoinInfo' }), + changePin: params => call({ ...params, method: 'changePin' }), - rebootToBootloader: params => call({ ...params, method: 'rebootToBootloader' }), + changeWipeCode: params => call({ ...params, method: 'changeWipeCode' }), - setBrightness: params => call({ ...params, method: 'setBrightness' }), + firmwareUpdate: params => call({ ...params, method: 'firmwareUpdate' }), - setBusy: params => call({ ...params, method: 'setBusy' }), + recoveryDevice: params => call({ ...params, method: 'recoveryDevice' }), - setProxy: params => call({ ...params, method: 'setProxy' }), + getCoinInfo: params => call({ ...params, method: 'getCoinInfo' }), - dispose, + setBrightness: params => call({ ...params, method: 'setBrightness' }), - cancel, + setBusy: params => call({ ...params, method: 'setBusy' }), - renderWebUSBButton, + setProxy: params => call({ ...params, method: 'setProxy' }), - disableWebUSB, + dispose, - requestWebUSBDevice, - }; + cancel, - return api; -}; + ...extraMethods, +}); diff --git a/packages/connect/src/impl/dynamic.ts b/packages/connect/src/impl/dynamic.ts new file mode 100644 index 00000000000..30a13f388f7 --- /dev/null +++ b/packages/connect/src/impl/dynamic.ts @@ -0,0 +1,145 @@ +import EventEmitter from 'events'; + +import { ConnectFactoryDependencies } from '../factory'; +import type { Manifest } from '../types/settings'; +import { CallMethodPayload } from '../events'; +import { ERRORS } from '../constants'; +import { ProxyEventEmitter } from '../utils/proxy-event-emitter'; +import { InitFullSettings } from '../types/api/init'; + +type TrezorConnectDynamicParams< + ImplType, + SettingsType extends Record, + ImplInterface extends ConnectFactoryDependencies, +> = { + implementations: { + type: ImplType; + impl: ImplInterface; + }[]; + getInitTarget: (settings: InitFullSettings) => ImplType; + handleErrorFallback: (errorCode: string) => Promise; +}; + +/** + * Implementation of TrezorConnect that can dynamically switch between different implementations. + * + */ +export class TrezorConnectDynamic< + ImplType, + SettingsType extends Record, + ImplInterface extends ConnectFactoryDependencies, +> implements ConnectFactoryDependencies +{ + public eventEmitter: EventEmitter; + + private currentTarget: ImplType; + private implementations: TrezorConnectDynamicParams< + ImplType, + SettingsType, + ImplInterface + >['implementations']; + private getInitTarget: TrezorConnectDynamicParams< + ImplType, + SettingsType, + ImplInterface + >['getInitTarget']; + private handleErrorFallback: TrezorConnectDynamicParams< + ImplType, + SettingsType, + ImplInterface + >['handleErrorFallback']; + + public lastSettings?: InitFullSettings; + + public constructor({ + implementations, + getInitTarget, + handleErrorFallback, + }: TrezorConnectDynamicParams) { + this.implementations = implementations; + this.currentTarget = this.implementations[0].type; + this.getInitTarget = getInitTarget; + this.handleErrorFallback = handleErrorFallback; + this.eventEmitter = new ProxyEventEmitter( + this.implementations.map(impl => impl.impl.eventEmitter), + ); + } + + public getTarget() { + return this.implementations.find(impl => impl.type === this.currentTarget)!.impl; + } + + public async switchTarget(target: ImplType) { + if (this.currentTarget === target) { + return; + } + + if (!this.lastSettings) { + throw ERRORS.TypedError('Init_NotInitialized'); + } + await this.getTarget().dispose(); + this.currentTarget = target; + await this.getTarget().init(this.lastSettings); + } + + public manifest(manifest: Manifest) { + // @ts-expect-error hell knows why this is not working + this.lastSettings = { + ...this.lastSettings, + manifest, + }; + + this.getTarget().manifest(manifest); + } + + public async init(settings: InitFullSettings) { + if (!settings?.manifest) { + throw ERRORS.TypedError('Init_ManifestMissing'); + } + // Save settings for later use + this.lastSettings = settings; + + this.currentTarget = this.getInitTarget(settings); + + // Initialize the target + try { + return await this.getTarget().init(this.lastSettings); + } catch (error) { + // Handle error by switching to other implementation if available as defined in `handleErrorFallback`. + if (await this.handleErrorFallback(error.code)) { + return await this.getTarget().init(settings); + } + + throw error; + } + } + + public async call(params: CallMethodPayload) { + const response = await this.getTarget().call(params); + if (!response.success) { + if (await this.handleErrorFallback(response.payload.code)) { + return await this.getTarget().call(params); + } + } + + return response; + } + + public requestLogin(params: any) { + return this.getTarget().requestLogin(params); + } + + public uiResponse(params: any) { + return this.getTarget().uiResponse(params); + } + + public cancel(error?: string) { + return this.getTarget().cancel(error); + } + + public dispose() { + this.eventEmitter.removeAllListeners(); + + return this.getTarget().dispose(); + } +} diff --git a/packages/connect/src/index-browser.ts b/packages/connect/src/index-browser.ts index 02e6249dc98..bdf93de6ff4 100644 --- a/packages/connect/src/index-browser.ts +++ b/packages/connect/src/index-browser.ts @@ -17,10 +17,7 @@ const TrezorConnect = factory({ init: fallback, call: fallback, requestLogin: fallback, - requestWebUSBDevice: fallback, uiResponse: fallback, - renderWebUSBButton: fallback, - disableWebUSB: fallback, cancel: fallback, dispose: fallback, }); diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 20fe0b18ae6..96988bcf66f 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -208,31 +208,19 @@ const cancel = (error?: string) => { }); }; -const renderWebUSBButton = (_className?: string) => { - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - -const disableWebUSB = () => { - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - -const requestWebUSBDevice = () => { - throw ERRORS.TypedError('Method_InvalidPackage'); -}; - -const TrezorConnect = factory({ - eventEmitter, - manifest, - init, - call, - requestLogin, - uiResponse, - renderWebUSBButton, - disableWebUSB, - requestWebUSBDevice, - cancel, - dispose, -}); +const TrezorConnect = factory( + { + eventEmitter, + manifest, + init, + call, + requestLogin, + uiResponse, + cancel, + dispose, + }, + {}, +); export default TrezorConnect; export * from './exports'; diff --git a/packages/connect/src/types/api/__tests__/binance.ts b/packages/connect/src/types/api/__tests__/binance.ts index 44c9df72139..507d9df4a43 100644 --- a/packages/connect/src/types/api/__tests__/binance.ts +++ b/packages/connect/src/types/api/__tests__/binance.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const binanceGetAddress = async (api: TrezorConnect) => { // regular @@ -29,7 +29,7 @@ export const binanceGetAddress = async (api: TrezorConnect) => { // with all possible params api.binanceGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/bitcoin.ts b/packages/connect/src/types/api/__tests__/bitcoin.ts index 64adc77b2ad..279abc3d425 100644 --- a/packages/connect/src/types/api/__tests__/bitcoin.ts +++ b/packages/connect/src/types/api/__tests__/bitcoin.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const getAddress = async (api: TrezorConnect) => { // regular @@ -29,7 +29,7 @@ export const getAddress = async (api: TrezorConnect) => { // with all possible params api.getAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/cardano.ts b/packages/connect/src/types/api/__tests__/cardano.ts index ab59c6d05be..b39c80f628e 100644 --- a/packages/connect/src/types/api/__tests__/cardano.ts +++ b/packages/connect/src/types/api/__tests__/cardano.ts @@ -1,4 +1,4 @@ -import { TrezorConnect, PROTO } from '../../..'; +import { TrezorConnect, PROTO, DeviceUniquePath } from '../../..'; const { CardanoAddressType, @@ -122,7 +122,7 @@ export const cardanoGetAddress = async (api: TrezorConnect) => { // with all possible params api.cardanoGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/ethereum.ts b/packages/connect/src/types/api/__tests__/ethereum.ts index cb6776ac942..ae6008f5ba3 100644 --- a/packages/connect/src/types/api/__tests__/ethereum.ts +++ b/packages/connect/src/types/api/__tests__/ethereum.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const ethereumGetAddress = async (api: TrezorConnect) => { // regular @@ -30,7 +30,7 @@ export const ethereumGetAddress = async (api: TrezorConnect) => { // with all possible params api.ethereumGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/index.ts b/packages/connect/src/types/api/__tests__/index.ts index 35cae9718f5..5b7c9a225f8 100644 --- a/packages/connect/src/types/api/__tests__/index.ts +++ b/packages/connect/src/types/api/__tests__/index.ts @@ -32,6 +32,4 @@ export const init = async (api: TrezorConnect) => { api.dispose(); api.cancel(); api.cancel('Interruption error'); - api.renderWebUSBButton(); - api.disableWebUSB(); }; diff --git a/packages/connect/src/types/api/__tests__/management.ts b/packages/connect/src/types/api/__tests__/management.ts index 232101e9ed5..d72095ded2e 100644 --- a/packages/connect/src/types/api/__tests__/management.ts +++ b/packages/connect/src/types/api/__tests__/management.ts @@ -70,7 +70,4 @@ export const management = async (api: TrezorConnect) => { word_count: 24, }); if (recovery.success) recovery.payload.message.toLowerCase(); - - const reboot = await api.rebootToBootloader(); - if (reboot.success) reboot.payload.message.toLowerCase(); }; diff --git a/packages/connect/src/types/api/__tests__/nem.ts b/packages/connect/src/types/api/__tests__/nem.ts index 6fb7a3350af..ab1e8d4c647 100644 --- a/packages/connect/src/types/api/__tests__/nem.ts +++ b/packages/connect/src/types/api/__tests__/nem.ts @@ -1,4 +1,4 @@ -import { TrezorConnect, NEM } from '../../..'; +import { TrezorConnect, NEM, DeviceUniquePath } from '../../..'; export const nemGetAddress = async (api: TrezorConnect) => { // regular @@ -33,7 +33,7 @@ export const nemGetAddress = async (api: TrezorConnect) => { // with all possible params api.nemGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/ripple.ts b/packages/connect/src/types/api/__tests__/ripple.ts index f84d8a6ab31..7a63fc0d5e6 100644 --- a/packages/connect/src/types/api/__tests__/ripple.ts +++ b/packages/connect/src/types/api/__tests__/ripple.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const rippleGetAddress = async (api: TrezorConnect) => { // regular @@ -31,7 +31,7 @@ export const rippleGetAddress = async (api: TrezorConnect) => { // with all possible params api.rippleGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/stellar.ts b/packages/connect/src/types/api/__tests__/stellar.ts index 4fc3811d59c..1164974b88b 100644 --- a/packages/connect/src/types/api/__tests__/stellar.ts +++ b/packages/connect/src/types/api/__tests__/stellar.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const stellarGetAddress = async (api: TrezorConnect) => { // regular @@ -29,7 +29,7 @@ export const stellarGetAddress = async (api: TrezorConnect) => { // with all possible params api.stellarGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/__tests__/tezos.ts b/packages/connect/src/types/api/__tests__/tezos.ts index 7396ce5418d..e4887411f96 100644 --- a/packages/connect/src/types/api/__tests__/tezos.ts +++ b/packages/connect/src/types/api/__tests__/tezos.ts @@ -1,4 +1,4 @@ -import { TrezorConnect } from '../../..'; +import { DeviceUniquePath, TrezorConnect } from '../../..'; export const tezosGetAddress = async (api: TrezorConnect) => { // regular @@ -29,7 +29,7 @@ export const tezosGetAddress = async (api: TrezorConnect) => { // with all possible params api.tezosGetAddress({ device: { - path: '1', + path: DeviceUniquePath('1'), instance: 1, state: 'state@device-id:1', }, diff --git a/packages/connect/src/types/api/applySettings.ts b/packages/connect/src/types/api/applySettings.ts index e004e802b75..cd46568e8f3 100644 --- a/packages/connect/src/types/api/applySettings.ts +++ b/packages/connect/src/types/api/applySettings.ts @@ -1,4 +1,5 @@ import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import type { Params, Response } from '../params'; diff --git a/packages/connect/src/types/api/authenticateDevice.ts b/packages/connect/src/types/api/authenticateDevice.ts index f81852dc68e..8a0cd30485c 100644 --- a/packages/connect/src/types/api/authenticateDevice.ts +++ b/packages/connect/src/types/api/authenticateDevice.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import type { Params, Response } from '../params'; import { DeviceAuthenticityConfig } from '../../data/deviceAuthenticityConfigTypes'; diff --git a/packages/connect/src/types/api/authorizeCoinjoin.ts b/packages/connect/src/types/api/authorizeCoinjoin.ts index a1aea105f65..ed0b47b17a9 100644 --- a/packages/connect/src/types/api/authorizeCoinjoin.ts +++ b/packages/connect/src/types/api/authorizeCoinjoin.ts @@ -1,6 +1,6 @@ -import type { Params, Response } from '../params'; - import { Static, Type } from '@trezor/schema-utils'; + +import type { Params, Response } from '../params'; import { DerivationPath } from '../params'; import { PROTO } from '../../constants'; diff --git a/packages/connect/src/types/api/binance/index.ts b/packages/connect/src/types/api/binance/index.ts index 503c1ef1038..89497418438 100644 --- a/packages/connect/src/types/api/binance/index.ts +++ b/packages/connect/src/types/api/binance/index.ts @@ -1,6 +1,7 @@ +import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { DerivationPath } from '../../params'; -import { Type, Static } from '@trezor/schema-utils'; // BinanceSDKTransaction from https://github.com/binance-chain/javascript-sdk/blob/master/src/tx/index.js diff --git a/packages/connect/src/types/api/bitcoin/index.ts b/packages/connect/src/types/api/bitcoin/index.ts index 4877c0189bb..d7a1c33974e 100644 --- a/packages/connect/src/types/api/bitcoin/index.ts +++ b/packages/connect/src/types/api/bitcoin/index.ts @@ -1,9 +1,10 @@ import type { AccountAddresses } from '@trezor/blockchain-link'; import type { Transaction as BlockbookTransaction } from '@trezor/blockchain-link-types/src/blockbook'; +import { Static, Type } from '@trezor/schema-utils'; + import type { PROTO } from '../../../constants'; import type { AccountTransaction } from '../../account'; import { DerivationPath, ProtoWithDerivationPath } from '../../params'; -import { Static, Type } from '@trezor/schema-utils'; // signMessage diff --git a/packages/connect/src/types/api/blockchainEstimateFee.ts b/packages/connect/src/types/api/blockchainEstimateFee.ts index a3e36f5efb5..e46fa9ebcaa 100644 --- a/packages/connect/src/types/api/blockchainEstimateFee.ts +++ b/packages/connect/src/types/api/blockchainEstimateFee.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkParams, BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { FeeLevel, FeeInfo } from '../fees'; import type { CommonParamsWithCoin, Response } from '../params'; diff --git a/packages/connect/src/types/api/blockchainEvmRpcCall.ts b/packages/connect/src/types/api/blockchainEvmRpcCall.ts new file mode 100644 index 00000000000..4573be6583c --- /dev/null +++ b/packages/connect/src/types/api/blockchainEvmRpcCall.ts @@ -0,0 +1,7 @@ +import { BlockchainLinkParams, BlockchainLinkResponse } from '@trezor/blockchain-link'; + +import type { CommonParamsWithCoin, Response } from '../params'; + +export declare function blockchainEvmRpcCall( + params: CommonParamsWithCoin & BlockchainLinkParams<'rpcCall'>, +): Response>; diff --git a/packages/connect/src/types/api/blockchainGetAccountBalanceHistory.ts b/packages/connect/src/types/api/blockchainGetAccountBalanceHistory.ts index f2782d7f292..7e61b91450f 100644 --- a/packages/connect/src/types/api/blockchainGetAccountBalanceHistory.ts +++ b/packages/connect/src/types/api/blockchainGetAccountBalanceHistory.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkParams, BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export declare function blockchainGetAccountBalanceHistory( diff --git a/packages/connect/src/types/api/blockchainGetCurrentFiatRates.ts b/packages/connect/src/types/api/blockchainGetCurrentFiatRates.ts index 08035337f1e..d98612dbd4a 100644 --- a/packages/connect/src/types/api/blockchainGetCurrentFiatRates.ts +++ b/packages/connect/src/types/api/blockchainGetCurrentFiatRates.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkParams, BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export declare function blockchainGetCurrentFiatRates( diff --git a/packages/connect/src/types/api/blockchainGetFiatRatesForTimestamps.ts b/packages/connect/src/types/api/blockchainGetFiatRatesForTimestamps.ts index 6f9617afdd3..0c44f7c06c6 100644 --- a/packages/connect/src/types/api/blockchainGetFiatRatesForTimestamps.ts +++ b/packages/connect/src/types/api/blockchainGetFiatRatesForTimestamps.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkParams, BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export declare function blockchainGetFiatRatesForTimestamps( diff --git a/packages/connect/src/types/api/blockchainGetTransactions.ts b/packages/connect/src/types/api/blockchainGetTransactions.ts index 51c2a9eeaed..294cef72b98 100644 --- a/packages/connect/src/types/api/blockchainGetTransactions.ts +++ b/packages/connect/src/types/api/blockchainGetTransactions.ts @@ -1,4 +1,5 @@ import type { Transaction } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export type BlockchainGetTransactions = CommonParamsWithCoin & { diff --git a/packages/connect/src/types/api/blockchainSubscribe.ts b/packages/connect/src/types/api/blockchainSubscribe.ts index 63be8510669..ab66828cd3b 100644 --- a/packages/connect/src/types/api/blockchainSubscribe.ts +++ b/packages/connect/src/types/api/blockchainSubscribe.ts @@ -1,4 +1,5 @@ import type { SubscriptionAccountInfo, BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export type BlockchainSubscribe = CommonParamsWithCoin & { diff --git a/packages/connect/src/types/api/blockchainSubscribeFiatRates.ts b/packages/connect/src/types/api/blockchainSubscribeFiatRates.ts index 77c2312baa1..13e0d01af11 100644 --- a/packages/connect/src/types/api/blockchainSubscribeFiatRates.ts +++ b/packages/connect/src/types/api/blockchainSubscribeFiatRates.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { CommonParamsWithCoin, Response } from '../params'; export type BlockchainSubscribeFiatRates = CommonParamsWithCoin & { diff --git a/packages/connect/src/types/api/blockchainUnsubscribe.ts b/packages/connect/src/types/api/blockchainUnsubscribe.ts index 66c1290ea90..4b30fa3088a 100644 --- a/packages/connect/src/types/api/blockchainUnsubscribe.ts +++ b/packages/connect/src/types/api/blockchainUnsubscribe.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { Response } from '../params'; import type { BlockchainSubscribe } from './blockchainSubscribe'; diff --git a/packages/connect/src/types/api/blockchainUnsubscribeFiatRates.ts b/packages/connect/src/types/api/blockchainUnsubscribeFiatRates.ts index f7b5052e83f..88e8d87dd17 100644 --- a/packages/connect/src/types/api/blockchainUnsubscribeFiatRates.ts +++ b/packages/connect/src/types/api/blockchainUnsubscribeFiatRates.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkResponse } from '@trezor/blockchain-link'; + import type { Response } from '../params'; import type { BlockchainSubscribeFiatRates } from './blockchainSubscribeFiatRates'; diff --git a/packages/connect/src/types/api/cancelCoinjoinAuthorization.ts b/packages/connect/src/types/api/cancelCoinjoinAuthorization.ts index 56de14b2300..c8faf394f3c 100644 --- a/packages/connect/src/types/api/cancelCoinjoinAuthorization.ts +++ b/packages/connect/src/types/api/cancelCoinjoinAuthorization.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import type { PROTO } from '../../constants'; import type { Params, Response } from '../params'; diff --git a/packages/connect/src/types/api/cardano/index.ts b/packages/connect/src/types/api/cardano/index.ts index 7170a522713..8a836516863 100644 --- a/packages/connect/src/types/api/cardano/index.ts +++ b/packages/connect/src/types/api/cardano/index.ts @@ -1,4 +1,5 @@ import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { GetPublicKey, PublicKey, DerivationPath } from '../../params'; diff --git a/packages/connect/src/types/api/cardanoComposeTransaction.ts b/packages/connect/src/types/api/cardanoComposeTransaction.ts index a9f01b7eef1..418d45f7e07 100644 --- a/packages/connect/src/types/api/cardanoComposeTransaction.ts +++ b/packages/connect/src/types/api/cardanoComposeTransaction.ts @@ -1,4 +1,5 @@ import type { types, trezorUtils } from '@fivebinaries/coin-selection'; + import type { AccountAddresses, AccountUtxo } from '../../exports'; import type { Params, Response } from '../params'; import type { CardanoCertificate, CardanoInput, CardanoOutput } from './cardano'; diff --git a/packages/connect/src/types/api/changeLanguage.ts b/packages/connect/src/types/api/changeLanguage.ts index 5709faaf2bf..1d1d2a37e52 100644 --- a/packages/connect/src/types/api/changeLanguage.ts +++ b/packages/connect/src/types/api/changeLanguage.ts @@ -1,4 +1,5 @@ import { Type, Static } from '@trezor/schema-utils'; + import type { Params, Response } from '../params'; import { PROTO } from '../../constants'; diff --git a/packages/connect/src/types/api/checkFirmwareAuthenticity.ts b/packages/connect/src/types/api/checkFirmwareAuthenticity.ts deleted file mode 100644 index a8584e567d8..00000000000 --- a/packages/connect/src/types/api/checkFirmwareAuthenticity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Response, CommonParams } from '../params'; - -export interface CheckFirmwareAuthenticityResponse { - expectedFirmwareHash: string; - actualFirmwareHash: string; - valid: boolean; -} - -export type CheckFirmwareAuthenticityParams = CommonParams & { baseUrl?: string }; - -export declare function checkFirmwareAuthenticity( - params: CheckFirmwareAuthenticityParams, -): Response; diff --git a/packages/connect/src/types/api/cipherKeyValue.ts b/packages/connect/src/types/api/cipherKeyValue.ts index dff6e40e849..1a652d5b76a 100644 --- a/packages/connect/src/types/api/cipherKeyValue.ts +++ b/packages/connect/src/types/api/cipherKeyValue.ts @@ -1,6 +1,7 @@ -import { Params, BundledParams, Response, DerivationPath } from '../params'; import { Static, Type } from '@trezor/schema-utils'; +import { Params, BundledParams, Response, DerivationPath } from '../params'; + export type CipherKeyValue = Static; export const CipherKeyValue = Type.Object({ path: DerivationPath, diff --git a/packages/connect/src/types/api/composeTransaction.ts b/packages/connect/src/types/api/composeTransaction.ts index b8aa202444a..fae24cb4adb 100644 --- a/packages/connect/src/types/api/composeTransaction.ts +++ b/packages/connect/src/types/api/composeTransaction.ts @@ -9,7 +9,9 @@ import type { ComposeResultError as ComposeResultErrorBase, ComposeResultFinal as ComposeResultFinalBase, ComposeResultNonFinal as ComposeResultNonFinalBase, + TransactionInputOutputSortingStrategy, } from '@trezor/utxo-lib'; + import type { PROTO } from '../../constants'; import type { Params, Response } from '../params'; @@ -20,7 +22,19 @@ export type ComposeOutputPayment = Omit | ComposeOutputPayment; -export interface ComposeParams { +type SortingStrategyPropsWithBackCompatibility = + | { + /** @deprecated use sortingStrategy=none instead */ + skipPermutation?: boolean; + sortingStrategy?: undefined; + } + | { + /** @deprecated use sortingStrategy=none instead */ + skipPermutation?: undefined; + sortingStrategy?: TransactionInputOutputSortingStrategy; + }; + +export type ComposeParams = { outputs: ComposeOutput[]; coin: string; identity?: string; @@ -30,8 +44,7 @@ export interface ComposeParams { sequence?: number; baseFee?: number; floorBaseFee?: boolean; - skipPermutation?: boolean; -} +} & SortingStrategyPropsWithBackCompatibility; export type SignedTransaction = { signatures: string[]; @@ -42,7 +55,7 @@ export type SignedTransaction = { // @trezor/utxo-lib `composeTx` ComposeInput required fields intersects AccountUtxo export type ComposeUtxo = AccountUtxo & Partial; -export interface PrecomposeParams { +export type PrecomposeParams = { outputs: ComposeOutput[]; coin: string; identity?: string; @@ -56,8 +69,7 @@ export interface PrecomposeParams { baseFee?: number; floorBaseFee?: boolean; sequence?: number; - skipPermutation?: boolean; -} +} & SortingStrategyPropsWithBackCompatibility; // @trezor/utxo-lib `composeTx` transaction.input (ComposeInput) response intersects AccountUtxo export type ComposedInputs = AccountUtxo & ComposeInputBase; diff --git a/packages/connect/src/types/api/eos/index.ts b/packages/connect/src/types/api/eos/index.ts index 468b0405dee..2e005b1578c 100644 --- a/packages/connect/src/types/api/eos/index.ts +++ b/packages/connect/src/types/api/eos/index.ts @@ -1,6 +1,7 @@ +import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { DerivationPath } from '../../params'; -import { Type, Static } from '@trezor/schema-utils'; // eosGetPublicKey diff --git a/packages/connect/src/types/api/ethereum/index.ts b/packages/connect/src/types/api/ethereum/index.ts index 777a78055f4..97de567044f 100644 --- a/packages/connect/src/types/api/ethereum/index.ts +++ b/packages/connect/src/types/api/ethereum/index.ts @@ -1,4 +1,5 @@ import { Type, Static } from '@trezor/schema-utils'; + import { DerivationPath } from '../../params'; // ethereumSignMessage diff --git a/packages/connect/src/types/api/firmwareUpdate.ts b/packages/connect/src/types/api/firmwareUpdate.ts index b2aaf881692..ff5238e53af 100644 --- a/packages/connect/src/types/api/firmwareUpdate.ts +++ b/packages/connect/src/types/api/firmwareUpdate.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import type { Params, Response } from '../params'; export type FirmwareUpdate = Static; diff --git a/packages/connect/src/types/api/getAccountDescriptor.ts b/packages/connect/src/types/api/getAccountDescriptor.ts index f80e193e8a7..80f043b37fe 100644 --- a/packages/connect/src/types/api/getAccountDescriptor.ts +++ b/packages/connect/src/types/api/getAccountDescriptor.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { Params, BundledParams, Response, DerivationPath } from '../params'; diff --git a/packages/connect/src/types/api/getAccountInfo.ts b/packages/connect/src/types/api/getAccountInfo.ts index 4c607b989d9..5d0d540d7a0 100644 --- a/packages/connect/src/types/api/getAccountInfo.ts +++ b/packages/connect/src/types/api/getAccountInfo.ts @@ -1,4 +1,5 @@ import type { BlockchainLinkParams } from '@trezor/blockchain-link'; + import type { PROTO } from '../../constants'; import type { Params, BundledParams, Response } from '../params'; import type { AccountInfo, DiscoveryAccountType } from '../account'; diff --git a/packages/connect/src/types/api/getAddress.ts b/packages/connect/src/types/api/getAddress.ts index 6ab2e1e2b1f..162196f3407 100644 --- a/packages/connect/src/types/api/getAddress.ts +++ b/packages/connect/src/types/api/getAddress.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { GetAddress as GetAddressShared, diff --git a/packages/connect/src/types/api/getOwnershipId.ts b/packages/connect/src/types/api/getOwnershipId.ts index 81e7b77e057..9d7b4cda194 100644 --- a/packages/connect/src/types/api/getOwnershipId.ts +++ b/packages/connect/src/types/api/getOwnershipId.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { Params, BundledParams, Response, DerivationPath } from '../params'; diff --git a/packages/connect/src/types/api/getOwnershipProof.ts b/packages/connect/src/types/api/getOwnershipProof.ts index a14d1879050..682588682a2 100644 --- a/packages/connect/src/types/api/getOwnershipProof.ts +++ b/packages/connect/src/types/api/getOwnershipProof.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { Params, BundledParams, Response, DerivationPath } from '../params'; diff --git a/packages/connect/src/types/api/getPublicKey.ts b/packages/connect/src/types/api/getPublicKey.ts index a7a07d115b7..2d0eaacb7a9 100644 --- a/packages/connect/src/types/api/getPublicKey.ts +++ b/packages/connect/src/types/api/getPublicKey.ts @@ -1,6 +1,7 @@ +import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import { GetPublicKey as GetPublicKeyShared, Params, BundledParams, Response } from '../params'; -import { Type, Static } from '@trezor/schema-utils'; export type GetPublicKey = Static; export const GetPublicKey = Type.Intersect([ diff --git a/packages/connect/src/types/api/index.ts b/packages/connect/src/types/api/index.ts index 0b4de7a08ce..131a38ca99e 100644 --- a/packages/connect/src/types/api/index.ts +++ b/packages/connect/src/types/api/index.ts @@ -10,6 +10,7 @@ import { blockchainDisconnect } from './blockchainDisconnect'; import { blockchainEstimateFee } from './blockchainEstimateFee'; import { blockchainGetAccountBalanceHistory } from './blockchainGetAccountBalanceHistory'; import { blockchainGetCurrentFiatRates } from './blockchainGetCurrentFiatRates'; +import { blockchainEvmRpcCall } from './blockchainEvmRpcCall'; import { blockchainGetFiatRatesForTimestamps } from './blockchainGetFiatRatesForTimestamps'; import { blockchainGetTransactions } from './blockchainGetTransactions'; import { blockchainSetCustomBackend } from './blockchainSetCustomBackend'; @@ -27,10 +28,8 @@ import { cardanoSignTransaction } from './cardanoSignTransaction'; import { changeLanguage } from './changeLanguage'; import { changePin } from './changePin'; import { changeWipeCode } from './changeWipeCode'; -import { checkFirmwareAuthenticity } from './checkFirmwareAuthenticity'; import { cipherKeyValue } from './cipherKeyValue'; import { composeTransaction } from './composeTransaction'; -import { disableWebUSB } from './disableWebUSB'; import { dispose } from './dispose'; import { eosGetPublicKey } from './eosGetPublicKey'; import { eosSignTransaction } from './eosSignTransaction'; @@ -59,13 +58,11 @@ import { nemSignTransaction } from './nemSignTransaction'; import { off } from './off'; import { on } from './on'; import { pushTransaction } from './pushTransaction'; -import { rebootToBootloader } from './rebootToBootloader'; import { recoveryDevice } from './recoveryDevice'; import { removeAllListeners } from './removeAllListeners'; -import { renderWebUSBButton } from './renderWebUSBButton'; import { requestLogin } from './requestLogin'; -import { requestWebUSBDevice } from './requestWebUSBDevice'; import { resetDevice } from './resetDevice'; +import { loadDevice } from './loadDevice'; import { rippleGetAddress } from './rippleGetAddress'; import { rippleSignTransaction } from './rippleSignTransaction'; import { setBrightness } from './setBrightness'; @@ -88,265 +85,257 @@ import { verifyMessage } from './verifyMessage'; import { wipeDevice } from './wipeDevice'; export interface TrezorConnect { - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/applyFlags.md + // https://connect.trezor.io/9/methods/device/applyFlags/ applyFlags: typeof applyFlags; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/applySettings.md + // https://connect.trezor.io/9/methods/device/applySettings/ applySettings: typeof applySettings; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/authenticateDevice.md + // https://connect.trezor.io/9/methods/device/authenticateDevice/ authenticateDevice: typeof authenticateDevice; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/authorizeCoinjoin.md + // https://connect.trezor.io/9/methods/bitcoin/authorizeCoinjoin/ authorizeCoinjoin: typeof authorizeCoinjoin; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cancelCoinjoinAuthorization.md + // https://connect.trezor.io/9/methods/bitcoin/cancelCoinjoinAuthorization/ cancelCoinjoinAuthorization: typeof cancelCoinjoinAuthorization; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/showDeviceTutorial.md + // https://connect.trezor.io/9/methods/device/showDeviceTutorial/ showDeviceTutorial: typeof showDeviceTutorial; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/backupDevice.md + // https://connect.trezor.io/9/methods/device/backupDevice/ backupDevice: typeof backupDevice; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/binanceGetAddress.md + // https://connect.trezor.io/9/methods/binance/binanceGetAddress/ binanceGetAddress: typeof binanceGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/binanceGetPublicKey.md + // https://connect.trezor.io/9/methods/binance/binanceGetPublicKey/ binanceGetPublicKey: typeof binanceGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/binanceSignTransaction.md + // https://connect.trezor.io/9/methods/binance/binanceSignTransaction/ binanceSignTransaction: typeof binanceSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainDisconnect.md + // todo: link docs blockchainDisconnect: typeof blockchainDisconnect; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainEstimateFee.md + // todo: link docs blockchainEstimateFee: typeof blockchainEstimateFee; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainGetAccountBalanceHistory.md + // todo: link docs blockchainGetAccountBalanceHistory: typeof blockchainGetAccountBalanceHistory; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainGetCurrentFiatRates.md + // todo: link docs blockchainGetCurrentFiatRates: typeof blockchainGetCurrentFiatRates; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainGetFiatRatesForTimestamps.md + blockchainEvmRpcCall: typeof blockchainEvmRpcCall; + + // todo: link docs blockchainGetFiatRatesForTimestamps: typeof blockchainGetFiatRatesForTimestamps; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainGetTransactions.md + // todo: link docs blockchainGetTransactions: typeof blockchainGetTransactions; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainSetCustomBackend.md + // todo: link docs blockchainSetCustomBackend: typeof blockchainSetCustomBackend; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainSubscribe.md + // todo: link docs blockchainSubscribe: typeof blockchainSubscribe; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainSubscribeFiatRates.md + // todo: link docs blockchainSubscribeFiatRates: typeof blockchainSubscribeFiatRates; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainUnsubscribe.md + // todo: link docs blockchainUnsubscribe: typeof blockchainUnsubscribe; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/blockchainUnsubscribeFiatRates.md + // todo: link docs blockchainUnsubscribeFiatRates: typeof blockchainUnsubscribeFiatRates; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cancel.md + // todo: link docs cancel: typeof cancel; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cardanoGetAddress.md + // https://connect.trezor.io/9/methods/cardano/cardanoGetAddress/ cardanoGetAddress: typeof cardanoGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cardanoGetNativeScriptHash.md + // https://connect.trezor.io/9/methods/cardano/cardanoGetNativeScriptHash/ cardanoGetNativeScriptHash: typeof cardanoGetNativeScriptHash; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cardanoGetPublicKey.md + // https://connect.trezor.io/9/methods/cardano/cardanoGetPublicKey/ cardanoGetPublicKey: typeof cardanoGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cardanoSignTransaction.md + // https://connect.trezor.io/9/methods/cardano/cardanoSignTransaction/ cardanoSignTransaction: typeof cardanoSignTransaction; + // todo: link docs cardanoComposeTransaction: typeof cardanoComposeTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/changeLanguage.md + // https://connect.trezor.io/9/methods/device/changeLanguage/ changeLanguage: typeof changeLanguage; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/changePin.md + // https://connect.trezor.io/9/methods/device/changePin/ changePin: typeof changePin; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/changeWipeCode.md + // https://connect.trezor.io/9/methods/device/changeWipeCode/ changeWipeCode: typeof changeWipeCode; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/cipherKeyValue.md + // https://connect.trezor.io/9/methods/other/cipherKeyValue/ cipherKeyValue: typeof cipherKeyValue; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/composeTransaction.md + // https://connect.trezor.io/9/methods/bitcoin/composeTransaction/ composeTransaction: typeof composeTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/disableWebUSB.md - disableWebUSB: typeof disableWebUSB; - - requestWebUSBDevice: typeof requestWebUSBDevice; - - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/dispose.md + // todo: link docs dispose: typeof dispose; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/eosGetPublicKey.md + // https://connect.trezor.io/9/methods/eos/eosGetPublicKey/ eosGetPublicKey: typeof eosGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/eosSignTransaction.md + // https://connect.trezor.io/9/methods/eos/eosSignTransaction/ eosSignTransaction: typeof eosSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumGetAddress.md + // https://connect.trezor.io/9/methods/ethereum/ethereumGetAddress/ ethereumGetAddress: typeof ethereumGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumGetPublicKey.md + // https://connect.trezor.io/9/methods/ethereum/ethereumGetPublicKey/ ethereumGetPublicKey: typeof ethereumGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumSignMessage.md + // https://connect.trezor.io/9/methods/ethereum/ethereumSignMessage/ ethereumSignMessage: typeof ethereumSignMessage; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumSignTransaction.md + // https://connect.trezor.io/9/methods/ethereum/ethereumSignTransaction/ ethereumSignTransaction: typeof ethereumSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumSignTypedData.md + // https://connect.trezor.io/9/methods/ethereum/ethereumSignTypedData/ ethereumSignTypedData: typeof ethereumSignTypedData; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/ethereumVerifyMessage.md + // https://connect.trezor.io/9/methods/ethereum/ethereumVerifyMessage/ ethereumVerifyMessage: typeof ethereumVerifyMessage; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/firmwareUpdate.md + // https://connect.trezor.io/9/methods/device/firmwareUpdate/ firmwareUpdate: typeof firmwareUpdate; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getAccountDescriptor.md + // https://connect.trezor.io/9/methods/other/getAccountDescriptor/ getAccountDescriptor: typeof getAccountDescriptor; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getAccountInfo.md + // https://connect.trezor.io/9/methods/bitcoin/getAccountInfo/ getAccountInfo: typeof getAccountInfo; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getAddress.md + // https://connect.trezor.io/9/methods/bitcoin/getAddress/ getAddress: typeof getAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getCoinInfo.md + // https://connect.trezor.io/9/methods/other/getCoinInfo/ getCoinInfo: typeof getCoinInfo; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getDeviceState.md + // https://connect.trezor.io/9/methods/device/getDeviceState/ getDeviceState: typeof getDeviceState; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getFeatures.md + // https://connect.trezor.io/9/methods/device/getFeatures/ getFeatures: typeof getFeatures; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getFirmwareHash.md + // https://connect.trezor.io/9/methods/device/getFirmwareHash/ getFirmwareHash: typeof getFirmwareHash; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getOwnershipId.md + // https://connect.trezor.io/9/methods/other/getOwnershipId/ getOwnershipId: typeof getOwnershipId; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getOwnershipProof.md + // https://connect.trezor.io/9/methods/other/getOwnershipProof/ getOwnershipProof: typeof getOwnershipProof; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getPublicKey.md + // https://connect.trezor.io/9/methods/bitcoin/getPublicKey/ getPublicKey: typeof getPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/getSettings.md + // todo: link docs getSettings: typeof getSettings; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/init.md + // https://connect.trezor.io/9/methods/other/init/ init: typeof init; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/manifest.md + // https://connect.trezor.io/9/methods/other/manifest/ manifest: typeof manifest; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/nemGetAddress.md + // https://connect.trezor.io/9/methods/nem/nemGetAddress/ nemGetAddress: typeof nemGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/nemSignTransaction.md + // https://connect.trezor.io/9/methods/nem/nemSignTransaction/ nemSignTransaction: typeof nemSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/off.md + // todo: link docs off: typeof off; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/on.md + // todo: link docs on: typeof on; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/pushTransaction.md + // https://connect.trezor.io/9/methods/bitcoin/pushTransaction/ pushTransaction: typeof pushTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/rebootToBootloader.md - rebootToBootloader: typeof rebootToBootloader; - - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/recoveryDevice.md + // todo: link docs recoveryDevice: typeof recoveryDevice; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/removeAllListeners.md + // todo link docs removeAllListeners: typeof removeAllListeners; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/renderWebUSBButton.md - renderWebUSBButton: typeof renderWebUSBButton; - - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/requestLogin.md + // https://connect.trezor.io/9/methods/other/requestLogin/ requestLogin: typeof requestLogin; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/resetDevice.md + // https://connect.trezor.io/9/methods/device/resetDevice/ resetDevice: typeof resetDevice; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/rippleGetAddress.md + // https://connect.trezor.io/9/methods/device/loadDevice/ + loadDevice: typeof loadDevice; + + // https://connect.trezor.io/9/methods/ripple/rippleGetAddress/ rippleGetAddress: typeof rippleGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/rippleSignTransaction.md + // https://connect.trezor.io/9/methods/ripple/rippleSignTransaction/ rippleSignTransaction: typeof rippleSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/setBrightness.md + // todo: link docs setBrightness: typeof setBrightness; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/setBusy.md + // https://connect.trezor.io/9/methods/device/setBusy/ setBusy: typeof setBusy; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/setProxy.md + // todo: link docs setProxy: typeof setProxy; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/signMessage.md + // https://connect.trezor.io/9/methods/bitcoin/signMessage/ signMessage: typeof signMessage; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/signTransaction.md + // https://connect.trezor.io/9/methods/bitcoin/signTransaction/ signTransaction: typeof signTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/solanaGetPublicKey.md + // https://connect.trezor.io/9/methods/solana/solanaGetPublicKey/ solanaGetPublicKey: typeof solanaGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/solanaGetAddress.md + // https://connect.trezor.io/9/methods/solana/solanaGetAddress/ solanaGetAddress: typeof solanaGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/solanaSignTransaction.md + // https://connect.trezor.io/9/methods/solana/solanaSignTransaction/ solanaSignTransaction: typeof solanaSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/stellarGetAddress.md + // https://connect.trezor.io/9/methods/stellar/stellarGetAddress/ stellarGetAddress: typeof stellarGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/stellarSignTransaction.md + // https://connect.trezor.io/9/methods/stellar/stellarSignTransaction/ stellarSignTransaction: typeof stellarSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/tezosGetAddress.md + // https://connect.trezor.io/9/methods/tezos/tezosGetAddress/ tezosGetAddress: typeof tezosGetAddress; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/tezosGetPublicKey.md + // https://connect.trezor.io/9/methods/tezos/tezosGetPublicKey/ tezosGetPublicKey: typeof tezosGetPublicKey; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/tezosSignTransaction.md + // https://connect.trezor.io/9/methods/tezos/tezosSignTransaction/ tezosSignTransaction: typeof tezosSignTransaction; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/uiResponse.md + // todo: link docs uiResponse: typeof uiResponse; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/unlockPath.md + // https://connect.trezor.io/9/methods/other/unlockPath/ unlockPath: typeof unlockPath; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/verifyMessage.md + // https://connect.trezor.io/9/methods/bitcoin/verifyMessage/ verifyMessage: typeof verifyMessage; - // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/wipeDevice.md + // https://connect.trezor.io/9/methods/device/wipeDevice/ wipeDevice: typeof wipeDevice; - - // todo: link docs - checkFirmwareAuthenticity: typeof checkFirmwareAuthenticity; } diff --git a/packages/connect/src/types/api/init.ts b/packages/connect/src/types/api/init.ts index bfd421126fd..25556144701 100644 --- a/packages/connect/src/types/api/init.ts +++ b/packages/connect/src/types/api/init.ts @@ -5,6 +5,17 @@ import type { ConnectSettingsPublic, Manifest } from '../settings'; -export declare function init( - settings: { manifest: Manifest } & Partial, +// explicitly don't overlap types +export type InitFullSettings> = { + manifest: Manifest; +} & Partial< + Omit & Omit +>; + +export type InitType> = ( + settings: InitFullSettings, +) => Promise; + +export declare function init>( + settings: InitFullSettings, ): Promise; diff --git a/packages/connect/src/types/api/loadDevice.ts b/packages/connect/src/types/api/loadDevice.ts new file mode 100644 index 00000000000..b0209a6db24 --- /dev/null +++ b/packages/connect/src/types/api/loadDevice.ts @@ -0,0 +1,8 @@ +/** + * Performs device setup and generates a new seed. + */ + +import { PROTO } from '../../constants'; +import type { Params, Response } from '../params'; + +export declare function loadDevice(params: Params): Response; diff --git a/packages/connect/src/types/api/nem/index.ts b/packages/connect/src/types/api/nem/index.ts index 4aac576f3ea..56b13a9f8e7 100644 --- a/packages/connect/src/types/api/nem/index.ts +++ b/packages/connect/src/types/api/nem/index.ts @@ -1,6 +1,7 @@ +import { Type, Static } from '@trezor/schema-utils'; + import { PROTO, NEM } from '../../../constants'; import { DerivationPath } from '../../params'; -import { Type, Static } from '@trezor/schema-utils'; // NEM types from nem-sdk // https://nemproject.github.io/#transferTransaction diff --git a/packages/connect/src/types/api/nemGetAddress.ts b/packages/connect/src/types/api/nemGetAddress.ts index 9c4c25041b5..80d10b409e0 100644 --- a/packages/connect/src/types/api/nemGetAddress.ts +++ b/packages/connect/src/types/api/nemGetAddress.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { GetAddress, Address, Params, BundledParams, Response } from '../params'; export type NEMGetAddress = Static; diff --git a/packages/connect/src/types/api/pushTransaction.ts b/packages/connect/src/types/api/pushTransaction.ts index f62a9abaf76..aa991098b95 100644 --- a/packages/connect/src/types/api/pushTransaction.ts +++ b/packages/connect/src/types/api/pushTransaction.ts @@ -3,6 +3,7 @@ * Broadcasts the transaction to the selected network. */ import { Static, Type } from '@trezor/schema-utils'; + import type { Params, Response } from '../params'; export type PushTransaction = Static; diff --git a/packages/connect/src/types/api/rebootToBootloader.ts b/packages/connect/src/types/api/rebootToBootloader.ts deleted file mode 100644 index 855b9d467bc..00000000000 --- a/packages/connect/src/types/api/rebootToBootloader.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Reboots device (currently only T1B1 with fw >= 1.10.0) in bootloader mode - */ - -import type { PROTO } from '../../constants'; -import type { Params, Response } from '../params'; - -export declare function rebootToBootloader( - params?: Params, -): Response; diff --git a/packages/connect/src/types/api/recoveryDevice.ts b/packages/connect/src/types/api/recoveryDevice.ts index e1cd1350607..1316f77ca99 100644 --- a/packages/connect/src/types/api/recoveryDevice.ts +++ b/packages/connect/src/types/api/recoveryDevice.ts @@ -3,6 +3,7 @@ */ import { Static, Type } from '@trezor/schema-utils'; + import { PROTO } from '../../constants'; import type { Params, Response } from '../params'; diff --git a/packages/connect/src/types/api/requestLogin.ts b/packages/connect/src/types/api/requestLogin.ts index f5acbd76a8b..ba6acf441e8 100644 --- a/packages/connect/src/types/api/requestLogin.ts +++ b/packages/connect/src/types/api/requestLogin.ts @@ -6,6 +6,7 @@ */ import { Static, Type } from '@trezor/schema-utils'; + import type { Params, Response } from '../params'; export type LoginChallenge = Static; diff --git a/packages/connect/src/types/api/ripple/index.ts b/packages/connect/src/types/api/ripple/index.ts index fa9d3955344..6ccf8d4f524 100644 --- a/packages/connect/src/types/api/ripple/index.ts +++ b/packages/connect/src/types/api/ripple/index.ts @@ -1,6 +1,7 @@ -import { DerivationPath } from '../../params'; import { Type, Static } from '@trezor/schema-utils'; +import { DerivationPath } from '../../params'; + export type RipplePayment = Static; export const RipplePayment = Type.Object({ amount: Type.String(), diff --git a/packages/connect/src/types/api/solana/index.ts b/packages/connect/src/types/api/solana/index.ts index 1e9e5d2b1a2..f1498e78359 100644 --- a/packages/connect/src/types/api/solana/index.ts +++ b/packages/connect/src/types/api/solana/index.ts @@ -1,6 +1,7 @@ -import { PublicKey } from '../../params'; import { Type, Static } from '@trezor/schema-utils'; +import { PublicKey } from '../../params'; + // solanaGetPublicKey export type SolanaPublicKey = Static; diff --git a/packages/connect/src/types/api/stellar/index.ts b/packages/connect/src/types/api/stellar/index.ts index bf26040150c..33247e2f72b 100644 --- a/packages/connect/src/types/api/stellar/index.ts +++ b/packages/connect/src/types/api/stellar/index.ts @@ -1,9 +1,10 @@ // Stellar types from stellar-sdk // https://github.com/stellar/js-stellar-base +import { Type, Static } from '@trezor/schema-utils'; + import { PROTO } from '../../../constants'; import { DerivationPath } from '../../params'; -import { Type, Static } from '@trezor/schema-utils'; export type StellarAsset = Static; export const StellarAsset = Type.Object({ diff --git a/packages/connect/src/types/api/stellarSignTransaction.ts b/packages/connect/src/types/api/stellarSignTransaction.ts index 2771aa9b106..19931f1b77c 100644 --- a/packages/connect/src/types/api/stellarSignTransaction.ts +++ b/packages/connect/src/types/api/stellarSignTransaction.ts @@ -1,5 +1,4 @@ import type { Params, Response } from '../params'; - // todo:maybe we could unify terminology SignTransaction vs SignTx (across all methods) import type { StellarSignTransaction, StellarSignedTx } from './stellar'; diff --git a/packages/connect/src/types/api/tezos/index.ts b/packages/connect/src/types/api/tezos/index.ts index 4f50db8e2d7..7630a640a34 100644 --- a/packages/connect/src/types/api/tezos/index.ts +++ b/packages/connect/src/types/api/tezos/index.ts @@ -1,6 +1,7 @@ -import { DerivationPath } from '../../params'; import { Type, Static } from '@trezor/schema-utils'; +import { DerivationPath } from '../../params'; + export type TezosRevealOperation = Static; export const TezosRevealOperation = Type.Object({ source: Type.String(), diff --git a/packages/connect/src/types/api/unlockPath.ts b/packages/connect/src/types/api/unlockPath.ts index 9e83ed30a5b..705648e71d3 100644 --- a/packages/connect/src/types/api/unlockPath.ts +++ b/packages/connect/src/types/api/unlockPath.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@trezor/schema-utils'; + import { Params, Response, DerivationPath } from '../params'; import type { PROTO } from '../../constants'; diff --git a/packages/connect/src/types/coinInfo.ts b/packages/connect/src/types/coinInfo.ts index f7afdf736d7..933d67595a5 100644 --- a/packages/connect/src/types/coinInfo.ts +++ b/packages/connect/src/types/coinInfo.ts @@ -1,4 +1,5 @@ import { Type, Static } from '@trezor/schema-utils'; + import { FeeLevel } from './fees'; import { DeviceModelInternal } from './device'; diff --git a/packages/connect/src/types/device.ts b/packages/connect/src/types/device.ts index d1d4737d7e1..405a9c2a477 100644 --- a/packages/connect/src/types/device.ts +++ b/packages/connect/src/types/device.ts @@ -1,3 +1,5 @@ +import { Descriptor } from '@trezor/transport'; + import type { PROTO } from '../constants'; import type { ReleaseInfo } from './firmware'; @@ -35,6 +37,7 @@ export type DeviceState = { sessionId?: string; // dynamic value: Features.session_id // ${first testnet address}@${device.features.device_id}:${device.instance} staticSessionId?: StaticSessionId; + deriveCardano?: boolean; }; // NOTE: unavailableCapabilities is an object with information what is NOT supported by this device. @@ -55,17 +58,33 @@ export type FirmwareRevisionCheckResult = error: FirmwareRevisionCheckError; }; -export type KnownDevice = { +export type FirmwareHashCheckError = + | 'hash-mismatch' + | 'check-skipped' + | 'check-unsupported' + | 'unknown-release' + | 'other-error'; +export type FirmwareHashCheckResult = + | { success: true } + | { success: false; error: FirmwareHashCheckError }; + +export type DeviceUniquePath = string & { __type: 'DeviceUniquePath' }; +export const DeviceUniquePath = (id: string) => id as DeviceUniquePath; + +type BaseDevice = { + path: DeviceUniquePath; + name: string; +}; + +export type KnownDevice = BaseDevice & { type: 'acquired'; id: string | null; - path: string; /** @deprecated, use features.label instead */ label: string; error?: typeof undefined; firmware: DeviceFirmwareStatus; firmwareRelease?: ReleaseInfo | null; firmwareType?: FirmwareType; - name: string; color?: string; status: DeviceStatus; mode: DeviceMode; @@ -76,23 +95,23 @@ export type KnownDevice = { availableTranslations: string[]; authenticityChecks?: { firmwareRevision: FirmwareRevisionCheckResult | null; - // Maybe add FirmwareHashCheck result here? + firmwareHash: FirmwareHashCheckResult | null; // Maybe add AuthenticityCheck result here? }; + transportSessionOwner?: undefined; + transportDescriptorType?: typeof undefined; }; -export type UnknownDevice = { +export type UnknownDevice = BaseDevice & { type: 'unacquired'; - id?: null; - path: string; /** @deprecated, use features.label instead */ - label: string; + label: 'Unacquired device'; + id?: typeof undefined; error?: typeof undefined; features?: typeof undefined; firmware?: typeof undefined; firmwareRelease?: typeof undefined; firmwareType?: typeof undefined; - name: string; color?: typeof undefined; status?: typeof undefined; mode?: typeof undefined; @@ -100,20 +119,20 @@ export type UnknownDevice = { state?: typeof undefined; unavailableCapabilities?: typeof undefined; availableTranslations?: typeof undefined; + transportSessionOwner?: string; + transportDescriptorType?: typeof undefined; }; -export type UnreadableDevice = { +export type UnreadableDevice = BaseDevice & { type: 'unreadable'; - id?: null; - path: string; /** @deprecated, use features.label instead */ - label: string; + label: 'Unreadable device'; error: string; + id?: typeof undefined; features?: typeof undefined; firmware?: typeof undefined; firmwareRelease?: typeof undefined; firmwareType?: typeof undefined; - name: string; color?: typeof undefined; status?: typeof undefined; mode?: typeof undefined; @@ -121,6 +140,8 @@ export type UnreadableDevice = { state?: typeof undefined; unavailableCapabilities?: typeof undefined; availableTranslations?: typeof undefined; + transportSessionOwner?: undefined; + transportDescriptorType: Descriptor['type']; }; export type Device = KnownDevice | UnknownDevice | UnreadableDevice; diff --git a/packages/connect/src/types/params.ts b/packages/connect/src/types/params.ts index 22ad6d7455c..21b86638679 100644 --- a/packages/connect/src/types/params.ts +++ b/packages/connect/src/types/params.ts @@ -1,17 +1,18 @@ // API params import { Type, TSchema, Static } from '@trezor/schema-utils'; -import { DeviceState } from './device'; + +import { DeviceState, DeviceUniquePath } from './device'; import { ErrorCode } from '../constants/errors'; export interface DeviceIdentity { - path?: string; - state?: string | DeviceState; + path?: DeviceUniquePath; + state?: DeviceState; instance?: number; } export interface CommonParams { - device?: DeviceIdentity; + device?: DeviceIdentity & { state?: DeviceState | string }; // Note: state as string should be removed https://github.com/trezor/trezor-suite/issues/12710 useEmptyPassphrase?: boolean; useEventListener?: boolean; // this param is set automatically in factory allowSeedlessDevice?: boolean; @@ -20,6 +21,11 @@ export interface CommonParams { skipFinalReload?: boolean; useCardanoDerivation?: boolean; chunkify?: boolean; + /** + * internal flag. if set to true, call will only return info about the method, not execute it. + * todo: this should be moved to another argument instead of mixing this with params + */ + __info?: boolean; } export type Params = CommonParams & T & { bundle?: undefined }; diff --git a/packages/connect/src/types/settings.ts b/packages/connect/src/types/settings.ts index 842b2fdbe66..867cc44f6a9 100644 --- a/packages/connect/src/types/settings.ts +++ b/packages/connect/src/types/settings.ts @@ -13,27 +13,22 @@ export interface ConnectSettingsPublic { manifest?: Manifest; connectSrc?: string; debug?: boolean; - hostLabel?: string; - hostIcon?: string; popup?: boolean; transportReconnect?: boolean; - webusb?: boolean; // deprecated transports?: (Transport['name'] | Transport | (new (...args: any[]) => Transport))[]; pendingTransportEvent?: boolean; lazyLoad?: boolean; interactionTimeout?: number; trustedHost: boolean; - coreMode?: 'auto' | 'popup' | 'iframe'; - /* _extendWebextensionLifetime features makes the service worker in @trezor/connect-webextension stay alive longer */ - _extendWebextensionLifetime?: boolean; /** * for internal use only! * in some exotic setups (suite-web where iframe is embedded locally), you might need to tell connect where it should search for sessions background shared-worker */ - _sessionsBackgroundUrl?: string; - deeplinkOpen?: (url: string) => void; - deeplinkCallbackUrl?: string; - deeplinkUrl?: string; + _sessionsBackgroundUrl?: null | string; + // URL for binary files such as firmware, may be local or remote + binFilesBaseUrl?: string; + // enable firmware hash check automatically when device connects. Requires binFilesBaseUrl to be set. + enableFirmwareHashCheck?: boolean; } // internal part, not to be accepted from .init() @@ -53,4 +48,23 @@ export interface ConnectSettingsInternal { useCoreInPopup?: boolean; } -export type ConnectSettings = ConnectSettingsPublic & ConnectSettingsInternal; +export interface ConnectSettingsWeb { + hostLabel?: string; + hostIcon?: string; + coreMode?: 'auto' | 'popup' | 'iframe' | 'deeplink'; +} +export interface ConnectSettingsWebextension { + /** _extendWebextensionLifetime features makes the service worker in @trezor/connect-webextension stay alive longer */ + _extendWebextensionLifetime?: boolean; +} +export interface ConnectSettingsMobile { + deeplinkUrl: string; + deeplinkOpen?: (url: string) => void; + deeplinkCallbackUrl?: string; +} + +export type ConnectSettings = ConnectSettingsPublic & + ConnectSettingsInternal & + ConnectSettingsWeb & + ConnectSettingsWebextension & + ConnectSettingsMobile; diff --git a/packages/connect/src/types/utils.ts b/packages/connect/src/types/utils.ts index f2f63f42afb..0c0a70d8631 100644 --- a/packages/connect/src/types/utils.ts +++ b/packages/connect/src/types/utils.ts @@ -18,3 +18,6 @@ export type MessageFactoryFn = UnionToIntersection< ) => { event: Group; type: Event['type']; payload: undefined } : never >; + +export const typedObjectKeys = >(obj: T): Array => + Object.keys(obj) as Array; diff --git a/packages/connect/src/utils/__fixtures__/accountUtils.ts b/packages/connect/src/utils/__fixtures__/accountUtils.ts index bb875aacee5..367b60e0154 100644 --- a/packages/connect/src/utils/__fixtures__/accountUtils.ts +++ b/packages/connect/src/utils/__fixtures__/accountUtils.ts @@ -2,7 +2,6 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import coinsJSONEth from '@trezor/connect-common/files/coins-eth.json'; import { getAccountLabel, isUtxoBased } from '../accountUtils'; - import { parseCoinsJson, getBitcoinNetwork, diff --git a/packages/connect/src/utils/__fixtures__/ethereumUtils.ts b/packages/connect/src/utils/__fixtures__/ethereumUtils.ts index d796759e323..cb2e164f79d 100644 --- a/packages/connect/src/utils/__fixtures__/ethereumUtils.ts +++ b/packages/connect/src/utils/__fixtures__/ethereumUtils.ts @@ -2,7 +2,6 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import coinsJSONEth from '@trezor/connect-common/files/coins-eth.json'; import { getNetworkLabel } from '../ethereumUtils'; - import { parseCoinsJson, getEthereumNetwork } from '../../data/coinInfo'; parseCoinsJson({ diff --git a/packages/connect/src/utils/__fixtures__/formatUtils.ts b/packages/connect/src/utils/__fixtures__/formatUtils.ts index 52d9a6fd123..1bcab841b21 100644 --- a/packages/connect/src/utils/__fixtures__/formatUtils.ts +++ b/packages/connect/src/utils/__fixtures__/formatUtils.ts @@ -1,7 +1,6 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import { formatAmount } from '../formatUtils'; - import { parseCoinsJson, getBitcoinNetwork } from '../../data/coinInfo'; parseCoinsJson(coinsJSON); diff --git a/packages/connect/src/utils/__tests__/accountUtils.test.ts b/packages/connect/src/utils/__tests__/accountUtils.test.ts index 52293b79030..6136ef991a1 100644 --- a/packages/connect/src/utils/__tests__/accountUtils.test.ts +++ b/packages/connect/src/utils/__tests__/accountUtils.test.ts @@ -1,5 +1,4 @@ import { getAccountLabel, isUtxoBased } from '../accountUtils'; - import * as fixtures from '../__fixtures__/accountUtils'; describe('utils/accountUtils', () => { @@ -20,6 +19,5 @@ describe('utils/accountUtils', () => { // todo: describe.skip('getAccountAddressN', () => {}); - describe.skip('getAccountLabel', () => {}); describe.skip('getPublicKeyLabel', () => {}); }); diff --git a/packages/connect/src/utils/__tests__/addressUtils.test.ts b/packages/connect/src/utils/__tests__/addressUtils.test.ts index 392d87fb38f..2676b5e00d4 100644 --- a/packages/connect/src/utils/__tests__/addressUtils.test.ts +++ b/packages/connect/src/utils/__tests__/addressUtils.test.ts @@ -1,4 +1,5 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; + import { parseCoinsJson, getBitcoinNetwork } from '../../data/coinInfo'; import * as utils from '../addressUtils'; import * as fixtures from '../__fixtures__/addressUtils'; diff --git a/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts b/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts index 6ea27f6dd3e..7340ea089c5 100644 --- a/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts +++ b/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts @@ -2,7 +2,6 @@ import coinsJSON from '@trezor/connect-common/files/coins.json'; import coinsJSONEth from '@trezor/connect-common/files/coins-eth.json'; import { parseCoinsJson, getAllNetworks } from '../../data/coinInfo'; - import { getUnavailableCapabilities, parseCapabilities, @@ -118,16 +117,16 @@ describe('utils/deviceFeaturesUtils', () => { const featT2B1 = { major_version: 2, minor_version: 6, - patch_version: 1, + patch_version: 2, capabilities: undefined, internal_model: DeviceModelInternal.T2B1, } as unknown as Features; featT2B1.capabilities = parseCapabilities(featT2B1); it('default T1B1', () => { - const coins = getAllNetworks(); + const coins2 = getAllNetworks(); - expect(getUnavailableCapabilities(featT1B1, coins)).toEqual({ + expect(getUnavailableCapabilities(featT1B1, coins2)).toEqual({ ada: 'no-support', tada: 'no-support', bnb: 'update-required', @@ -135,6 +134,7 @@ describe('utils/deviceFeaturesUtils', () => { eos: 'no-support', maid: 'no-capability', pol: 'update-required', + op: 'update-required', omni: 'no-capability', ppc: 'update-required', sol: 'no-support', @@ -165,9 +165,9 @@ describe('utils/deviceFeaturesUtils', () => { }); it('default T2T1', () => { - const coins = getAllNetworks(); + const coins2 = getAllNetworks(); - expect(getUnavailableCapabilities(featT2T1, coins)).toEqual({ + expect(getUnavailableCapabilities(featT2T1, coins2)).toEqual({ replaceTransaction: 'update-required', amountUnit: 'update-required', bnb: 'update-required', @@ -176,6 +176,7 @@ describe('utils/deviceFeaturesUtils', () => { 'eip712-domain-only': 'update-required', maid: 'no-capability', pol: 'update-required', + op: 'update-required', omni: 'no-capability', taproot: 'update-required', tsep: 'update-required', @@ -191,9 +192,9 @@ describe('utils/deviceFeaturesUtils', () => { }); it('default T2B1', () => { - const coins = getAllNetworks(); + const coins2 = getAllNetworks(); - expect(getUnavailableCapabilities(featT2B1, coins)).toEqual({ + expect(getUnavailableCapabilities(featT2B1, coins2)).toEqual({ breeze: 'no-support', btg: 'no-support', tbtg: 'no-support', @@ -221,53 +222,57 @@ describe('utils/deviceFeaturesUtils', () => { }); }); - it('T2T1 update-required', done => { - jest.resetModules(); - - jest.mock('../../data/config', () => ({ - __esModule: true, - config: { - supportedFirmware: [ - { - min: { T1B1: '0', T2T1: '2.99.99' }, - capabilities: ['newCapabilityOrFeature'], - }, - ], - }, - })); - - import('../deviceFeaturesUtils').then(({ getUnavailableCapabilities }) => { - // added new capability - expect(getUnavailableCapabilities(featT2T1, coins)).toEqual({ - newCapabilityOrFeature: 'update-required', + it('T2T1 update-required', () => + new Promise(done => { + jest.resetModules(); + + jest.mock('../../data/config', () => ({ + __esModule: true, + config: { + supportedFirmware: [ + { + min: { T1B1: '0', T2T1: '2.99.99' }, + capabilities: ['newCapabilityOrFeature'], + }, + ], + }, + })); + + // eslint-disable-next-line @typescript-eslint/no-shadow + import('../deviceFeaturesUtils').then(({ getUnavailableCapabilities }) => { + // added new capability + expect(getUnavailableCapabilities(featT2T1, coins)).toEqual({ + newCapabilityOrFeature: 'update-required', + }); + done(); }); - done(); - }); - }); - - it('T2T1 no-support', done => { - jest.resetModules(); - - jest.mock('../../data/config', () => ({ - __esModule: true, - config: { - supportedFirmware: [ - { - min: { T1B1: '0', T2T1: '0' }, - capabilities: ['newCapabilityOrFeature'], - }, - ], - }, })); - import('../deviceFeaturesUtils').then(({ getUnavailableCapabilities }) => { - // added new capability - expect(getUnavailableCapabilities(featT2T1, coins)).toEqual({ - newCapabilityOrFeature: 'no-support', + it('T2T1 no-support', () => + new Promise(done => { + jest.resetModules(); + + jest.mock('../../data/config', () => ({ + __esModule: true, + config: { + supportedFirmware: [ + { + min: { T1B1: '0', T2T1: '0' }, + capabilities: ['newCapabilityOrFeature'], + }, + ], + }, + })); + + // eslint-disable-next-line @typescript-eslint/no-shadow + import('../deviceFeaturesUtils').then(({ getUnavailableCapabilities }) => { + // added new capability + expect(getUnavailableCapabilities(featT2T1, coins)).toEqual({ + newCapabilityOrFeature: 'no-support', + }); + done(); }); - done(); - }); - }); + })); it('handles duplicated shortcuts correctly, ', () => { const customCoins = [ diff --git a/packages/connect/src/utils/__tests__/ethereumUtils.test.ts b/packages/connect/src/utils/__tests__/ethereumUtils.test.ts index b2b628f5594..4d9eff7f02c 100644 --- a/packages/connect/src/utils/__tests__/ethereumUtils.test.ts +++ b/packages/connect/src/utils/__tests__/ethereumUtils.test.ts @@ -1,5 +1,4 @@ import { getNetworkLabel } from '../ethereumUtils'; - import * as fixtures from '../__fixtures__/ethereumUtils'; describe('utils/ethereumUtils', () => { diff --git a/packages/connect/src/utils/__tests__/formatUtils.test.ts b/packages/connect/src/utils/__tests__/formatUtils.test.ts index a37413155c5..5d9d24ecbc7 100644 --- a/packages/connect/src/utils/__tests__/formatUtils.test.ts +++ b/packages/connect/src/utils/__tests__/formatUtils.test.ts @@ -1,5 +1,4 @@ import { formatAmount } from '../formatUtils'; - import * as fixtures from '../__fixtures__/formatUtils'; describe('utils/formatUtils', () => { diff --git a/packages/connect/src/utils/addressUtils.ts b/packages/connect/src/utils/addressUtils.ts index 7355dc8a2f6..339ff1f4672 100644 --- a/packages/connect/src/utils/addressUtils.ts +++ b/packages/connect/src/utils/addressUtils.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/utils/addressUtils.js import { address as BitcoinJSAddress } from '@trezor/utxo-lib'; + import type { BitcoinNetworkInfo } from '../types'; // Base58 @@ -10,7 +11,7 @@ const isValidBase58Address = (address: string, network: BitcoinNetworkInfo['netw if (decoded.version !== network.pubKeyHash && decoded.version !== network.scriptHash) { return false; } - } catch (e) { + } catch { return false; } @@ -24,7 +25,7 @@ const isValidBech32Address = (address: string, network: BitcoinNetworkInfo['netw if (decoded.prefix !== network.bech32) { return false; } - } catch (e) { + } catch { return false; } diff --git a/packages/connect/src/utils/assetUtils.ts b/packages/connect/src/utils/assetUtils.ts index be07387c59b..1a6b182bc47 100644 --- a/packages/connect/src/utils/assetUtils.ts +++ b/packages/connect/src/utils/assetUtils.ts @@ -1,8 +1,9 @@ +import { isArrayMember } from '@trezor/utils'; + import { DeviceModelInternal } from '../types'; -const isDeviceModel = (model: string): model is DeviceModelInternal => { - return Object.values(DeviceModelInternal).includes(model as DeviceModelInternal); -}; +const isDeviceModel = (model: string): model is DeviceModelInternal => + isArrayMember(model, Object.values(DeviceModelInternal)); const firmwareAssets: Record = { [DeviceModelInternal.T1B1]: require('@trezor/connect-common/files/firmware/t1b1/releases.json'), @@ -10,9 +11,10 @@ const firmwareAssets: Record = { [DeviceModelInternal.T2B1]: require('@trezor/connect-common/files/firmware/t2b1/releases.json'), [DeviceModelInternal.T3B1]: require('@trezor/connect-common/files/firmware/t3b1/releases.json'), [DeviceModelInternal.T3T1]: require('@trezor/connect-common/files/firmware/t3t1/releases.json'), + [DeviceModelInternal.T3W1]: require('@trezor/connect-common/files/firmware/t3w1/releases.json'), }; -export const getAssetByUrl = (url: string) => { +export const tryLocalAssetRequire = (url: string) => { const fileUrl = url.split('?')[0]; switch (fileUrl) { diff --git a/packages/connect/src/utils/assets.native.ts b/packages/connect/src/utils/assets.native.ts index f0bac432de7..d5564403e9d 100644 --- a/packages/connect/src/utils/assets.native.ts +++ b/packages/connect/src/utils/assets.native.ts @@ -1,3 +1,3 @@ -import { getAssetByUrl } from './assetUtils'; +import { tryLocalAssetRequire } from './assetUtils'; -export const httpRequest = (url: string, _type: string): any => getAssetByUrl(url); +export const httpRequest = (url: string, _type: string): any => tryLocalAssetRequire(url); diff --git a/packages/connect/src/utils/assets.ts b/packages/connect/src/utils/assets.ts index 2439e3ab2e3..457970ada86 100644 --- a/packages/connect/src/utils/assets.ts +++ b/packages/connect/src/utils/assets.ts @@ -2,8 +2,9 @@ import fetch from 'cross-fetch'; import { promises as fs } from 'fs'; + import { httpRequest as browserHttpRequest } from './assets-browser'; -import { getAssetByUrl } from './assetUtils'; +import { tryLocalAssetRequire } from './assetUtils'; if (global && typeof global.fetch !== 'function') { global.fetch = fetch; @@ -36,7 +37,7 @@ export function httpRequest( options?: RequestInit, skipLocalForceDownload?: boolean, ) { - const asset = skipLocalForceDownload ? null : getAssetByUrl(url); + const asset = skipLocalForceDownload ? null : tryLocalAssetRequire(url); if (!asset) { return /^https?/.test(url) ? browserHttpRequest(url, type, options) : fs.readFile(url); diff --git a/packages/connect/src/utils/deviceFeaturesUtils.ts b/packages/connect/src/utils/deviceFeaturesUtils.ts index 56668275b10..a110c86c500 100644 --- a/packages/connect/src/utils/deviceFeaturesUtils.ts +++ b/packages/connect/src/utils/deviceFeaturesUtils.ts @@ -1,4 +1,5 @@ -import { versionUtils } from '@trezor/utils'; +import { isArrayMember, versionUtils } from '@trezor/utils'; + import { PROTO } from '../constants'; import { config } from '../data/config'; import { Features, CoinInfo, UnavailableCapabilities, DeviceModelInternal } from '../types'; @@ -63,7 +64,7 @@ export const getUnavailableCapabilities = (features: Features, coins: CoinInfo[] } else { const occurrences = coins.filter(coin => shortcut == coin.shortcut.toLowerCase()); const allUnsupported = occurrences.every( - info => !info.support || info.support[key] === false, + info2 => !info2.support || info2.support[key] === false, ); if (allUnsupported) { @@ -106,7 +107,7 @@ export const getUnavailableCapabilities = (features: Features, coins: CoinInfo[] return !capabilities.includes('Capability_Solana'); } - return !capabilities.includes(`Capability_${info.name}` as PROTO.Capability); + return !isArrayMember(`Capability_${info.name}`, capabilities); }); // add unavailable coins to list @@ -128,7 +129,7 @@ export const getUnavailableCapabilities = (features: Features, coins: CoinInfo[] // 4. check if firmware version is in range of capabilities in "config.supportedFirmware" config.supportedFirmware.forEach(s => { if (!s.capabilities) return; - const min = s.min ? s.min[key] : null; + const min = s.min ? (s.min as Record)[key] : null; const max = s.max ? s.max[key] : null; if (min && (min === '0' || versionUtils.isNewer(min, fw))) { const value = min === '0' ? 'no-support' : 'update-required'; diff --git a/packages/connect/src/utils/firmwareUtils.ts b/packages/connect/src/utils/firmwareUtils.ts index 73d82126e05..85a7656fccf 100644 --- a/packages/connect/src/utils/firmwareUtils.ts +++ b/packages/connect/src/utils/firmwareUtils.ts @@ -1,4 +1,5 @@ import { versionUtils } from '@trezor/utils'; + import type { Features, StrictFeatures, FirmwareRelease, VersionArray } from '../types'; export const isStrictFeatures = (extFeatures: Features): extFeatures is StrictFeatures => diff --git a/packages/connect/src/utils/formatUtils.ts b/packages/connect/src/utils/formatUtils.ts index 47d92ae34f5..dce493ca259 100644 --- a/packages/connect/src/utils/formatUtils.ts +++ b/packages/connect/src/utils/formatUtils.ts @@ -1,6 +1,7 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/utils/formatUtils.js import { BigNumber } from '@trezor/utils/src/bigNumber'; + import type { CoinInfo } from '../types'; export const formatAmount = (n: string, coinInfo: CoinInfo) => diff --git a/packages/connect/src/utils/hdnodeUtils.ts b/packages/connect/src/utils/hdnodeUtils.ts index 6f4b29f5067..87a4c2e92b5 100644 --- a/packages/connect/src/utils/hdnodeUtils.ts +++ b/packages/connect/src/utils/hdnodeUtils.ts @@ -2,6 +2,7 @@ import { bip32 } from '@trezor/utxo-lib'; import type { Network, BIP32Interface } from '@trezor/utxo-lib'; + import { PROTO, ERRORS } from '../constants'; const pubNode2bjsNode = (node: PROTO.HDNodeType, network?: Network) => { diff --git a/packages/connect-web/src/utils/proxy-event-emitter.ts b/packages/connect/src/utils/proxy-event-emitter.ts similarity index 97% rename from packages/connect-web/src/utils/proxy-event-emitter.ts rename to packages/connect/src/utils/proxy-event-emitter.ts index 77c0ea2b036..ad92f7b3728 100644 --- a/packages/connect-web/src/utils/proxy-event-emitter.ts +++ b/packages/connect/src/utils/proxy-event-emitter.ts @@ -4,7 +4,7 @@ import EventEmitter from 'events'; * ProxyEventEmitter is an EventEmitter that allows to use multiple EventEmitters as one * This is used in connect-web to allow switching between iframe and core-in-popup implementations */ -export default class ProxyEventEmitter implements EventEmitter { +export class ProxyEventEmitter implements EventEmitter { private eventEmitters: EventEmitter[]; constructor(eventEmitters: EventEmitter[]) { diff --git a/packages/connect/src/utils/uiPromiseManager.ts b/packages/connect/src/utils/uiPromiseManager.ts index b80349afcb8..18c73e37b68 100644 --- a/packages/connect/src/utils/uiPromiseManager.ts +++ b/packages/connect/src/utils/uiPromiseManager.ts @@ -1,6 +1,8 @@ -import { DEVICE, UiPromise, AnyUiPromise, UiPromiseCreator, UiPromiseResponse } from '../events'; import { createDeferred, arrayPartition } from '@trezor/utils'; +import { DEVICE, UiPromise, AnyUiPromise, UiPromiseCreator, UiPromiseResponse } from '../events'; +import { DeviceUniquePath } from '../types/device'; + export const createUiPromiseManager = (interactionTimeout: () => void) => { let _uiPromises: AnyUiPromise[] = []; @@ -36,16 +38,16 @@ export const createUiPromiseManager = (interactionTimeout: () => void) => { _uiPromises = []; }; - const disconnected = (devicePath: string) => { + const disconnected = (devicePath: DeviceUniquePath) => { const [toResolve, toKeep] = arrayPartition( _uiPromises, (p): p is UiPromise => - p.device?.getDevicePath() === devicePath && p.id === DEVICE.DISCONNECT, + p.device?.getUniquePath() === devicePath && p.id === DEVICE.DISCONNECT, ); toResolve.forEach(p => p.resolve({ type: DEVICE.DISCONNECT })); _uiPromises = toKeep; - return !!toResolve.length || toKeep.some(p => p.device?.getDevicePath() === devicePath); + return !!toResolve.length || toKeep.some(p => p.device?.getUniquePath() === devicePath); }; const get = (type: T) => { diff --git a/packages/connect/tsconfig.json b/packages/connect/tsconfig.json index 06ad952f548..9461881cd2a 100644 --- a/packages/connect/tsconfig.json +++ b/packages/connect/tsconfig.json @@ -12,6 +12,7 @@ { "path": "../transport" }, { "path": "../utils" }, { "path": "../utxo-lib" }, + { "path": "../eslint" }, { "path": "../trezor-user-env-link" } ] } diff --git a/packages/connect/tsconfig.lib.json b/packages/connect/tsconfig.lib.json index dca0a4a6e3b..d9412084d95 100644 --- a/packages/connect/tsconfig.lib.json +++ b/packages/connect/tsconfig.lib.json @@ -34,6 +34,9 @@ { "path": "../utxo-lib" }, + { + "path": "../eslint" + }, { "path": "../trezor-user-env-link" } diff --git a/packages/crypto-utils/package.json b/packages/crypto-utils/package.json index 8d0c3bd654f..32f127f095b 100644 --- a/packages/crypto-utils/package.json +++ b/packages/crypto-utils/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" } diff --git a/packages/device-utils/package.json b/packages/device-utils/package.json index d26e3cdd7d2..8cef35c13bc 100644 --- a/packages/device-utils/package.json +++ b/packages/device-utils/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "test:unit": "yarn g:jest -c ../../jest.config.base.js" diff --git a/packages/device-utils/src/bootloaderUtils.ts b/packages/device-utils/src/bootloaderUtils.ts index 016e67f5a72..6e035046f55 100644 --- a/packages/device-utils/src/bootloaderUtils.ts +++ b/packages/device-utils/src/bootloaderUtils.ts @@ -1,10 +1,10 @@ -import { Device } from '@trezor/connect'; - import { isDeviceInBootloaderMode } from './modeUtils'; +import { PartialDevice } from './types'; -export const getBootloaderHash = (device?: Device) => device?.features?.bootloader_hash || ''; +export const getBootloaderHash = (device?: PartialDevice) => + device?.features?.bootloader_hash || ''; -export const getBootloaderVersion = (device?: Device) => { +export const getBootloaderVersion = (device?: PartialDevice) => { if (!device?.features) { return ''; } diff --git a/packages/device-utils/src/firmwareUtils.ts b/packages/device-utils/src/firmwareUtils.ts index 1a93ceaf753..f595bb6e514 100644 --- a/packages/device-utils/src/firmwareUtils.ts +++ b/packages/device-utils/src/firmwareUtils.ts @@ -1,10 +1,11 @@ -import { FirmwareType, Device, VersionArray } from '@trezor/connect'; +import { FirmwareType, VersionArray } from '@trezor/connect'; import { isDeviceInBootloaderMode } from './modeUtils'; +import { PartialDevice } from './types'; -export const getFirmwareRevision = (device?: Device) => device?.features?.revision || ''; +export const getFirmwareRevision = (device?: PartialDevice) => device?.features?.revision || ''; -export const getFirmwareVersionArray = (device?: Device): VersionArray | null => { +export const getFirmwareVersionArray = (device?: PartialDevice): VersionArray | null => { if (!device?.features) { return null; } @@ -19,12 +20,15 @@ export const getFirmwareVersionArray = (device?: Device): VersionArray | null => return [features.major_version, features.minor_version, features.patch_version]; }; -export const getFirmwareVersion = (device?: Device) => { +export const getFirmwareVersion = ( + device?: PartialDevice, +): '' | `${number}.${number}.${number}` => { if (!device?.features) { return ''; } const { features } = device; if (isDeviceInBootloaderMode(device)) { + // @ts-expect-error fw_minor and fw_patch is imho always defined. maybe only for some very old firmwares only major version is defined. return features.fw_major ? `${features.fw_major}.${features.fw_minor}.${features.fw_patch}` : ''; @@ -34,9 +38,9 @@ export const getFirmwareVersion = (device?: Device) => { }; // This can give a false negative in bootloader mode for T1B1 and T2T1. -export const hasBitcoinOnlyFirmware = (device?: Device) => +export const hasBitcoinOnlyFirmware = (device?: PartialDevice) => device?.firmwareType === FirmwareType.BitcoinOnly; // Bitcoin-only device with Universal firmware is treated as a regular device. -export const isBitcoinOnlyDevice = (device?: Device) => +export const isBitcoinOnlyDevice = (device?: PartialDevice) => !!device?.features?.unit_btconly && device?.firmwareType !== FirmwareType.Regular; diff --git a/packages/device-utils/src/modeUtils.ts b/packages/device-utils/src/modeUtils.ts index 4694d9cf593..8efde1e9cc8 100644 --- a/packages/device-utils/src/modeUtils.ts +++ b/packages/device-utils/src/modeUtils.ts @@ -1,8 +1,9 @@ -import { Device } from '@trezor/connect'; +import { PartialDevice } from './types'; -export const isDeviceInBootloaderMode = (device?: Device) => !!device?.features?.bootloader_mode; +export const isDeviceInBootloaderMode = (device?: PartialDevice) => + !!device?.features?.bootloader_mode; -export const getDeviceMode = (device?: Device) => { +export const getDeviceMode = (device?: PartialDevice) => { if (device?.features?.bootloader_mode) return 'bootloader'; if (!device?.features?.initialized) return 'initialize'; if (device?.features?.no_backup) return 'seedless'; diff --git a/packages/device-utils/src/types.ts b/packages/device-utils/src/types.ts new file mode 100644 index 00000000000..9dc084d4f30 --- /dev/null +++ b/packages/device-utils/src/types.ts @@ -0,0 +1,6 @@ +import { Device } from '@trezor/connect'; + +export type PartialDevice = { + features?: Device['features']; + firmwareType?: Device['firmwareType']; +}; diff --git a/packages/dom-utils/package.json b/packages/dom-utils/package.json index 776bc5c2716..8ef95fdd525 100644 --- a/packages/dom-utils/package.json +++ b/packages/dom-utils/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" } diff --git a/packages/e2e-utils/package.json b/packages/e2e-utils/package.json index 07f2539c3bb..f60299a36ba 100644 --- a/packages/e2e-utils/package.json +++ b/packages/e2e-utils/package.json @@ -6,16 +6,12 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" }, "dependencies": { - "express": "^4.21.0", - "uuid": "^10.0.0", + "express": "^4.21.1", + "uuid": "^11.0.2", "ws": "^8.18.0" - }, - "devDependencies": { - "@types/uuid": "^10.0.0" } } diff --git a/packages/e2e-utils/src/mocks/bridge.ts b/packages/e2e-utils/src/mocks/bridge.ts index 1a813c11271..c6c7037c4bf 100644 --- a/packages/e2e-utils/src/mocks/bridge.ts +++ b/packages/e2e-utils/src/mocks/bridge.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck /* eslint-disable no-console */ diff --git a/packages/env-utils/package.json b/packages/env-utils/package.json index 42552faaba4..a84f6b4f01f 100644 --- a/packages/env-utils/package.json +++ b/packages/env-utils/package.json @@ -21,7 +21,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", diff --git a/packages/eslint/eslint.config.mjs b/packages/eslint/eslint.config.mjs new file mode 100644 index 00000000000..0c2379d00cf --- /dev/null +++ b/packages/eslint/eslint.config.mjs @@ -0,0 +1,14 @@ +import { eslint } from './src/index.mjs'; + +// This config is just for this package. +// The exported config, that shall be used in other packages, is in src/index.mjs. +export default [ + ...eslint, + { + files: ['**/*.mjs'], // disable for all other config-files in this package + rules: { + 'import/no-default-export': 'off', + 'import/no-extraneous-dependencies': 'off', + }, + }, +]; diff --git a/packages/eslint/package.json b/packages/eslint/package.json new file mode 100644 index 00000000000..4e0ff84d559 --- /dev/null +++ b/packages/eslint/package.json @@ -0,0 +1,24 @@ +{ + "name": "@trezor/eslint", + "version": "1.0.0", + "license": "See LICENSE.md in repo root", + "private": true, + "sideEffects": false, + "main": "src/index.mjs", + "scripts": { + "depcheck": "yarn g:depcheck", + "type-check": "yarn g:tsc --build" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "eslint": "^9.13.0", + "eslint-plugin-chai-friendly": "^1.0.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest": "^28.8.3", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-local-rules": "^3.0.2", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "typescript-eslint": "^8.12.2" + } +} diff --git a/packages/eslint/src/chaiFriendlyConfig.mjs b/packages/eslint/src/chaiFriendlyConfig.mjs new file mode 100644 index 00000000000..89a8328c907 --- /dev/null +++ b/packages/eslint/src/chaiFriendlyConfig.mjs @@ -0,0 +1,13 @@ +import pluginChaiFriendly from 'eslint-plugin-chai-friendly'; + +export const chaiFriendlyConfig = [ + { + plugins: { 'chai-friendly': pluginChaiFriendly }, + rules: { + 'no-unused-expressions': 'off', // disable original rule + // However this does not work for @typescript-eslint/no-unused-expressions + // See: https://github.com/ihordiachenko/eslint-plugin-chai-friendly/issues/41 + 'chai-friendly/no-unused-expressions': 'error', + }, + }, +]; diff --git a/packages/eslint/src/importConfig.mjs b/packages/eslint/src/importConfig.mjs new file mode 100644 index 00000000000..479e8252a9d --- /dev/null +++ b/packages/eslint/src/importConfig.mjs @@ -0,0 +1,73 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import pluginImport from 'eslint-plugin-import'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export const globalNoExtraneousDependenciesDevDependencies = [ + // ---------------------------------------------------------------- + // !!! DO NOT PUT STUFF THAT BELONGS TO THE PACKAGE ITSELF HERE !!! + // Only shared stuff (like tests.*.ts(x) or fixtures shall be here) + // ---------------------------------------------------------------- + '**/*fixtures*/**', + '**/*.test.{tsx,ts,js}', + '**/eslint.config.mjs', + + '**/*e2e/**', // Todo: This shall be only in packages that has e2e tests +]; + +export const importConfig = [ + pluginImport.flatConfigs.recommended, + { + settings: { + 'import/ignore': ['node_modules', '\\.(coffee|scss|css|less|hbs|svg|json)$'], + 'import/resolver': { + node: { + paths: [path.resolve(__dirname, 'eslint-rules')], + }, + }, + }, + rules: { + // Additional + 'import/no-default-export': 'error', // We don't want to use default exports, always use named exports + 'import/no-anonymous-default-export': [ + 'error', + { + allowArray: true, + allowLiteral: true, + allowObject: true, + }, + ], + 'import/order': [ + 1, + { + groups: [['builtin', 'external'], 'internal', ['sibling', 'parent']], + pathGroups: [ + { + pattern: 'react*', + group: 'external', + position: 'before', + }, + { pattern: '@trezor/**', group: 'internal' }, // Translates to /packages/** */ + { pattern: '@suite-native/**', group: 'internal' }, + { pattern: '@suite-common/**', group: 'internal' }, + { pattern: 'src/**', group: 'internal', position: 'after' }, + ], + pathGroupsExcludedImportTypes: ['internal', 'react'], + 'newlines-between': 'always', + }, + ], + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: globalNoExtraneousDependenciesDevDependencies, + includeTypes: true, + }, + ], + + // Offs + 'import/no-unresolved': 'off', // Does not work with Babel react-native to react-native-web + }, + }, +]; diff --git a/packages/eslint/src/index.mjs b/packages/eslint/src/index.mjs new file mode 100644 index 00000000000..5d36b2de05b --- /dev/null +++ b/packages/eslint/src/index.mjs @@ -0,0 +1,68 @@ +import globals from 'globals'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; + +import { reactConfig } from './reactConfig.mjs'; +import { javascriptConfig } from './javascriptConfig.mjs'; +import { typescriptConfig } from './typescriptConfig.mjs'; +import { importConfig, globalNoExtraneousDependenciesDevDependencies } from './importConfig.mjs'; +import { jestConfig } from './jestConfig.mjs'; +import { javascriptNodejsConfig } from './javascriptNodejsConfig.mjs'; +import { localRulesConfig } from './localRulesConfig.mjs'; +import { chaiFriendlyConfig } from './chaiFriendlyConfig.mjs'; + +export { globalNoExtraneousDependenciesDevDependencies }; + +export const eslint = [ + { + ignores: [ + '**/.nx/*', + '**/lib/*', + '**/libDev/*', + '**/dist/*', + '**/coverage/*', + '**/build/*', + '**/build-electron/*', + '**/node_modules/*', + '**/public/*', + '**/ci/', + 'eslint-local-rules/*', + ], + }, + { files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] }, + { languageOptions: { globals: globals.browser } }, + { + languageOptions: { + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + + ...reactConfig, + ...javascriptConfig, + ...javascriptNodejsConfig, + ...typescriptConfig, + ...importConfig, + ...jestConfig, + ...localRulesConfig, + ...chaiFriendlyConfig, + + jsxA11y.flatConfigs.recommended, + + // Tests + { + files: ['**/__fixtures__/**/*'], + rules: { + 'import/no-default-export': 'off', // Todo: we have many default exports in fixtures, we shall get rid of them + }, + }, + + // ESLint config itself + { + files: ['eslint.config.mjs'], + rules: { + 'import/no-default-export': 'off', + }, + }, +]; diff --git a/packages/eslint/src/javascriptConfig.mjs b/packages/eslint/src/javascriptConfig.mjs new file mode 100644 index 00000000000..2869b9e8ea3 --- /dev/null +++ b/packages/eslint/src/javascriptConfig.mjs @@ -0,0 +1,91 @@ +import pluginJs from '@eslint/js'; + +export const javascriptConfig = [ + pluginJs.configs.recommended, + { + rules: { + // Additional rules + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'require-await': ['error'], + 'no-nested-ternary': 'error', + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: false, + object: false, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + 'no-label-var': 'error', // Disallow labels that share a name with a variable + 'no-undef-init': 'error', // Disallow initializing variables to undefined + 'no-restricted-syntax': [ + 'error', + { + message: + "Please don't use createAsyncThunk. Use createThunk from @suite-common/redux-utils instead.", + selector: "CallExpression[callee.name='createAsyncThunk']", + }, + { + message: + 'Please don\'t use getState directly. Always use strongly typed selector, because geState is typed as "any" and it\'s dangerous to use it directly.', + selector: + 'MemberExpression[property.type="Identifier"]:matches([object.callee.name="getState"])', + }, + { + message: + 'Do not assign "getState" directly. Always use strongly typed selector, because geState is typed as "any" and it\'s dangerous to use it directly.', + selector: + "VariableDeclarator[init.type='CallExpression']:matches([init.callee.name='getState'])", + }, + { + message: + 'Please don\'t use "state" directly because it\'s typed as "any". Always use it only as parameter for strongly typed selector function.', + selector: + "CallExpression[callee.name='useSelector'] MemberExpression[object.name='state']:matches([property.type='Identifier'])", + }, + ], + 'object-shorthand': [ + 'error', + 'always', + { + ignoreConstructors: false, + avoidQuotes: true, + }, + ], + 'no-useless-rename': [ + 'error', + { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }, + ], + 'prefer-numeric-literals': 'error', + 'padding-line-between-statements': [ + 'error', // Todo: deprecated, use @stylistic/eslint-plugin-js instead + { blankLine: 'always', prev: '*', next: 'return' }, + ], + + // Offs + 'no-undef': 'off', // Todo: write description + + // Offs for Node.js + 'no-sync': 'off', // disallow use of synchronous methods (off by default) + 'no-process-exit': 'off', // disallow process.exit() (on by default in the node environment) + }, + }, + { + files: ['**/*.js'], // Usually config files + rules: { + 'no-console': 'off', + }, + }, +]; diff --git a/packages/eslint/src/javascriptNodejsConfig.mjs b/packages/eslint/src/javascriptNodejsConfig.mjs new file mode 100644 index 00000000000..b98c1c6434d --- /dev/null +++ b/packages/eslint/src/javascriptNodejsConfig.mjs @@ -0,0 +1,18 @@ +export const javascriptNodejsConfig = [ + { + // These rules are specific to JavaScript running on Node.js. + rules: { + // Additional + 'handle-callback-err': 'error', // enforces error handling in callbacks (off by default) (on by default in the node environment) + 'no-mixed-requires': 'error', // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) + 'no-new-require': 'error', // disallow use of new operator with the require function (off by default) (on by default in the node environment) + 'no-path-concat': 'error', // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) + 'no-restricted-modules': 'error', // restrict usage of specified node modules (off by default) + 'eol-last': 'error', + + // Offs + 'no-sync': 'off', // disallow use of synchronous methods (off by default) + 'no-process-exit': 'off', // disallow process.exit() (on by default in the node environment) + }, + }, +]; diff --git a/packages/eslint/src/jestConfig.mjs b/packages/eslint/src/jestConfig.mjs new file mode 100644 index 00000000000..27579f01fed --- /dev/null +++ b/packages/eslint/src/jestConfig.mjs @@ -0,0 +1,19 @@ +import pluginJest from 'eslint-plugin-jest'; + +export const jestConfig = [ + pluginJest.configs['flat/recommended'], + { + rules: { + // Additions + // Enforce arrow functions only is afaik not possible. But this helps. + 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], + + // Offs + 'jest/valid-title': 'off', // This rule does not use Typescript and produces false positives + 'jest/valid-describe-callback': 'off', // This rule does not use Typescript and produces false positives + 'jest/no-disabled-tests': 'off', + 'jest/no-conditional-expect': 'off', // Todo: we shall solve this, this is bad practice + 'jest/expect-expect': 'off', // Todo: we have test with no assertions, this may be legit but it needs to be checked + }, + }, +]; diff --git a/packages/eslint/src/localRulesConfig.mjs b/packages/eslint/src/localRulesConfig.mjs new file mode 100644 index 00000000000..3eee2159afc --- /dev/null +++ b/packages/eslint/src/localRulesConfig.mjs @@ -0,0 +1,15 @@ +import pluginLocalRules from 'eslint-plugin-local-rules'; + +export const localRulesConfig = [ + { + plugins: { + 'local-rules': pluginLocalRules, + }, + rules: { + 'local-rules/no-override-ds-component': [ + 'error', + { packageNames: ['@trezor/components', '@trezor/product-components'] }, + ], + }, + }, +]; diff --git a/packages/eslint/src/reactConfig.mjs b/packages/eslint/src/reactConfig.mjs new file mode 100644 index 00000000000..839f3db9174 --- /dev/null +++ b/packages/eslint/src/reactConfig.mjs @@ -0,0 +1,34 @@ +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; + +export const reactConfig = [ + // React + pluginReact.configs.flat.recommended, + { + languageOptions: { + ...pluginReact.configs.flat.recommended.languageOptions, + }, + settings: { react: { version: 'detect' } }, + rules: { + // Additions + 'react/jsx-filename-extension': ['error', { extensions: ['.tsx', '.jsx'] }], + 'react/jsx-curly-brace-presence': ['warn', { props: 'never', children: 'never' }], + + // Offs + 'react/react-in-jsx-scope': 'off', // We are not importing React in every file + 'react/prop-types': 'off', // This rule is not needed when using TypeScript + 'react/display-name': 'off', // This is annoying for stuff like `forwardRef`. Todo: reconsider + 'no-prototype-builtins': 'off', // Todo: just temporary, reconsider to remove it + }, + }, + + // React Hooks + { + plugins: { 'react-hooks': pluginReactHooks }, + rules: { + ...pluginReactHooks.configs.recommended.rules, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + }, + }, +]; diff --git a/packages/eslint/src/typescriptConfig.mjs b/packages/eslint/src/typescriptConfig.mjs new file mode 100644 index 00000000000..97728af8195 --- /dev/null +++ b/packages/eslint/src/typescriptConfig.mjs @@ -0,0 +1,49 @@ +import tseslint from 'typescript-eslint'; + +export const typescriptConfig = [ + ...tseslint.configs.recommended, + { + rules: { + // Additional rules + '@typescript-eslint/no-use-before-define': ['error'], + '@typescript-eslint/no-shadow': [ + 'error', + { + builtinGlobals: false, + allow: ['_', 'error', 'resolve', 'reject', 'fetch'], + }, + ], + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [{ name: '.' }, { name: '..' }, { name: '../..' }], + patterns: ['@trezor/*/lib', '@trezor/*/lib/**'], + }, + ], + + // Additions from "plugin:@typescript-eslint/strict" (we may turn this on one day as a whole) + '@typescript-eslint/no-useless-constructor': ['error'], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + vars: 'all', + args: 'none', + ignoreRestSiblings: true, + varsIgnorePattern: '^_', + }, + ], + + // Offs + '@typescript-eslint/no-require-imports': 'off', // We just use require a lot (mostly for dynamic imports) + '@typescript-eslint/no-explicit-any': 'off', // Todo: write description + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + minimumDescriptionLength: 0, // Todo: reconsider + }, + ], + '@typescript-eslint/no-empty-object-type': 'off', // Todo: we shall solve this, this is bad practice + '@typescript-eslint/triple-slash-reference': 'off', // Todo: solve before merge + }, + }, +]; diff --git a/packages/eslint/tsconfig.json b/packages/eslint/tsconfig.json new file mode 100644 index 00000000000..c7ebe855e21 --- /dev/null +++ b/packages/eslint/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "libDev" }, + "references": [] +} diff --git a/packages/ipc-proxy/package.json b/packages/ipc-proxy/package.json index 510756be7d0..278259f2d72 100644 --- a/packages/ipc-proxy/package.json +++ b/packages/ipc-proxy/package.json @@ -16,7 +16,6 @@ "main": "src/index", "browser": "src/proxy", "scripts": { - "lint:js": "eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" diff --git a/packages/ipc-proxy/src/proxy-handler.ts b/packages/ipc-proxy/src/proxy-handler.ts index b1420d322b9..1ec24ec1a83 100644 --- a/packages/ipc-proxy/src/proxy-handler.ts +++ b/packages/ipc-proxy/src/proxy-handler.ts @@ -43,10 +43,11 @@ interface IpcMainHandlers { '/invoke': Parameters>; // methodName, ...params } +// Electron.IpcMainInvokeEvent narowed down only to properties we actually need export interface ElectronIpcMainInvokeEvent { senderFrame: { url: string; - }; + } | null; } export interface ElectronIpcMainEvent { diff --git a/packages/ipc-proxy/src/validateIpcMessage.ts b/packages/ipc-proxy/src/validateIpcMessage.ts index 821fd1ec171..5f997f706b5 100644 --- a/packages/ipc-proxy/src/validateIpcMessage.ts +++ b/packages/ipc-proxy/src/validateIpcMessage.ts @@ -1,6 +1,5 @@ import { ElectronIpcMainInvokeEvent } from './proxy-handler'; -// ipcEvent: Electron.IpcMainInvokeEvent export const validateIpcMessage = (ipcEvent: ElectronIpcMainInvokeEvent) => { if (ipcEvent?.senderFrame && 'url' in ipcEvent.senderFrame) { const parsedUrl = new URL(ipcEvent.senderFrame.url); diff --git a/packages/node-utils/package.json b/packages/node-utils/package.json index 578067e307b..8fa40893642 100644 --- a/packages/node-utils/package.json +++ b/packages/node-utils/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest --verbose -c ../../jest.config.base.js", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" diff --git a/packages/node-utils/src/http.ts b/packages/node-utils/src/http.ts index 2d6094a9fe0..498c966eab6 100644 --- a/packages/node-utils/src/http.ts +++ b/packages/node-utils/src/http.ts @@ -3,8 +3,7 @@ import * as net from 'net'; import * as url from 'url'; import type { RequiredKey } from '@trezor/type-utils'; -import { Log, TypedEmitter } from '@trezor/utils'; -import { arrayPartition } from '@trezor/utils'; +import { Log, TypedEmitter, arrayPartition } from '@trezor/utils'; import { getFreePort } from './getFreePort'; @@ -478,6 +477,14 @@ export const allowReferers = export const parseBodyTextHelper = (request: Request) => new Promise(resolve => { + const hasData = + (request.headers['content-length'] && + Number.parseInt(request.headers['content-length']) > 0) || + request.headers['transfer-encoding'] === 'chunked'; + + if (!hasData) { + return resolve(''); + } const tmp: Buffer[] = []; request .on('data', chunk => { @@ -495,7 +502,13 @@ export const parseBodyTextHelper = (request: Request) => */ export const parseBodyJSON: RequestHandler = (request, response, next) => { parseBodyTextHelper(request) - .then(body => JSON.parse(body)) + .then(body => { + if (!body) { + return {}; + } + + return JSON.parse(body); + }) .then(body => { next({ ...request, body }, response); }) diff --git a/packages/node-utils/src/tests/http.test.ts b/packages/node-utils/src/tests/http.test.ts index 72e12481f41..9b8401e05d0 100644 --- a/packages/node-utils/src/tests/http.test.ts +++ b/packages/node-utils/src/tests/http.test.ts @@ -26,14 +26,17 @@ describe('HttpServer', () => { }); }); - afterEach(done => { - server.stop().finally(() => { - done(); - }); - }); + afterEach( + () => + new Promise(done => { + server.stop().finally(() => { + done(); + }); + }), + ); test('getServerAddress before server start', () => { - expect(() => server.getServerAddress()).toThrowError(); + expect(() => server.getServerAddress()).toThrow(); }); test('getServerAddress after server start', async () => { @@ -279,7 +282,7 @@ describe('HttpServer', () => { res = await post('foo-1/321', undefined); // body != array, fails in parseBodyJSON expect(res.status).toEqual(400); const { error } = await res.json(); - expect(error).toMatch('Invalid json body:'); + expect(error).toMatch('Invalid body'); res = await post('foo-1/not-a-number', { foo: 'bar' }); expect(res.status).toEqual(400); diff --git a/packages/product-components/.storybook/main.js b/packages/product-components/.storybook/main.js index 681db99f95d..63df8f0b85d 100644 --- a/packages/product-components/.storybook/main.js +++ b/packages/product-components/.storybook/main.js @@ -1,5 +1,13 @@ import { dirname, join } from 'path'; +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value) { + return dirname(require.resolve(join(value, 'package.json'))); +} + module.exports = { stories: ['../src/**/*.stories.*'], logLevel: 'debug', @@ -14,18 +22,12 @@ module.exports = { name: getAbsolutePath('@storybook/react-webpack5'), options: {}, }, - babel: async options => { + babel: options => { options.presets.push('@babel/preset-typescript'); + return options; }, features: { storyStoreV7: false, // Remove this line when storiesOf is not used anymore }, }; -/** - * This function is used to resolve the absolute path of a package. - * It is needed in projects that use Yarn PnP or are set up within a monorepo. - */ -function getAbsolutePath(value) { - return dirname(require.resolve(join(value, 'package.json'))); -} diff --git a/packages/product-components/.storybook/manager.js b/packages/product-components/.storybook/manager.js index 17b478d55a6..f6074ea0baa 100644 --- a/packages/product-components/.storybook/manager.js +++ b/packages/product-components/.storybook/manager.js @@ -1,6 +1,7 @@ import { addons } from '@storybook/addons'; + import theme from './theme'; addons.setConfig({ - theme: theme, + theme, }); diff --git a/packages/product-components/.storybook/preview.js b/packages/product-components/.storybook/preview.jsx similarity index 100% rename from packages/product-components/.storybook/preview.js rename to packages/product-components/.storybook/preview.jsx diff --git a/packages/product-components/.storybook/theme.js b/packages/product-components/.storybook/theme.js index 6e73ee71658..a386cff0cb1 100644 --- a/packages/product-components/.storybook/theme.js +++ b/packages/product-components/.storybook/theme.js @@ -1,5 +1,6 @@ import { create } from '@storybook/theming/create'; +// eslint-disable-next-line import/no-default-export export default create({ base: 'light', fontBase: 'TT Satoshi', diff --git a/packages/product-components/eslint.config.mjs b/packages/product-components/eslint.config.mjs new file mode 100644 index 00000000000..406a8926dca --- /dev/null +++ b/packages/product-components/eslint.config.mjs @@ -0,0 +1,29 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['**/.build-storybook/*'], + }, + { + files: ['**/*.stories.tsx'], + rules: { + 'no-console': 'off', + 'import/no-default-export': 'off', + }, + }, + { + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/*.stories.*', + '**/.storybook/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/product-components/package.json b/packages/product-components/package.json index aef8aa6dc87..170e55f0b1e 100644 --- a/packages/product-components/package.json +++ b/packages/product-components/package.json @@ -8,7 +8,6 @@ "sideEffects": false, "scripts": { "lint": "yarn lint:js && yarn lint:styles", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "lint:styles": "npx stylelint './src/**/*{.ts,.tsx}' --cache --config ../../.stylelintrc", "lint-fix": "npx eslint ./src --fix", "type-check": "yarn g:tsc --build tsconfig.json", @@ -19,12 +18,14 @@ "dependencies": { "@suite-common/validators": "workspace:*", "@suite-common/wallet-config": "workspace:*", + "@suite-common/wallet-utils": "workspace:*", "@trezor/components": "workspace:*", "@trezor/connect": "workspace:*", "@trezor/dom-utils": "workspace:*", "@trezor/env-utils": "workspace:*", "@trezor/react-utils": "workspace:*", "@trezor/theme": "workspace:*", + "@trezor/urls": "workspace:*", "@trezor/utils": "workspace:*", "framer-motion": "^11.0.3", "react": "18.2.0", @@ -42,6 +43,8 @@ "@storybook/client-api": "^7.6.13", "@storybook/react": "^7.6.13", "@storybook/react-webpack5": "^7.6.13", + "@storybook/theming": "^7.6.13", + "@trezor/eslint": "workspace:*", "@types/react": "18.2.79", "storybook": "^7.6.13", "stylelint": "^16.2.1", diff --git a/packages/product-components/src/components/AssetShareIndicator/AssetShareIndicator.tsx b/packages/product-components/src/components/AssetShareIndicator/AssetShareIndicator.tsx index cfdbe8b60b4..f9c7aa89215 100644 --- a/packages/product-components/src/components/AssetShareIndicator/AssetShareIndicator.tsx +++ b/packages/product-components/src/components/AssetShareIndicator/AssetShareIndicator.tsx @@ -1,9 +1,13 @@ import { SVGProps } from 'react'; + import { motion, AnimationProps, SVGMotionProps } from 'framer-motion'; import styled, { useTheme } from 'styled-components'; + import { coinsColors } from '@trezor/theme'; -import { CoinLogo, CoinLogoProps } from '../CoinLogo/CoinLogo'; import { motionEasing } from '@trezor/components'; +import { NetworkSymbol } from '@suite-common/wallet-config'; + +import { CoinLogo, CoinLogoProps } from '../CoinLogo/CoinLogo'; const Container = styled.div` position: relative; @@ -16,6 +20,7 @@ const Container = styled.div` `; export interface AssetShareIndicatorProps extends CoinLogoProps { + symbol: NetworkSymbol; percentageShare?: number; } diff --git a/packages/product-components/src/components/CoinLogo/CoinLogo.stories.tsx b/packages/product-components/src/components/CoinLogo/CoinLogo.stories.tsx index a3ee39f8c51..c98a9adee7e 100644 --- a/packages/product-components/src/components/CoinLogo/CoinLogo.stories.tsx +++ b/packages/product-components/src/components/CoinLogo/CoinLogo.stories.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; + import { CoinLogo as CoinLogoComponent } from '../../index'; import { COINS } from './coins'; import { CoinLogoProps } from './CoinLogo'; diff --git a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx index c53954a0e0d..7f60f9927a2 100644 --- a/packages/product-components/src/components/CoinLogo/CoinLogo.tsx +++ b/packages/product-components/src/components/CoinLogo/CoinLogo.tsx @@ -1,11 +1,14 @@ import { ImgHTMLAttributes } from 'react'; import { ReactSVG } from 'react-svg'; + import styled from 'styled-components'; -import { COINS } from './coins'; + import { NetworkSymbol } from '@suite-common/wallet-config'; +import { COINS, LegacyNetworkSymbol } from './coins'; + export interface CoinLogoProps extends ImgHTMLAttributes { - symbol: NetworkSymbol; + symbol: NetworkSymbol | LegacyNetworkSymbol; className?: string; size?: number; index?: number; diff --git a/packages/product-components/src/components/CoinLogo/coinLogos.stories.tsx b/packages/product-components/src/components/CoinLogo/coinLogos.stories.tsx index 0df5feb64f6..03056d600af 100644 --- a/packages/product-components/src/components/CoinLogo/coinLogos.stories.tsx +++ b/packages/product-components/src/components/CoinLogo/coinLogos.stories.tsx @@ -1,10 +1,12 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; -import { CoinLogo } from '../../index'; + import { StoryColumn } from '@trezor/components'; -import { COINS } from './coins'; import { NetworkSymbol } from '@suite-common/wallet-config'; +import { CoinLogo } from '../../index'; +import { COINS } from './coins'; + const CoinName = styled.div` margin-bottom: 0.5rem; color: ${({ theme }) => theme.legacy.TYPE_LIGHT_GREY}; diff --git a/packages/product-components/src/components/CoinLogo/coins.ts b/packages/product-components/src/components/CoinLogo/coins.ts index 360043461a1..bdfd08e8425 100644 --- a/packages/product-components/src/components/CoinLogo/coins.ts +++ b/packages/product-components/src/components/CoinLogo/coins.ts @@ -1,6 +1,9 @@ import { NetworkSymbol } from '@suite-common/wallet-config'; -export const COINS: Record = { +// These coins are not supported in Suite, but exist in Trezor Connect +export type LegacyNetworkSymbol = 'eos' | 'nem' | 'xlm' | 'xtz'; + +export const COINS: Record = { ada: require('../../images/coins/ada.svg'), bch: require('../../images/coins/bch.svg'), bnb: require('../../images/coins/bnb.svg'), @@ -10,10 +13,13 @@ export const COINS: Record = { dgb: require('../../images/coins/dgb.svg'), doge: require('../../images/coins/doge.svg'), dsol: require('../../images/coins/dsol.svg'), + eos: require('../../images/coins/eos.svg'), etc: require('../../images/coins/etc.svg'), eth: require('../../images/coins/eth.svg'), ltc: require('../../images/coins/ltc.svg'), + op: require('../../images/coins/op.svg'), pol: require('../../images/coins/pol.svg'), + nem: require('../../images/coins/nem.svg'), nmc: require('../../images/coins/nmc.svg'), regtest: require('../../images/coins/btc_test.svg'), sol: require('../../images/coins/sol.svg'), @@ -23,6 +29,8 @@ export const COINS: Record = { tsep: require('../../images/coins/tsep.svg'), txrp: require('../../images/coins/txrp.svg'), vtc: require('../../images/coins/vtc.svg'), + xlm: require('../../images/coins/xlm.svg'), xrp: require('../../images/coins/xrp.svg'), + xtz: require('../../images/coins/xtz.svg'), zec: require('../../images/coins/zec.svg'), }; diff --git a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.stories.tsx b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.stories.tsx index 483f80d0585..95ea5762569 100644 --- a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.stories.tsx +++ b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.stories.tsx @@ -1,8 +1,10 @@ import { Meta, StoryFn } from '@storybook/react'; + import { DeviceModelInternal } from '@trezor/connect'; -import { ConfirmOnDevice as ConfirmOnDeviceComponent } from './ConfirmOnDevice'; import { StoryColumn } from '@trezor/components'; +import { ConfirmOnDevice as ConfirmOnDeviceComponent } from './ConfirmOnDevice'; + const meta: Meta = { title: 'ConfirmOnDevice', parameters: { @@ -106,5 +108,15 @@ export const ConfirmOnDevice: StoryFn = () => ( deviceModelInternal={DeviceModelInternal.T3T1} /> + + {}} + deviceModelInternal={DeviceModelInternal.T3W1} + /> + ); diff --git a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.tsx b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.tsx index 4e188c2363d..15cbb77f71b 100644 --- a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.tsx +++ b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDevice.tsx @@ -1,9 +1,11 @@ import { ReactNode } from 'react'; + import styled, { css, keyframes } from 'styled-components'; + import { DeviceModelInternal } from '@trezor/connect'; import { borders, spacingsPx } from '@trezor/theme'; - import { ElevationUp } from '@trezor/components'; + import { ConfirmOnDeviceContent } from './ConfirmOnDeviceContent'; enum AnimationDirection { diff --git a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDeviceContent.tsx b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDeviceContent.tsx index 72a4ed44f86..f3f09898b19 100644 --- a/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDeviceContent.tsx +++ b/packages/product-components/src/components/ConfirmOnDevice/ConfirmOnDeviceContent.tsx @@ -1,6 +1,8 @@ import { ReactNode } from 'react'; + import styled, { css, useTheme } from 'styled-components'; -import { Icon } from '@trezor/components'; + +import { Icon, useElevation } from '@trezor/components'; import { DeviceModelInternal } from '@trezor/connect'; import { Elevation, @@ -9,7 +11,7 @@ import { spacingsPx, typography, } from '@trezor/theme'; -import { useElevation } from '@trezor/components'; + import { RotateDeviceImage } from '../RotateDeviceImage/RotateDeviceImage'; const Column = styled.div` @@ -84,7 +86,6 @@ const Step = styled.div<{ $isActive: boolean }>` `} `; -// eslint-disable-next-line local-rules/no-override-ds-component const StyledRotateDeviceImage = styled(RotateDeviceImage)` height: 34px; `; diff --git a/packages/product-components/src/components/PassphraseTypeCard/EnterOnTrezorButton.tsx b/packages/product-components/src/components/PassphraseTypeCard/EnterOnTrezorButton.tsx index e578675beb5..ec7a22d5f2f 100644 --- a/packages/product-components/src/components/PassphraseTypeCard/EnterOnTrezorButton.tsx +++ b/packages/product-components/src/components/PassphraseTypeCard/EnterOnTrezorButton.tsx @@ -1,5 +1,7 @@ import { FormattedMessage } from 'react-intl'; + import styled, { useTheme } from 'styled-components'; + import { Image, Card, Text, Row, Icon } from '@trezor/components'; import { DeviceModelInternal } from '@trezor/connect'; import { spacings } from '@trezor/theme'; diff --git a/packages/product-components/src/components/PassphraseTypeCard/NonAsciiBanner.tsx b/packages/product-components/src/components/PassphraseTypeCard/NonAsciiBanner.tsx new file mode 100644 index 00000000000..1d328866a43 --- /dev/null +++ b/packages/product-components/src/components/PassphraseTypeCard/NonAsciiBanner.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import styled from 'styled-components'; + +import { Banner, Button, Code, Text } from '@trezor/components'; +import { BannerVariant } from '@trezor/components/src/components/Banner/types'; +import { borders, spacingsPx, typography } from '@trezor/theme'; +import { HELP_CENTER_PASSPHRASE_URL } from '@trezor/urls'; + +const ButtonWrapper = styled.div` + flex-shrink: 0; + display: flex; + align-items: center; +`; + +const SpecialCharsWrapper = styled.div` + display: flex; + flex-direction: column; + margin: ${spacingsPx.xxs} 0 0; + ${typography.label}; + overflow: hidden; + border-radius: ${borders.radii.xs}; +`; + +const SpecialChars = styled.div` + padding: 0 ${spacingsPx.xxs}; + box-shadow: inset 0 0 0 2px #f2f2f2; + font-family: RobotoMono, 'PixelOperatorMono8', monospace; + font-weight: 400; + letter-spacing: 0.1em; + background-color: ${({ theme }) => theme.backgroundNeutralSubtleOnElevation1}; +`; + +const SpecialDescription = styled.div` + background-color: ${({ theme }) => theme.backgroundNeutralDisabled}; + padding: ${spacingsPx.xxs}; + display: flex; + gap: ${spacingsPx.sm}; + ${typography.label} +`; + +type NonAsciiBannerProps = { + variant: BannerVariant; +}; + +export const NonAsciiBanner = ({ variant }: NonAsciiBannerProps) => { + return ( + + + {text} }} + /> + + + + {'! " # $ % & \\ \' ( ) * + - . / : ; < = > ? @ [ ] ^ _ ` { | } ~'} + + + + + {/* TODO: better would be to reuse LearnMoreButton */} + + + + + + ); +}; diff --git a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.stories.tsx b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.stories.tsx index 4de9375d95d..c9dfae10a63 100644 --- a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.stories.tsx +++ b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.stories.tsx @@ -1,9 +1,11 @@ import { IntlProvider } from 'react-intl'; + +import { Meta, StoryObj } from '@storybook/react'; + import { PassphraseTypeCard as PassphraseTypeCardComponent, PassphraseTypeCardProps, } from './PassphraseTypeCard'; -import { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { title: 'PassphraseTypeCard', @@ -18,7 +20,7 @@ const meta: Meta = { } as Meta; export default meta; -export const PassphraseTypeCard: StoryObj = { +export const Standard: StoryObj = { args: { title: 'My Trezor', description: @@ -38,3 +40,22 @@ export const PassphraseTypeCard: StoryObj = { }, }, }; + +export const Hidden: StoryObj = { + args: { + submitLabel: 'Yes please', + offerPassphraseOnDevice: false, + singleColModal: true, + onSubmit: () => null, + type: 'hidden', + deviceBackup: 'Bip39', + }, + argTypes: { + type: { + options: ['standard', 'hidden'], + control: { + type: 'select', + }, + }, + }, +}; diff --git a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.tsx b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.tsx index ccd398b1abd..39a31c531d9 100644 --- a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.tsx +++ b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCard.tsx @@ -1,18 +1,23 @@ -import { useState, useRef, useEffect, useCallback, ReactNode } from 'react'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; + import { AnimatePresence } from 'framer-motion'; +import styled, { css } from 'styled-components'; + import { useKeyPress } from '@trezor/react-utils'; import { setCaretPosition } from '@trezor/dom-utils'; -import styled, { css } from 'styled-components'; import { countBytesInString } from '@trezor/utils'; import { formInputsMaxLength } from '@suite-common/validators'; import { TooltipProps } from '@trezor/components'; +import { DeviceModelInternal, Features } from '@trezor/connect'; +import { borders, spacingsPx } from '@trezor/theme'; + import { EnterOnTrezorButton } from './EnterOnTrezorButton'; -import { DeviceModelInternal } from '@trezor/connect'; import { PassphraseTypeCardHeading } from './PassphraseTypeCardHeading'; import { WalletType } from './types'; import { PassphraseTypeCardContent } from './PassphraseTypeCardContent'; import { DOT } from './consts'; -import { borders, spacingsPx } from '@trezor/theme'; +import { useNonAsciiChars } from './useNonAsciiChars'; +import { getSubmitLabel } from './getSubmitLabel'; type WrapperProps = { $type: WalletType; @@ -59,6 +64,7 @@ export type PassphraseTypeCardProps = { offerPassphraseOnDevice?: boolean; singleColModal?: boolean; deviceModel?: DeviceModelInternal; + deviceBackup?: Features['backup_type'] | null; onSubmit: (value: string, passphraseOnDevice?: boolean) => void; learnMoreTooltipOnClick?: TooltipProps['addon']; learnMoreTooltipAppendTo?: TooltipProps['appendTo']; @@ -69,6 +75,7 @@ export const PassphraseTypeCard = (props: PassphraseTypeCardProps) => { const [showPassword, setShowPassword] = useState(false); const [hiddenWalletTouched, setHiddenWalletTouched] = useState(false); const enterPressed = useKeyPress('Enter'); + const { nonAsciiChars, showAsciiBanner } = useNonAsciiChars(value); const ref = useRef(null); const caretRef = useRef(0); @@ -83,17 +90,22 @@ export const PassphraseTypeCard = (props: PassphraseTypeCardProps) => { title, description, singleColModal, - submitLabel, + submitLabel: submitLabelProp, offerPassphraseOnDevice, deviceModel, + deviceBackup, } = props; const submit = useCallback( - (value: string, passphraseOnDevice?: boolean) => { - onSubmit(value, passphraseOnDevice); + (value2: string, passphraseOnDevice?: boolean) => { + onSubmit(value2, passphraseOnDevice); }, [onSubmit], ); + const isBip39 = deviceBackup === 'Bip39'; + + const submitLabel = getSubmitLabel({ nonAsciiChars, label: submitLabelProp, showPassword }); + const canSubmit = (singleColModal || type === 'hidden') && !isPassphraseTooLong; // Trigger submit on pressing Enter in case of single col modal (creating/confirming passphrase wallet) @@ -144,9 +156,12 @@ export const PassphraseTypeCard = (props: PassphraseTypeCardProps) => { void; + showAsciiBanner?: boolean; + asciiBannerVariant?: BannerVariant; showPassword: boolean; setShowPassword: (showPassword: boolean) => void; hiddenWalletTouched: boolean; @@ -59,6 +68,7 @@ type PassphraseTypeCardContentProps = { }; export const PassphraseTypeCardContent = ({ + asciiBannerVariant = 'info', type, displayValue, isPassphraseTooLong, @@ -66,6 +76,8 @@ export const PassphraseTypeCardContent = ({ value, setValue, submitLabel, + submitVariant = 'primary', + showAsciiBanner, showPassword, setShowPassword, hiddenWalletTouched, @@ -150,6 +162,7 @@ export const PassphraseTypeCardContent = ({ ) : null } inputState={isPassphraseTooLong ? 'error' : undefined} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={!isAndroid()} innerAddon={ )} + {showAsciiBanner && } )} @@ -190,7 +204,7 @@ export const PassphraseTypeCardContent = ({ type === 'hidden' ? 'hidden' : 'standard' }/submit-button`} isDisabled={isPassphraseEmpty || isPassphraseTooLong} - variant="primary" + variant={submitVariant} onClick={() => submit(value)} isFullWidth > diff --git a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCardHeading.tsx b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCardHeading.tsx index a4b4f913cc6..4b7607f5313 100644 --- a/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCardHeading.tsx +++ b/packages/product-components/src/components/PassphraseTypeCard/PassphraseTypeCardHeading.tsx @@ -1,9 +1,12 @@ -import { borders, spacings, spacingsPx, typography } from '@trezor/theme'; import { FormattedMessage } from 'react-intl'; +import { ReactNode } from 'react'; + import styled, { useTheme } from 'styled-components'; + +import { borders, spacings, spacingsPx, typography } from '@trezor/theme'; import { Tooltip, TooltipProps, Column, Row, Icon } from '@trezor/components'; + import { WalletType } from './types'; -import { ReactNode } from 'react'; const IconWrapper = styled.div<{ $type: WalletType }>` width: 38px; diff --git a/packages/product-components/src/components/PassphraseTypeCard/getSubmitLabel.tsx b/packages/product-components/src/components/PassphraseTypeCard/getSubmitLabel.tsx new file mode 100644 index 00000000000..1dc8f15d82b --- /dev/null +++ b/packages/product-components/src/components/PassphraseTypeCard/getSubmitLabel.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +export const getSubmitLabel = ({ + nonAsciiChars, + label, + showPassword, +}: { + nonAsciiChars: null | string[]; + label: ReactNode; + showPassword: boolean; +}) => { + if (!nonAsciiChars) return label; + + const displaySingleChar = showPassword && nonAsciiChars.length === 1; + const messageId = displaySingleChar ? 'TR_NON_ASCII_CHAR' : 'TR_NON_ASCII_CHARS'; + const chars = displaySingleChar ? '"{char}"' : 'characters'; + const defaultMessage = `{label} (with non-recommended ${chars})`; + + return ( + + ); +}; diff --git a/packages/product-components/src/components/PassphraseTypeCard/useNonAsciiChars.ts b/packages/product-components/src/components/PassphraseTypeCard/useNonAsciiChars.ts new file mode 100644 index 00000000000..292ffe3d426 --- /dev/null +++ b/packages/product-components/src/components/PassphraseTypeCard/useNonAsciiChars.ts @@ -0,0 +1,18 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { getNonAsciiChars } from '@trezor/utils'; + +export const useNonAsciiChars = (value: string) => { + const [showAsciiBanner, setShowAsciiBanner] = useState(false); + + const nonAsciiChars = useMemo(() => getNonAsciiChars(value), [value]); + + useEffect(() => { + if (nonAsciiChars !== null) { + // If the banner was displayed once, we don't hide it again + setShowAsciiBanner(true); + } + }, [nonAsciiChars]); + + return { nonAsciiChars, showAsciiBanner }; +}; diff --git a/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.stories.tsx b/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.stories.tsx index 2cfa25c4087..c6a4dcd54d4 100644 --- a/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.stories.tsx +++ b/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { PasswordStrengthIndicatorProps, PasswordStrengthIndicator as PasswordStrengthIndicatorComponent, diff --git a/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.tsx b/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.tsx index b9ce15f084e..97c06047cca 100644 --- a/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.tsx +++ b/packages/product-components/src/components/PasswordStrengthIndicator/PasswordStrengthIndicator.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import styled from 'styled-components'; +import styled from 'styled-components'; import type { ZXCVBNScore } from 'zxcvbn'; interface WrapperProps { diff --git a/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.stories.tsx b/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.stories.tsx index 6103f698957..797c7d210ec 100644 --- a/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.stories.tsx +++ b/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.stories.tsx @@ -1,9 +1,11 @@ import { Meta, StoryObj } from '@storybook/react'; + +import { DeviceModelInternal } from '@trezor/connect'; + import { RotateDeviceImage as RotateDeviceImageComponent, RotateDeviceImageProps, } from './RotateDeviceImage'; -import { DeviceModelInternal } from '@trezor/connect'; const meta: Meta = { title: 'RotateDeviceImage', diff --git a/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.tsx b/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.tsx index e616ccffebd..7c6407a3dc0 100644 --- a/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.tsx +++ b/packages/product-components/src/components/RotateDeviceImage/RotateDeviceImage.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { DeviceAnimation } from '@trezor/components'; -import { DeviceModelInternal } from '@trezor/connect'; -import { Image } from '@trezor/components'; + import styled from 'styled-components'; +import { DeviceAnimation, Image } from '@trezor/components'; +import { DeviceModelInternal } from '@trezor/connect'; + export type RotateDeviceImageProps = { deviceModel?: DeviceModelInternal; deviceColor?: number; diff --git a/packages/product-components/src/components/SelectAssetModal/AssetItem.tsx b/packages/product-components/src/components/SelectAssetModal/AssetItem.tsx index f0e060b91ef..a547042bf9e 100644 --- a/packages/product-components/src/components/SelectAssetModal/AssetItem.tsx +++ b/packages/product-components/src/components/SelectAssetModal/AssetItem.tsx @@ -1,6 +1,7 @@ +import styled, { useTheme } from 'styled-components'; + import { spacings, spacingsPx } from '@trezor/theme'; import { Badge, Column, Icon, Row, Text } from '@trezor/components'; -import styled, { useTheme } from 'styled-components'; const ClickableContainer = styled.div` cursor: pointer; diff --git a/packages/product-components/src/components/SelectAssetModal/AssetItemNotFound.tsx b/packages/product-components/src/components/SelectAssetModal/AssetItemNotFound.tsx index dd49d68e646..243cd7df5b3 100644 --- a/packages/product-components/src/components/SelectAssetModal/AssetItemNotFound.tsx +++ b/packages/product-components/src/components/SelectAssetModal/AssetItemNotFound.tsx @@ -1,8 +1,10 @@ -import { Column, Paragraph, Text } from '@trezor/components'; import { FormattedMessage } from 'react-intl'; -import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal'; + +import { Column, Paragraph, Text } from '@trezor/components'; import { spacings } from '@trezor/theme'; +import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal'; + interface AssetItemNotFoundProps { searchCategory: SelectAssetSearchCategoryType; networkCategories: SelectAssetNetworkProps[]; diff --git a/packages/product-components/src/components/SelectAssetModal/CheckableTag.tsx b/packages/product-components/src/components/SelectAssetModal/CheckableTag.tsx index 60d836cba8b..2ad8fc28648 100644 --- a/packages/product-components/src/components/SelectAssetModal/CheckableTag.tsx +++ b/packages/product-components/src/components/SelectAssetModal/CheckableTag.tsx @@ -1,3 +1,5 @@ +import styled, { DefaultTheme } from 'styled-components'; + import { UIVariant } from '@trezor/components/src/config/types'; import { borders, @@ -7,7 +9,6 @@ import { spacingsPx, typography, } from '@trezor/theme'; -import styled, { DefaultTheme } from 'styled-components'; export const tagVariants = ['primary', 'tertiary'] as const; export type TagVariant = Extract; diff --git a/packages/product-components/src/components/SelectAssetModal/NetworkTabs.tsx b/packages/product-components/src/components/SelectAssetModal/NetworkTabs.tsx index 9792e71f9c3..ddfe9308405 100644 --- a/packages/product-components/src/components/SelectAssetModal/NetworkTabs.tsx +++ b/packages/product-components/src/components/SelectAssetModal/NetworkTabs.tsx @@ -1,8 +1,11 @@ +import { FormattedMessage } from 'react-intl'; + +import styled from 'styled-components'; + import { AssetLogo, Row, Tooltip, useElevation } from '@trezor/components'; import { Elevation, mapElevationToBorder, spacings, spacingsPx } from '@trezor/theme'; + import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal'; -import styled from 'styled-components'; -import { FormattedMessage } from 'react-intl'; import { CheckableTag } from './CheckableTag'; interface NetworkTabsWrapperProps { diff --git a/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.stories.tsx b/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.stories.tsx index 6a74a3394bf..8047029142d 100644 --- a/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.stories.tsx +++ b/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.stories.tsx @@ -1,9 +1,12 @@ +import { IntlProvider } from 'react-intl'; + import { Meta, StoryObj } from '@storybook/react'; -import { SelectAssetModal as SelectAssetModalComponent, SelectAssetModalProps } from '../../index'; import { ThemeProvider } from 'styled-components'; -import { intermediaryTheme, NewModal } from '@trezor/components'; import { action } from '@storybook/addon-actions'; -import { IntlProvider } from 'react-intl'; + +import { intermediaryTheme, NewModal } from '@trezor/components'; + +import { SelectAssetModal as SelectAssetModalComponent, SelectAssetModalProps } from '../../index'; import { selectAssetModalOptions, selectAssetModalNetworks } from './mockData'; const meta: Meta = { diff --git a/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.tsx b/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.tsx index f47e9a3acdc..723a890d45c 100644 --- a/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.tsx +++ b/packages/product-components/src/components/SelectAssetModal/SelectAssetModal.tsx @@ -1,4 +1,6 @@ import { useMemo, useState } from 'react'; +import { useIntl } from 'react-intl'; + import { AssetLogo, Column, @@ -9,11 +11,11 @@ import { VirtualizedList, } from '@trezor/components'; import { mapElevationToBackgroundToken, spacings } from '@trezor/theme'; +import { getNetworkByCoingeckoId, Network } from '@suite-common/wallet-config'; + import { AssetItem } from './AssetItem'; import { NetworkTabs } from './NetworkTabs'; -import { useIntl } from 'react-intl'; import { AssetItemNotFound } from './AssetItemNotFound'; -import { getNetworkByCoingeckoId, Network } from '@suite-common/wallet-config'; export interface SelectAssetOptionCurrencyProps { type: 'currency'; @@ -24,12 +26,14 @@ export interface SelectAssetOptionCurrencyProps { contractAddress?: string; // CryptoId (contractAddress) networkName?: string; } + export interface SelectAssetOptionGroupProps { type: 'group'; label: string; networkName?: string; coingeckoId?: string; } + export type SelectAssetOptionProps = SelectAssetOptionCurrencyProps | SelectAssetOptionGroupProps; export interface SelectAssetNetworkProps { @@ -158,6 +162,7 @@ export const SelectAssetModal = ({ })} value={search} onChange={event => setSearch(event.target.value)} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus onClear={() => { setSearch(''); diff --git a/packages/product-components/src/components/TokenIconSet/TokenIconSet.stories.tsx b/packages/product-components/src/components/TokenIconSet/TokenIconSet.stories.tsx new file mode 100644 index 00000000000..9593ac33187 --- /dev/null +++ b/packages/product-components/src/components/TokenIconSet/TokenIconSet.stories.tsx @@ -0,0 +1,47 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { TokenIconSet as TokenIconSetComponent, TokenIconSetProps } from './TokenIconSet'; + +const getToken = (contract: string, symbol: string, decimals: number) => ({ + contract, + symbol, + decimals, + type: 'ERC20', +}); + +const TOKEN_1 = getToken('0xaea46a60368a7bd060eec7df8cba43b7ef41ad85', 'FET', 6); +const TOKEN_2 = getToken('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', 'AAVE', 6); +const TOKEN_3 = getToken('0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce', 'SHIB', 18); +const TOKEN_4 = getToken('0xdAC17F958D2ee523a2206206994597C13D831ec7', 'usdt', 6); + +const meta: Meta = { + title: 'TokenIconSet', +} as Meta; +export default meta; +export const TokenIconSet: StoryObj = { + render: (props: TokenIconSetProps) => , + args: { + network: 'eth', + tokens: [TOKEN_1, TOKEN_2, TOKEN_3, TOKEN_4], + }, + argTypes: { + tokens: { + options: ['1', '2', '3', '4'], + mapping: { + '1': [TOKEN_1], + '2': [TOKEN_1, TOKEN_2], + '3': [TOKEN_1, TOKEN_2, TOKEN_3], + '4': [TOKEN_1, TOKEN_2, TOKEN_3, TOKEN_4], + }, + control: { + type: 'select', + labels: { + 1: '1 token', + 2: '2 tokens', + 3: '3 tokens', + 4: '4+ tokens', + }, + }, + }, + }, +}; diff --git a/packages/product-components/src/components/TokenIconSet/TokenIconSet.tsx b/packages/product-components/src/components/TokenIconSet/TokenIconSet.tsx new file mode 100644 index 00000000000..4a23a768c6f --- /dev/null +++ b/packages/product-components/src/components/TokenIconSet/TokenIconSet.tsx @@ -0,0 +1,67 @@ +import styled, { css } from 'styled-components'; + +import { AssetLogo, useElevation } from '@trezor/components'; +import { getCoingeckoId, NetworkSymbol } from '@suite-common/wallet-config'; +import { TokenInfo } from '@trezor/connect'; +import { getContractAddressForNetwork } from '@suite-common/wallet-utils'; +import { borders, Elevation, mapElevationToBackground, mapElevationToBorder } from '@trezor/theme'; + +export type TokenIconSetProps = { + network: NetworkSymbol; + tokens: TokenInfo[]; +}; + +const IconContainer = styled.div<{ $length: number }>` + width: 24px; + justify-content: center; + display: flex; + align-items: center; + ${({ $length }) => + $length > 1 && + css` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(${$length > 1 ? '1px' : '6px'}, 6px)); + direction: rtl; + justify-items: center; + `} +`; + +const TokenIconPlaceholder = styled.div<{ $elevation: Elevation }>` + width: 20px; + height: 20px; + border-radius: ${borders.radii.full}; + border: 1px solid ${mapElevationToBorder}; + background: ${mapElevationToBackground}; +`; + +/** + * @param tokens - provide already sorted tokens (for example by fiat value). + */ +export const TokenIconSet = ({ network, tokens }: TokenIconSetProps) => { + const { elevation } = useElevation(); + const { length } = tokens; + + if (length === 0) { + return null; + } + + const visibleTokens = tokens.slice(0, 3).reverse(); + + const coingeckoId = getCoingeckoId(network); + + return ( + + {length > 3 && } + {visibleTokens.map(token => ( + + ))} + + ); +}; diff --git a/packages/product-components/src/components/TrezorLogo/TrezorLogo.stories.tsx b/packages/product-components/src/components/TrezorLogo/TrezorLogo.stories.tsx index 35990acb837..88e043b585b 100644 --- a/packages/product-components/src/components/TrezorLogo/TrezorLogo.stories.tsx +++ b/packages/product-components/src/components/TrezorLogo/TrezorLogo.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; + import { TrezorLogoProps, TrezorLogo as TrezorLogoComponent } from './TrezorLogo'; const meta: Meta = { diff --git a/packages/product-components/src/components/TrezorLogo/TrezorLogo.tsx b/packages/product-components/src/components/TrezorLogo/TrezorLogo.tsx index 5d94bf063eb..056cb3429a3 100644 --- a/packages/product-components/src/components/TrezorLogo/TrezorLogo.tsx +++ b/packages/product-components/src/components/TrezorLogo/TrezorLogo.tsx @@ -1,5 +1,7 @@ import { ReactSVG } from 'react-svg'; + import styled, { useTheme } from 'styled-components'; + import { LOGOS } from './trezorLogos'; export type TrezorLogoType = diff --git a/packages/product-components/src/components/TrezorLogo/trezorLogos.stories.tsx b/packages/product-components/src/components/TrezorLogo/trezorLogos.stories.tsx index 53415f80ee5..494737ab363 100644 --- a/packages/product-components/src/components/TrezorLogo/trezorLogos.stories.tsx +++ b/packages/product-components/src/components/TrezorLogo/trezorLogos.stories.tsx @@ -1,8 +1,10 @@ import styled from 'styled-components'; import { Meta, StoryObj } from '@storybook/react'; -import { TrezorLogo } from './TrezorLogo'; + import { StoryColumn } from '@trezor/components'; +import { TrezorLogo } from './TrezorLogo'; + interface WrapperProps { isDark?: boolean; } diff --git a/packages/components/src/images/coins/eos.svg b/packages/product-components/src/images/coins/eos.svg similarity index 100% rename from packages/components/src/images/coins/eos.svg rename to packages/product-components/src/images/coins/eos.svg diff --git a/packages/components/src/images/coins/nem.svg b/packages/product-components/src/images/coins/nem.svg similarity index 100% rename from packages/components/src/images/coins/nem.svg rename to packages/product-components/src/images/coins/nem.svg diff --git a/packages/product-components/src/images/coins/op.svg b/packages/product-components/src/images/coins/op.svg new file mode 100644 index 00000000000..098392b7f62 --- /dev/null +++ b/packages/product-components/src/images/coins/op.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/packages/components/src/images/coins/xlm.svg b/packages/product-components/src/images/coins/xlm.svg similarity index 100% rename from packages/components/src/images/coins/xlm.svg rename to packages/product-components/src/images/coins/xlm.svg diff --git a/packages/components/src/images/coins/xtz.svg b/packages/product-components/src/images/coins/xtz.svg similarity index 100% rename from packages/components/src/images/coins/xtz.svg rename to packages/product-components/src/images/coins/xtz.svg diff --git a/packages/product-components/src/index.ts b/packages/product-components/src/index.ts index e4dbd2cfce1..dbc1071338b 100644 --- a/packages/product-components/src/index.ts +++ b/packages/product-components/src/index.ts @@ -15,3 +15,4 @@ export { TrezorLogo } from './components/TrezorLogo/TrezorLogo'; export { PasswordStrengthIndicator } from './components/PasswordStrengthIndicator/PasswordStrengthIndicator'; export { CoinLogo } from './components/CoinLogo/CoinLogo'; export { AssetShareIndicator } from './components/AssetShareIndicator/AssetShareIndicator'; +export * from './components/TokenIconSet/TokenIconSet'; diff --git a/packages/product-components/src/utils/mapTrezorModelToIcon.ts b/packages/product-components/src/utils/mapTrezorModelToIcon.ts index 0cbf0306da4..f2a92511fee 100644 --- a/packages/product-components/src/utils/mapTrezorModelToIcon.ts +++ b/packages/product-components/src/utils/mapTrezorModelToIcon.ts @@ -7,4 +7,5 @@ export const mapTrezorModelToIcon: Record = { [DeviceModelInternal.T2B1]: 'trezorSafe3Filled', [DeviceModelInternal.T3B1]: 'trezorSafe3Filled', [DeviceModelInternal.T3T1]: 'trezorSafe5Filled', + [DeviceModelInternal.T3W1]: 'trezorSafe7Filled', }; diff --git a/packages/product-components/styled.d.ts b/packages/product-components/styled.d.ts index ff56311cfb2..83b3a85013a 100644 --- a/packages/product-components/styled.d.ts +++ b/packages/product-components/styled.d.ts @@ -1,6 +1,7 @@ // import original module declarations import 'styled-components'; import { BoxShadows, Colors } from '@trezor/theme'; + import { SuiteThemeColors } from './src/config/colors'; declare module 'styled-components' { diff --git a/packages/product-components/tsconfig.json b/packages/product-components/tsconfig.json index 64b47d80c4c..5389a996a22 100644 --- a/packages/product-components/tsconfig.json +++ b/packages/product-components/tsconfig.json @@ -19,12 +19,17 @@ { "path": "../../suite-common/wallet-config" }, + { + "path": "../../suite-common/wallet-utils" + }, { "path": "../components" }, { "path": "../connect" }, { "path": "../dom-utils" }, { "path": "../env-utils" }, { "path": "../react-utils" }, { "path": "../theme" }, - { "path": "../utils" } + { "path": "../urls" }, + { "path": "../utils" }, + { "path": "../eslint" } ] } diff --git a/packages/protobuf/.eslintrc.js b/packages/protobuf/.eslintrc.js deleted file mode 100644 index 2c3cec6f758..00000000000 --- a/packages/protobuf/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - rules: { - '@typescript-eslint/ban-types': 'off', // allow {} in protobuf.d.ts - '@typescript-eslint/no-shadow': 'off', - }, -}; diff --git a/packages/protobuf/messages.json b/packages/protobuf/messages.json index 8a19dcaf460..9a1cef61826 100644 --- a/packages/protobuf/messages.json +++ b/packages/protobuf/messages.json @@ -607,10 +607,7 @@ }, "overwintered": { "type": "bool", - "id": 7, - "options": { - "deprecated": true - } + "id": 7 }, "version_group_id": { "type": "uint32", @@ -751,9 +748,6 @@ } }, "TxAck": { - "options": { - "deprecated": true - }, "fields": { "tx": { "type": "TransactionType", @@ -808,10 +802,7 @@ }, "overwintered": { "type": "bool", - "id": 11, - "options": { - "deprecated": true - } + "id": 11 }, "version_group_id": { "type": "uint32", @@ -976,10 +967,7 @@ }, "payment_req_index": { "type": "uint32", - "id": 12, - "options": { - "(experimental_field)": true - } + "id": 12 } } } @@ -1119,10 +1107,7 @@ }, "payment_req_index": { "type": "uint32", - "id": 12, - "options": { - "(experimental_field)": true - } + "id": 12 } } }, @@ -1220,9 +1205,6 @@ } }, "TxAckPaymentRequest": { - "options": { - "(experimental_message)": true - }, "fields": { "nonce": { "type": "bytes", @@ -2572,15 +2554,14 @@ "pages": { "type": "uint32", "id": 2 + }, + "name": { + "type": "string", + "id": 4 } }, "nested": { "ButtonRequestType": { - "valuesOptions": { - "_Deprecated_ButtonRequest_PassphraseType": { - "deprecated": true - } - }, "values": { "ButtonRequest_Other": 1, "ButtonRequest_FeeOverThreshold": 2, @@ -2641,10 +2622,7 @@ "fields": { "_on_device": { "type": "bool", - "id": 1, - "options": { - "deprecated": true - } + "id": 1 } } }, @@ -2656,10 +2634,7 @@ }, "_state": { "type": "bytes", - "id": 2, - "options": { - "deprecated": true - } + "id": 2 }, "on_device": { "type": "bool", @@ -2668,9 +2643,6 @@ } }, "Deprecated_PassphraseStateRequest": { - "options": { - "deprecated": true - }, "fields": { "state": { "type": "bytes", @@ -2679,9 +2651,6 @@ } }, "Deprecated_PassphraseStateAck": { - "options": { - "deprecated": true - }, "fields": {} }, "HDNodeType": { @@ -3118,6 +3087,9 @@ "DebugLinkResetDebugEvents": { "fields": {} }, + "DebugLinkOptigaSetSecMax": { + "fields": {} + }, "EosGetPublicKey": { "fields": { "address_n": { @@ -3952,10 +3924,7 @@ "fields": { "_old_address": { "type": "bytes", - "id": 1, - "options": { - "deprecated": true - } + "id": 1 }, "address": { "type": "string", @@ -4289,10 +4258,7 @@ }, "_skip_passphrase": { "type": "bool", - "id": 2, - "options": { - "deprecated": true - } + "id": 2 }, "derive_cardano": { "type": "bool", @@ -4370,10 +4336,7 @@ }, "_passphrase_cached": { "type": "bool", - "id": 17, - "options": { - "deprecated": true - } + "id": 17 }, "firmware_present": { "type": "bool", @@ -4543,38 +4506,6 @@ } }, "Capability": { - "options": { - "(has_bitcoin_only_values)": true - }, - "valuesOptions": { - "Capability_Bitcoin": { - "(bitcoin_only)": true - }, - "Capability_Crypto": { - "(bitcoin_only)": true - }, - "Capability_Lisk": { - "deprecated": true - }, - "Capability_Shamir": { - "(bitcoin_only)": true - }, - "Capability_ShamirGroups": { - "(bitcoin_only)": true - }, - "Capability_PassphraseEntry": { - "(bitcoin_only)": true - }, - "Capability_Translations": { - "(bitcoin_only)": true - }, - "Capability_Brightness": { - "(bitcoin_only)": true - }, - "Capability_Haptic": { - "(bitcoin_only)": true - } - }, "values": { "Capability_Bitcoin": 1, "Capability_Bitcoin_like": 2, @@ -4619,10 +4550,7 @@ "fields": { "language": { "type": "string", - "id": 1, - "options": { - "deprecated": true - } + "id": 1 }, "label": { "type": "string", @@ -4638,10 +4566,7 @@ }, "_passphrase_source": { "type": "uint32", - "id": 5, - "options": { - "deprecated": true - } + "id": 5 }, "auto_lock_delay_ms": { "type": "uint32", @@ -4848,10 +4773,7 @@ }, "language": { "type": "string", - "id": 5, - "options": { - "deprecated": true - } + "id": 5 }, "label": { "type": "string", @@ -4877,10 +4799,6 @@ }, "ResetDevice": { "fields": { - "display_random": { - "type": "bool", - "id": 1 - }, "strength": { "type": "uint32", "id": 2, @@ -4898,10 +4816,7 @@ }, "language": { "type": "string", - "id": 5, - "options": { - "deprecated": true - } + "id": 5 }, "label": { "type": "string", @@ -4985,10 +4900,7 @@ }, "language": { "type": "string", - "id": 4, - "options": { - "deprecated": true - } + "id": 4 }, "label": { "type": "string", @@ -5118,15 +5030,9 @@ } }, "GetNonce": { - "options": { - "(experimental_message)": true - }, "fields": {} }, "Nonce": { - "options": { - "(experimental_message)": true - }, "fields": { "nonce": { "rule": "required", @@ -5173,186 +5079,437 @@ } } }, - "MoneroNetworkType": { - "values": { - "MAINNET": 0, - "TESTNET": 1, - "STAGENET": 2, - "FAKECHAIN": 3 + "NEMGetAddress": { + "fields": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "network": { + "type": "uint32", + "id": 2, + "options": { + "default": 104 + } + }, + "show_display": { + "type": "bool", + "id": 3 + }, + "chunkify": { + "type": "bool", + "id": 4 + } } }, - "MoneroTransactionSourceEntry": { + "NEMAddress": { "fields": { - "outputs": { - "rule": "repeated", - "type": "MoneroOutputEntry", + "address": { + "rule": "required", + "type": "string", + "id": 1 + } + } + }, + "NEMSignTx": { + "fields": { + "transaction": { + "rule": "required", + "type": "NEMTransactionCommon", "id": 1 }, - "real_output": { - "type": "uint64", + "multisig": { + "type": "NEMTransactionCommon", "id": 2 }, - "real_out_tx_key": { - "type": "bytes", + "transfer": { + "type": "NEMTransfer", "id": 3 }, - "real_out_additional_tx_keys": { - "rule": "repeated", - "type": "bytes", + "cosigning": { + "type": "bool", "id": 4 }, - "real_output_in_tx_index": { - "type": "uint64", + "provision_namespace": { + "type": "NEMProvisionNamespace", "id": 5 }, - "amount": { - "type": "uint64", + "mosaic_creation": { + "type": "NEMMosaicCreation", "id": 6 }, - "rct": { - "type": "bool", + "supply_change": { + "type": "NEMMosaicSupplyChange", "id": 7 }, - "mask": { - "type": "bytes", + "aggregate_modification": { + "type": "NEMAggregateModification", "id": 8 }, - "multisig_kLRki": { - "type": "MoneroMultisigKLRki", + "importance_transfer": { + "type": "NEMImportanceTransfer", "id": 9 }, - "subaddr_minor": { - "type": "uint32", + "chunkify": { + "type": "bool", "id": 10 } }, "nested": { - "MoneroOutputEntry": { + "NEMTransactionCommon": { "fields": { - "idx": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "network": { + "type": "uint32", + "id": 2, + "options": { + "default": 104 + } + }, + "timestamp": { + "rule": "required", + "type": "uint32", + "id": 3 + }, + "fee": { + "rule": "required", "type": "uint64", + "id": 4 + }, + "deadline": { + "rule": "required", + "type": "uint32", + "id": 5 + }, + "signer": { + "type": "bytes", + "id": 6 + } + } + }, + "NEMTransfer": { + "fields": { + "recipient": { + "rule": "required", + "type": "string", "id": 1 }, - "key": { - "type": "MoneroRctKeyPublic", + "amount": { + "rule": "required", + "type": "uint64", "id": 2 + }, + "payload": { + "type": "bytes", + "id": 3 + }, + "public_key": { + "type": "bytes", + "id": 4 + }, + "mosaics": { + "rule": "repeated", + "type": "NEMMosaic", + "id": 5 } }, "nested": { - "MoneroRctKeyPublic": { + "NEMMosaic": { "fields": { - "dest": { + "namespace": { "rule": "required", - "type": "bytes", + "type": "string", "id": 1 }, - "commitment": { + "mosaic": { "rule": "required", - "type": "bytes", + "type": "string", "id": 2 + }, + "quantity": { + "rule": "required", + "type": "uint64", + "id": 3 } } } } }, - "MoneroMultisigKLRki": { + "NEMProvisionNamespace": { "fields": { - "K": { - "type": "bytes", + "namespace": { + "rule": "required", + "type": "string", "id": 1 }, - "L": { - "type": "bytes", + "parent": { + "type": "string", "id": 2 }, - "R": { - "type": "bytes", + "sink": { + "rule": "required", + "type": "string", "id": 3 }, - "ki": { - "type": "bytes", + "fee": { + "rule": "required", + "type": "uint64", "id": 4 } } - } - } - }, - "MoneroTransactionDestinationEntry": { - "fields": { - "amount": { - "type": "uint64", - "id": 1 - }, - "addr": { - "type": "MoneroAccountPublicAddress", - "id": 2 }, - "is_subaddress": { - "type": "bool", - "id": 3 - }, - "original": { - "type": "bytes", - "id": 4 + "NEMMosaicCreation": { + "fields": { + "definition": { + "rule": "required", + "type": "NEMMosaicDefinition", + "id": 1 + }, + "sink": { + "rule": "required", + "type": "string", + "id": 2 + }, + "fee": { + "rule": "required", + "type": "uint64", + "id": 3 + } + }, + "nested": { + "NEMMosaicDefinition": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "ticker": { + "type": "string", + "id": 2 + }, + "namespace": { + "rule": "required", + "type": "string", + "id": 3 + }, + "mosaic": { + "rule": "required", + "type": "string", + "id": 4 + }, + "divisibility": { + "type": "uint32", + "id": 5 + }, + "levy": { + "type": "NEMMosaicLevy", + "id": 6 + }, + "fee": { + "type": "uint64", + "id": 7 + }, + "levy_address": { + "type": "string", + "id": 8 + }, + "levy_namespace": { + "type": "string", + "id": 9 + }, + "levy_mosaic": { + "type": "string", + "id": 10 + }, + "supply": { + "type": "uint64", + "id": 11 + }, + "mutable_supply": { + "type": "bool", + "id": 12 + }, + "transferable": { + "type": "bool", + "id": 13 + }, + "description": { + "rule": "required", + "type": "string", + "id": 14 + }, + "networks": { + "rule": "repeated", + "type": "uint32", + "id": 15, + "options": { + "packed": false + } + } + }, + "nested": { + "NEMMosaicLevy": { + "values": { + "MosaicLevy_Absolute": 1, + "MosaicLevy_Percentile": 2 + } + } + } + } + } }, - "is_integrated": { - "type": "bool", - "id": 5 - } - }, - "nested": { - "MoneroAccountPublicAddress": { + "NEMMosaicSupplyChange": { "fields": { - "spend_public_key": { - "type": "bytes", + "namespace": { + "rule": "required", + "type": "string", + "id": 1 + }, + "mosaic": { + "rule": "required", + "type": "string", + "id": 2 + }, + "type": { + "rule": "required", + "type": "NEMSupplyChangeType", + "id": 3 + }, + "delta": { + "rule": "required", + "type": "uint64", + "id": 4 + } + }, + "nested": { + "NEMSupplyChangeType": { + "values": { + "SupplyChange_Increase": 1, + "SupplyChange_Decrease": 2 + } + } + } + }, + "NEMAggregateModification": { + "fields": { + "modifications": { + "rule": "repeated", + "type": "NEMCosignatoryModification", + "id": 1 + }, + "relative_change": { + "type": "sint32", + "id": 2 + } + }, + "nested": { + "NEMCosignatoryModification": { + "fields": { + "type": { + "rule": "required", + "type": "NEMModificationType", + "id": 1 + }, + "public_key": { + "rule": "required", + "type": "bytes", + "id": 2 + } + }, + "nested": { + "NEMModificationType": { + "values": { + "CosignatoryModification_Add": 1, + "CosignatoryModification_Delete": 2 + } + } + } + } + } + }, + "NEMImportanceTransfer": { + "fields": { + "mode": { + "rule": "required", + "type": "NEMImportanceTransferMode", "id": 1 }, - "view_public_key": { + "public_key": { + "rule": "required", "type": "bytes", "id": 2 } + }, + "nested": { + "NEMImportanceTransferMode": { + "values": { + "ImportanceTransfer_Activate": 1, + "ImportanceTransfer_Deactivate": 2 + } + } } } } }, - "MoneroTransactionRsigData": { + "NEMSignedTx": { "fields": { - "rsig_type": { - "type": "uint32", + "data": { + "rule": "required", + "type": "bytes", "id": 1 }, - "offload_type": { - "type": "uint32", + "signature": { + "rule": "required", + "type": "bytes", "id": 2 - }, - "grouping": { + } + } + }, + "NEMDecryptMessage": { + "fields": { + "address_n": { "rule": "repeated", - "type": "uint64", - "id": 3, + "type": "uint32", + "id": 1, "options": { "packed": false } }, - "mask": { - "type": "bytes", - "id": 4 + "network": { + "type": "uint32", + "id": 2 }, - "rsig": { + "public_key": { "type": "bytes", - "id": 5 + "id": 3 }, - "rsig_parts": { - "rule": "repeated", + "payload": { "type": "bytes", - "id": 6 - }, - "bp_version": { - "type": "uint32", - "id": 7 + "id": 4 } } }, - "MoneroGetAddress": { + "NEMDecryptedMessage": { + "fields": { + "payload": { + "rule": "required", + "type": "bytes", + "id": 1 + } + } + }, + "RippleGetAddress": { "fields": { "address_n": { "rule": "repeated", @@ -5366,40 +5523,22 @@ "type": "bool", "id": 2 }, - "network_type": { - "type": "MoneroNetworkType", - "id": 3, - "options": { - "default": "MAINNET" - } - }, - "account": { - "type": "uint32", - "id": 4 - }, - "minor": { - "type": "uint32", - "id": 5 - }, - "payment_id": { - "type": "bytes", - "id": 6 - }, "chunkify": { "type": "bool", - "id": 7 + "id": 3 } } }, - "MoneroAddress": { + "RippleAddress": { "fields": { "address": { - "type": "bytes", + "rule": "required", + "type": "string", "id": 1 } } }, - "MoneroGetWatchKey": { + "RippleSignTx": { "fields": { "address_n": { "rule": "repeated", @@ -5409,4206 +5548,1327 @@ "packed": false } }, - "network_type": { - "type": "MoneroNetworkType", - "id": 2, - "options": { - "default": "MAINNET" - } - } - } - }, - "MoneroWatchKey": { - "fields": { - "watch_key": { - "type": "bytes", - "id": 1 - }, - "address": { - "type": "bytes", + "fee": { + "rule": "required", + "type": "uint64", "id": 2 - } - } - }, - "MoneroTransactionInitRequest": { - "fields": { - "version": { - "type": "uint32", - "id": 1 }, - "address_n": { - "rule": "repeated", + "flags": { "type": "uint32", - "id": 2, - "options": { - "packed": false - } - }, - "network_type": { - "type": "MoneroNetworkType", "id": 3, "options": { - "default": "MAINNET" + "default": 0 } }, - "tsx_data": { - "type": "MoneroTransactionData", + "sequence": { + "rule": "required", + "type": "uint32", "id": 4 + }, + "last_ledger_sequence": { + "type": "uint32", + "id": 5 + }, + "payment": { + "rule": "required", + "type": "RipplePayment", + "id": 6 + }, + "chunkify": { + "type": "bool", + "id": 7 } }, "nested": { - "MoneroTransactionData": { + "RipplePayment": { "fields": { - "version": { - "type": "uint32", + "amount": { + "rule": "required", + "type": "uint64", "id": 1 }, - "payment_id": { - "type": "bytes", + "destination": { + "rule": "required", + "type": "string", "id": 2 }, - "unlock_time": { - "type": "uint64", - "id": 3 - }, - "outputs": { - "rule": "repeated", - "type": "MoneroTransactionDestinationEntry", - "id": 4 - }, - "change_dts": { - "type": "MoneroTransactionDestinationEntry", - "id": 5 - }, - "num_inputs": { - "type": "uint32", - "id": 6 - }, - "mixin": { - "type": "uint32", - "id": 7 - }, - "fee": { - "type": "uint64", - "id": 8 - }, - "account": { - "type": "uint32", - "id": 9 - }, - "minor_indices": { - "rule": "repeated", - "type": "uint32", - "id": 10, - "options": { - "packed": false - } - }, - "rsig_data": { - "type": "MoneroTransactionRsigData", - "id": 11 - }, - "integrated_indices": { - "rule": "repeated", - "type": "uint32", - "id": 12, - "options": { - "packed": false - } - }, - "client_version": { - "type": "uint32", - "id": 13 - }, - "hard_fork": { + "destination_tag": { "type": "uint32", - "id": 14 - }, - "monero_version": { - "type": "bytes", - "id": 15 - }, - "chunkify": { - "type": "bool", - "id": 16 + "id": 3 } } } } }, - "MoneroTransactionInitAck": { + "RippleSignedTx": { "fields": { - "hmacs": { - "rule": "repeated", + "signature": { + "rule": "required", "type": "bytes", "id": 1 }, - "rsig_data": { - "type": "MoneroTransactionRsigData", + "serialized_tx": { + "rule": "required", + "type": "bytes", "id": 2 } } }, - "MoneroTransactionSetInputRequest": { + "SolanaGetPublicKey": { "fields": { - "src_entr": { - "type": "MoneroTransactionSourceEntry", - "id": 1 + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "show_display": { + "type": "bool", + "id": 2 } } }, - "MoneroTransactionSetInputAck": { + "SolanaPublicKey": { "fields": { - "vini": { + "public_key": { + "rule": "required", "type": "bytes", "id": 1 - }, - "vini_hmac": { - "type": "bytes", - "id": 2 - }, - "pseudo_out": { - "type": "bytes", - "id": 3 - }, - "pseudo_out_hmac": { - "type": "bytes", - "id": 4 - }, - "pseudo_out_alpha": { - "type": "bytes", - "id": 5 - }, - "spend_key": { - "type": "bytes", - "id": 6 } } }, - "MoneroTransactionInputViniRequest": { + "SolanaGetAddress": { "fields": { - "src_entr": { - "type": "MoneroTransactionSourceEntry", - "id": 1 + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } }, - "vini": { - "type": "bytes", + "show_display": { + "type": "bool", "id": 2 }, - "vini_hmac": { - "type": "bytes", + "chunkify": { + "type": "bool", "id": 3 - }, - "pseudo_out": { - "type": "bytes", - "id": 4 - }, - "pseudo_out_hmac": { - "type": "bytes", - "id": 5 - }, - "orig_idx": { - "type": "uint32", - "id": 6 } } }, - "MoneroTransactionInputViniAck": { - "fields": {} - }, - "MoneroTransactionAllInputsSetRequest": { - "fields": {} - }, - "MoneroTransactionAllInputsSetAck": { + "SolanaAddress": { "fields": { - "rsig_data": { - "type": "MoneroTransactionRsigData", + "address": { + "rule": "required", + "type": "string", "id": 1 } } }, - "MoneroTransactionSetOutputRequest": { + "SolanaTxTokenAccountInfo": { "fields": { - "dst_entr": { - "type": "MoneroTransactionDestinationEntry", + "base_address": { + "rule": "required", + "type": "string", "id": 1 }, - "dst_entr_hmac": { - "type": "bytes", + "token_program": { + "rule": "required", + "type": "string", "id": 2 }, - "rsig_data": { - "type": "MoneroTransactionRsigData", + "token_mint": { + "rule": "required", + "type": "string", "id": 3 }, - "is_offloaded_bp": { - "type": "bool", + "token_account": { + "rule": "required", + "type": "string", "id": 4 } } }, - "MoneroTransactionSetOutputAck": { + "SolanaTxAdditionalInfo": { "fields": { - "tx_out": { - "type": "bytes", + "token_accounts_infos": { + "rule": "repeated", + "type": "SolanaTxTokenAccountInfo", "id": 1 + } + } + }, + "SolanaSignTx": { + "fields": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } }, - "vouti_hmac": { + "serialized_tx": { + "rule": "required", "type": "bytes", "id": 2 }, - "rsig_data": { - "type": "MoneroTransactionRsigData", + "additional_info": { + "type": "SolanaTxAdditionalInfo", "id": 3 - }, - "out_pk": { - "type": "bytes", - "id": 4 - }, - "ecdh_info": { - "type": "bytes", - "id": 5 } } }, - "MoneroTransactionAllOutSetRequest": { + "SolanaTxSignature": { "fields": { - "rsig_data": { - "type": "MoneroTransactionRsigData", + "signature": { + "rule": "required", + "type": "bytes", "id": 1 } } }, - "MoneroTransactionAllOutSetAck": { + "StellarAssetType": { + "values": { + "NATIVE": 0, + "ALPHANUM4": 1, + "ALPHANUM12": 2 + } + }, + "StellarAsset": { "fields": { - "extra": { - "type": "bytes", + "type": { + "rule": "required", + "type": "StellarAssetType", "id": 1 }, - "tx_prefix_hash": { - "type": "bytes", + "code": { + "type": "string", "id": 2 }, - "rv": { - "type": "MoneroRingCtSig", - "id": 4 - }, - "full_message_hash": { - "type": "bytes", - "id": 5 - } - }, - "nested": { - "MoneroRingCtSig": { - "fields": { - "txn_fee": { - "type": "uint64", - "id": 1 - }, - "message": { - "type": "bytes", - "id": 2 - }, - "rv_type": { - "type": "uint32", - "id": 3 - } - } + "issuer": { + "type": "string", + "id": 3 } } }, - "MoneroTransactionSignInputRequest": { + "StellarGetAddress": { "fields": { - "src_entr": { - "type": "MoneroTransactionSourceEntry", - "id": 1 + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } }, - "vini": { - "type": "bytes", + "show_display": { + "type": "bool", "id": 2 }, - "vini_hmac": { - "type": "bytes", + "chunkify": { + "type": "bool", "id": 3 - }, - "pseudo_out": { - "type": "bytes", - "id": 4 - }, - "pseudo_out_hmac": { - "type": "bytes", - "id": 5 - }, - "pseudo_out_alpha": { - "type": "bytes", - "id": 6 - }, - "spend_key": { - "type": "bytes", - "id": 7 - }, - "orig_idx": { - "type": "uint32", - "id": 8 } } }, - "MoneroTransactionSignInputAck": { + "StellarAddress": { "fields": { - "signature": { - "type": "bytes", + "address": { + "rule": "required", + "type": "string", "id": 1 - }, - "pseudo_out": { - "type": "bytes", - "id": 2 } } }, - "MoneroTransactionFinalRequest": { - "fields": {} - }, - "MoneroTransactionFinalAck": { + "StellarSignTx": { "fields": { - "cout_key": { - "type": "bytes", - "id": 1 - }, - "salt": { - "type": "bytes", - "id": 2 + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 2, + "options": { + "packed": false + } }, - "rand_mult": { - "type": "bytes", + "network_passphrase": { + "rule": "required", + "type": "string", "id": 3 }, - "tx_enc_keys": { - "type": "bytes", + "source_account": { + "rule": "required", + "type": "string", "id": 4 }, - "opening_key": { - "type": "bytes", + "fee": { + "rule": "required", + "type": "uint32", "id": 5 - } - } - }, - "MoneroKeyImageExportInitRequest": { - "fields": { - "num": { + }, + "sequence_number": { "rule": "required", "type": "uint64", - "id": 1 + "id": 6 }, - "hash": { + "timebounds_start": { "rule": "required", - "type": "bytes", - "id": 2 + "type": "uint32", + "id": 8 }, - "address_n": { - "rule": "repeated", + "timebounds_end": { + "rule": "required", "type": "uint32", - "id": 3, - "options": { - "packed": false - } + "id": 9 }, - "network_type": { - "type": "MoneroNetworkType", - "id": 4, - "options": { - "default": "MAINNET" - } + "memo_type": { + "rule": "required", + "type": "StellarMemoType", + "id": 10 }, - "subs": { - "rule": "repeated", - "type": "MoneroSubAddressIndicesList", - "id": 5 + "memo_text": { + "type": "string", + "id": 11 + }, + "memo_id": { + "type": "uint64", + "id": 12 + }, + "memo_hash": { + "type": "bytes", + "id": 13 + }, + "num_operations": { + "rule": "required", + "type": "uint32", + "id": 14 } }, "nested": { - "MoneroSubAddressIndicesList": { - "fields": { - "account": { - "rule": "required", - "type": "uint32", - "id": 1 - }, - "minor_indices": { - "rule": "repeated", - "type": "uint32", - "id": 2, - "options": { - "packed": false - } - } + "StellarMemoType": { + "values": { + "NONE": 0, + "TEXT": 1, + "ID": 2, + "HASH": 3, + "RETURN": 4 } } } }, - "MoneroKeyImageExportInitAck": { + "StellarTxOpRequest": { "fields": {} }, - "MoneroKeyImageSyncStepRequest": { + "StellarPaymentOp": { "fields": { - "tdis": { - "rule": "repeated", - "type": "MoneroTransferDetails", + "source_account": { + "type": "string", "id": 1 - } - }, - "nested": { - "MoneroTransferDetails": { - "fields": { - "out_key": { - "rule": "required", - "type": "bytes", - "id": 1 - }, - "tx_pub_key": { - "rule": "required", - "type": "bytes", - "id": 2 - }, - "additional_tx_pub_keys": { - "rule": "repeated", - "type": "bytes", - "id": 3 - }, - "internal_output_index": { - "rule": "required", - "type": "uint64", - "id": 4 - }, - "sub_addr_major": { - "type": "uint32", - "id": 5 - }, - "sub_addr_minor": { - "type": "uint32", - "id": 6 - } - } + }, + "destination_account": { + "rule": "required", + "type": "string", + "id": 2 + }, + "asset": { + "rule": "required", + "type": "StellarAsset", + "id": 3 + }, + "amount": { + "rule": "required", + "type": "sint64", + "id": 4 } } }, - "MoneroKeyImageSyncStepAck": { + "StellarCreateAccountOp": { "fields": { - "kis": { - "rule": "repeated", - "type": "MoneroExportedKeyImage", + "source_account": { + "type": "string", "id": 1 - } - }, - "nested": { - "MoneroExportedKeyImage": { - "fields": { - "iv": { - "type": "bytes", - "id": 1 - }, - "blob": { - "type": "bytes", - "id": 3 - } - } + }, + "new_account": { + "rule": "required", + "type": "string", + "id": 2 + }, + "starting_balance": { + "rule": "required", + "type": "sint64", + "id": 3 } } }, - "MoneroKeyImageSyncFinalRequest": { - "fields": {} - }, - "MoneroKeyImageSyncFinalAck": { + "StellarPathPaymentStrictReceiveOp": { "fields": { - "enc_key": { - "type": "bytes", + "source_account": { + "type": "string", "id": 1 - } - } - }, - "MoneroGetTxKeyRequest": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } }, - "network_type": { - "type": "MoneroNetworkType", - "id": 2, - "options": { - "default": "MAINNET" - } + "send_asset": { + "rule": "required", + "type": "StellarAsset", + "id": 2 }, - "salt1": { + "send_max": { "rule": "required", - "type": "bytes", + "type": "sint64", "id": 3 }, - "salt2": { + "destination_account": { "rule": "required", - "type": "bytes", + "type": "string", "id": 4 }, - "tx_enc_keys": { + "destination_asset": { "rule": "required", - "type": "bytes", + "type": "StellarAsset", "id": 5 }, - "tx_prefix_hash": { + "destination_amount": { "rule": "required", - "type": "bytes", + "type": "sint64", "id": 6 }, - "reason": { - "type": "uint32", + "paths": { + "rule": "repeated", + "type": "StellarAsset", "id": 7 - }, - "view_public_key": { - "type": "bytes", - "id": 8 } } }, - "MoneroGetTxKeyAck": { + "StellarPathPaymentStrictSendOp": { "fields": { - "salt": { - "type": "bytes", + "source_account": { + "type": "string", "id": 1 }, - "tx_keys": { - "type": "bytes", + "send_asset": { + "rule": "required", + "type": "StellarAsset", "id": 2 }, - "tx_derivations": { - "type": "bytes", + "send_amount": { + "rule": "required", + "type": "sint64", "id": 3 - } - } - }, - "MoneroLiveRefreshStartRequest": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } }, - "network_type": { - "type": "MoneroNetworkType", - "id": 2, - "options": { - "default": "MAINNET" - } + "destination_account": { + "rule": "required", + "type": "string", + "id": 4 + }, + "destination_asset": { + "rule": "required", + "type": "StellarAsset", + "id": 5 + }, + "destination_min": { + "rule": "required", + "type": "sint64", + "id": 6 + }, + "paths": { + "rule": "repeated", + "type": "StellarAsset", + "id": 7 } } }, - "MoneroLiveRefreshStartAck": { - "fields": {} - }, - "MoneroLiveRefreshStepRequest": { + "StellarManageSellOfferOp": { "fields": { - "out_key": { - "rule": "required", - "type": "bytes", + "source_account": { + "type": "string", "id": 1 }, - "recv_deriv": { + "selling_asset": { "rule": "required", - "type": "bytes", + "type": "StellarAsset", "id": 2 }, - "real_out_idx": { + "buying_asset": { "rule": "required", - "type": "uint64", + "type": "StellarAsset", "id": 3 }, - "sub_addr_major": { + "amount": { "rule": "required", - "type": "uint32", + "type": "sint64", "id": 4 }, - "sub_addr_minor": { + "price_n": { "rule": "required", "type": "uint32", "id": 5 + }, + "price_d": { + "rule": "required", + "type": "uint32", + "id": 6 + }, + "offer_id": { + "rule": "required", + "type": "uint64", + "id": 7 } } }, - "MoneroLiveRefreshStepAck": { + "StellarManageBuyOfferOp": { "fields": { - "salt": { - "type": "bytes", + "source_account": { + "type": "string", "id": 1 }, - "key_image": { - "type": "bytes", + "selling_asset": { + "rule": "required", + "type": "StellarAsset", "id": 2 + }, + "buying_asset": { + "rule": "required", + "type": "StellarAsset", + "id": 3 + }, + "amount": { + "rule": "required", + "type": "sint64", + "id": 4 + }, + "price_n": { + "rule": "required", + "type": "uint32", + "id": 5 + }, + "price_d": { + "rule": "required", + "type": "uint32", + "id": 6 + }, + "offer_id": { + "rule": "required", + "type": "uint64", + "id": 7 } } }, - "MoneroLiveRefreshFinalRequest": { - "fields": {} - }, - "MoneroLiveRefreshFinalAck": { - "fields": {} - }, - "DebugMoneroDiagRequest": { + "StellarCreatePassiveSellOfferOp": { "fields": { - "ins": { - "type": "uint64", + "source_account": { + "type": "string", "id": 1 }, - "p1": { - "type": "uint64", + "selling_asset": { + "rule": "required", + "type": "StellarAsset", "id": 2 }, - "p2": { - "type": "uint64", + "buying_asset": { + "rule": "required", + "type": "StellarAsset", "id": 3 }, - "pd": { - "rule": "repeated", - "type": "uint64", - "id": 4, - "options": { - "packed": false - } + "amount": { + "rule": "required", + "type": "sint64", + "id": 4 }, - "data1": { - "type": "bytes", + "price_n": { + "rule": "required", + "type": "uint32", "id": 5 }, - "data2": { - "type": "bytes", + "price_d": { + "rule": "required", + "type": "uint32", "id": 6 } } }, - "DebugMoneroDiagAck": { + "StellarSetOptionsOp": { "fields": { - "ins": { - "type": "uint64", + "source_account": { + "type": "string", "id": 1 }, - "p1": { - "type": "uint64", + "inflation_destination_account": { + "type": "string", "id": 2 }, - "p2": { - "type": "uint64", + "clear_flags": { + "type": "uint32", "id": 3 }, - "pd": { - "rule": "repeated", - "type": "uint64", - "id": 4, - "options": { - "packed": false - } + "set_flags": { + "type": "uint32", + "id": 4 }, - "data1": { - "type": "bytes", + "master_weight": { + "type": "uint32", "id": 5 }, - "data2": { - "type": "bytes", + "low_threshold": { + "type": "uint32", "id": 6 - } - } - }, - "NEMGetAddress": { - "fields": { - "address_n": { - "rule": "repeated", + }, + "medium_threshold": { "type": "uint32", - "id": 1, - "options": { - "packed": false - } + "id": 7 }, - "network": { + "high_threshold": { "type": "uint32", - "id": 2, - "options": { - "default": 104 - } + "id": 8 }, - "show_display": { - "type": "bool", - "id": 3 + "home_domain": { + "type": "string", + "id": 9 }, - "chunkify": { - "type": "bool", - "id": 4 + "signer_type": { + "type": "StellarSignerType", + "id": 10 + }, + "signer_key": { + "type": "bytes", + "id": 11 + }, + "signer_weight": { + "type": "uint32", + "id": 12 + } + }, + "nested": { + "StellarSignerType": { + "values": { + "ACCOUNT": 0, + "PRE_AUTH": 1, + "HASH": 2 + } } } }, - "NEMAddress": { + "StellarChangeTrustOp": { "fields": { - "address": { - "rule": "required", + "source_account": { "type": "string", "id": 1 + }, + "asset": { + "rule": "required", + "type": "StellarAsset", + "id": 2 + }, + "limit": { + "rule": "required", + "type": "uint64", + "id": 3 } } }, - "NEMSignTx": { + "StellarAllowTrustOp": { "fields": { - "transaction": { - "rule": "required", - "type": "NEMTransactionCommon", + "source_account": { + "type": "string", "id": 1 }, - "multisig": { - "type": "NEMTransactionCommon", + "trusted_account": { + "rule": "required", + "type": "string", "id": 2 }, - "transfer": { - "type": "NEMTransfer", + "asset_type": { + "rule": "required", + "type": "StellarAssetType", "id": 3 }, - "cosigning": { - "type": "bool", + "asset_code": { + "type": "string", "id": 4 }, - "provision_namespace": { - "type": "NEMProvisionNamespace", + "is_authorized": { + "rule": "required", + "type": "bool", "id": 5 + } + } + }, + "StellarAccountMergeOp": { + "fields": { + "source_account": { + "type": "string", + "id": 1 }, - "mosaic_creation": { - "type": "NEMMosaicCreation", + "destination_account": { + "rule": "required", + "type": "string", + "id": 2 + } + } + }, + "StellarManageDataOp": { + "fields": { + "source_account": { + "type": "string", + "id": 1 + }, + "key": { + "rule": "required", + "type": "string", + "id": 2 + }, + "value": { + "type": "bytes", + "id": 3 + } + } + }, + "StellarBumpSequenceOp": { + "fields": { + "source_account": { + "type": "string", + "id": 1 + }, + "bump_to": { + "rule": "required", + "type": "uint64", + "id": 2 + } + } + }, + "StellarClaimClaimableBalanceOp": { + "fields": { + "source_account": { + "type": "string", + "id": 1 + }, + "balance_id": { + "rule": "required", + "type": "bytes", + "id": 2 + } + } + }, + "StellarSignedTx": { + "fields": { + "public_key": { + "rule": "required", + "type": "bytes", + "id": 1 + }, + "signature": { + "rule": "required", + "type": "bytes", + "id": 2 + } + } + }, + "TezosGetAddress": { + "fields": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "show_display": { + "type": "bool", + "id": 2 + }, + "chunkify": { + "type": "bool", + "id": 3 + } + } + }, + "TezosAddress": { + "fields": { + "address": { + "rule": "required", + "type": "string", + "id": 1 + } + } + }, + "TezosGetPublicKey": { + "fields": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "show_display": { + "type": "bool", + "id": 2 + }, + "chunkify": { + "type": "bool", + "id": 3 + } + } + }, + "TezosPublicKey": { + "fields": { + "public_key": { + "rule": "required", + "type": "string", + "id": 1 + } + } + }, + "TezosSignTx": { + "fields": { + "address_n": { + "rule": "repeated", + "type": "uint32", + "id": 1, + "options": { + "packed": false + } + }, + "branch": { + "rule": "required", + "type": "bytes", + "id": 2 + }, + "reveal": { + "type": "TezosRevealOp", + "id": 3 + }, + "transaction": { + "type": "TezosTransactionOp", + "id": 4 + }, + "origination": { + "type": "TezosOriginationOp", + "id": 5 + }, + "delegation": { + "type": "TezosDelegationOp", "id": 6 }, - "supply_change": { - "type": "NEMMosaicSupplyChange", + "proposal": { + "type": "TezosProposalOp", "id": 7 }, - "aggregate_modification": { - "type": "NEMAggregateModification", + "ballot": { + "type": "TezosBallotOp", "id": 8 }, - "importance_transfer": { - "type": "NEMImportanceTransfer", - "id": 9 - }, "chunkify": { "type": "bool", - "id": 10 + "id": 9 } }, "nested": { - "NEMTransactionCommon": { + "TezosContractID": { "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } + "tag": { + "rule": "required", + "type": "TezosContractType", + "id": 1 }, - "network": { - "type": "uint32", - "id": 2, - "options": { - "default": 104 + "hash": { + "rule": "required", + "type": "bytes", + "id": 2 + } + }, + "nested": { + "TezosContractType": { + "values": { + "Implicit": 0, + "Originated": 1 } + } + } + }, + "TezosRevealOp": { + "fields": { + "source": { + "rule": "required", + "type": "bytes", + "id": 7 }, - "timestamp": { + "fee": { "rule": "required", - "type": "uint32", + "type": "uint64", + "id": 2 + }, + "counter": { + "rule": "required", + "type": "uint64", "id": 3 }, - "fee": { + "gas_limit": { "rule": "required", "type": "uint64", "id": 4 }, - "deadline": { + "storage_limit": { "rule": "required", - "type": "uint32", + "type": "uint64", "id": 5 }, - "signer": { + "public_key": { + "rule": "required", "type": "bytes", "id": 6 } } }, - "NEMTransfer": { + "TezosTransactionOp": { "fields": { - "recipient": { + "source": { "rule": "required", - "type": "string", - "id": 1 + "type": "bytes", + "id": 9 }, - "amount": { + "fee": { "rule": "required", "type": "uint64", "id": 2 }, - "payload": { - "type": "bytes", + "counter": { + "rule": "required", + "type": "uint64", "id": 3 }, - "public_key": { - "type": "bytes", + "gas_limit": { + "rule": "required", + "type": "uint64", "id": 4 }, - "mosaics": { - "rule": "repeated", - "type": "NEMMosaic", + "storage_limit": { + "rule": "required", + "type": "uint64", "id": 5 - } - }, - "nested": { - "NEMMosaic": { - "fields": { - "namespace": { - "rule": "required", - "type": "string", - "id": 1 + }, + "amount": { + "rule": "required", + "type": "uint64", + "id": 6 + }, + "destination": { + "rule": "required", + "type": "TezosContractID", + "id": 7 + }, + "parameters": { + "type": "bytes", + "id": 8 + }, + "parameters_manager": { + "type": "TezosParametersManager", + "id": 10 + } + }, + "nested": { + "TezosParametersManager": { + "fields": { + "set_delegate": { + "type": "bytes", + "id": 1 }, - "mosaic": { - "rule": "required", - "type": "string", + "cancel_delegate": { + "type": "bool", "id": 2 }, - "quantity": { - "rule": "required", - "type": "uint64", + "transfer": { + "type": "TezosManagerTransfer", "id": 3 } + }, + "nested": { + "TezosManagerTransfer": { + "fields": { + "destination": { + "rule": "required", + "type": "TezosContractID", + "id": 1 + }, + "amount": { + "rule": "required", + "type": "uint64", + "id": 2 + } + } + } } } } }, - "NEMProvisionNamespace": { + "TezosOriginationOp": { "fields": { - "namespace": { + "source": { "rule": "required", - "type": "string", - "id": 1 + "type": "bytes", + "id": 12 }, - "parent": { - "type": "string", + "fee": { + "rule": "required", + "type": "uint64", "id": 2 }, - "sink": { + "counter": { "rule": "required", - "type": "string", + "type": "uint64", "id": 3 }, - "fee": { + "gas_limit": { "rule": "required", "type": "uint64", "id": 4 + }, + "storage_limit": { + "rule": "required", + "type": "uint64", + "id": 5 + }, + "manager_pubkey": { + "type": "bytes", + "id": 6 + }, + "balance": { + "rule": "required", + "type": "uint64", + "id": 7 + }, + "spendable": { + "type": "bool", + "id": 8 + }, + "delegatable": { + "type": "bool", + "id": 9 + }, + "delegate": { + "type": "bytes", + "id": 10 + }, + "script": { + "rule": "required", + "type": "bytes", + "id": 11 } } }, - "NEMMosaicCreation": { + "TezosDelegationOp": { "fields": { - "definition": { + "source": { "rule": "required", - "type": "NEMMosaicDefinition", - "id": 1 + "type": "bytes", + "id": 7 }, - "sink": { + "fee": { "rule": "required", - "type": "string", + "type": "uint64", "id": 2 }, - "fee": { + "counter": { "rule": "required", "type": "uint64", "id": 3 + }, + "gas_limit": { + "rule": "required", + "type": "uint64", + "id": 4 + }, + "storage_limit": { + "rule": "required", + "type": "uint64", + "id": 5 + }, + "delegate": { + "rule": "required", + "type": "bytes", + "id": 6 } - }, - "nested": { - "NEMMosaicDefinition": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "ticker": { - "type": "string", - "id": 2 - }, - "namespace": { - "rule": "required", - "type": "string", - "id": 3 - }, - "mosaic": { - "rule": "required", - "type": "string", - "id": 4 - }, - "divisibility": { - "type": "uint32", - "id": 5 - }, - "levy": { - "type": "NEMMosaicLevy", - "id": 6 - }, - "fee": { - "type": "uint64", - "id": 7 - }, - "levy_address": { - "type": "string", - "id": 8 - }, - "levy_namespace": { - "type": "string", - "id": 9 - }, - "levy_mosaic": { - "type": "string", - "id": 10 - }, - "supply": { - "type": "uint64", - "id": 11 - }, - "mutable_supply": { - "type": "bool", - "id": 12 - }, - "transferable": { - "type": "bool", - "id": 13 - }, - "description": { - "rule": "required", - "type": "string", - "id": 14 - }, - "networks": { - "rule": "repeated", - "type": "uint32", - "id": 15, - "options": { - "packed": false - } - } - }, - "nested": { - "NEMMosaicLevy": { - "values": { - "MosaicLevy_Absolute": 1, - "MosaicLevy_Percentile": 2 - } - } - } + } + }, + "TezosProposalOp": { + "fields": { + "source": { + "rule": "required", + "type": "bytes", + "id": 1 + }, + "period": { + "rule": "required", + "type": "uint64", + "id": 2 + }, + "proposals": { + "rule": "repeated", + "type": "bytes", + "id": 4 } } }, - "NEMMosaicSupplyChange": { + "TezosBallotOp": { "fields": { - "namespace": { + "source": { "rule": "required", - "type": "string", + "type": "bytes", "id": 1 }, - "mosaic": { + "period": { "rule": "required", - "type": "string", + "type": "uint64", "id": 2 }, - "type": { + "proposal": { "rule": "required", - "type": "NEMSupplyChangeType", + "type": "bytes", "id": 3 }, - "delta": { + "ballot": { "rule": "required", - "type": "uint64", + "type": "TezosBallotType", "id": 4 } }, "nested": { - "NEMSupplyChangeType": { + "TezosBallotType": { "values": { - "SupplyChange_Increase": 1, - "SupplyChange_Decrease": 2 - } - } - } - }, - "NEMAggregateModification": { - "fields": { - "modifications": { - "rule": "repeated", - "type": "NEMCosignatoryModification", - "id": 1 - }, - "relative_change": { - "type": "sint32", - "id": 2 - } - }, - "nested": { - "NEMCosignatoryModification": { - "fields": { - "type": { - "rule": "required", - "type": "NEMModificationType", - "id": 1 - }, - "public_key": { - "rule": "required", - "type": "bytes", - "id": 2 - } - }, - "nested": { - "NEMModificationType": { - "values": { - "CosignatoryModification_Add": 1, - "CosignatoryModification_Delete": 2 - } - } - } - } - } - }, - "NEMImportanceTransfer": { - "fields": { - "mode": { - "rule": "required", - "type": "NEMImportanceTransferMode", - "id": 1 - }, - "public_key": { - "rule": "required", - "type": "bytes", - "id": 2 - } - }, - "nested": { - "NEMImportanceTransferMode": { - "values": { - "ImportanceTransfer_Activate": 1, - "ImportanceTransfer_Deactivate": 2 + "Yay": 0, + "Nay": 1, + "Pass": 2 } } } } } }, - "NEMSignedTx": { + "TezosSignedTx": { "fields": { - "data": { - "rule": "required", - "type": "bytes", - "id": 1 - }, "signature": { - "rule": "required", - "type": "bytes", - "id": 2 - } - } - }, - "NEMDecryptMessage": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "network": { - "type": "uint32", - "id": 2 - }, - "public_key": { - "type": "bytes", - "id": 3 - }, - "payload": { - "type": "bytes", - "id": 4 - } - } - }, - "NEMDecryptedMessage": { - "fields": { - "payload": { - "rule": "required", - "type": "bytes", - "id": 1 - } - } - }, - "RippleGetAddress": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "show_display": { - "type": "bool", - "id": 2 - }, - "chunkify": { - "type": "bool", - "id": 3 - } - } - }, - "RippleAddress": { - "fields": { - "address": { "rule": "required", "type": "string", "id": 1 - } - } - }, - "RippleSignTx": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "fee": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "flags": { - "type": "uint32", - "id": 3, - "options": { - "default": 0 - } - }, - "sequence": { - "rule": "required", - "type": "uint32", - "id": 4 - }, - "last_ledger_sequence": { - "type": "uint32", - "id": 5 - }, - "payment": { - "rule": "required", - "type": "RipplePayment", - "id": 6 - }, - "chunkify": { - "type": "bool", - "id": 7 - } - }, - "nested": { - "RipplePayment": { - "fields": { - "amount": { - "rule": "required", - "type": "uint64", - "id": 1 - }, - "destination": { - "rule": "required", - "type": "string", - "id": 2 - }, - "destination_tag": { - "type": "uint32", - "id": 3 - } - } - } - } - }, - "RippleSignedTx": { - "fields": { - "signature": { - "rule": "required", - "type": "bytes", - "id": 1 - }, - "serialized_tx": { - "rule": "required", - "type": "bytes", - "id": 2 - } - } - }, - "SolanaGetPublicKey": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } }, - "show_display": { - "type": "bool", - "id": 2 - } - } - }, - "SolanaPublicKey": { - "fields": { - "public_key": { + "sig_op_contents": { "rule": "required", "type": "bytes", - "id": 1 - } - } - }, - "SolanaGetAddress": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "show_display": { - "type": "bool", "id": 2 }, - "chunkify": { - "type": "bool", - "id": 3 - } - } - }, - "SolanaAddress": { - "fields": { - "address": { + "operation_hash": { "rule": "required", "type": "string", - "id": 1 + "id": 3 } } }, - "SolanaTxTokenAccountInfo": { - "fields": { - "base_address": { - "rule": "required", - "type": "string", - "id": 1 - }, - "token_program": { - "rule": "required", - "type": "string", - "id": 2 - }, - "token_mint": { - "rule": "required", - "type": "string", - "id": 3 - }, - "token_account": { - "rule": "required", - "type": "string", - "id": 4 - } - } - }, - "SolanaTxAdditionalInfo": { - "fields": { - "token_accounts_infos": { - "rule": "repeated", - "type": "SolanaTxTokenAccountInfo", - "id": 1 - } - } - }, - "SolanaSignTx": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "serialized_tx": { - "rule": "required", - "type": "bytes", - "id": 2 - }, - "additional_info": { - "type": "SolanaTxAdditionalInfo", - "id": 3 - } - } - }, - "SolanaTxSignature": { - "fields": { - "signature": { - "rule": "required", - "type": "bytes", - "id": 1 - } - } - }, - "StellarAssetType": { + "MessageType": { "values": { - "NATIVE": 0, - "ALPHANUM4": 1, - "ALPHANUM12": 2 - } - }, - "StellarAsset": { - "fields": { - "type": { - "rule": "required", - "type": "StellarAssetType", - "id": 1 - }, - "code": { - "type": "string", - "id": 2 - }, - "issuer": { - "type": "string", - "id": 3 - } - } - }, - "StellarGetAddress": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "show_display": { - "type": "bool", - "id": 2 - }, - "chunkify": { - "type": "bool", - "id": 3 - } - } - }, - "StellarAddress": { - "fields": { - "address": { - "rule": "required", - "type": "string", - "id": 1 - } - } - }, - "StellarSignTx": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 2, - "options": { - "packed": false - } - }, - "network_passphrase": { - "rule": "required", - "type": "string", - "id": 3 - }, - "source_account": { - "rule": "required", - "type": "string", - "id": 4 - }, - "fee": { - "rule": "required", - "type": "uint32", - "id": 5 - }, - "sequence_number": { - "rule": "required", - "type": "uint64", - "id": 6 - }, - "timebounds_start": { - "rule": "required", - "type": "uint32", - "id": 8 - }, - "timebounds_end": { - "rule": "required", - "type": "uint32", - "id": 9 - }, - "memo_type": { - "rule": "required", - "type": "StellarMemoType", - "id": 10 - }, - "memo_text": { - "type": "string", - "id": 11 - }, - "memo_id": { - "type": "uint64", - "id": 12 - }, - "memo_hash": { - "type": "bytes", - "id": 13 - }, - "num_operations": { - "rule": "required", - "type": "uint32", - "id": 14 - } - }, - "nested": { - "StellarMemoType": { - "values": { - "NONE": 0, - "TEXT": 1, - "ID": 2, - "HASH": 3, - "RETURN": 4 - } - } - } - }, - "StellarTxOpRequest": { - "fields": {} - }, - "StellarPaymentOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "destination_account": { - "rule": "required", - "type": "string", - "id": 2 - }, - "asset": { - "rule": "required", - "type": "StellarAsset", - "id": 3 - }, - "amount": { - "rule": "required", - "type": "sint64", - "id": 4 - } - } - }, - "StellarCreateAccountOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "new_account": { - "rule": "required", - "type": "string", - "id": 2 - }, - "starting_balance": { - "rule": "required", - "type": "sint64", - "id": 3 - } - } - }, - "StellarPathPaymentStrictReceiveOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "send_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "send_max": { - "rule": "required", - "type": "sint64", - "id": 3 - }, - "destination_account": { - "rule": "required", - "type": "string", - "id": 4 - }, - "destination_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 5 - }, - "destination_amount": { - "rule": "required", - "type": "sint64", - "id": 6 - }, - "paths": { - "rule": "repeated", - "type": "StellarAsset", - "id": 7 - } - } - }, - "StellarPathPaymentStrictSendOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "send_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "send_amount": { - "rule": "required", - "type": "sint64", - "id": 3 - }, - "destination_account": { - "rule": "required", - "type": "string", - "id": 4 - }, - "destination_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 5 - }, - "destination_min": { - "rule": "required", - "type": "sint64", - "id": 6 - }, - "paths": { - "rule": "repeated", - "type": "StellarAsset", - "id": 7 - } - } - }, - "StellarManageSellOfferOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "selling_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "buying_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 3 - }, - "amount": { - "rule": "required", - "type": "sint64", - "id": 4 - }, - "price_n": { - "rule": "required", - "type": "uint32", - "id": 5 - }, - "price_d": { - "rule": "required", - "type": "uint32", - "id": 6 - }, - "offer_id": { - "rule": "required", - "type": "uint64", - "id": 7 - } - } - }, - "StellarManageBuyOfferOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "selling_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "buying_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 3 - }, - "amount": { - "rule": "required", - "type": "sint64", - "id": 4 - }, - "price_n": { - "rule": "required", - "type": "uint32", - "id": 5 - }, - "price_d": { - "rule": "required", - "type": "uint32", - "id": 6 - }, - "offer_id": { - "rule": "required", - "type": "uint64", - "id": 7 - } - } - }, - "StellarCreatePassiveSellOfferOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "selling_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "buying_asset": { - "rule": "required", - "type": "StellarAsset", - "id": 3 - }, - "amount": { - "rule": "required", - "type": "sint64", - "id": 4 - }, - "price_n": { - "rule": "required", - "type": "uint32", - "id": 5 - }, - "price_d": { - "rule": "required", - "type": "uint32", - "id": 6 - } - } - }, - "StellarSetOptionsOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "inflation_destination_account": { - "type": "string", - "id": 2 - }, - "clear_flags": { - "type": "uint32", - "id": 3 - }, - "set_flags": { - "type": "uint32", - "id": 4 - }, - "master_weight": { - "type": "uint32", - "id": 5 - }, - "low_threshold": { - "type": "uint32", - "id": 6 - }, - "medium_threshold": { - "type": "uint32", - "id": 7 - }, - "high_threshold": { - "type": "uint32", - "id": 8 - }, - "home_domain": { - "type": "string", - "id": 9 - }, - "signer_type": { - "type": "StellarSignerType", - "id": 10 - }, - "signer_key": { - "type": "bytes", - "id": 11 - }, - "signer_weight": { - "type": "uint32", - "id": 12 - } - }, - "nested": { - "StellarSignerType": { - "values": { - "ACCOUNT": 0, - "PRE_AUTH": 1, - "HASH": 2 - } - } - } - }, - "StellarChangeTrustOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "asset": { - "rule": "required", - "type": "StellarAsset", - "id": 2 - }, - "limit": { - "rule": "required", - "type": "uint64", - "id": 3 - } - } - }, - "StellarAllowTrustOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "trusted_account": { - "rule": "required", - "type": "string", - "id": 2 - }, - "asset_type": { - "rule": "required", - "type": "StellarAssetType", - "id": 3 - }, - "asset_code": { - "type": "string", - "id": 4 - }, - "is_authorized": { - "rule": "required", - "type": "bool", - "id": 5 - } - } - }, - "StellarAccountMergeOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "destination_account": { - "rule": "required", - "type": "string", - "id": 2 - } - } - }, - "StellarManageDataOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "key": { - "rule": "required", - "type": "string", - "id": 2 - }, - "value": { - "type": "bytes", - "id": 3 - } - } - }, - "StellarBumpSequenceOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "bump_to": { - "rule": "required", - "type": "uint64", - "id": 2 - } - } - }, - "StellarClaimClaimableBalanceOp": { - "fields": { - "source_account": { - "type": "string", - "id": 1 - }, - "balance_id": { - "rule": "required", - "type": "bytes", - "id": 2 - } - } - }, - "StellarSignedTx": { - "fields": { - "public_key": { - "rule": "required", - "type": "bytes", - "id": 1 - }, - "signature": { - "rule": "required", - "type": "bytes", - "id": 2 - } - } - }, - "TezosGetAddress": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "show_display": { - "type": "bool", - "id": 2 - }, - "chunkify": { - "type": "bool", - "id": 3 - } - } - }, - "TezosAddress": { - "fields": { - "address": { - "rule": "required", - "type": "string", - "id": 1 - } - } - }, - "TezosGetPublicKey": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "show_display": { - "type": "bool", - "id": 2 - }, - "chunkify": { - "type": "bool", - "id": 3 - } - } - }, - "TezosPublicKey": { - "fields": { - "public_key": { - "rule": "required", - "type": "string", - "id": 1 - } - } - }, - "TezosSignTx": { - "fields": { - "address_n": { - "rule": "repeated", - "type": "uint32", - "id": 1, - "options": { - "packed": false - } - }, - "branch": { - "rule": "required", - "type": "bytes", - "id": 2 - }, - "reveal": { - "type": "TezosRevealOp", - "id": 3 - }, - "transaction": { - "type": "TezosTransactionOp", - "id": 4 - }, - "origination": { - "type": "TezosOriginationOp", - "id": 5 - }, - "delegation": { - "type": "TezosDelegationOp", - "id": 6 - }, - "proposal": { - "type": "TezosProposalOp", - "id": 7 - }, - "ballot": { - "type": "TezosBallotOp", - "id": 8 - }, - "chunkify": { - "type": "bool", - "id": 9 - } - }, - "nested": { - "TezosContractID": { - "fields": { - "tag": { - "rule": "required", - "type": "TezosContractType", - "id": 1 - }, - "hash": { - "rule": "required", - "type": "bytes", - "id": 2 - } - }, - "nested": { - "TezosContractType": { - "values": { - "Implicit": 0, - "Originated": 1 - } - } - } - }, - "TezosRevealOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 7 - }, - "fee": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "counter": { - "rule": "required", - "type": "uint64", - "id": 3 - }, - "gas_limit": { - "rule": "required", - "type": "uint64", - "id": 4 - }, - "storage_limit": { - "rule": "required", - "type": "uint64", - "id": 5 - }, - "public_key": { - "rule": "required", - "type": "bytes", - "id": 6 - } - } - }, - "TezosTransactionOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 9 - }, - "fee": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "counter": { - "rule": "required", - "type": "uint64", - "id": 3 - }, - "gas_limit": { - "rule": "required", - "type": "uint64", - "id": 4 - }, - "storage_limit": { - "rule": "required", - "type": "uint64", - "id": 5 - }, - "amount": { - "rule": "required", - "type": "uint64", - "id": 6 - }, - "destination": { - "rule": "required", - "type": "TezosContractID", - "id": 7 - }, - "parameters": { - "type": "bytes", - "id": 8 - }, - "parameters_manager": { - "type": "TezosParametersManager", - "id": 10 - } - }, - "nested": { - "TezosParametersManager": { - "fields": { - "set_delegate": { - "type": "bytes", - "id": 1 - }, - "cancel_delegate": { - "type": "bool", - "id": 2 - }, - "transfer": { - "type": "TezosManagerTransfer", - "id": 3 - } - }, - "nested": { - "TezosManagerTransfer": { - "fields": { - "destination": { - "rule": "required", - "type": "TezosContractID", - "id": 1 - }, - "amount": { - "rule": "required", - "type": "uint64", - "id": 2 - } - } - } - } - } - } - }, - "TezosOriginationOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 12 - }, - "fee": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "counter": { - "rule": "required", - "type": "uint64", - "id": 3 - }, - "gas_limit": { - "rule": "required", - "type": "uint64", - "id": 4 - }, - "storage_limit": { - "rule": "required", - "type": "uint64", - "id": 5 - }, - "manager_pubkey": { - "type": "bytes", - "id": 6 - }, - "balance": { - "rule": "required", - "type": "uint64", - "id": 7 - }, - "spendable": { - "type": "bool", - "id": 8 - }, - "delegatable": { - "type": "bool", - "id": 9 - }, - "delegate": { - "type": "bytes", - "id": 10 - }, - "script": { - "rule": "required", - "type": "bytes", - "id": 11 - } - } - }, - "TezosDelegationOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 7 - }, - "fee": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "counter": { - "rule": "required", - "type": "uint64", - "id": 3 - }, - "gas_limit": { - "rule": "required", - "type": "uint64", - "id": 4 - }, - "storage_limit": { - "rule": "required", - "type": "uint64", - "id": 5 - }, - "delegate": { - "rule": "required", - "type": "bytes", - "id": 6 - } - } - }, - "TezosProposalOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 1 - }, - "period": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "proposals": { - "rule": "repeated", - "type": "bytes", - "id": 4 - } - } - }, - "TezosBallotOp": { - "fields": { - "source": { - "rule": "required", - "type": "bytes", - "id": 1 - }, - "period": { - "rule": "required", - "type": "uint64", - "id": 2 - }, - "proposal": { - "rule": "required", - "type": "bytes", - "id": 3 - }, - "ballot": { - "rule": "required", - "type": "TezosBallotType", - "id": 4 - } - }, - "nested": { - "TezosBallotType": { - "values": { - "Yay": 0, - "Nay": 1, - "Pass": 2 - } - } - } - } - } - }, - "TezosSignedTx": { - "fields": { - "signature": { - "rule": "required", - "type": "string", - "id": 1 - }, - "sig_op_contents": { - "rule": "required", - "type": "bytes", - "id": 2 - }, - "operation_hash": { - "rule": "required", - "type": "string", - "id": 3 - } - } - }, - "WebAuthnListResidentCredentials": { - "fields": {} - }, - "WebAuthnAddResidentCredential": { - "fields": { - "credential_id": { - "type": "bytes", - "id": 1 - } - } - }, - "WebAuthnRemoveResidentCredential": { - "fields": { - "index": { - "type": "uint32", - "id": 1 - } - } - }, - "WebAuthnCredentials": { - "fields": { - "credentials": { - "rule": "repeated", - "type": "WebAuthnCredential", - "id": 1 - } - }, - "nested": { - "WebAuthnCredential": { - "fields": { - "index": { - "type": "uint32", - "id": 1 - }, - "id": { - "type": "bytes", - "id": 2 - }, - "rp_id": { - "type": "string", - "id": 3 - }, - "rp_name": { - "type": "string", - "id": 4 - }, - "user_id": { - "type": "bytes", - "id": 5 - }, - "user_name": { - "type": "string", - "id": 6 - }, - "user_display_name": { - "type": "string", - "id": 7 - }, - "creation_time": { - "type": "uint32", - "id": 8 - }, - "hmac_secret": { - "type": "bool", - "id": 9 - }, - "use_sign_count": { - "type": "bool", - "id": 10 - }, - "algorithm": { - "type": "sint32", - "id": 11 - }, - "curve": { - "type": "sint32", - "id": 12 - } - } - } - } - }, - "wire_in": { - "type": "bool", - "id": 50002, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_out": { - "type": "bool", - "id": 50003, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_debug_in": { - "type": "bool", - "id": 50004, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_debug_out": { - "type": "bool", - "id": 50005, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_tiny": { - "type": "bool", - "id": 50006, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_bootloader": { - "type": "bool", - "id": 50007, - "extend": "google.protobuf.EnumValueOptions" - }, - "wire_no_fsm": { - "type": "bool", - "id": 50008, - "extend": "google.protobuf.EnumValueOptions" - }, - "bitcoin_only": { - "type": "bool", - "id": 60000, - "extend": "google.protobuf.EnumValueOptions" - }, - "has_bitcoin_only_values": { - "type": "bool", - "id": 51001, - "extend": "google.protobuf.EnumOptions" - }, - "experimental_message": { - "type": "bool", - "id": 52001, - "extend": "google.protobuf.MessageOptions" - }, - "wire_type": { - "type": "uint32", - "id": 52002, - "extend": "google.protobuf.MessageOptions" - }, - "experimental_field": { - "type": "bool", - "id": 53001, - "extend": "google.protobuf.FieldOptions" - }, - "include_in_bitcoin_only": { - "type": "bool", - "id": 60000, - "extend": "google.protobuf.FileOptions" - }, - "MessageType": { - "options": { - "(has_bitcoin_only_values)": true - }, - "valuesOptions": { - "MessageType_Initialize": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_tiny)": true - }, - "MessageType_Ping": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_Success": { - "(bitcoin_only)": true, - "(wire_out)": true, - "(wire_debug_out)": true - }, - "MessageType_Failure": { - "(bitcoin_only)": true, - "(wire_out)": true, - "(wire_debug_out)": true - }, - "MessageType_ChangePin": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_WipeDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_GetEntropy": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_Entropy": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_LoadDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ResetDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_SetBusy": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_Features": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_PinMatrixRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_PinMatrixAck": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_tiny)": true, - "(wire_no_fsm)": true - }, - "MessageType_Cancel": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_tiny)": true - }, - "MessageType_LockDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ApplySettings": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ButtonRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_ButtonAck": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_tiny)": true, - "(wire_no_fsm)": true - }, - "MessageType_ApplyFlags": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_GetNonce": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_Nonce": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_BackupDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_EntropyRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_EntropyAck": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_PassphraseRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_PassphraseAck": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_tiny)": true, - "(wire_no_fsm)": true - }, - "MessageType_RecoveryDevice": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_WordRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_WordAck": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_GetFeatures": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_SdProtect": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ChangeWipeCode": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_EndSession": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_DoPreauthorized": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_PreauthorizedRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_CancelAuthorization": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_RebootToBootloader": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_GetFirmwareHash": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_FirmwareHash": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_UnlockPath": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_UnlockedPathRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_ShowDeviceTutorial": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_UnlockBootloader": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_AuthenticateDevice": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_AuthenticityProof": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ChangeLanguage": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_TranslationDataRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_TranslationDataAck": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_SetBrightness": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_SetU2FCounter": { - "(wire_in)": true - }, - "MessageType_GetNextU2FCounter": { - "(wire_in)": true - }, - "MessageType_NextU2FCounter": { - "(wire_out)": true - }, - "MessageType_Deprecated_PassphraseStateRequest": { - "deprecated": true - }, - "MessageType_Deprecated_PassphraseStateAck": { - "deprecated": true - }, - "MessageType_FirmwareErase": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_bootloader)": true - }, - "MessageType_FirmwareUpload": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_bootloader)": true - }, - "MessageType_FirmwareRequest": { - "(bitcoin_only)": true, - "(wire_out)": true, - "(wire_bootloader)": true - }, - "MessageType_ProdTestT1": { - "(bitcoin_only)": true, - "(wire_in)": true, - "(wire_bootloader)": true - }, - "MessageType_GetPublicKey": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_PublicKey": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_SignTx": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_TxRequest": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_TxAck": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_GetAddress": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_Address": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_TxAckPaymentRequest": { - "(wire_in)": true - }, - "MessageType_SignMessage": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_VerifyMessage": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_MessageSignature": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_GetOwnershipId": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_OwnershipId": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_GetOwnershipProof": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_OwnershipProof": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_AuthorizeCoinJoin": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_CipherKeyValue": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_CipheredKeyValue": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_SignIdentity": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_SignedIdentity": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_GetECDHSessionKey": { - "(bitcoin_only)": true, - "(wire_in)": true - }, - "MessageType_ECDHSessionKey": { - "(bitcoin_only)": true, - "(wire_out)": true - }, - "MessageType_DebugLinkDecision": { - "(bitcoin_only)": true, - "(wire_debug_in)": true, - "(wire_tiny)": true, - "(wire_no_fsm)": true - }, - "MessageType_DebugLinkGetState": { - "(bitcoin_only)": true, - "(wire_debug_in)": true, - "(wire_tiny)": true - }, - "MessageType_DebugLinkState": { - "(bitcoin_only)": true, - "(wire_debug_out)": true - }, - "MessageType_DebugLinkStop": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkLog": { - "(bitcoin_only)": true, - "(wire_debug_out)": true - }, - "MessageType_DebugLinkMemoryRead": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkMemory": { - "(bitcoin_only)": true, - "(wire_debug_out)": true - }, - "MessageType_DebugLinkMemoryWrite": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkFlashErase": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkLayout": { - "(bitcoin_only)": true, - "(wire_debug_out)": true - }, - "MessageType_DebugLinkReseedRandom": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkRecordScreen": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkEraseSdCard": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkWatchLayout": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_DebugLinkResetDebugEvents": { - "(bitcoin_only)": true, - "(wire_debug_in)": true - }, - "MessageType_EthereumGetPublicKey": { - "(wire_in)": true - }, - "MessageType_EthereumPublicKey": { - "(wire_out)": true - }, - "MessageType_EthereumGetAddress": { - "(wire_in)": true - }, - "MessageType_EthereumAddress": { - "(wire_out)": true - }, - "MessageType_EthereumSignTx": { - "(wire_in)": true - }, - "MessageType_EthereumSignTxEIP1559": { - "(wire_in)": true - }, - "MessageType_EthereumTxRequest": { - "(wire_out)": true - }, - "MessageType_EthereumTxAck": { - "(wire_in)": true - }, - "MessageType_EthereumSignMessage": { - "(wire_in)": true - }, - "MessageType_EthereumVerifyMessage": { - "(wire_in)": true - }, - "MessageType_EthereumMessageSignature": { - "(wire_out)": true - }, - "MessageType_EthereumSignTypedData": { - "(wire_in)": true - }, - "MessageType_EthereumTypedDataStructRequest": { - "(wire_out)": true - }, - "MessageType_EthereumTypedDataStructAck": { - "(wire_in)": true - }, - "MessageType_EthereumTypedDataValueRequest": { - "(wire_out)": true - }, - "MessageType_EthereumTypedDataValueAck": { - "(wire_in)": true - }, - "MessageType_EthereumTypedDataSignature": { - "(wire_out)": true - }, - "MessageType_EthereumSignTypedHash": { - "(wire_in)": true - }, - "MessageType_NEMGetAddress": { - "(wire_in)": true - }, - "MessageType_NEMAddress": { - "(wire_out)": true - }, - "MessageType_NEMSignTx": { - "(wire_in)": true - }, - "MessageType_NEMSignedTx": { - "(wire_out)": true - }, - "MessageType_NEMDecryptMessage": { - "(wire_in)": true - }, - "MessageType_NEMDecryptedMessage": { - "(wire_out)": true - }, - "MessageType_TezosGetAddress": { - "(wire_in)": true - }, - "MessageType_TezosAddress": { - "(wire_out)": true - }, - "MessageType_TezosSignTx": { - "(wire_in)": true - }, - "MessageType_TezosSignedTx": { - "(wire_out)": true - }, - "MessageType_TezosGetPublicKey": { - "(wire_in)": true - }, - "MessageType_TezosPublicKey": { - "(wire_out)": true - }, - "MessageType_StellarSignTx": { - "(wire_in)": true - }, - "MessageType_StellarTxOpRequest": { - "(wire_out)": true - }, - "MessageType_StellarGetAddress": { - "(wire_in)": true - }, - "MessageType_StellarAddress": { - "(wire_out)": true - }, - "MessageType_StellarCreateAccountOp": { - "(wire_in)": true - }, - "MessageType_StellarPaymentOp": { - "(wire_in)": true - }, - "MessageType_StellarPathPaymentStrictReceiveOp": { - "(wire_in)": true - }, - "MessageType_StellarManageSellOfferOp": { - "(wire_in)": true - }, - "MessageType_StellarCreatePassiveSellOfferOp": { - "(wire_in)": true - }, - "MessageType_StellarSetOptionsOp": { - "(wire_in)": true - }, - "MessageType_StellarChangeTrustOp": { - "(wire_in)": true - }, - "MessageType_StellarAllowTrustOp": { - "(wire_in)": true - }, - "MessageType_StellarAccountMergeOp": { - "(wire_in)": true - }, - "MessageType_StellarManageDataOp": { - "(wire_in)": true - }, - "MessageType_StellarBumpSequenceOp": { - "(wire_in)": true - }, - "MessageType_StellarManageBuyOfferOp": { - "(wire_in)": true - }, - "MessageType_StellarPathPaymentStrictSendOp": { - "(wire_in)": true - }, - "MessageType_StellarClaimClaimableBalanceOp": { - "(wire_in)": true - }, - "MessageType_StellarSignedTx": { - "(wire_out)": true - }, - "MessageType_CardanoGetPublicKey": { - "(wire_in)": true - }, - "MessageType_CardanoPublicKey": { - "(wire_out)": true - }, - "MessageType_CardanoGetAddress": { - "(wire_in)": true - }, - "MessageType_CardanoAddress": { - "(wire_out)": true - }, - "MessageType_CardanoTxItemAck": { - "(wire_out)": true - }, - "MessageType_CardanoTxAuxiliaryDataSupplement": { - "(wire_out)": true - }, - "MessageType_CardanoTxWitnessRequest": { - "(wire_in)": true - }, - "MessageType_CardanoTxWitnessResponse": { - "(wire_out)": true - }, - "MessageType_CardanoTxHostAck": { - "(wire_in)": true - }, - "MessageType_CardanoTxBodyHash": { - "(wire_out)": true - }, - "MessageType_CardanoSignTxFinished": { - "(wire_out)": true - }, - "MessageType_CardanoSignTxInit": { - "(wire_in)": true - }, - "MessageType_CardanoTxInput": { - "(wire_in)": true - }, - "MessageType_CardanoTxOutput": { - "(wire_in)": true - }, - "MessageType_CardanoAssetGroup": { - "(wire_in)": true - }, - "MessageType_CardanoToken": { - "(wire_in)": true - }, - "MessageType_CardanoTxCertificate": { - "(wire_in)": true - }, - "MessageType_CardanoTxWithdrawal": { - "(wire_in)": true - }, - "MessageType_CardanoTxAuxiliaryData": { - "(wire_in)": true - }, - "MessageType_CardanoPoolOwner": { - "(wire_in)": true - }, - "MessageType_CardanoPoolRelayParameters": { - "(wire_in)": true - }, - "MessageType_CardanoGetNativeScriptHash": { - "(wire_in)": true - }, - "MessageType_CardanoNativeScriptHash": { - "(wire_out)": true - }, - "MessageType_CardanoTxMint": { - "(wire_in)": true - }, - "MessageType_CardanoTxCollateralInput": { - "(wire_in)": true - }, - "MessageType_CardanoTxRequiredSigner": { - "(wire_in)": true - }, - "MessageType_CardanoTxInlineDatumChunk": { - "(wire_in)": true - }, - "MessageType_CardanoTxReferenceScriptChunk": { - "(wire_in)": true - }, - "MessageType_CardanoTxReferenceInput": { - "(wire_in)": true - }, - "MessageType_RippleGetAddress": { - "(wire_in)": true - }, - "MessageType_RippleAddress": { - "(wire_out)": true - }, - "MessageType_RippleSignTx": { - "(wire_in)": true - }, - "MessageType_RippleSignedTx": { - "(wire_in)": true - }, - "MessageType_MoneroTransactionInitRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionInitAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSetInputRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSetInputAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionInputViniRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionInputViniAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionAllInputsSetRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionAllInputsSetAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSetOutputRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSetOutputAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionAllOutSetRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionAllOutSetAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSignInputRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionSignInputAck": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionFinalRequest": { - "(wire_out)": true - }, - "MessageType_MoneroTransactionFinalAck": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageExportInitRequest": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageExportInitAck": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageSyncStepRequest": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageSyncStepAck": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageSyncFinalRequest": { - "(wire_out)": true - }, - "MessageType_MoneroKeyImageSyncFinalAck": { - "(wire_out)": true - }, - "MessageType_MoneroGetAddress": { - "(wire_in)": true - }, - "MessageType_MoneroAddress": { - "(wire_out)": true - }, - "MessageType_MoneroGetWatchKey": { - "(wire_in)": true - }, - "MessageType_MoneroWatchKey": { - "(wire_out)": true - }, - "MessageType_DebugMoneroDiagRequest": { - "(wire_in)": true - }, - "MessageType_DebugMoneroDiagAck": { - "(wire_out)": true - }, - "MessageType_MoneroGetTxKeyRequest": { - "(wire_in)": true - }, - "MessageType_MoneroGetTxKeyAck": { - "(wire_out)": true - }, - "MessageType_MoneroLiveRefreshStartRequest": { - "(wire_in)": true - }, - "MessageType_MoneroLiveRefreshStartAck": { - "(wire_out)": true - }, - "MessageType_MoneroLiveRefreshStepRequest": { - "(wire_in)": true - }, - "MessageType_MoneroLiveRefreshStepAck": { - "(wire_out)": true - }, - "MessageType_MoneroLiveRefreshFinalRequest": { - "(wire_in)": true - }, - "MessageType_MoneroLiveRefreshFinalAck": { - "(wire_out)": true - }, - "MessageType_EosGetPublicKey": { - "(wire_in)": true - }, - "MessageType_EosPublicKey": { - "(wire_out)": true - }, - "MessageType_EosSignTx": { - "(wire_in)": true - }, - "MessageType_EosTxActionRequest": { - "(wire_out)": true - }, - "MessageType_EosTxActionAck": { - "(wire_in)": true - }, - "MessageType_EosSignedTx": { - "(wire_out)": true - }, - "MessageType_BinanceGetAddress": { - "(wire_in)": true - }, - "MessageType_BinanceAddress": { - "(wire_out)": true - }, - "MessageType_BinanceGetPublicKey": { - "(wire_in)": true - }, - "MessageType_BinancePublicKey": { - "(wire_out)": true - }, - "MessageType_BinanceSignTx": { - "(wire_in)": true - }, - "MessageType_BinanceTxRequest": { - "(wire_out)": true - }, - "MessageType_BinanceTransferMsg": { - "(wire_in)": true - }, - "MessageType_BinanceOrderMsg": { - "(wire_in)": true - }, - "MessageType_BinanceCancelMsg": { - "(wire_in)": true - }, - "MessageType_BinanceSignedTx": { - "(wire_out)": true - }, - "MessageType_WebAuthnListResidentCredentials": { - "(wire_in)": true - }, - "MessageType_WebAuthnCredentials": { - "(wire_out)": true - }, - "MessageType_WebAuthnAddResidentCredential": { - "(wire_in)": true - }, - "MessageType_WebAuthnRemoveResidentCredential": { - "(wire_in)": true - }, - "MessageType_SolanaGetPublicKey": { - "(wire_in)": true - }, - "MessageType_SolanaPublicKey": { - "(wire_out)": true - }, - "MessageType_SolanaGetAddress": { - "(wire_in)": true - }, - "MessageType_SolanaAddress": { - "(wire_out)": true - }, - "MessageType_SolanaSignTx": { - "(wire_in)": true - }, - "MessageType_SolanaTxSignature": { - "(wire_out)": true - } - }, - "values": { - "MessageType_Initialize": 0, - "MessageType_Ping": 1, - "MessageType_Success": 2, - "MessageType_Failure": 3, - "MessageType_ChangePin": 4, - "MessageType_WipeDevice": 5, - "MessageType_GetEntropy": 9, - "MessageType_Entropy": 10, - "MessageType_LoadDevice": 13, - "MessageType_ResetDevice": 14, - "MessageType_SetBusy": 16, - "MessageType_Features": 17, - "MessageType_PinMatrixRequest": 18, - "MessageType_PinMatrixAck": 19, - "MessageType_Cancel": 20, - "MessageType_LockDevice": 24, - "MessageType_ApplySettings": 25, - "MessageType_ButtonRequest": 26, - "MessageType_ButtonAck": 27, - "MessageType_ApplyFlags": 28, - "MessageType_GetNonce": 31, - "MessageType_Nonce": 33, - "MessageType_BackupDevice": 34, - "MessageType_EntropyRequest": 35, - "MessageType_EntropyAck": 36, - "MessageType_PassphraseRequest": 41, - "MessageType_PassphraseAck": 42, - "MessageType_RecoveryDevice": 45, - "MessageType_WordRequest": 46, - "MessageType_WordAck": 47, - "MessageType_GetFeatures": 55, - "MessageType_SdProtect": 79, - "MessageType_ChangeWipeCode": 82, - "MessageType_EndSession": 83, - "MessageType_DoPreauthorized": 84, - "MessageType_PreauthorizedRequest": 85, - "MessageType_CancelAuthorization": 86, - "MessageType_RebootToBootloader": 87, - "MessageType_GetFirmwareHash": 88, - "MessageType_FirmwareHash": 89, - "MessageType_UnlockPath": 93, - "MessageType_UnlockedPathRequest": 94, - "MessageType_ShowDeviceTutorial": 95, - "MessageType_UnlockBootloader": 96, - "MessageType_AuthenticateDevice": 97, - "MessageType_AuthenticityProof": 98, - "MessageType_ChangeLanguage": 990, - "MessageType_TranslationDataRequest": 991, - "MessageType_TranslationDataAck": 992, - "MessageType_SetBrightness": 993, - "MessageType_SetU2FCounter": 63, - "MessageType_GetNextU2FCounter": 80, - "MessageType_NextU2FCounter": 81, - "MessageType_Deprecated_PassphraseStateRequest": 77, - "MessageType_Deprecated_PassphraseStateAck": 78, - "MessageType_FirmwareErase": 6, - "MessageType_FirmwareUpload": 7, - "MessageType_FirmwareRequest": 8, - "MessageType_ProdTestT1": 32, - "MessageType_GetPublicKey": 11, - "MessageType_PublicKey": 12, - "MessageType_SignTx": 15, - "MessageType_TxRequest": 21, - "MessageType_TxAck": 22, - "MessageType_GetAddress": 29, - "MessageType_Address": 30, - "MessageType_TxAckPaymentRequest": 37, - "MessageType_SignMessage": 38, - "MessageType_VerifyMessage": 39, - "MessageType_MessageSignature": 40, - "MessageType_GetOwnershipId": 43, - "MessageType_OwnershipId": 44, - "MessageType_GetOwnershipProof": 49, - "MessageType_OwnershipProof": 50, - "MessageType_AuthorizeCoinJoin": 51, - "MessageType_CipherKeyValue": 23, - "MessageType_CipheredKeyValue": 48, - "MessageType_SignIdentity": 53, - "MessageType_SignedIdentity": 54, - "MessageType_GetECDHSessionKey": 61, - "MessageType_ECDHSessionKey": 62, - "MessageType_DebugLinkDecision": 100, - "MessageType_DebugLinkGetState": 101, - "MessageType_DebugLinkState": 102, - "MessageType_DebugLinkStop": 103, - "MessageType_DebugLinkLog": 104, - "MessageType_DebugLinkMemoryRead": 110, - "MessageType_DebugLinkMemory": 111, - "MessageType_DebugLinkMemoryWrite": 112, - "MessageType_DebugLinkFlashErase": 113, - "MessageType_DebugLinkLayout": 9001, - "MessageType_DebugLinkReseedRandom": 9002, - "MessageType_DebugLinkRecordScreen": 9003, - "MessageType_DebugLinkEraseSdCard": 9005, - "MessageType_DebugLinkWatchLayout": 9006, - "MessageType_DebugLinkResetDebugEvents": 9007, - "MessageType_EthereumGetPublicKey": 450, - "MessageType_EthereumPublicKey": 451, - "MessageType_EthereumGetAddress": 56, - "MessageType_EthereumAddress": 57, - "MessageType_EthereumSignTx": 58, - "MessageType_EthereumSignTxEIP1559": 452, - "MessageType_EthereumTxRequest": 59, - "MessageType_EthereumTxAck": 60, - "MessageType_EthereumSignMessage": 64, - "MessageType_EthereumVerifyMessage": 65, - "MessageType_EthereumMessageSignature": 66, - "MessageType_EthereumSignTypedData": 464, - "MessageType_EthereumTypedDataStructRequest": 465, - "MessageType_EthereumTypedDataStructAck": 466, - "MessageType_EthereumTypedDataValueRequest": 467, - "MessageType_EthereumTypedDataValueAck": 468, - "MessageType_EthereumTypedDataSignature": 469, - "MessageType_EthereumSignTypedHash": 470, - "MessageType_NEMGetAddress": 67, - "MessageType_NEMAddress": 68, - "MessageType_NEMSignTx": 69, - "MessageType_NEMSignedTx": 70, - "MessageType_NEMDecryptMessage": 75, - "MessageType_NEMDecryptedMessage": 76, - "MessageType_TezosGetAddress": 150, - "MessageType_TezosAddress": 151, - "MessageType_TezosSignTx": 152, - "MessageType_TezosSignedTx": 153, - "MessageType_TezosGetPublicKey": 154, - "MessageType_TezosPublicKey": 155, - "MessageType_StellarSignTx": 202, - "MessageType_StellarTxOpRequest": 203, - "MessageType_StellarGetAddress": 207, - "MessageType_StellarAddress": 208, - "MessageType_StellarCreateAccountOp": 210, - "MessageType_StellarPaymentOp": 211, - "MessageType_StellarPathPaymentStrictReceiveOp": 212, - "MessageType_StellarManageSellOfferOp": 213, - "MessageType_StellarCreatePassiveSellOfferOp": 214, - "MessageType_StellarSetOptionsOp": 215, - "MessageType_StellarChangeTrustOp": 216, - "MessageType_StellarAllowTrustOp": 217, - "MessageType_StellarAccountMergeOp": 218, - "MessageType_StellarManageDataOp": 220, - "MessageType_StellarBumpSequenceOp": 221, - "MessageType_StellarManageBuyOfferOp": 222, - "MessageType_StellarPathPaymentStrictSendOp": 223, - "MessageType_StellarClaimClaimableBalanceOp": 225, - "MessageType_StellarSignedTx": 230, - "MessageType_CardanoGetPublicKey": 305, - "MessageType_CardanoPublicKey": 306, - "MessageType_CardanoGetAddress": 307, - "MessageType_CardanoAddress": 308, - "MessageType_CardanoTxItemAck": 313, - "MessageType_CardanoTxAuxiliaryDataSupplement": 314, - "MessageType_CardanoTxWitnessRequest": 315, - "MessageType_CardanoTxWitnessResponse": 316, - "MessageType_CardanoTxHostAck": 317, - "MessageType_CardanoTxBodyHash": 318, - "MessageType_CardanoSignTxFinished": 319, - "MessageType_CardanoSignTxInit": 320, - "MessageType_CardanoTxInput": 321, - "MessageType_CardanoTxOutput": 322, - "MessageType_CardanoAssetGroup": 323, - "MessageType_CardanoToken": 324, - "MessageType_CardanoTxCertificate": 325, - "MessageType_CardanoTxWithdrawal": 326, - "MessageType_CardanoTxAuxiliaryData": 327, - "MessageType_CardanoPoolOwner": 328, - "MessageType_CardanoPoolRelayParameters": 329, - "MessageType_CardanoGetNativeScriptHash": 330, - "MessageType_CardanoNativeScriptHash": 331, - "MessageType_CardanoTxMint": 332, - "MessageType_CardanoTxCollateralInput": 333, - "MessageType_CardanoTxRequiredSigner": 334, - "MessageType_CardanoTxInlineDatumChunk": 335, - "MessageType_CardanoTxReferenceScriptChunk": 336, - "MessageType_CardanoTxReferenceInput": 337, - "MessageType_RippleGetAddress": 400, - "MessageType_RippleAddress": 401, - "MessageType_RippleSignTx": 402, - "MessageType_RippleSignedTx": 403, - "MessageType_MoneroTransactionInitRequest": 501, - "MessageType_MoneroTransactionInitAck": 502, - "MessageType_MoneroTransactionSetInputRequest": 503, - "MessageType_MoneroTransactionSetInputAck": 504, - "MessageType_MoneroTransactionInputViniRequest": 507, - "MessageType_MoneroTransactionInputViniAck": 508, - "MessageType_MoneroTransactionAllInputsSetRequest": 509, - "MessageType_MoneroTransactionAllInputsSetAck": 510, - "MessageType_MoneroTransactionSetOutputRequest": 511, - "MessageType_MoneroTransactionSetOutputAck": 512, - "MessageType_MoneroTransactionAllOutSetRequest": 513, - "MessageType_MoneroTransactionAllOutSetAck": 514, - "MessageType_MoneroTransactionSignInputRequest": 515, - "MessageType_MoneroTransactionSignInputAck": 516, - "MessageType_MoneroTransactionFinalRequest": 517, - "MessageType_MoneroTransactionFinalAck": 518, - "MessageType_MoneroKeyImageExportInitRequest": 530, - "MessageType_MoneroKeyImageExportInitAck": 531, - "MessageType_MoneroKeyImageSyncStepRequest": 532, - "MessageType_MoneroKeyImageSyncStepAck": 533, - "MessageType_MoneroKeyImageSyncFinalRequest": 534, - "MessageType_MoneroKeyImageSyncFinalAck": 535, - "MessageType_MoneroGetAddress": 540, - "MessageType_MoneroAddress": 541, - "MessageType_MoneroGetWatchKey": 542, - "MessageType_MoneroWatchKey": 543, - "MessageType_DebugMoneroDiagRequest": 546, - "MessageType_DebugMoneroDiagAck": 547, - "MessageType_MoneroGetTxKeyRequest": 550, - "MessageType_MoneroGetTxKeyAck": 551, - "MessageType_MoneroLiveRefreshStartRequest": 552, - "MessageType_MoneroLiveRefreshStartAck": 553, - "MessageType_MoneroLiveRefreshStepRequest": 554, - "MessageType_MoneroLiveRefreshStepAck": 555, - "MessageType_MoneroLiveRefreshFinalRequest": 556, - "MessageType_MoneroLiveRefreshFinalAck": 557, - "MessageType_EosGetPublicKey": 600, - "MessageType_EosPublicKey": 601, - "MessageType_EosSignTx": 602, - "MessageType_EosTxActionRequest": 603, - "MessageType_EosTxActionAck": 604, - "MessageType_EosSignedTx": 605, - "MessageType_BinanceGetAddress": 700, - "MessageType_BinanceAddress": 701, - "MessageType_BinanceGetPublicKey": 702, - "MessageType_BinancePublicKey": 703, - "MessageType_BinanceSignTx": 704, - "MessageType_BinanceTxRequest": 705, - "MessageType_BinanceTransferMsg": 706, - "MessageType_BinanceOrderMsg": 707, - "MessageType_BinanceCancelMsg": 708, - "MessageType_BinanceSignedTx": 709, - "MessageType_WebAuthnListResidentCredentials": 800, - "MessageType_WebAuthnCredentials": 801, - "MessageType_WebAuthnAddResidentCredential": 802, - "MessageType_WebAuthnRemoveResidentCredential": 803, - "MessageType_SolanaGetPublicKey": 900, - "MessageType_SolanaPublicKey": 901, - "MessageType_SolanaGetAddress": 902, - "MessageType_SolanaAddress": 903, - "MessageType_SolanaSignTx": 904, - "MessageType_SolanaTxSignature": 905 - } - }, - "google": { - "nested": { - "protobuf": { - "nested": { - "FileDescriptorSet": { - "fields": { - "file": { - "rule": "repeated", - "type": "FileDescriptorProto", - "id": 1 - } - } - }, - "FileDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "package": { - "type": "string", - "id": 2 - }, - "dependency": { - "rule": "repeated", - "type": "string", - "id": 3 - }, - "public_dependency": { - "rule": "repeated", - "type": "int32", - "id": 10, - "options": { - "packed": false - } - }, - "weak_dependency": { - "rule": "repeated", - "type": "int32", - "id": 11, - "options": { - "packed": false - } - }, - "message_type": { - "rule": "repeated", - "type": "DescriptorProto", - "id": 4 - }, - "enum_type": { - "rule": "repeated", - "type": "EnumDescriptorProto", - "id": 5 - }, - "service": { - "rule": "repeated", - "type": "ServiceDescriptorProto", - "id": 6 - }, - "extension": { - "rule": "repeated", - "type": "FieldDescriptorProto", - "id": 7 - }, - "options": { - "type": "FileOptions", - "id": 8 - }, - "source_code_info": { - "type": "SourceCodeInfo", - "id": 9 - }, - "syntax": { - "type": "string", - "id": 12 - } - } - }, - "DescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "field": { - "rule": "repeated", - "type": "FieldDescriptorProto", - "id": 2 - }, - "extension": { - "rule": "repeated", - "type": "FieldDescriptorProto", - "id": 6 - }, - "nested_type": { - "rule": "repeated", - "type": "DescriptorProto", - "id": 3 - }, - "enum_type": { - "rule": "repeated", - "type": "EnumDescriptorProto", - "id": 4 - }, - "extension_range": { - "rule": "repeated", - "type": "ExtensionRange", - "id": 5 - }, - "oneof_decl": { - "rule": "repeated", - "type": "OneofDescriptorProto", - "id": 8 - }, - "options": { - "type": "MessageOptions", - "id": 7 - }, - "reserved_range": { - "rule": "repeated", - "type": "ReservedRange", - "id": 9 - }, - "reserved_name": { - "rule": "repeated", - "type": "string", - "id": 10 - } - }, - "nested": { - "ExtensionRange": { - "fields": { - "start": { - "type": "int32", - "id": 1 - }, - "end": { - "type": "int32", - "id": 2 - } - } - }, - "ReservedRange": { - "fields": { - "start": { - "type": "int32", - "id": 1 - }, - "end": { - "type": "int32", - "id": 2 - } - } - } - } - }, - "FieldDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "number": { - "type": "int32", - "id": 3 - }, - "label": { - "type": "Label", - "id": 4 - }, - "type": { - "type": "Type", - "id": 5 - }, - "type_name": { - "type": "string", - "id": 6 - }, - "extendee": { - "type": "string", - "id": 2 - }, - "default_value": { - "type": "string", - "id": 7 - }, - "oneof_index": { - "type": "int32", - "id": 9 - }, - "json_name": { - "type": "string", - "id": 10 - }, - "options": { - "type": "FieldOptions", - "id": 8 - } - }, - "nested": { - "Type": { - "values": { - "TYPE_DOUBLE": 1, - "TYPE_FLOAT": 2, - "TYPE_INT64": 3, - "TYPE_UINT64": 4, - "TYPE_INT32": 5, - "TYPE_FIXED64": 6, - "TYPE_FIXED32": 7, - "TYPE_BOOL": 8, - "TYPE_STRING": 9, - "TYPE_GROUP": 10, - "TYPE_MESSAGE": 11, - "TYPE_BYTES": 12, - "TYPE_UINT32": 13, - "TYPE_ENUM": 14, - "TYPE_SFIXED32": 15, - "TYPE_SFIXED64": 16, - "TYPE_SINT32": 17, - "TYPE_SINT64": 18 - } - }, - "Label": { - "values": { - "LABEL_OPTIONAL": 1, - "LABEL_REQUIRED": 2, - "LABEL_REPEATED": 3 - } - } - } - }, - "OneofDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "options": { - "type": "OneofOptions", - "id": 2 - } - } - }, - "EnumDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "value": { - "rule": "repeated", - "type": "EnumValueDescriptorProto", - "id": 2 - }, - "options": { - "type": "EnumOptions", - "id": 3 - } - } - }, - "EnumValueDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "number": { - "type": "int32", - "id": 2 - }, - "options": { - "type": "EnumValueOptions", - "id": 3 - } - } - }, - "ServiceDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "method": { - "rule": "repeated", - "type": "MethodDescriptorProto", - "id": 2 - }, - "options": { - "type": "ServiceOptions", - "id": 3 - } - } - }, - "MethodDescriptorProto": { - "fields": { - "name": { - "type": "string", - "id": 1 - }, - "input_type": { - "type": "string", - "id": 2 - }, - "output_type": { - "type": "string", - "id": 3 - }, - "options": { - "type": "MethodOptions", - "id": 4 - }, - "client_streaming": { - "type": "bool", - "id": 5 - }, - "server_streaming": { - "type": "bool", - "id": 6 - } - } - }, - "FileOptions": { - "fields": { - "java_package": { - "type": "string", - "id": 1 - }, - "java_outer_classname": { - "type": "string", - "id": 8 - }, - "java_multiple_files": { - "type": "bool", - "id": 10 - }, - "java_generate_equals_and_hash": { - "type": "bool", - "id": 20, - "options": { - "deprecated": true - } - }, - "java_string_check_utf8": { - "type": "bool", - "id": 27 - }, - "optimize_for": { - "type": "OptimizeMode", - "id": 9, - "options": { - "default": "SPEED" - } - }, - "go_package": { - "type": "string", - "id": 11 - }, - "cc_generic_services": { - "type": "bool", - "id": 16 - }, - "java_generic_services": { - "type": "bool", - "id": 17 - }, - "py_generic_services": { - "type": "bool", - "id": 18 - }, - "deprecated": { - "type": "bool", - "id": 23 - }, - "cc_enable_arenas": { - "type": "bool", - "id": 31 - }, - "objc_class_prefix": { - "type": "string", - "id": 36 - }, - "csharp_namespace": { - "type": "string", - "id": 37 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]], - "reserved": [[38, 38]], - "nested": { - "OptimizeMode": { - "values": { - "SPEED": 1, - "CODE_SIZE": 2, - "LITE_RUNTIME": 3 - } - } - } - }, - "MessageOptions": { - "fields": { - "message_set_wire_format": { - "type": "bool", - "id": 1 - }, - "no_standard_descriptor_accessor": { - "type": "bool", - "id": 2 - }, - "deprecated": { - "type": "bool", - "id": 3 - }, - "map_entry": { - "type": "bool", - "id": 7 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]], - "reserved": [[8, 8]] - }, - "FieldOptions": { - "fields": { - "ctype": { - "type": "CType", - "id": 1, - "options": { - "default": "STRING" - } - }, - "packed": { - "type": "bool", - "id": 2 - }, - "jstype": { - "type": "JSType", - "id": 6, - "options": { - "default": "JS_NORMAL" - } - }, - "lazy": { - "type": "bool", - "id": 5 - }, - "deprecated": { - "type": "bool", - "id": 3 - }, - "weak": { - "type": "bool", - "id": 10 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]], - "reserved": [[4, 4]], - "nested": { - "CType": { - "values": { - "STRING": 0, - "CORD": 1, - "STRING_PIECE": 2 - } - }, - "JSType": { - "values": { - "JS_NORMAL": 0, - "JS_STRING": 1, - "JS_NUMBER": 2 - } - } - } - }, - "OneofOptions": { - "fields": { - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]] - }, - "EnumOptions": { - "fields": { - "allow_alias": { - "type": "bool", - "id": 2 - }, - "deprecated": { - "type": "bool", - "id": 3 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]] - }, - "EnumValueOptions": { - "fields": { - "deprecated": { - "type": "bool", - "id": 1 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]] - }, - "ServiceOptions": { - "fields": { - "deprecated": { - "type": "bool", - "id": 33 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]] - }, - "MethodOptions": { - "fields": { - "deprecated": { - "type": "bool", - "id": 33 - }, - "uninterpreted_option": { - "rule": "repeated", - "type": "UninterpretedOption", - "id": 999 - } - }, - "extensions": [[1000, 536870911]] - }, - "UninterpretedOption": { - "fields": { - "name": { - "rule": "repeated", - "type": "NamePart", - "id": 2 - }, - "identifier_value": { - "type": "string", - "id": 3 - }, - "positive_int_value": { - "type": "uint64", - "id": 4 - }, - "negative_int_value": { - "type": "int64", - "id": 5 - }, - "double_value": { - "type": "double", - "id": 6 - }, - "string_value": { - "type": "bytes", - "id": 7 - }, - "aggregate_value": { - "type": "string", - "id": 8 - } - }, - "nested": { - "NamePart": { - "fields": { - "name_part": { - "rule": "required", - "type": "string", - "id": 1 - }, - "is_extension": { - "rule": "required", - "type": "bool", - "id": 2 - } - } - } - } - }, - "SourceCodeInfo": { - "fields": { - "location": { - "rule": "repeated", - "type": "Location", - "id": 1 - } - }, - "nested": { - "Location": { - "fields": { - "path": { - "rule": "repeated", - "type": "int32", - "id": 1 - }, - "span": { - "rule": "repeated", - "type": "int32", - "id": 2 - }, - "leading_comments": { - "type": "string", - "id": 3 - }, - "trailing_comments": { - "type": "string", - "id": 4 - }, - "leading_detached_comments": { - "rule": "repeated", - "type": "string", - "id": 6 - } - } - } - } - }, - "GeneratedCodeInfo": { - "fields": { - "annotation": { - "rule": "repeated", - "type": "Annotation", - "id": 1 - } - }, - "nested": { - "Annotation": { - "fields": { - "path": { - "rule": "repeated", - "type": "int32", - "id": 1 - }, - "source_file": { - "type": "string", - "id": 2 - }, - "begin": { - "type": "int32", - "id": 3 - }, - "end": { - "type": "int32", - "id": 4 - } - } - } - } - } - } - } + "Initialize": 0, + "Ping": 1, + "Success": 2, + "Failure": 3, + "ChangePin": 4, + "WipeDevice": 5, + "GetEntropy": 9, + "Entropy": 10, + "LoadDevice": 13, + "ResetDevice": 14, + "SetBusy": 16, + "Features": 17, + "PinMatrixRequest": 18, + "PinMatrixAck": 19, + "Cancel": 20, + "LockDevice": 24, + "ApplySettings": 25, + "ButtonRequest": 26, + "ButtonAck": 27, + "ApplyFlags": 28, + "GetNonce": 31, + "Nonce": 33, + "BackupDevice": 34, + "EntropyRequest": 35, + "EntropyAck": 36, + "PassphraseRequest": 41, + "PassphraseAck": 42, + "RecoveryDevice": 45, + "WordRequest": 46, + "WordAck": 47, + "GetFeatures": 55, + "SdProtect": 79, + "ChangeWipeCode": 82, + "EndSession": 83, + "DoPreauthorized": 84, + "PreauthorizedRequest": 85, + "CancelAuthorization": 86, + "RebootToBootloader": 87, + "GetFirmwareHash": 88, + "FirmwareHash": 89, + "UnlockPath": 93, + "UnlockedPathRequest": 94, + "ShowDeviceTutorial": 95, + "UnlockBootloader": 96, + "AuthenticateDevice": 97, + "AuthenticityProof": 98, + "ChangeLanguage": 990, + "TranslationDataRequest": 991, + "TranslationDataAck": 992, + "SetBrightness": 993, + "SetU2FCounter": 63, + "GetNextU2FCounter": 80, + "NextU2FCounter": 81, + "Deprecated_PassphraseStateRequest": 77, + "Deprecated_PassphraseStateAck": 78, + "FirmwareErase": 6, + "FirmwareUpload": 7, + "FirmwareRequest": 8, + "ProdTestT1": 32, + "GetPublicKey": 11, + "PublicKey": 12, + "SignTx": 15, + "TxRequest": 21, + "TxAck": 22, + "GetAddress": 29, + "Address": 30, + "TxAckPaymentRequest": 37, + "SignMessage": 38, + "VerifyMessage": 39, + "MessageSignature": 40, + "GetOwnershipId": 43, + "OwnershipId": 44, + "GetOwnershipProof": 49, + "OwnershipProof": 50, + "AuthorizeCoinJoin": 51, + "CipherKeyValue": 23, + "CipheredKeyValue": 48, + "SignIdentity": 53, + "SignedIdentity": 54, + "GetECDHSessionKey": 61, + "ECDHSessionKey": 62, + "DebugLinkDecision": 100, + "DebugLinkGetState": 101, + "DebugLinkState": 102, + "DebugLinkStop": 103, + "DebugLinkLog": 104, + "DebugLinkMemoryRead": 110, + "DebugLinkMemory": 111, + "DebugLinkMemoryWrite": 112, + "DebugLinkFlashErase": 113, + "DebugLinkLayout": 9001, + "DebugLinkReseedRandom": 9002, + "DebugLinkRecordScreen": 9003, + "DebugLinkEraseSdCard": 9005, + "DebugLinkWatchLayout": 9006, + "DebugLinkResetDebugEvents": 9007, + "DebugLinkOptigaSetSecMax": 9008, + "EthereumGetPublicKey": 450, + "EthereumPublicKey": 451, + "EthereumGetAddress": 56, + "EthereumAddress": 57, + "EthereumSignTx": 58, + "EthereumSignTxEIP1559": 452, + "EthereumTxRequest": 59, + "EthereumTxAck": 60, + "EthereumSignMessage": 64, + "EthereumVerifyMessage": 65, + "EthereumMessageSignature": 66, + "EthereumSignTypedData": 464, + "EthereumTypedDataStructRequest": 465, + "EthereumTypedDataStructAck": 466, + "EthereumTypedDataValueRequest": 467, + "EthereumTypedDataValueAck": 468, + "EthereumTypedDataSignature": 469, + "EthereumSignTypedHash": 470, + "NEMGetAddress": 67, + "NEMAddress": 68, + "NEMSignTx": 69, + "NEMSignedTx": 70, + "NEMDecryptMessage": 75, + "NEMDecryptedMessage": 76, + "TezosGetAddress": 150, + "TezosAddress": 151, + "TezosSignTx": 152, + "TezosSignedTx": 153, + "TezosGetPublicKey": 154, + "TezosPublicKey": 155, + "StellarSignTx": 202, + "StellarTxOpRequest": 203, + "StellarGetAddress": 207, + "StellarAddress": 208, + "StellarCreateAccountOp": 210, + "StellarPaymentOp": 211, + "StellarPathPaymentStrictReceiveOp": 212, + "StellarManageSellOfferOp": 213, + "StellarCreatePassiveSellOfferOp": 214, + "StellarSetOptionsOp": 215, + "StellarChangeTrustOp": 216, + "StellarAllowTrustOp": 217, + "StellarAccountMergeOp": 218, + "StellarManageDataOp": 220, + "StellarBumpSequenceOp": 221, + "StellarManageBuyOfferOp": 222, + "StellarPathPaymentStrictSendOp": 223, + "StellarClaimClaimableBalanceOp": 225, + "StellarSignedTx": 230, + "CardanoGetPublicKey": 305, + "CardanoPublicKey": 306, + "CardanoGetAddress": 307, + "CardanoAddress": 308, + "CardanoTxItemAck": 313, + "CardanoTxAuxiliaryDataSupplement": 314, + "CardanoTxWitnessRequest": 315, + "CardanoTxWitnessResponse": 316, + "CardanoTxHostAck": 317, + "CardanoTxBodyHash": 318, + "CardanoSignTxFinished": 319, + "CardanoSignTxInit": 320, + "CardanoTxInput": 321, + "CardanoTxOutput": 322, + "CardanoAssetGroup": 323, + "CardanoToken": 324, + "CardanoTxCertificate": 325, + "CardanoTxWithdrawal": 326, + "CardanoTxAuxiliaryData": 327, + "CardanoPoolOwner": 328, + "CardanoPoolRelayParameters": 329, + "CardanoGetNativeScriptHash": 330, + "CardanoNativeScriptHash": 331, + "CardanoTxMint": 332, + "CardanoTxCollateralInput": 333, + "CardanoTxRequiredSigner": 334, + "CardanoTxInlineDatumChunk": 335, + "CardanoTxReferenceScriptChunk": 336, + "CardanoTxReferenceInput": 337, + "RippleGetAddress": 400, + "RippleAddress": 401, + "RippleSignTx": 402, + "RippleSignedTx": 403, + "EosGetPublicKey": 600, + "EosPublicKey": 601, + "EosSignTx": 602, + "EosTxActionRequest": 603, + "EosTxActionAck": 604, + "EosSignedTx": 605, + "BinanceGetAddress": 700, + "BinanceAddress": 701, + "BinanceGetPublicKey": 702, + "BinancePublicKey": 703, + "BinanceSignTx": 704, + "BinanceTxRequest": 705, + "BinanceTransferMsg": 706, + "BinanceOrderMsg": 707, + "BinanceCancelMsg": 708, + "BinanceSignedTx": 709, + "SolanaGetPublicKey": 900, + "SolanaPublicKey": 901, + "SolanaGetAddress": 902, + "SolanaAddress": 903, + "SolanaSignTx": 904, + "SolanaTxSignature": 905 } } } diff --git a/packages/protobuf/package.json b/packages/protobuf/package.json index 898cadbb197..305824ed1aa 100644 --- a/packages/protobuf/package.json +++ b/packages/protobuf/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/protobuf", - "version": "1.2.2", + "version": "1.2.3-beta.2", "license": "See LICENSE.md in repo root", "repository": { "type": "git", @@ -24,7 +24,6 @@ "messages.json" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", diff --git a/packages/protobuf/scripts/protobuf-build.sh b/packages/protobuf/scripts/protobuf-build.sh index 1224afda5aa..967be987e23 100755 --- a/packages/protobuf/scripts/protobuf-build.sh +++ b/packages/protobuf/scripts/protobuf-build.sh @@ -4,11 +4,14 @@ set -euxo pipefail echo $# -PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +get_abs_path() { + echo "$( cd -- "$(dirname "$1")" >/dev/null 2>&1 ; pwd -P )" +} -BRANCH="main" -DIST="." -REPO_DIR_NAME="trezor-firmware-probuf-update" +SCRIPTS_PATH=$(get_abs_path "${BASH_SOURCE[0]}") + +REPO_BRANCH="main" +REPO_PATH=$(get_abs_path "$SCRIPTS_PATH/../../../../.")/trezor-firmware-probuf-update if [[ $# -ne 0 && $# -ne 1 ]] then @@ -18,51 +21,26 @@ fi if [[ $# -eq 1 ]] then - BRANCH=$1 + REPO_BRANCH=$1 fi -cd ../../../ -ls -la -if test -d ./$REPO_DIR_NAME; then - echo "$REPO_DIR_NAME directory exists" +if test -d "$REPO_PATH"; then + echo "$REPO_PATH directory exists" else - echo "$REPO_DIR_NAME directory does not exist" - git clone https://github.com/trezor/trezor-firmware.git $REPO_DIR_NAME + echo "$REPO_PATH directory does not exist" + git clone https://github.com/trezor/trezor-firmware.git "$REPO_PATH" fi -cd $REPO_DIR_NAME +cd "$REPO_PATH" git fetch origin -git checkout "$BRANCH" -git reset "origin/$BRANCH" --hard +git checkout "$REPO_BRANCH" +git reset "origin/$REPO_BRANCH" --hard cd .. -cd "$PARENT_PATH/.." - -SRC="../../../$REPO_DIR_NAME/common/protob" - -# BUILD combined messages.proto file from protobuf files -# this code was copied from ./submodules/trezor-common/protob Makekile -# clear protobuf syntax and remove unknown values to be able to work with proto2js -echo 'syntax = "proto2";' > "$DIST"/messages.proto -echo 'import "google/protobuf/descriptor.proto";' >> "$DIST"/messages.proto -echo "Build proto file from $SRC" -# NOTE: grep sorting is not cross platform deterministic, make sure that the content of messages.proto ("Message_Type") is at the end of the generated file -grep -hv -e '^import ' -e '^syntax' -e '^package' -e 'option java_' "$SRC"/messages-*.proto "$SRC"/messages.proto \ -| sed 's/ hw\.trezor\.messages\.common\./ /' \ -| sed 's/ common\./ /' \ -| sed 's/ ethereum_definitions\./ /' \ -| sed 's/ management\./ /' \ -| sed 's/^option /\/\/ option /' \ -| grep -v ' reserved '>> "$DIST"/messages.proto - -# BUILD messages.json from message.proto -# pbjs command is added by protobufjs-cli package -node ../../node_modules/.bin/pbjs -t json -p "$DIST" -o "$DIST"/messages.json --keep-case messages.proto -rm "$DIST"/messages.proto - -cd "$PARENT_PATH" +cd "$SCRIPTS_PATH" -node ./protobuf-types.js typescript +yarn tsx ./protobuf-definitions.ts "$REPO_PATH/common/protob" --skip=monero,webauthn,thp,benchmark +yarn tsx ./protobuf-types.ts yarn workspace @trezor/protobuf g:prettier --write {messages.json,src/messages.ts} -yarn workspace @trezor/protobuf g:eslint --fix ./src/messages.ts \ No newline at end of file +yarn workspace @trezor/protobuf g:eslint --fix ./src/messages.ts diff --git a/packages/protobuf/scripts/protobuf-definitions.ts b/packages/protobuf/scripts/protobuf-definitions.ts new file mode 100644 index 00000000000..805c0acb088 --- /dev/null +++ b/packages/protobuf/scripts/protobuf-definitions.ts @@ -0,0 +1,200 @@ +/* eslint-disable no-console */ +import fs from 'fs'; +import path from 'path'; +import * as protobuf from 'protobufjs'; + +// protobuf.ReflectionObject to JSON +type Definition = { + reserved?: unknown[]; + options?: Record; + valuesOptions?: Record; + rule?: string; + type?: string; + extend?: string; + nested?: Record; + fields?: Record; +}; + +const modifyDefinitionsJSON = (root: protobuf.Root, def: Definition) => { + // remove "reserved" fields + delete def.reserved; + // remove "valuesOptions" fields + delete def.valuesOptions; + // remove unused "options" + if (def.options) { + const ignoreOptions = [ + '(experimental_field)', + '(experimental_message)', + '(has_bitcoin_only_values)', + 'deprecated', + ]; + const options = Object.keys(def.options); + // compatibility with json generated by `node_modules/.bin/pbjs` + // `packed` option is not set for custom types, it is set only for primitives like uint32 + if (def.type && options.includes('packed')) { + const obj = root.lookup(def.type); + if (obj) { + try { + root.lookupType(def.type); + ignoreOptions.push('packed'); + } catch { + /* empty */ + } + } + } + const opts = options + .filter(opt => !ignoreOptions.includes(opt)) + .reduce((prev, curr) => { + prev[curr] = def.options?.[curr]; + + return prev; + }, {}); + + if (Object.keys(opts).length < 1) { + delete def.options; + } + } + + // replace types pointing to different packages like "hw.trezor.messages.common" + if (def.type && def.type.includes('.')) { + def.type = def.type.split('.').pop(); + } + + // modify recursively for nested types and fields + const { nested } = def; + if (nested) { + Object.keys(nested).forEach(key => { + const item = nested[key]; + if (item.extend && item.extend.startsWith('google')) { + delete nested[key]; + } else { + modifyDefinitionsJSON(root, item); + } + }); + } + + if (def.fields) { + Object.values(def.fields).forEach(item => modifyDefinitionsJSON(root, item)); + } + + return def; +}; + +type BuildOptions = { + skipPackages?: string[]; + onlyPackages?: string[]; + includeImports?: boolean; + messageType?: string; // enum name, default MessageType +}; + +const modifyMessageType = (proto: protobuf.Root, name: string) => { + const messageTypeEnum = proto.lookupEnum(name); + if (messageTypeEnum) { + Object.keys(messageTypeEnum.values).forEach(key => { + // replace key `MessageType_Initialize` > `Initialize` + const newKey = key.replace(name + '_', ''); + const value = messageTypeEnum.values[key]; + // remove old key + messageTypeEnum.remove(key); + // check if MessageType is needed, package could be excluded + if (proto.lookup(newKey)) { + // add new key + messageTypeEnum.add(newKey, value); + } + }); + } + + if (name !== 'MessageType') { + // rename custom enum to be MessageType + const { parent } = messageTypeEnum; + parent?.remove(messageTypeEnum); + + messageTypeEnum.name = 'MessageType'; + parent?.add(messageTypeEnum); + } +}; + +export const buildDefinitions = (protoDir: string, args: BuildOptions) => { + // https://github.com/protobufjs/protobuf.js/blob/master/README.md#compatibility + // Because the internals of this package do not rely on google/protobuf/descriptor.proto, options are parsed and presented literally. + const root = new protobuf.Root({ + common: protobuf.common('descriptor', {}), + }); + + const files: string[] = []; + const packages: string[] = []; + const { skipPackages, onlyPackages, includeImports } = args; + + // read all messages*.proto files from directory + fs.readdirSync(protoDir).forEach(fileName => { + if (!/^messages.*.proto$/.test(fileName)) { + return; + } + // messages.proto file => empty pkg + const pkg = fileName.replace(/messages-?(.+)?.proto$/, '$1').replace('-', '_'); + if (skipPackages?.includes(pkg)) { + return console.log('Skipping', pkg); + } + if (onlyPackages && !onlyPackages.includes(pkg)) { + return console.log('Skipping', pkg); + } + + if (pkg) { + packages.push(pkg); + } + files.push(path.join(protoDir, fileName)); + }); + + console.log('Loading files:', files); + + const proto = root.loadSync(files, { keepCase: true }); + const messages = proto.lookup('hw.trezor.messages'); + if (!messages) { + throw new Error('hw.trezor.messages not found'); + } + + modifyMessageType(proto, args.messageType || 'MessageType'); + + const result = {}; + // remove deep nesting (hw.trezor.messages.*) + packages.forEach(p => { + const pkg = proto.lookup(`hw.trezor.messages.${p}`); + if (!pkg) { + throw new Error(`hw.trezor.messages.${p} not found`); + } + const json = pkg.toJSON(); + Object.assign(result, json.nested); + }); + + // @ts-expect-error typed as protobuf.Reflection but in fact it is a protobuf.Namespace + const topLevelMessages = includeImports ? messages.nested : {}; + // hw.trezor.messages Namespace contains all the packages, ignore already processed + Object.keys(topLevelMessages).forEach(name => { + if (!packages.includes(name)) { + Object.assign(result, { [name]: topLevelMessages[name].toJSON() }); + } + }); + + return modifyDefinitionsJSON(proto, { nested: result }); +}; + +if (require.main === module) { + // called directly, otherwise required as a module + const [protoDir, ...args] = process.argv.slice(2); + + // get --arg=X + const getArgValue = (args2: string[], arg: string) => { + return args2.find(a => a.startsWith(arg))?.substring(arg.length + 1); + }; + + const json = buildDefinitions(protoDir, { + includeImports: true, + skipPackages: getArgValue(args, '--skip')?.split(','), + onlyPackages: getArgValue(args, '--only')?.split(','), + }); + + const distDir = path.join(__dirname, '../'); + fs.writeFile(`${distDir}/messages.json`, JSON.stringify(json, null, 2), err => { + if (err) throw err; + }); +} diff --git a/packages/protobuf/scripts/protobuf-patches/MessageType.ts b/packages/protobuf/scripts/protobuf-patches/MessageType.ts new file mode 100644 index 00000000000..6705283c24f --- /dev/null +++ b/packages/protobuf/scripts/protobuf-patches/MessageType.ts @@ -0,0 +1,15 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + +export type MessageKey = keyof MessageType; + +export type MessageResponse = { + type: T; + message: MessageType[T]; +}; + +export type TypedCall = ( + type: T, + resType: R, + message?: MessageType[T], +) => Promise>; diff --git a/packages/protobuf/scripts/protobuf-patches/TxAck.js b/packages/protobuf/scripts/protobuf-patches/TxAck.js deleted file mode 100644 index a236f5a9655..00000000000 --- a/packages/protobuf/scripts/protobuf-patches/TxAck.js +++ /dev/null @@ -1,35 +0,0 @@ -// TxAck replacement -// TxAck needs more exact types -// PrevInput and TxInputType requires exact responses in TxAckResponse -// main difference: PrevInput should not contain address_n (unexpected field by protobuf) - -export type TxAckResponse = - | { - inputs: Array, - } - | { - bin_outputs: TxOutputBinType[], - } - | { - outputs: TxOutputType[], - } - | { - extra_data: string, - } - | { - version?: number, - lock_time?: number, - inputs_cnt: number, - outputs_cnt: number, - extra_data?: string, - extra_data_len?: number, - timestamp?: number, - version_group_id?: number, - expiry?: number, - branch_id?: number, - }; - -export type TxAck = { - tx: TxAckResponse, -}; -// - TxAck replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/TxAck.ts b/packages/protobuf/scripts/protobuf-patches/TxAck.ts new file mode 100644 index 00000000000..d265cf09034 --- /dev/null +++ b/packages/protobuf/scripts/protobuf-patches/TxAck.ts @@ -0,0 +1,37 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +// TxAck replacement +// TxAck needs more exact types +// PrevInput and TxInputType requires exact responses in TxAckResponse +// main difference: PrevInput should not contain address_n (unexpected field by protobuf) + +export type TxAckResponse = + | { + inputs: Array; + } + | { + bin_outputs: TxOutputBinType[]; + } + | { + outputs: TxOutputType[]; + } + | { + extra_data: string; + } + | { + version?: number; + lock_time?: number; + inputs_cnt: number; + outputs_cnt: number; + extra_data?: string; + extra_data_len?: number; + timestamp?: number; + version_group_id?: number; + expiry?: number; + branch_id?: number; + }; + +export type TxAck = { + tx: TxAckResponse; +}; +// - TxAck replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/TxInputType.js b/packages/protobuf/scripts/protobuf-patches/TxInputType.js deleted file mode 100644 index e53e2db0ba9..00000000000 --- a/packages/protobuf/scripts/protobuf-patches/TxInputType.js +++ /dev/null @@ -1,38 +0,0 @@ -// TxInputType replacement -// TxInputType needs more exact types -// differences: external input (no address_n + required script_pubkey) - -export type InternalInputScriptType = Exclude; - -type CommonTxInputType = { - prev_hash: string, // required: previous transaction hash (reversed) - prev_index: number, // required: previous transaction index - amount: UintType, // required - sequence?: number, - multisig?: MultisigRedeemScriptType, - decred_tree?: number, - orig_hash?: string, // RBF - orig_index?: number, // RBF - decred_staking_spend?: DecredStakingSpendType, - script_pubkey?: string, // required if script_type=EXTERNAL - coinjoin_flags?: number, // bit field of coinjoin-specific flags - script_sig?: string, // used by EXTERNAL, depending on script_pubkey - witness?: string, // used by EXTERNAL, depending on script_pubkey - ownership_proof?: string, // used by EXTERNAL, depending on script_pubkey - commitment_data?: string, // used by EXTERNAL, depending on ownership_proof -}; - -export type TxInputType = - | (CommonTxInputType & { - address_n: number[], - script_type?: InternalInputScriptType, - }) - | (CommonTxInputType & { - address_n?: typeof undefined, - script_type: 'EXTERNAL', - script_pubkey: string, - }); - -export type TxInput = TxInputType; - -// TxInputType replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/TxInputType.ts b/packages/protobuf/scripts/protobuf-patches/TxInputType.ts new file mode 100644 index 00000000000..d2766ede51f --- /dev/null +++ b/packages/protobuf/scripts/protobuf-patches/TxInputType.ts @@ -0,0 +1,40 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +// TxInputType replacement +// TxInputType needs more exact types +// differences: external input (no address_n + required script_pubkey) + +export type InternalInputScriptType = Exclude; + +type CommonTxInputType = { + prev_hash: string; // required: previous transaction hash (reversed) + prev_index: number; // required: previous transaction index + amount: UintType; // required + sequence?: number; + multisig?: MultisigRedeemScriptType; + decred_tree?: number; + orig_hash?: string; // RBF + orig_index?: number; // RBF + decred_staking_spend?: DecredStakingSpendType; + script_pubkey?: string; // required if script_type=EXTERNAL + coinjoin_flags?: number; // bit field of coinjoin-specific flags + script_sig?: string; // used by EXTERNAL, depending on script_pubkey + witness?: string; // used by EXTERNAL, depending on script_pubkey + ownership_proof?: string; // used by EXTERNAL, depending on script_pubkey + commitment_data?: string; // used by EXTERNAL, depending on ownership_proof +}; + +export type TxInputType = + | (CommonTxInputType & { + address_n: number[]; + script_type?: InternalInputScriptType; + }) + | (CommonTxInputType & { + address_n?: typeof undefined; + script_type: 'EXTERNAL'; + script_pubkey: string; + }); + +export type TxInput = TxInputType; + +// TxInputType replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/TxOutputType.js b/packages/protobuf/scripts/protobuf-patches/TxOutputType.js deleted file mode 100644 index 2dd0de18798..00000000000 --- a/packages/protobuf/scripts/protobuf-patches/TxOutputType.js +++ /dev/null @@ -1,53 +0,0 @@ -// TxOutputType replacement -// TxOutputType needs more exact types -// differences: external output (no address_n), opreturn output (no address_n, no address) - -export type ChangeOutputScriptType = Exclude; - -export type TxOutputType = - | { - address: string, - address_n?: typeof undefined, - script_type: 'PAYTOADDRESS', - amount: UintType, - multisig?: MultisigRedeemScriptType, - orig_hash?: string, - orig_index?: number, - payment_req_index?: number, - } - | { - address?: typeof undefined, - address_n: number[], - script_type?: ChangeOutputScriptType, - amount: UintType, - multisig?: MultisigRedeemScriptType, - orig_hash?: string, - orig_index?: number, - payment_req_index?: number, - } - // NOTE: the type was loosened for compatibility (issue #10474) - // It is not originally intended to use address instead of address_n with change output - | { - address: string, - address_n?: typeof undefined, - script_type?: ChangeOutputScriptType, - amount: UintType, - multisig?: MultisigRedeemScriptType, - orig_hash?: string, - orig_index?: number, - payment_req_index?: number, - } - | { - address?: typeof undefined, - address_n?: typeof undefined, - amount: '0' | 0, - op_return_data: string, - script_type: 'PAYTOOPRETURN', - orig_hash?: string, - orig_index?: number, - payment_req_index?: number, - }; - -export type TxOutput = TxOutputType; - -// - TxOutputType replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/TxOutputType.ts b/packages/protobuf/scripts/protobuf-patches/TxOutputType.ts new file mode 100644 index 00000000000..e9524f937e5 --- /dev/null +++ b/packages/protobuf/scripts/protobuf-patches/TxOutputType.ts @@ -0,0 +1,55 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +// TxOutputType replacement +// TxOutputType needs more exact types +// differences: external output (no address_n), opreturn output (no address_n, no address) + +export type ChangeOutputScriptType = Exclude; + +export type TxOutputType = + | { + address: string; + address_n?: typeof undefined; + script_type: 'PAYTOADDRESS'; + amount: UintType; + multisig?: MultisigRedeemScriptType; + orig_hash?: string; + orig_index?: number; + payment_req_index?: number; + } + | { + address?: typeof undefined; + address_n: number[]; + script_type?: ChangeOutputScriptType; + amount: UintType; + multisig?: MultisigRedeemScriptType; + orig_hash?: string; + orig_index?: number; + payment_req_index?: number; + } + // NOTE: the type was loosened for compatibility (issue #10474) + // It is not originally intended to use address instead of address_n with change output + | { + address: string; + address_n?: typeof undefined; + script_type?: ChangeOutputScriptType; + amount: UintType; + multisig?: MultisigRedeemScriptType; + orig_hash?: string; + orig_index?: number; + payment_req_index?: number; + } + | { + address?: typeof undefined; + address_n?: typeof undefined; + amount: '0' | 0; + op_return_data: string; + script_type: 'PAYTOOPRETURN'; + orig_hash?: string; + orig_index?: number; + payment_req_index?: number; + }; + +export type TxOutput = TxOutputType; + +// - TxOutputType replacement end diff --git a/packages/protobuf/scripts/protobuf-patches/customTypes.ts b/packages/protobuf/scripts/protobuf-patches/customTypes.ts new file mode 100644 index 00000000000..7f8814da2a5 --- /dev/null +++ b/packages/protobuf/scripts/protobuf-patches/customTypes.ts @@ -0,0 +1,8 @@ +export enum DeviceModelInternal { + T1B1 = 'T1B1', + T2T1 = 'T2T1', + T2B1 = 'T2B1', + T3B1 = 'T3B1', + T3T1 = 'T3T1', + T3W1 = 'T3W1', +} diff --git a/packages/protobuf/scripts/protobuf-patches/index.js b/packages/protobuf/scripts/protobuf-patches/index.ts similarity index 79% rename from packages/protobuf/scripts/protobuf-patches/index.js rename to packages/protobuf/scripts/protobuf-patches/index.ts index 0b42d4e6b5e..d03e611ee57 100644 --- a/packages/protobuf/scripts/protobuf-patches/index.js +++ b/packages/protobuf/scripts/protobuf-patches/index.ts @@ -1,13 +1,55 @@ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; -const UINT_TYPE = 'UintType'; -const SINT_TYPE = 'SintType'; +export const UINT_TYPE = 'UintType'; +export const SINT_TYPE = 'SintType'; const DeviceModelInternal = 'DeviceModelInternal'; +// proto types to javascript types +export const FIELD_TYPES = { + uint32: 'number', + uint64: 'number', + sint32: 'number', + sint64: 'number', + bool: 'boolean', + bytes: 'string', + // 'bytes': 'Uint8Array | number[] | Buffer | string', // protobuf will handle conversion +}; + +// Types needs reordering (used before defined). +// The Type in the Value NEEDs (depends on) the Type in the Key. +export const ORDER = { + BinanceCoin: 'BinanceInputOutput', + HDNodeType: 'HDNodePathType', + TxAck: 'TxAckInputWrapper', + EthereumFieldType: 'EthereumStructMember', + EthereumDataType: 'EthereumFieldType', + PaymentRequestMemo: 'TxAckPaymentRequest', + RecoveryDevice: 'Features', + RecoveryType: 'RecoveryDevice', + RecoveryDeviceInputMethod: 'RecoveryType', +}; + +// enums used as keys (string), used as values (number) by default +export const ENUM_KEYS = [ + 'InputScriptType', + 'OutputScriptType', + 'RequestType', + 'BackupType', + 'Capability', + 'SafetyCheckLevel', + 'ButtonRequestType', + 'PinMatrixRequestType', + 'WordRequestType', + 'HomescreenFormat', + 'RecoveryStatus', + 'BackupAvailability', + 'RecoveryType', +]; + // type rule fixes, ideally it should not be here -const RULE_PATCH = { - 'BackupDevice.groups': "optional", // protobuf repeated bytes are always optional (fallback to []) +export const RULE_PATCH = { + 'BackupDevice.groups': 'optional', // protobuf repeated bytes are always optional (fallback to []) 'MultisigRedeemScriptType.nodes': 'optional', // its valid to be undefined according to implementation/tests 'MultisigRedeemScriptType.address_n': 'optional', // its valid to be undefined according to implementation/tests 'TxRequestDetailsType.request_index': 'required', @@ -82,7 +124,7 @@ const RULE_PATCH = { // custom types IN to trezor // protobuf lib will handle the translation to required type // connect or other 3rd party libs are using compatible types (string as number etc...) -const TYPE_PATCH = { +export const TYPE_PATCH = { 'Features.bootloader_mode': 'boolean | null', 'Features.device_id': 'string | null', 'Features.pin_protection': 'boolean | null', @@ -207,23 +249,25 @@ const TYPE_PATCH = { 'Features.recovery_type': 'RecoveryType', }; -const DEFINITION_PATCH = { - TxInputType: fs.readFileSync(path.join(__dirname, './TxInputType.js'), 'utf8'), - TxOutputType: fs.readFileSync(path.join(__dirname, './TxOutputType.js'), 'utf8'), - TxAck: fs.readFileSync(path.join(__dirname, './TxAck.js'), 'utf8'), +export const readPatch = (file: string) => { + return fs + .readFileSync(path.join(__dirname, file), 'utf8') + .replace(/^\/\/ @ts-nocheck.*\n?/gm, ''); +}; + +export const DEFINITION_PATCH = { + TxInputType: () => readPatch('./TxInputType.ts'), + TxOutputType: () => readPatch('./TxOutputType.ts'), + TxAck: () => readPatch('./TxAck.ts'), }; // skip unnecessary types -const SKIP = [ +export const SKIP = [ 'MessageType', // connect uses custom definition 'TransactionType', // connect uses custom definition 'TxInput', // declared in TxInputType patch 'TxOutput', // declared in TxOutputType patch // not implemented - 'CosiCommit', - 'CosiCommitment', - 'CosiSign', - 'CosiSignature', 'DebugSwipeDirection', 'DebugLinkDecision', 'DebugLinkLayout', @@ -239,82 +283,4 @@ const SKIP = [ 'DebugLinkFlashErase', 'DebugLinkEraseSdCard', 'DebugLinkWatchLayout', - 'LoadDevice', - 'MoneroRctKeyPublic', - 'MoneroOutputEntry', - 'MoneroMultisigKLRki', - 'MoneroTransactionSourceEntry', - 'MoneroAccountPublicAddress', - 'MoneroTransactionDestinationEntry', - 'MoneroTransactionRsigData', - 'MoneroGetAddress', - 'MoneroAddress', - 'MoneroGetWatchKey', - 'MoneroWatchKey', - 'MoneroTransactionData', - 'MoneroTransactionInitRequest', - 'MoneroTransactionInitAck', - 'MoneroTransactionSetInputRequest', - 'MoneroTransactionSetInputAck', - 'MoneroTransactionInputsPermutationRequest', - 'MoneroTransactionInputsPermutationAck', - 'MoneroTransactionInputViniRequest', - 'MoneroTransactionInputViniAck', - 'MoneroTransactionAllInputsSetRequest', - 'MoneroTransactionAllInputsSetAck', - 'MoneroTransactionSetOutputRequest', - 'MoneroTransactionSetOutputAck', - 'MoneroTransactionAllOutSetRequest', - 'MoneroRingCtSig', - 'MoneroTransactionAllOutSetAck', - 'MoneroTransactionSignInputRequest', - 'MoneroTransactionSignInputAck', - 'MoneroTransactionFinalRequest', - 'MoneroTransactionFinalAck', - 'MoneroSubAddressIndicesList', - 'MoneroKeyImageExportInitRequest', - 'MoneroKeyImageExportInitAck', - 'MoneroTransferDetails', - 'MoneroKeyImageSyncStepRequest', - 'MoneroExportedKeyImage', - 'MoneroKeyImageSyncStepAck', - 'MoneroKeyImageSyncFinalRequest', - 'MoneroKeyImageSyncFinalAck', - 'MoneroGetTxKeyRequest', - 'MoneroGetTxKeyAck', - 'MoneroLiveRefreshStartRequest', - 'MoneroLiveRefreshStartAck', - 'MoneroLiveRefreshStepRequest', - 'MoneroLiveRefreshStepAck', - 'MoneroLiveRefreshFinalRequest', - 'MoneroLiveRefreshFinalAck', - 'DebugMoneroDiagRequest', - 'DebugMoneroDiagAck', - 'WebAuthnListResidentCredentials', - 'WebAuthnAddResidentCredential', - 'WebAuthnRemoveResidentCredential', - 'WebAuthnCredential', - 'WebAuthnCredentials', - 'wire_in', - 'wire_out', - 'wire_debug_in', - 'wire_debug_out', - 'wire_tiny', - 'wire_bootloader', - 'wire_no_fsm', - 'bitcoin_only', - 'has_bitcoin_only_values', - 'unstable', - 'wire_type', - 'experimental', - 'include_in_bitcoin_only', ]; - -module.exports = { - RULE_PATCH, - TYPE_PATCH, - DEFINITION_PATCH, - SKIP, - UINT_TYPE, - SINT_TYPE, -}; diff --git a/packages/protobuf/scripts/protobuf-types.js b/packages/protobuf/scripts/protobuf-types.js deleted file mode 100644 index f10cc7bd3fc..00000000000 --- a/packages/protobuf/scripts/protobuf-types.js +++ /dev/null @@ -1,240 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const json = require('../messages.json'); -const { - RULE_PATCH, - TYPE_PATCH, - DEFINITION_PATCH, - SKIP, - UINT_TYPE, - SINT_TYPE, -} = require('./protobuf-patches'); - -CONSOLE_RED = "\x1b[31m" -CONSOLE_RESET = "\x1b[0m" - -const logError = (text) => { - console.error(`Error: ${CONSOLE_RED}${text}${CONSOLE_RESET}`); -} - -const INDENT = ' '.repeat(4); - -// proto types to javascript types -const FIELD_TYPES = { - uint32: 'number', - uint64: 'number', - sint32: 'number', - sint64: 'number', - bool: 'boolean', - bytes: 'string', - // 'bytes': 'Uint8Array | number[] | Buffer | string', // protobuf will handle conversion -}; - -const types = []; // { type: 'enum | message', name: string, value: string[], exact?: boolean }; - -// enums used as keys (string), used as values (number) by default -const ENUM_KEYS = [ - 'InputScriptType', - 'OutputScriptType', - 'RequestType', - 'BackupType', - 'Capability', - 'SafetyCheckLevel', - 'ButtonRequestType', - 'PinMatrixRequestType', - 'WordRequestType', - 'HomescreenFormat', - "RecoveryStatus", - "BackupAvailability", - "RecoveryType", -]; - -const parseEnum = (itemName, item) => { - const IS_KEY = ENUM_KEYS.includes(itemName); - - // declare enum - const enumName = IS_KEY ? `Enum_${itemName}` : itemName; - const value = [`export enum ${enumName} {`]; - - // declare fields - value.push(...Object.entries(item.values).map(([name, id]) => `${INDENT}${name} = ${id},`)); - - // close enum declaration - value.push('}', ''); - - if (IS_KEY) { - value.push(`export type ${itemName} = keyof typeof ${enumName};`, ''); - } - - types.push({ - type: 'enum', - name: itemName, - value: value.join('\n'), - }); -}; - -const parseMessage = (messageName, message, depth = 0) => { - if (messageName === 'google') return; - const value = []; - // add comment line - if (!depth) value.push(`// ${messageName}`); - // declare nested enums - - // declare nested values - if (message.nested) { - Object.keys(message.nested).map(item => - parseMessage(item, message.nested[item], depth + 1), - ); - } - - if (message.values) { - return parseEnum(messageName, message); - } - if (!message.fields || !Object.keys(message.fields).length) { - // few types are just empty objects, make it one line - value.push(`export type ${messageName} = {};`); - value.push(''); - } else { - // find patch - const definition = DEFINITION_PATCH[messageName]; - if (definition) { - // replace whole declaration - value.push(definition); - } else { - // declare type - value.push(`export type ${messageName} = {`); - Object.keys(message.fields).forEach(fieldName => { - const field = message.fields[fieldName]; - const fieldKey = `${messageName}.${fieldName}`; - // find patch for "rule" - const fieldRule = RULE_PATCH[fieldKey] || field.rule; - const rule = fieldRule === 'required' || fieldRule === 'repeated' ? ': ' : '?: '; - // find patch for "type" - let type = TYPE_PATCH[fieldKey] || FIELD_TYPES[field.type] || field.type; - // automatically convert all amount and fee fields to UINT_TYPE - if (['amount', 'fee'].includes(fieldName)) { - type = UINT_TYPE; - } - // array - if (field.rule === 'repeated') { - type = type.split('|').length > 1 ? `Array<${type}>` : `${type}[]`; - } - value.push(` ${fieldName}${rule}${type};`); - }); - // close type declaration - value.push('};'); - // empty line - value.push(''); - } - } - // type doest have to be e - const exact = message.fields && Object.values(message.fields).find(f => f.rule === 'required'); - types.push({ - type: 'message', - name: messageName, - value: value.join('\n'), - exact, - }); -}; - -// top level messages and nested messages -Object.keys(json.nested).map(e => parseMessage(e, json.nested[e])); - -// Types needs reordering (used before defined). -// The Type in the Value NEEDs (depends on) the Type in the Key. -const ORDER = { - BinanceCoin: 'BinanceInputOutput', - HDNodeType: 'HDNodePathType', - TxAck: 'TxAckInputWrapper', - EthereumFieldType: 'EthereumStructMember', - EthereumDataType: 'EthereumFieldType', - PaymentRequestMemo: 'TxAckPaymentRequest', - RecoveryDevice: 'Features', - RecoveryType: 'RecoveryDevice', - RecoveryDeviceInputMethod: 'RecoveryType', -}; - -Object.keys(ORDER).forEach(key => { - if (key === ORDER[key]) { - logError(`ORDER map cannot have key=value`) - } - - // find indexes - const indexOfDependency = types.findIndex(t => t && t.name === key); - if (indexOfDependency === -1) { - logError(`Type from key: '${key}' not found in the 'types' variable!`) - } - - const indexOfDependant = types.findIndex(t => t && t.name === ORDER[key]); - if (indexOfDependant === -1) { - logError(`Type from value: '${ORDER[key]}' not found in the 'types' variable!`) - } - - const dependency = types[indexOfDependency]; - // replace values - delete types[indexOfDependency]; - types.splice(indexOfDependant, 0, dependency); -}); - -// skip not needed types -SKIP.forEach(key => { - const index = types.findIndex(t => t && t.name === key); - delete types[index]; -}); - -// create content from types -const content = types.flatMap(t => (t ? [t.value] : [])).join('\n'); - -const lines = []; // string[] - -lines.push('// This file is auto generated from data/messages/message.json', ''); -lines.push('// custom type uint32/64 may be represented as string'); -lines.push(`export type ${UINT_TYPE} = string | number;`, ''); -lines.push('// custom type sint32/64'); -lines.push(`export type ${SINT_TYPE} = string | number;`, ''); -lines.push( - `export enum DeviceModelInternal { - T1B1 = 'T1B1', - T2T1 = 'T2T1', - T2B1 = 'T2B1', - T3B1 = 'T3B1', - T3T1 = 'T3T1', -}`, - '', -); -lines.push(content); - -// create custom definition - -lines.push('// custom connect definitions'); -lines.push('export type MessageType = {'); -types - .flatMap(t => (t && t.type === 'message' ? [t] : [])) - .forEach(t => { - lines.push(` ${t.name}: ${t.name};`); - }); -lines.push('};'); - -// additional types utilities -lines.push(` -export type MessageKey = keyof MessageType; - -export type MessageResponse = { - type: T; - message: MessageType[T]; -}; - -export type TypedCall = ( - type: T, - resType: R, - message?: MessageType[T], -) => Promise>; -`); - -// save to file -const filePath = path.join(__dirname, '../src/messages.ts'); - -fs.writeFile(filePath, lines.join('\n'), err => { - if (err) return console.log(err); -}); diff --git a/packages/protobuf/scripts/protobuf-types.ts b/packages/protobuf/scripts/protobuf-types.ts new file mode 100644 index 00000000000..45b3af909fb --- /dev/null +++ b/packages/protobuf/scripts/protobuf-types.ts @@ -0,0 +1,226 @@ +import fs from 'fs'; +import path from 'path'; + +import json from '../messages.json'; +import { + RULE_PATCH, + TYPE_PATCH, + DEFINITION_PATCH, + SKIP, + UINT_TYPE, + SINT_TYPE, + FIELD_TYPES, + ENUM_KEYS, + ORDER, + readPatch, +} from './protobuf-patches'; + +const CONSOLE_RED = '\x1b[31m'; +const CONSOLE_RESET = '\x1b[0m'; + +const logError = (text: string) => { + console.error(`Error: ${CONSOLE_RED}${text}${CONSOLE_RESET}`); +}; + +const INDENT = ' '.repeat(4); + +type TypeItem = { + type: 'enum' | 'message'; + name: string; + value: string; +}; + +type Definition = { + options?: Record; + rule?: string; + type?: string; + nested?: Record; + fields?: Record; + values?: Record; +}; + +type ParseMessageOptions = { + defaultRule?: 'required' | 'optional'; + fixOrder?: boolean; +}; + +const parseEnum = (itemName: string, item: Definition): TypeItem => { + const IS_KEY = ENUM_KEYS.includes(itemName); + + // declare enum + const enumName = IS_KEY ? `Enum_${itemName}` : itemName; + const value = [`export enum ${enumName} {`]; + + // declare fields + value.push( + ...Object.entries(item.values || {}).map(([name, id]) => `${INDENT}${name} = ${id},`), + ); + + // close enum declaration + value.push('}', ''); + + if (IS_KEY) { + value.push(`export type ${itemName} = keyof typeof ${enumName};`, ''); + } + + return { + type: 'enum', + name: itemName, + value: value.join('\n'), + }; +}; + +const parseMessage = ( + messageName: string, + message: Definition, + options?: ParseMessageOptions, +): TypeItem | TypeItem[] => { + const lines: string[] = []; + // parse nested values + const { nested, fields } = message; + let nestedTypes; + if (nested) { + nestedTypes = Object.keys(nested).flatMap(item => + parseMessage(item, nested[item], options), + ); + } + + // parse enum + if (message.values) { + return parseEnum(messageName, message); + } + + if (DEFINITION_PATCH[messageName]) { + // replace whole declaration with patch + lines.push(DEFINITION_PATCH[messageName]()); + } else if (!fields || !Object.keys(fields).length) { + // few types are just empty objects, make it one line + lines.push(`export type ${messageName} = {};`, ''); + } else { + // declare type + lines.push(`export type ${messageName} = {`); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + const fieldKey = `${messageName}.${fieldName}`; + // find patch for "rule" + const fieldRule = RULE_PATCH[fieldKey] || field.rule || options?.defaultRule; + const rule = fieldRule === 'required' || fieldRule === 'repeated' ? ': ' : '?: '; + // find patch for "type" + let type = TYPE_PATCH[fieldKey] || FIELD_TYPES[field.type || ''] || field.type; + // automatically convert all amount and fee fields to UINT_TYPE + if (['amount', 'fee'].includes(fieldName)) { + type = UINT_TYPE; + } + // array + if (field.rule === 'repeated') { + type = type.split('|').length > 1 ? `Array<${type}>` : `${type}[]`; + } + lines.push(` ${fieldName}${rule}${type};`); + }); + // close type declaration + lines.push('};', ''); + } + + return [ + ...(nestedTypes || []), + { + type: 'message', + name: messageName, + value: lines.join('\n'), + }, + ]; +}; + +const fixOrder = (types: TypeItem[]) => { + Object.keys(ORDER).forEach(key => { + if (key === ORDER[key]) { + logError(`ORDER map cannot have key=value`); + } + + // find indexes + const indexOfDependency = types.findIndex(t => t && t.name === key); + if (indexOfDependency === -1) { + logError(`Type from key: '${key}' not found in the 'types' variable!`); + } + + const indexOfDependant = types.findIndex(t => t && t.name === ORDER[key]); + if (indexOfDependant === -1) { + logError(`Type from value: '${ORDER[key]}' not found in the 'types' variable!`); + } + + const dependency = types[indexOfDependency]; + // replace values + delete types[indexOfDependency]; + types.splice(indexOfDependant, 0, dependency); + }); +}; + +// skip not needed types +const skipTypes = (types: TypeItem[]) => { + SKIP.forEach(key => { + const index = types.findIndex(t => t && t.name === key); + delete types[index]; + }); +}; + +export const createTypes = ( + def: Definition | Record, + options?: ParseMessageOptions, +) => { + const root = def.nested ? def.nested : def; + const types = Object.keys(root).flatMap(key => { + return parseMessage(key, root[key], options); + }); + skipTypes(types); + if (options?.fixOrder) { + fixOrder(types); + } + + return types; +}; + +const createCustomTypes = () => { + const lines: string[] = []; + lines.push('// This file is auto generated by @trezor/protobuf package', ''); + lines.push('// custom type uint32/64 may be represented as string'); + lines.push(`export type ${UINT_TYPE} = string | number;`, ''); + lines.push('// custom type sint32/64'); + lines.push(`export type ${SINT_TYPE} = string | number;`, ''); + lines.push(readPatch('customTypes.ts'), ''); + + return lines; +}; + +const createMessageType = (types: TypeItem[]) => { + const lines: string[] = []; + lines.push('// custom connect definitions'); + lines.push('export type MessageType = {'); + types + .flatMap(t => (t && t.type === 'message' ? t : [])) + .forEach(t => { + lines.push(` ${t.name}: ${t.name};`); + }); + lines.push('};'); + + lines.push(readPatch('MessageType.ts')); + + return lines; +}; + +if (require.main === module) { + // called directly, otherwise required as a module + + const types = createTypes(json, { fixOrder: true }); + const lines = createCustomTypes(); + // create content from types + const content = types.flatMap(t => (t ? t.value : [])).join('\n'); + lines.push(content); + // create custom MessageType definition + lines.push(...createMessageType(types)); + + // save to file + const filePath = path.join(__dirname, '../src/messages.ts'); + fs.writeFile(filePath, lines.join('\n'), err => { + if (err) return logError(err.message); + }); +} diff --git a/packages/protobuf/src/messages-schema.ts b/packages/protobuf/src/messages-schema.ts index c1ec385fa17..332335f0b9a 100644 --- a/packages/protobuf/src/messages-schema.ts +++ b/packages/protobuf/src/messages-schema.ts @@ -6,6 +6,7 @@ export enum DeviceModelInternal { T2B1 = 'T2B1', T3B1 = 'T3B1', T3T1 = 'T3T1', + T3W1 = 'T3W1', } export type EnumDeviceModelInternal = Static; @@ -1416,6 +1417,7 @@ export const ButtonRequest = Type.Object( { code: Type.Optional(ButtonRequestType), pages: Type.Optional(Type.Number()), + name: Type.Optional(Type.String()), }, { $id: 'ButtonRequest' }, ); @@ -1583,6 +1585,9 @@ export const EnumDebugPhysicalButton = Type.Enum(DebugPhysicalButton); export type DebugLinkResetDebugEvents = Static; export const DebugLinkResetDebugEvents = Type.Object({}, { $id: 'DebugLinkResetDebugEvents' }); +export type DebugLinkOptigaSetSecMax = Static; +export const DebugLinkOptigaSetSecMax = Type.Object({}, { $id: 'DebugLinkOptigaSetSecMax' }); + export type EosGetPublicKey = Static; export const EosGetPublicKey = Type.Object( { @@ -2516,10 +2521,25 @@ export const AuthenticityProof = Type.Object( export type WipeDevice = Static; export const WipeDevice = Type.Object({}, { $id: 'WipeDevice' }); +export type LoadDevice = Static; +export const LoadDevice = Type.Object( + { + mnemonics: Type.Array(Type.String()), + pin: Type.Optional(Type.String()), + passphrase_protection: Type.Optional(Type.Boolean()), + language: Type.Optional(Type.String()), + label: Type.Optional(Type.String()), + skip_checksum: Type.Optional(Type.Boolean()), + u2f_counter: Type.Optional(Type.Number()), + needs_backup: Type.Optional(Type.Boolean()), + no_backup: Type.Optional(Type.Boolean()), + }, + { $id: 'LoadDevice' }, +); + export type ResetDevice = Static; export const ResetDevice = Type.Object( { - display_random: Type.Optional(Type.Boolean()), strength: Type.Optional(Type.Number()), passphrase_protection: Type.Optional(Type.Boolean()), pin_protection: Type.Optional(Type.Boolean()), @@ -2678,16 +2698,6 @@ export const SetBrightness = Type.Object( { $id: 'SetBrightness' }, ); -export enum MoneroNetworkType { - MAINNET = 0, - TESTNET = 1, - STAGENET = 2, - FAKECHAIN = 3, -} - -export type EnumMoneroNetworkType = Static; -export const EnumMoneroNetworkType = Type.Enum(MoneroNetworkType); - export type NEMGetAddress = Static; export const NEMGetAddress = Type.Object( { @@ -3474,12 +3484,6 @@ export const TezosSignedTx = Type.Object( { $id: 'TezosSignedTx' }, ); -export type experimental_message = Static; -export const experimental_message = Type.Object({}, { $id: 'experimental_message' }); - -export type experimental_field = Static; -export const experimental_field = Type.Object({}, { $id: 'experimental_field' }); - export type MessageType = Static; export const MessageType = Type.Object( { @@ -3597,6 +3601,7 @@ export const MessageType = Type.Object( GetECDHSessionKey, ECDHSessionKey, DebugLinkResetDebugEvents, + DebugLinkOptigaSetSecMax, EosGetPublicKey, EosPublicKey, EosTxHeader, @@ -3673,6 +3678,7 @@ export const MessageType = Type.Object( AuthenticateDevice, AuthenticityProof, WipeDevice, + LoadDevice, ResetDevice, Slip39Group, BackupDevice, @@ -3758,8 +3764,6 @@ export const MessageType = Type.Object( TezosBallotOp, TezosSignTx, TezosSignedTx, - experimental_message, - experimental_field, }, { $id: 'MessageType' }, ); diff --git a/packages/protobuf/src/messages.ts b/packages/protobuf/src/messages.ts index 056985692f0..afb8dba4f55 100644 --- a/packages/protobuf/src/messages.ts +++ b/packages/protobuf/src/messages.ts @@ -1,4 +1,4 @@ -// This file is auto generated from data/messages/message.json +// This file is auto generated by @trezor/protobuf package // custom type uint32/64 may be represented as string export type UintType = string | number; @@ -10,33 +10,30 @@ export enum DeviceModelInternal { T1B1 = 'T1B1', T2T1 = 'T2T1', T2B1 = 'T2B1', + T3B1 = 'T3B1', T3T1 = 'T3T1', + T3W1 = 'T3W1', } -// BinanceGetAddress export type BinanceGetAddress = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// BinanceAddress export type BinanceAddress = { address: string; }; -// BinanceGetPublicKey export type BinanceGetPublicKey = { address_n: number[]; show_display?: boolean; }; -// BinancePublicKey export type BinancePublicKey = { public_key: string; }; -// BinanceSignTx export type BinanceSignTx = { address_n: number[]; msg_count: number; @@ -48,7 +45,6 @@ export type BinanceSignTx = { chunkify?: boolean; }; -// BinanceTxRequest export type BinanceTxRequest = {}; export type BinanceCoin = { @@ -61,7 +57,6 @@ export type BinanceInputOutput = { coins: BinanceCoin[]; }; -// BinanceTransferMsg export type BinanceTransferMsg = { inputs: BinanceInputOutput[]; outputs: BinanceInputOutput[]; @@ -88,7 +83,6 @@ export enum BinanceTimeInForce { IOC = 3, } -// BinanceOrderMsg export type BinanceOrderMsg = { id?: string; ordertype: BinanceOrderType; @@ -100,14 +94,12 @@ export type BinanceOrderMsg = { timeinforce: BinanceTimeInForce; }; -// BinanceCancelMsg export type BinanceCancelMsg = { refid?: string; sender?: string; symbol?: string; }; -// BinanceSignedTx export type BinanceSignedTx = { signature: string; public_key: string; @@ -148,7 +140,6 @@ export enum AmountUnit { SATOSHI = 3, } -// HDNodeType export type HDNodeType = { depth: number; fingerprint: number; @@ -163,7 +154,6 @@ export type HDNodePathType = { address_n: number[]; }; -// MultisigRedeemScriptType export type MultisigRedeemScriptType = { pubkeys: HDNodePathType[]; signatures: string[]; @@ -172,7 +162,6 @@ export type MultisigRedeemScriptType = { address_n?: number[]; }; -// GetPublicKey export type GetPublicKey = { address_n: number[]; ecdsa_curve_name?: string; @@ -182,7 +171,6 @@ export type GetPublicKey = { ignore_xpub_magic?: boolean; }; -// PublicKey export type PublicKey = { node: HDNodeType; xpub: string; @@ -190,7 +178,6 @@ export type PublicKey = { descriptor?: string; }; -// GetAddress export type GetAddress = { address_n: number[]; coin_name?: string; @@ -201,13 +188,11 @@ export type GetAddress = { chunkify?: boolean; }; -// Address export type Address = { address: string; mac?: string; }; -// GetOwnershipId export type GetOwnershipId = { address_n: number[]; coin_name?: string; @@ -215,12 +200,10 @@ export type GetOwnershipId = { script_type?: InputScriptType; }; -// OwnershipId export type OwnershipId = { ownership_id: string; }; -// SignMessage export type SignMessage = { address_n: number[]; message: string; @@ -230,13 +213,11 @@ export type SignMessage = { chunkify?: boolean; }; -// MessageSignature export type MessageSignature = { address: string; signature: string; }; -// VerifyMessage export type VerifyMessage = { address: string; signature: string; @@ -253,7 +234,6 @@ export type CoinJoinRequest = { signature?: string; }; -// SignTx export type SignTx = { outputs_count: number; inputs_count: number; @@ -298,7 +278,6 @@ export type TxRequestSerializedType = { serialized_tx?: string; }; -// TxRequest export type TxRequest = { request_type: RequestType; details: TxRequestDetailsType; @@ -404,7 +383,6 @@ export type TxOutput = TxOutputType; // - TxOutputType replacement end -// PrevTx export type PrevTx = { version: number; lock_time: number; @@ -417,7 +395,6 @@ export type PrevTx = { branch_id?: number; }; -// PrevInput export type PrevInput = { prev_hash: string; prev_index: number; @@ -426,7 +403,6 @@ export type PrevInput = { decred_tree?: number; }; -// PrevOutput export type PrevOutput = { amount: UintType; script_pubkey: string; @@ -455,7 +431,6 @@ export type PaymentRequestMemo = { coin_purchase_memo?: CoinPurchaseMemo; }; -// TxAckPaymentRequest export type TxAckPaymentRequest = { nonce?: string; recipient_name: string; @@ -464,7 +439,6 @@ export type TxAckPaymentRequest = { signature: string; }; -// TxAck // TxAck replacement // TxAck needs more exact types // PrevInput and TxInputType requires exact responses in TxAckResponse @@ -505,7 +479,6 @@ export type TxAckInputWrapper = { input: TxInput; }; -// TxAckInput export type TxAckInput = { tx: TxAckInputWrapper; }; @@ -514,12 +487,10 @@ export type TxAckOutputWrapper = { output: TxOutput; }; -// TxAckOutput export type TxAckOutput = { tx: TxAckOutputWrapper; }; -// TxAckPrevMeta export type TxAckPrevMeta = { tx: PrevTx; }; @@ -528,7 +499,6 @@ export type TxAckPrevInputWrapper = { input: PrevInput; }; -// TxAckPrevInput export type TxAckPrevInput = { tx: TxAckPrevInputWrapper; }; @@ -537,7 +507,6 @@ export type TxAckPrevOutputWrapper = { output: PrevOutput; }; -// TxAckPrevOutput export type TxAckPrevOutput = { tx: TxAckPrevOutputWrapper; }; @@ -546,12 +515,10 @@ export type TxAckPrevExtraDataWrapper = { extra_data_chunk: string; }; -// TxAckPrevExtraData export type TxAckPrevExtraData = { tx: TxAckPrevExtraDataWrapper; }; -// GetOwnershipProof export type GetOwnershipProof = { address_n: number[]; coin_name?: string; @@ -562,13 +529,11 @@ export type GetOwnershipProof = { commitment_data?: string; }; -// OwnershipProof export type OwnershipProof = { ownership_proof: string; signature: string; }; -// AuthorizeCoinJoin export type AuthorizeCoinJoin = { coordinator: string; max_rounds: number; @@ -580,24 +545,20 @@ export type AuthorizeCoinJoin = { amount_unit?: AmountUnit; }; -// FirmwareErase export type FirmwareErase = { length?: number; }; -// FirmwareRequest export type FirmwareRequest = { offset: number; length: number; }; -// FirmwareUpload export type FirmwareUpload = { payload: Buffer | ArrayBuffer; hash?: string; }; -// ProdTestT1 export type ProdTestT1 = { payload?: string; }; @@ -687,14 +648,12 @@ export enum CardanoTxWitnessType { SHELLEY_WITNESS = 1, } -// CardanoBlockchainPointerType export type CardanoBlockchainPointerType = { block_index: number; tx_index: number; certificate_index: number; }; -// CardanoNativeScript export type CardanoNativeScript = { type: CardanoNativeScriptType; scripts?: CardanoNativeScript[]; @@ -705,19 +664,16 @@ export type CardanoNativeScript = { invalid_hereafter?: UintType; }; -// CardanoGetNativeScriptHash export type CardanoGetNativeScriptHash = { script: CardanoNativeScript; display_format: CardanoNativeScriptHashDisplayFormat; derivation_type: CardanoDerivationType; }; -// CardanoNativeScriptHash export type CardanoNativeScriptHash = { script_hash: string; }; -// CardanoAddressParametersType export type CardanoAddressParametersType = { address_type: CardanoAddressType; address_n: number[]; @@ -728,7 +684,6 @@ export type CardanoAddressParametersType = { script_staking_hash?: string; }; -// CardanoGetAddress export type CardanoGetAddress = { show_display?: boolean; protocol_magic: number; @@ -738,25 +693,21 @@ export type CardanoGetAddress = { chunkify?: boolean; }; -// CardanoAddress export type CardanoAddress = { address: string; }; -// CardanoGetPublicKey export type CardanoGetPublicKey = { address_n: number[]; show_display?: boolean; derivation_type: CardanoDerivationType; }; -// CardanoPublicKey export type CardanoPublicKey = { xpub: string; node: HDNodeType; }; -// CardanoSignTxInit export type CardanoSignTxInit = { signing_mode: CardanoTxSigningMode; protocol_magic: number; @@ -783,13 +734,11 @@ export type CardanoSignTxInit = { tag_cbor_sets?: boolean; }; -// CardanoTxInput export type CardanoTxInput = { prev_hash: string; prev_index: number; }; -// CardanoTxOutput export type CardanoTxOutput = { address?: string; address_parameters?: CardanoAddressParametersType; @@ -801,36 +750,30 @@ export type CardanoTxOutput = { reference_script_size?: number; }; -// CardanoAssetGroup export type CardanoAssetGroup = { policy_id: string; tokens_count: number; }; -// CardanoToken export type CardanoToken = { asset_name_bytes: string; amount?: UintType; mint_amount?: SintType; }; -// CardanoTxInlineDatumChunk export type CardanoTxInlineDatumChunk = { data: string; }; -// CardanoTxReferenceScriptChunk export type CardanoTxReferenceScriptChunk = { data: string; }; -// CardanoPoolOwner export type CardanoPoolOwner = { staking_key_path?: number[]; staking_key_hash?: string; }; -// CardanoPoolRelayParameters export type CardanoPoolRelayParameters = { type: CardanoPoolRelayType; ipv4_address?: string; @@ -839,13 +782,11 @@ export type CardanoPoolRelayParameters = { port?: number; }; -// CardanoPoolMetadataType export type CardanoPoolMetadataType = { url: string; hash: string; }; -// CardanoPoolParametersType export type CardanoPoolParametersType = { pool_id: string; vrf_key_hash: string; @@ -859,14 +800,12 @@ export type CardanoPoolParametersType = { relays_count: number; }; -// CardanoDRep export type CardanoDRep = { type: CardanoDRepType; key_hash?: string; script_hash?: string; }; -// CardanoTxCertificate export type CardanoTxCertificate = { type: CardanoCertificateType; path?: number[]; @@ -878,7 +817,6 @@ export type CardanoTxCertificate = { drep?: CardanoDRep; }; -// CardanoTxWithdrawal export type CardanoTxWithdrawal = { path?: number[]; amount: UintType; @@ -886,13 +824,11 @@ export type CardanoTxWithdrawal = { key_hash?: string; }; -// CardanoCVoteRegistrationDelegation export type CardanoCVoteRegistrationDelegation = { vote_public_key: string; weight: UintType; }; -// CardanoCVoteRegistrationParametersType export type CardanoCVoteRegistrationParametersType = { vote_public_key?: string; staking_path: number[]; @@ -904,51 +840,42 @@ export type CardanoCVoteRegistrationParametersType = { payment_address?: string; }; -// CardanoTxAuxiliaryData export type CardanoTxAuxiliaryData = { cvote_registration_parameters?: CardanoCVoteRegistrationParametersType; hash?: string; }; -// CardanoTxMint export type CardanoTxMint = { asset_groups_count: number; }; -// CardanoTxCollateralInput export type CardanoTxCollateralInput = { prev_hash: string; prev_index: number; }; -// CardanoTxRequiredSigner export type CardanoTxRequiredSigner = { key_hash?: string; key_path?: number[]; }; -// CardanoTxReferenceInput export type CardanoTxReferenceInput = { prev_hash: string; prev_index: number; }; -// CardanoTxItemAck export type CardanoTxItemAck = {}; -// CardanoTxAuxiliaryDataSupplement export type CardanoTxAuxiliaryDataSupplement = { type: CardanoTxAuxiliaryDataSupplementType; auxiliary_data_hash?: string; cvote_registration_signature?: string; }; -// CardanoTxWitnessRequest export type CardanoTxWitnessRequest = { path: number[]; }; -// CardanoTxWitnessResponse export type CardanoTxWitnessResponse = { type: CardanoTxWitnessType; pub_key: string; @@ -956,18 +883,14 @@ export type CardanoTxWitnessResponse = { chain_code?: string; }; -// CardanoTxHostAck export type CardanoTxHostAck = {}; -// CardanoTxBodyHash export type CardanoTxBodyHash = { tx_hash: string; }; -// CardanoSignTxFinished export type CardanoSignTxFinished = {}; -// Success export type Success = { message: string; }; @@ -990,7 +913,6 @@ export enum FailureType { Failure_FirmwareError = 99, } -// Failure export type Failure = { code?: FailureType; message?: string; @@ -1021,13 +943,12 @@ export enum Enum_ButtonRequestType { export type ButtonRequestType = keyof typeof Enum_ButtonRequestType; -// ButtonRequest export type ButtonRequest = { code?: ButtonRequestType; pages?: number; + name?: string; }; -// ButtonAck export type ButtonAck = {}; export enum Enum_PinMatrixRequestType { @@ -1040,37 +961,30 @@ export enum Enum_PinMatrixRequestType { export type PinMatrixRequestType = keyof typeof Enum_PinMatrixRequestType; -// PinMatrixRequest export type PinMatrixRequest = { type?: PinMatrixRequestType; }; -// PinMatrixAck export type PinMatrixAck = { pin: string; }; -// PassphraseRequest export type PassphraseRequest = { _on_device?: boolean; }; -// PassphraseAck export type PassphraseAck = { passphrase?: string; _state?: string; on_device?: boolean; }; -// Deprecated_PassphraseStateRequest export type Deprecated_PassphraseStateRequest = { state?: string; }; -// Deprecated_PassphraseStateAck export type Deprecated_PassphraseStateAck = {}; -// CipherKeyValue export type CipherKeyValue = { address_n: number[]; key: string; @@ -1081,12 +995,10 @@ export type CipherKeyValue = { iv?: string; }; -// CipheredKeyValue export type CipheredKeyValue = { value: string; }; -// IdentityType export type IdentityType = { proto?: string; user?: string; @@ -1096,7 +1008,6 @@ export type IdentityType = { index?: number; }; -// SignIdentity export type SignIdentity = { identity: IdentityType; challenge_hidden?: string; @@ -1104,21 +1015,18 @@ export type SignIdentity = { ecdsa_curve_name?: string; }; -// SignedIdentity export type SignedIdentity = { address: string; public_key: string; signature: string; }; -// GetECDHSessionKey export type GetECDHSessionKey = { identity: IdentityType; peer_public_key: string; ecdsa_curve_name?: string; }; -// ECDHSessionKey export type ECDHSessionKey = { session_key: string; public_key?: string; @@ -1136,17 +1044,16 @@ export enum DebugPhysicalButton { RIGHT_BTN = 2, } -// DebugLinkResetDebugEvents export type DebugLinkResetDebugEvents = {}; -// EosGetPublicKey +export type DebugLinkOptigaSetSecMax = {}; + export type EosGetPublicKey = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// EosPublicKey export type EosPublicKey = { wif_public_key: string; raw_public_key: string; @@ -1161,7 +1068,6 @@ export type EosTxHeader = { delay_sec: number; }; -// EosSignTx export type EosSignTx = { address_n: number[]; chain_id: string; @@ -1170,7 +1076,6 @@ export type EosSignTx = { chunkify?: boolean; }; -// EosTxActionRequest export type EosTxActionRequest = { data_size?: number; }; @@ -1301,7 +1206,6 @@ export type EosActionUnknown = { data_chunk: string; }; -// EosTxActionAck export type EosTxActionAck = { common: EosActionCommon; transfer?: EosActionTransfer; @@ -1320,7 +1224,6 @@ export type EosTxActionAck = { unknown?: EosActionUnknown; }; -// EosSignedTx export type EosSignedTx = { signature: string; }; @@ -1330,7 +1233,6 @@ export enum EthereumDefinitionType { TOKEN = 1, } -// EthereumNetworkInfo export type EthereumNetworkInfo = { chain_id: number; symbol: string; @@ -1338,7 +1240,6 @@ export type EthereumNetworkInfo = { name: string; }; -// EthereumTokenInfo export type EthereumTokenInfo = { address: string; chain_id: number; @@ -1347,13 +1248,11 @@ export type EthereumTokenInfo = { name: string; }; -// EthereumDefinitions export type EthereumDefinitions = { encoded_network?: ArrayBuffer; encoded_token?: ArrayBuffer; }; -// EthereumSignTypedData export type EthereumSignTypedData = { address_n: number[]; primary_type: string; @@ -1361,7 +1260,6 @@ export type EthereumSignTypedData = { definitions?: EthereumDefinitions; }; -// EthereumTypedDataStructRequest export type EthereumTypedDataStructRequest = { name: string; }; @@ -1389,34 +1287,28 @@ export type EthereumStructMember = { name: string; }; -// EthereumTypedDataStructAck export type EthereumTypedDataStructAck = { members: EthereumStructMember[]; }; -// EthereumTypedDataValueRequest export type EthereumTypedDataValueRequest = { member_path: number[]; }; -// EthereumTypedDataValueAck export type EthereumTypedDataValueAck = { value: string; }; -// EthereumGetPublicKey export type EthereumGetPublicKey = { address_n: number[]; show_display?: boolean; }; -// EthereumPublicKey export type EthereumPublicKey = { node: HDNodeType; xpub: string; }; -// EthereumGetAddress export type EthereumGetAddress = { address_n: number[]; show_display?: boolean; @@ -1424,13 +1316,11 @@ export type EthereumGetAddress = { chunkify?: boolean; }; -// EthereumAddress export type EthereumAddress = { _old_address?: string; address: string; }; -// EthereumSignTx export type EthereumSignTx = { address_n: number[]; nonce?: string; @@ -1451,7 +1341,6 @@ export type EthereumAccessList = { storage_keys: string[]; }; -// EthereumSignTxEIP1559 export type EthereumSignTxEIP1559 = { address_n: number[]; nonce: string; @@ -1468,7 +1357,6 @@ export type EthereumSignTxEIP1559 = { chunkify?: boolean; }; -// EthereumTxRequest export type EthereumTxRequest = { data_length?: number; signature_v?: number; @@ -1476,12 +1364,10 @@ export type EthereumTxRequest = { signature_s?: string; }; -// EthereumTxAck export type EthereumTxAck = { data_chunk: string; }; -// EthereumSignMessage export type EthereumSignMessage = { address_n: number[]; message: string; @@ -1489,13 +1375,11 @@ export type EthereumSignMessage = { chunkify?: boolean; }; -// EthereumMessageSignature export type EthereumMessageSignature = { signature: string; address: string; }; -// EthereumVerifyMessage export type EthereumVerifyMessage = { signature: string; message: string; @@ -1503,7 +1387,6 @@ export type EthereumVerifyMessage = { chunkify?: boolean; }; -// EthereumSignTypedHash export type EthereumSignTypedHash = { address_n: number[]; domain_separator_hash: string; @@ -1511,7 +1394,6 @@ export type EthereumSignTypedHash = { encoded_network?: ArrayBuffer; }; -// EthereumTypedDataSignature export type EthereumTypedDataSignature = { signature: string; address: string; @@ -1544,14 +1426,12 @@ export enum Enum_HomescreenFormat { export type HomescreenFormat = keyof typeof Enum_HomescreenFormat; -// Initialize export type Initialize = { session_id?: string; _skip_passphrase?: boolean; derive_cardano?: boolean; }; -// GetFeatures export type GetFeatures = {}; export enum Enum_BackupAvailability { @@ -1609,7 +1489,6 @@ export enum Enum_RecoveryType { export type RecoveryType = keyof typeof Enum_RecoveryType; -// RecoveryDevice export type RecoveryDevice = { word_count?: number; passphrase_protection?: boolean; @@ -1622,7 +1501,6 @@ export type RecoveryDevice = { type?: RecoveryType; }; -// Features export type Features = { vendor: string; major_version: number; @@ -1678,18 +1556,14 @@ export type Features = { optiga_sec?: number; }; -// LockDevice export type LockDevice = {}; -// SetBusy export type SetBusy = { expiry_ms?: number; }; -// EndSession export type EndSession = {}; -// ApplySettings export type ApplySettings = { language?: string; label?: string; @@ -1705,34 +1579,28 @@ export type ApplySettings = { haptic_feedback?: boolean; }; -// ChangeLanguage export type ChangeLanguage = { data_length: number; show_display?: boolean; }; -// TranslationDataRequest export type TranslationDataRequest = { data_length: number; data_offset: number; }; -// TranslationDataAck export type TranslationDataAck = { data_chunk: string; }; -// ApplyFlags export type ApplyFlags = { flags: number; }; -// ChangePin export type ChangePin = { remove?: boolean; }; -// ChangeWipeCode export type ChangeWipeCode = { remove?: boolean; }; @@ -1743,57 +1611,57 @@ export enum SdProtectOperationType { REFRESH = 2, } -// SdProtect export type SdProtect = { operation: SdProtectOperationType; }; -// Ping export type Ping = { message?: string; button_protection?: boolean; }; -// Cancel export type Cancel = {}; -// GetEntropy export type GetEntropy = { size: number; }; -// Entropy export type Entropy = { entropy: string; }; -// GetFirmwareHash export type GetFirmwareHash = { challenge?: string; }; -// FirmwareHash export type FirmwareHash = { hash: string; }; -// AuthenticateDevice export type AuthenticateDevice = { challenge: string; }; -// AuthenticityProof export type AuthenticityProof = { certificates: string[]; signature: string; }; -// WipeDevice export type WipeDevice = {}; -// ResetDevice +export type LoadDevice = { + mnemonics: string[]; + pin?: string; + passphrase_protection?: boolean; + language?: string; + label?: string; + skip_checksum?: boolean; + u2f_counter?: number; + needs_backup?: boolean; + no_backup?: boolean; +}; + export type ResetDevice = { - display_random?: boolean; strength?: number; passphrase_protection?: boolean; pin_protection?: boolean; @@ -1810,16 +1678,13 @@ export type Slip39Group = { member_count: number; }; -// BackupDevice export type BackupDevice = { group_threshold?: number; groups?: Slip39Group[]; }; -// EntropyRequest export type EntropyRequest = {}; -// EntropyAck export type EntropyAck = { entropy: string; }; @@ -1832,36 +1697,28 @@ export enum Enum_WordRequestType { export type WordRequestType = keyof typeof Enum_WordRequestType; -// WordRequest export type WordRequest = { type: WordRequestType; }; -// WordAck export type WordAck = { word: string; }; -// SetU2FCounter export type SetU2FCounter = { u2f_counter: number; }; -// GetNextU2FCounter export type GetNextU2FCounter = {}; -// NextU2FCounter export type NextU2FCounter = { u2f_counter: number; }; -// DoPreauthorized export type DoPreauthorized = {}; -// PreauthorizedRequest export type PreauthorizedRequest = {}; -// CancelAuthorization export type CancelAuthorization = {}; export enum BootCommand { @@ -1869,51 +1726,35 @@ export enum BootCommand { INSTALL_UPGRADE = 1, } -// RebootToBootloader export type RebootToBootloader = { boot_command?: BootCommand; firmware_header?: string; language_data_length?: number; }; -// GetNonce export type GetNonce = {}; -// Nonce export type Nonce = { nonce: string; }; -// UnlockPath export type UnlockPath = { address_n: number[]; mac?: string; }; -// UnlockedPathRequest export type UnlockedPathRequest = { mac?: string; }; -// ShowDeviceTutorial export type ShowDeviceTutorial = {}; -// UnlockBootloader export type UnlockBootloader = {}; -// SetBrightness export type SetBrightness = { value?: number; }; -export enum MoneroNetworkType { - MAINNET = 0, - TESTNET = 1, - STAGENET = 2, - FAKECHAIN = 3, -} - -// NEMGetAddress export type NEMGetAddress = { address_n: number[]; network?: number; @@ -1921,7 +1762,6 @@ export type NEMGetAddress = { chunkify?: boolean; }; -// NEMAddress export type NEMAddress = { address: string; }; @@ -2022,7 +1862,6 @@ export type NEMImportanceTransfer = { public_key: string; }; -// NEMSignTx export type NEMSignTx = { transaction: NEMTransactionCommon; multisig?: NEMTransactionCommon; @@ -2036,13 +1875,11 @@ export type NEMSignTx = { chunkify?: boolean; }; -// NEMSignedTx export type NEMSignedTx = { data: string; signature: string; }; -// NEMDecryptMessage export type NEMDecryptMessage = { address_n: number[]; network?: number; @@ -2050,19 +1887,16 @@ export type NEMDecryptMessage = { payload?: string; }; -// NEMDecryptedMessage export type NEMDecryptedMessage = { payload: string; }; -// RippleGetAddress export type RippleGetAddress = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// RippleAddress export type RippleAddress = { address: string; }; @@ -2073,7 +1907,6 @@ export type RipplePayment = { destination_tag?: number; }; -// RippleSignTx export type RippleSignTx = { address_n: number[]; fee: UintType; @@ -2084,36 +1917,30 @@ export type RippleSignTx = { chunkify?: boolean; }; -// RippleSignedTx export type RippleSignedTx = { signature: string; serialized_tx: string; }; -// SolanaGetPublicKey export type SolanaGetPublicKey = { address_n: number[]; show_display?: boolean; }; -// SolanaPublicKey export type SolanaPublicKey = { public_key: string; }; -// SolanaGetAddress export type SolanaGetAddress = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// SolanaAddress export type SolanaAddress = { address: string; }; -// SolanaTxTokenAccountInfo export type SolanaTxTokenAccountInfo = { base_address: string; token_program: string; @@ -2121,19 +1948,16 @@ export type SolanaTxTokenAccountInfo = { token_account: string; }; -// SolanaTxAdditionalInfo export type SolanaTxAdditionalInfo = { token_accounts_infos: SolanaTxTokenAccountInfo[]; }; -// SolanaSignTx export type SolanaSignTx = { address_n: number[]; serialized_tx: string; additional_info?: SolanaTxAdditionalInfo; }; -// SolanaTxSignature export type SolanaTxSignature = { signature: string; }; @@ -2144,21 +1968,18 @@ export enum StellarAssetType { ALPHANUM12 = 2, } -// StellarAsset export type StellarAsset = { type: 0 | 1 | 2 | 'NATIVE' | 'ALPHANUM4' | 'ALPHANUM12'; code?: string; issuer?: string; }; -// StellarGetAddress export type StellarGetAddress = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// StellarAddress export type StellarAddress = { address: string; }; @@ -2171,7 +1992,6 @@ export enum StellarMemoType { RETURN = 4, } -// StellarSignTx export type StellarSignTx = { address_n: number[]; network_passphrase: string; @@ -2187,10 +2007,8 @@ export type StellarSignTx = { num_operations: number; }; -// StellarTxOpRequest export type StellarTxOpRequest = {}; -// StellarPaymentOp export type StellarPaymentOp = { source_account?: string; destination_account: string; @@ -2198,14 +2016,12 @@ export type StellarPaymentOp = { amount: UintType; }; -// StellarCreateAccountOp export type StellarCreateAccountOp = { source_account?: string; new_account: string; starting_balance: UintType; }; -// StellarPathPaymentStrictReceiveOp export type StellarPathPaymentStrictReceiveOp = { source_account?: string; send_asset: StellarAsset; @@ -2216,7 +2032,6 @@ export type StellarPathPaymentStrictReceiveOp = { paths?: StellarAsset[]; }; -// StellarPathPaymentStrictSendOp export type StellarPathPaymentStrictSendOp = { source_account?: string; send_asset: StellarAsset; @@ -2227,7 +2042,6 @@ export type StellarPathPaymentStrictSendOp = { paths?: StellarAsset[]; }; -// StellarManageSellOfferOp export type StellarManageSellOfferOp = { source_account?: string; selling_asset: StellarAsset; @@ -2238,7 +2052,6 @@ export type StellarManageSellOfferOp = { offer_id: UintType; }; -// StellarManageBuyOfferOp export type StellarManageBuyOfferOp = { source_account?: string; selling_asset: StellarAsset; @@ -2249,7 +2062,6 @@ export type StellarManageBuyOfferOp = { offer_id: UintType; }; -// StellarCreatePassiveSellOfferOp export type StellarCreatePassiveSellOfferOp = { source_account?: string; selling_asset: StellarAsset; @@ -2265,7 +2077,6 @@ export enum StellarSignerType { HASH = 2, } -// StellarSetOptionsOp export type StellarSetOptionsOp = { source_account?: string; inflation_destination_account?: string; @@ -2281,14 +2092,12 @@ export type StellarSetOptionsOp = { signer_weight?: number; }; -// StellarChangeTrustOp export type StellarChangeTrustOp = { source_account?: string; asset: StellarAsset; limit: UintType; }; -// StellarAllowTrustOp export type StellarAllowTrustOp = { source_account?: string; trusted_account: string; @@ -2297,57 +2106,48 @@ export type StellarAllowTrustOp = { is_authorized: boolean; }; -// StellarAccountMergeOp export type StellarAccountMergeOp = { source_account?: string; destination_account: string; }; -// StellarManageDataOp export type StellarManageDataOp = { source_account?: string; key: string; value?: Buffer | string; }; -// StellarBumpSequenceOp export type StellarBumpSequenceOp = { source_account?: string; bump_to: UintType; }; -// StellarClaimClaimableBalanceOp export type StellarClaimClaimableBalanceOp = { source_account?: string; balance_id: string; }; -// StellarSignedTx export type StellarSignedTx = { public_key: string; signature: string; }; -// TezosGetAddress export type TezosGetAddress = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// TezosAddress export type TezosAddress = { address: string; }; -// TezosGetPublicKey export type TezosGetPublicKey = { address_n: number[]; show_display?: boolean; chunkify?: boolean; }; -// TezosPublicKey export type TezosPublicKey = { public_key: string; }; @@ -2436,7 +2236,6 @@ export type TezosBallotOp = { ballot: TezosBallotType; }; -// TezosSignTx export type TezosSignTx = { address_n: number[]; branch: Uint8Array; @@ -2449,19 +2248,12 @@ export type TezosSignTx = { chunkify?: boolean; }; -// TezosSignedTx export type TezosSignedTx = { signature: string; sig_op_contents: string; operation_hash: string; }; -// experimental_message -export type experimental_message = {}; - -// experimental_field -export type experimental_field = {}; - // custom connect definitions export type MessageType = { BinanceGetAddress: BinanceGetAddress; @@ -2578,6 +2370,7 @@ export type MessageType = { GetECDHSessionKey: GetECDHSessionKey; ECDHSessionKey: ECDHSessionKey; DebugLinkResetDebugEvents: DebugLinkResetDebugEvents; + DebugLinkOptigaSetSecMax: DebugLinkOptigaSetSecMax; EosGetPublicKey: EosGetPublicKey; EosPublicKey: EosPublicKey; EosTxHeader: EosTxHeader; @@ -2654,6 +2447,7 @@ export type MessageType = { AuthenticateDevice: AuthenticateDevice; AuthenticityProof: AuthenticityProof; WipeDevice: WipeDevice; + LoadDevice: LoadDevice; ResetDevice: ResetDevice; Slip39Group: Slip39Group; BackupDevice: BackupDevice; @@ -2739,8 +2533,6 @@ export type MessageType = { TezosBallotOp: TezosBallotOp; TezosSignTx: TezosSignTx; TezosSignedTx: TezosSignedTx; - experimental_message: experimental_message; - experimental_field: experimental_field; }; export type MessageKey = keyof MessageType; diff --git a/packages/protobuf/src/utils.ts b/packages/protobuf/src/utils.ts index 4ae8b156cc0..53d0eb3c31f 100644 --- a/packages/protobuf/src/utils.ts +++ b/packages/protobuf/src/utils.ts @@ -39,8 +39,8 @@ export function parseConfigure(data: protobuf.INamespace) { export const createMessageFromName = (messages: protobuf.Root, name: string) => { const Message = messages.lookupType(name); - const MessageType = messages.lookupEnum('MessageType'); - let messageTypeId = MessageType.values[`MessageType_${name}`]; + const messageTypes = messages.lookupEnum('MessageType'); + let messageTypeId = messageTypes.values[name]; if (typeof messageTypeId !== 'number' && Message.options) { messageTypeId = Message.options['(wire_type)']; @@ -64,10 +64,7 @@ export const createMessageFromType = (messages: protobuf.Root, messageType: numb const messageTypes = messages.lookupEnum('MessageType'); - const messageName = messageTypes.valuesById[messageType].replace( - 'MessageType_', - '', - ) as MessageFromTrezor['type']; + const messageName = messageTypes.valuesById[messageType] as MessageFromTrezor['type']; const Message = messages.lookupType(messageName); diff --git a/packages/protobuf/tests/encode-decode.test.ts b/packages/protobuf/tests/encode-decode.test.ts index e2a0d27d197..755ebdf7c4d 100644 --- a/packages/protobuf/tests/encode-decode.test.ts +++ b/packages/protobuf/tests/encode-decode.test.ts @@ -541,6 +541,6 @@ describe('Real messages', () => { expect(() => { // @ts-expect-error encode(null, {}); - }).toThrowError(); + }).toThrow(); }); }); diff --git a/packages/protobuf/tests/messages.test.ts b/packages/protobuf/tests/messages.test.ts index 325488b9aed..0539229653f 100644 --- a/packages/protobuf/tests/messages.test.ts +++ b/packages/protobuf/tests/messages.test.ts @@ -47,7 +47,7 @@ const json = { }, MessageType: { values: { - MessageType_Initialize: 0, + Initialize: 0, }, }, }, diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 7c709a228ad..31b71f25e54 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -20,7 +20,6 @@ "!**/*.map" ], "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", diff --git a/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt b/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt index e87aa1508dd..14cdf92544f 100644 --- a/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt +++ b/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt @@ -150,7 +150,15 @@ class ReactNativeUsbModule : Module() { for (device in devicesList) { if (usbManager.hasPermission(device)) { Log.d(LOG_TAG, "Has permission, send event onDeviceConnected: $device") - val webUsbDevice = openDevice(device.deviceName) + + val webUsbDevice = if (hasOpenedConnection(device.deviceName)) { + Log.d(LOG_TAG, "Device already opened: $device") + getWebUSBDevice(device) + } else { + Log.d(LOG_TAG, "Opening device: $device") + openDevice(device.deviceName) + } + sendEvent(ON_DEVICE_CONNECT_EVENT_NAME, webUsbDevice) devicesHistory[device.deviceName] = webUsbDevice } else if (!devicesRequestedPermissions.contains( @@ -218,7 +226,7 @@ class ReactNativeUsbModule : Module() { Log.e(LOG_TAG, "Failed to open device ${device.deviceName}") throw Exception("Failed to open device ${device.deviceName}") } - Log.d(LOG_TAG, "Opening device ${device.deviceName}") + openedConnections[device.deviceName] = usbConnection // log all endpoints for debug purposes @@ -300,7 +308,10 @@ class ReactNativeUsbModule : Module() { val dataByteArray = data.split(",").map { it.toInt().toByte() }.toByteArray() Log.d(LOG_TAG, "dataByteArray: $dataByteArray") val device = getDeviceByName(deviceName) - val usbConnection = getOpenedConnection(deviceName) + val usbConnection = openedConnections.getOrPut(device.deviceName) { + Log.d(LOG_TAG, "Reopening device ${device.deviceName}") + usbManager.openDevice(device) ?: throw Exception("Failed to open device ${device.deviceName}") + } val usbEndpoint = device.getInterface(INTERFACE_INDEX).getEndpoint(1) if (usbEndpoint == null) { @@ -315,7 +326,11 @@ class ReactNativeUsbModule : Module() { private fun transferIn(deviceName: String, endpointNumber: Int, length: Int): IntArray { Log.d(LOG_TAG, "Reading data from device $deviceName") val device = getDeviceByName(deviceName) - val usbConnection = getOpenedConnection(deviceName) + + val usbConnection = openedConnections.getOrPut(device.deviceName) { + Log.d(LOG_TAG, "Reopening device ${device.deviceName}") + usbManager.openDevice(device) ?: throw Exception("Failed to open device ${device.deviceName}") + } val usbEndpoint = device.getInterface(INTERFACE_INDEX).getEndpoint(0) diff --git a/packages/react-native-usb/package.json b/packages/react-native-usb/package.json index f35497c05f3..27e9d0c0d4a 100644 --- a/packages/react-native-usb/package.json +++ b/packages/react-native-usb/package.json @@ -4,7 +4,6 @@ "description": "React Native WebUSB implementation", "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "type-check": "yarn g:tsc --build tsconfig.json", "open:ios": "open -a \"Xcode\" example/ios", "open:android": "open -a \"Android Studio\" example/android" diff --git a/packages/react-native-usb/plugins/withUSBDevice.js b/packages/react-native-usb/plugins/withUSBDevice.js index 9bf33101c75..13c2e9335ff 100644 --- a/packages/react-native-usb/plugins/withUSBDevice.js +++ b/packages/react-native-usb/plugins/withUSBDevice.js @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable require-await */ const { AndroidConfig, withAndroidManifest } = require('expo/config-plugins'); @@ -62,9 +61,9 @@ async function setCustomConfigAsync(config, androidManifest) { } module.exports = config => - withAndroidManifest(config, async config => { + withAndroidManifest(config, async config2 => { // Modifiers can be async, but try to keep them fast. - config.modResults = await setCustomConfigAsync(config, config.modResults); + config2.modResults = await setCustomConfigAsync(config2, config2.modResults); - return config; + return config2; }); diff --git a/packages/react-utils/package.json b/packages/react-utils/package.json index c597ced1f47..96ee09c0d76 100644 --- a/packages/react-utils/package.json +++ b/packages/react-utils/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" }, diff --git a/packages/react-utils/src/hooks/useTimer.ts b/packages/react-utils/src/hooks/useTimer.ts index 4185af06797..d65929fcd29 100644 --- a/packages/react-utils/src/hooks/useTimer.ts +++ b/packages/react-utils/src/hooks/useTimer.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; export interface Timer { timeSpend: { @@ -17,24 +17,32 @@ export const useTimer = (): Timer => { const [isLoading, setIsLoading] = useState(false); const [isStopped, setIsStopped] = useState(false); const [resetCount, setResetCount] = useState(0); + const intervalRef = useRef(null); useEffect(() => { - const timeout = setTimeout(() => { - setTimeSpend(timeSpend + 1000); - }, 1000); - if (isStopped || isLoading) { - clearTimeout(timeout); + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + + return; } + intervalRef.current = setInterval(() => { + setTimeSpend(prevTime => prevTime + 1000); + }, 1000); + return () => { - clearTimeout(timeout); + if (intervalRef.current) { + clearInterval(intervalRef.current); + } }; - }); + }, [isLoading, isStopped]); const reset = () => { setIsLoading(false); - setResetCount(resetCount + 1); + setResetCount(prev => prev + 1); setTimeSpend(0); setIsStopped(false); }; diff --git a/packages/request-manager/e2e/identities-stress.ts b/packages/request-manager/e2e/identities-stress.ts index e8d2bc647e5..5f1289c8c53 100644 --- a/packages/request-manager/e2e/identities-stress.ts +++ b/packages/request-manager/e2e/identities-stress.ts @@ -1,4 +1,5 @@ import path from 'path'; + import { createInterceptor, TorController } from '../src'; import { torRunner } from './torRunner'; @@ -55,8 +56,8 @@ const intervalBetweenRequests = 1000 * 20; identities.push(`Basic ${identity}`); } - const makeRequests = async (identities: any) => { - const promises: Promise[] = identities.map((identity: string) => { + const makeRequests = async (identities2: any) => { + const promises: Promise[] = identities2.map((identity: string) => { console.log('identity', identity); return fetch(testGetUrlHttps, { diff --git a/packages/request-manager/e2e/interceptor.test.ts b/packages/request-manager/e2e/interceptor.test.ts index c77b9c9bb98..dd12649c141 100644 --- a/packages/request-manager/e2e/interceptor.test.ts +++ b/packages/request-manager/e2e/interceptor.test.ts @@ -6,7 +6,7 @@ import { TorController, createInterceptor } from '../src'; import { torRunner } from './torRunner'; import { TorIdentities } from '../src/torIdentities'; -const host = '127.0.0.1'; +const hostIp = '127.0.0.1'; const port = 38835; const controlPort = 35527; const processId = process.pid; @@ -29,7 +29,7 @@ describe('Interceptor', () => { let torController: TorController; let torIdentities: TorIdentities; - const torSettings = { running: true, host, port, snowflakeBinaryPath }; + const torSettings = { running: true, host: hostIp, port, snowflakeBinaryPath }; const INTERCEPTOR = { handler: () => {}, @@ -37,11 +37,11 @@ describe('Interceptor', () => { }; beforeAll(async () => { - // Callback in in createInterceptor should return true in order for the request to use Tor. + // Callback in createInterceptor should return true in order for the request to use Tor. torIdentities = createInterceptor(INTERCEPTOR).torIdentities; // Starting Tor controller to make sure that Tor is running. torController = new TorController({ - host, + host: hostIp, port, controlPort, torDataDir, diff --git a/packages/request-manager/eslint.config.mjs b/packages/request-manager/eslint.config.mjs new file mode 100644 index 00000000000..ef851c90a97 --- /dev/null +++ b/packages/request-manager/eslint.config.mjs @@ -0,0 +1,11 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + files: ['**/e2e/**/*'], + rules: { + 'no-console': 'off', // used in e2e tests + }, + }, +]; diff --git a/packages/request-manager/package.json b/packages/request-manager/package.json index 37d4f21411b..f4ec89d00f9 100644 --- a/packages/request-manager/package.json +++ b/packages/request-manager/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index.ts", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:e2e": "yarn g:jest --runInBand -c ../../jest.config.base.js", "type-check": "yarn g:tsc --build tsconfig.json", "test:stress": "ts-node -O '{\"module\": \"commonjs\"}' ./e2e/identities-stress.ts" @@ -14,9 +13,10 @@ "dependencies": { "@trezor/node-utils": "workspace:^", "@trezor/utils": "workspace:*", - "socks-proxy-agent": "6.1.1" + "socks-proxy-agent": "8.0.4" }, "devDependencies": { + "@trezor/eslint": "workspace:*", "ts-node": "^10.9.1", "ws": "^8.18.0" } diff --git a/packages/request-manager/src/httpPool.ts b/packages/request-manager/src/httpPool.ts index 7bf2450b825..fd678cc7756 100644 --- a/packages/request-manager/src/httpPool.ts +++ b/packages/request-manager/src/httpPool.ts @@ -1,4 +1,5 @@ import http from 'http'; + import { InterceptorOptions } from './types'; export const createRequestPool = (interceptorOptions: InterceptorOptions) => { diff --git a/packages/request-manager/src/interceptor.ts b/packages/request-manager/src/interceptor.ts index 00af5aef024..63afdabd7d5 100644 --- a/packages/request-manager/src/interceptor.ts +++ b/packages/request-manager/src/interceptor.ts @@ -2,7 +2,9 @@ import net from 'net'; import http from 'http'; import https from 'https'; import tls from 'tls'; + import { getWeakRandomId } from '@trezor/utils'; + import { TorIdentities } from './torIdentities'; import { InterceptorOptions } from './types'; import { createRequestPool } from './httpPool'; diff --git a/packages/request-manager/src/torControlPort.ts b/packages/request-manager/src/torControlPort.ts index 0803129aedb..134e8e40455 100644 --- a/packages/request-manager/src/torControlPort.ts +++ b/packages/request-manager/src/torControlPort.ts @@ -3,7 +3,9 @@ import fs from 'fs'; import crypto from 'crypto'; import util from 'util'; import path from 'path'; + import { promiseAllSequence } from '@trezor/utils'; + import { TorConnectionOptions, TorCommandResponse } from './types'; const readFile = util.promisify(fs.readFile); @@ -67,7 +69,7 @@ export class TorControlPort { let cookieString; try { cookieString = await getCookieString(this.options.torDataDir); - } catch (error) { + } catch { reject(new Error('TOR control port control_auth_cookie cannot be read')); } const serverNonce = authchallengeResponse[2]; @@ -117,7 +119,7 @@ export class TorControlPort { } try { return !!this.write('GETINFO'); - } catch (error) { + } catch { return false; } } @@ -175,11 +177,11 @@ export class TorControlPort { /^[0-9]+ (LAUNCHED|BUILT|GUARD_WAIT|EXTENDED|FAILED|CLOSED)/.test(line), ) .map(line => { - const [id, status, ...values] = line.split(' '); + const [id, status2, ...values] = line.split(' '); return { id, - status, + status: status2, // not used for now, left as example: // buildFlags: getValue('BUILD_FLAGS', values), // purpose: getValue('PURPOSE', values), diff --git a/packages/request-manager/src/torIdentities.ts b/packages/request-manager/src/torIdentities.ts index d6794f08b4d..0039c36d4a2 100644 --- a/packages/request-manager/src/torIdentities.ts +++ b/packages/request-manager/src/torIdentities.ts @@ -1,4 +1,5 @@ import { SocksProxyAgent } from 'socks-proxy-agent'; + import type { TorSettings } from './types'; export class TorIdentities { @@ -29,13 +30,11 @@ export class TorIdentities { const { host, port } = this.getTorSettings(); // TODO clean agents when host/port changes? - if (!this.identities[user]) { - this.identities[user] = new SocksProxyAgent({ - hostname: host, - port, - userId: user, - password: password || user, + const socksServerUrl = new URL(`socks://${host}:${port}`); + socksServerUrl.username = user; + socksServerUrl.password = password; + this.identities[user] = new SocksProxyAgent(socksServerUrl, { timeout, }); } diff --git a/packages/request-manager/tsconfig.json b/packages/request-manager/tsconfig.json index dca48f8097d..27750a8ccf5 100644 --- a/packages/request-manager/tsconfig.json +++ b/packages/request-manager/tsconfig.json @@ -7,6 +7,7 @@ "include": ["."], "references": [ { "path": "../node-utils" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../eslint" } ] } diff --git a/packages/schema-utils/.eslintrc.js b/packages/schema-utils/.eslintrc.js deleted file mode 100644 index 7e4917ac7e6..00000000000 --- a/packages/schema-utils/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - rules: { - 'no-console': 'warn', - 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], - }, - ignorePatterns: ['**/__snapshots__/**'], -}; diff --git a/packages/schema-utils/eslint.config.mjs b/packages/schema-utils/eslint.config.mjs new file mode 100644 index 00000000000..9e581420940 --- /dev/null +++ b/packages/schema-utils/eslint.config.mjs @@ -0,0 +1,13 @@ +import { eslint } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['**/__snapshots__/**/*'], + }, + { + rules: { + 'no-console': 'warn', + }, + }, +]; diff --git a/packages/schema-utils/package.json b/packages/schema-utils/package.json index c6d1b201844..d319d9a6e88 100644 --- a/packages/schema-utils/package.json +++ b/packages/schema-utils/package.json @@ -1,6 +1,6 @@ { "name": "@trezor/schema-utils", - "version": "1.2.1", + "version": "1.2.2-beta.1", "license": "See LICENSE.md in repo root", "sideEffects": false, "main": "src/index.ts", @@ -13,7 +13,6 @@ ], "scripts": { "test:unit": "yarn g:jest", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", "build:lib": "yarn g:rimraf ./lib && yarn g:tsc --build tsconfig.lib.json && ../../scripts/replace-imports.sh ./lib", @@ -23,6 +22,7 @@ }, "devDependencies": { "@sinclair/typebox-codegen": "^0.10.4", + "@trezor/eslint": "workspace:*", "ts-node": "^10.9.2", "tsx": "^4.16.3" }, diff --git a/packages/schema-utils/src/codegen.ts b/packages/schema-utils/src/codegen.ts index f9c17715d33..6a390d6de18 100644 --- a/packages/schema-utils/src/codegen.ts +++ b/packages/schema-utils/src/codegen.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/no-extraneous-dependencies import * as Codegen from '@sinclair/typebox-codegen/typescript'; import fs from 'fs'; diff --git a/packages/schema-utils/src/index.ts b/packages/schema-utils/src/index.ts index 589e4e45918..64fd196a1d0 100644 --- a/packages/schema-utils/src/index.ts +++ b/packages/schema-utils/src/index.ts @@ -121,4 +121,4 @@ export function AssertWeak( export const Type = new CustomTypeBuilder(); export { Optional, CloneType }; -export type { Static, TObject, TSchema }; +export type * from '@sinclair/typebox'; diff --git a/packages/schema-utils/tsconfig.json b/packages/schema-utils/tsconfig.json index c7ebe855e21..d836e07709c 100644 --- a/packages/schema-utils/tsconfig.json +++ b/packages/schema-utils/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "libDev" }, - "references": [] + "references": [{ "path": "../eslint" }] } diff --git a/packages/schema-utils/tsconfig.lib.json b/packages/schema-utils/tsconfig.lib.json index 81f5735adfc..3528a06e0f9 100644 --- a/packages/schema-utils/tsconfig.lib.json +++ b/packages/schema-utils/tsconfig.lib.json @@ -4,5 +4,9 @@ "outDir": "lib" }, "include": ["./src"], - "references": [] + "references": [ + { + "path": "../eslint" + } + ] } diff --git a/packages/styles/package.json b/packages/styles/package.json index 35eb020c6b0..1a64426f9de 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -8,7 +8,6 @@ "scripts": { "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "test:unit": "yarn g:jest -c ../../jest.config.base.js" }, "dependencies": { diff --git a/packages/suite-analytics/package.json b/packages/suite-analytics/package.json index f459c5a72a8..d17691893e0 100644 --- a/packages/suite-analytics/package.json +++ b/packages/suite-analytics/package.json @@ -6,7 +6,6 @@ "sideEffects": false, "main": "src/index", "scripts": { - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "depcheck": "yarn g:depcheck", "type-check": "yarn g:tsc --build" }, diff --git a/packages/suite-analytics/src/constants.ts b/packages/suite-analytics/src/constants.ts index 6a01143ae6d..1b88cb281eb 100644 --- a/packages/suite-analytics/src/constants.ts +++ b/packages/suite-analytics/src/constants.ts @@ -5,7 +5,6 @@ export enum AppUpdateEventStatus { InstallAndRestart = 'install-and-restart', InstallOnQuit = 'install-on-quit', Closed = 'closed', - // eslint-disable-next-line @typescript-eslint/no-shadow Error = 'error', } @@ -17,6 +16,8 @@ export enum EventType { AppUriHandler = 'app/uri-handler', + DashboardActions = 'dashboard/actions', + DeviceConnect = 'device-connect', DeviceDisconnect = 'device-disconnect', DeviceUpdateFirmware = 'device-update-firmware', @@ -45,10 +46,10 @@ export enum EventType { CoinmarketConfirmTrade = 'trade/confirm-trade', + MenuGuide = 'menu/guide', MenuNotificationsToggle = 'menu/notifications/toggle', MenuToggleDiscreet = 'menu/toggle-discreet', - MenuGuide = 'menu/guide', GuideHeaderNavigation = 'guide/header/navigation', GuideNodeNavigation = 'guide/node/navigation', GuideFeedbackNavigation = 'guide/feedback/navigation', diff --git a/packages/suite-analytics/src/types/events.ts b/packages/suite-analytics/src/types/events.ts index 1df0488d33b..ef2d2bae379 100644 --- a/packages/suite-analytics/src/types/events.ts +++ b/packages/suite-analytics/src/types/events.ts @@ -52,6 +52,12 @@ export type SuiteAnalyticsEvent = isAmountPresent: boolean; }; } + | { + type: EventType.DashboardActions; + payload: { + type: string; + }; + } | { type: EventType.DeviceConnect; payload: { diff --git a/packages/suite-build/configs/base.webpack.config.ts b/packages/suite-build/configs/base.webpack.config.ts index d66451a8db9..30ad065ce8c 100644 --- a/packages/suite-build/configs/base.webpack.config.ts +++ b/packages/suite-build/configs/base.webpack.config.ts @@ -134,7 +134,7 @@ const config: webpack.Configuration = { }, // Workers { - test: /\/workers\/[^\/]+\/index\.ts$/, + test: /\/workers\/[^/]+\/index\.ts$/, use: [ { loader: 'worker-loader', diff --git a/packages/suite-build/configs/desktop.webpack.config.ts b/packages/suite-build/configs/desktop.webpack.config.ts index b249c022a97..db7bd8fb2cf 100644 --- a/packages/suite-build/configs/desktop.webpack.config.ts +++ b/packages/suite-build/configs/desktop.webpack.config.ts @@ -17,7 +17,7 @@ const baseDirUI = getPathForProject('desktop-ui'); const baseDir = getPathForProject('desktop'); const config: webpack.Configuration = { - target: 'browserslist:Chrome >= 128', // Electron 32 is running on chromium 128 + target: 'browserslist:Chrome >= 130', // Electron 33 is running on chromium 130 entry: [path.join(baseDirUI, 'src', 'index.tsx')], output: { path: path.join(baseDir, 'build'), diff --git a/packages/suite-build/eslint.config.mjs b/packages/suite-build/eslint.config.mjs new file mode 100644 index 00000000000..e4553dfa266 --- /dev/null +++ b/packages/suite-build/eslint.config.mjs @@ -0,0 +1,22 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + rules: { + 'no-console': 'off', + 'import/no-default-export': 'off', + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/webpack/**', + '**/builds/**', + '**/configs/**', + ], + }, + ], + }, + }, +]; diff --git a/packages/suite-build/package.json b/packages/suite-build/package.json index 9ce7577117c..29308fc8de7 100644 --- a/packages/suite-build/package.json +++ b/packages/suite-build/package.json @@ -17,7 +17,6 @@ "desktop": "PROJECT=desktop yarn run base", "dev:desktop": "yarn run desktop", "build:desktop": "NODE_ENV=production yarn run desktop", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "type-check": "yarn g:tsc --build tsconfig.json", "type-check:watch": "yarn type-check -- --watch" }, @@ -35,7 +34,7 @@ "stream-browserify": "^3.0.0", "style-loader": "^3.3.4", "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-bundle-analyzer": "^4.10.1", "webpack-merge": "^6.0.1", "webpack-nano": "^1.1.1", @@ -45,6 +44,7 @@ "devDependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@sentry/webpack-plugin": "^2.14.0", + "@trezor/eslint": "workspace:*", "@types/copy-webpack-plugin": "^10.1.0", "@types/webpack-bundle-analyzer": "^4.7.0", "@types/webpack-plugin-serve": "^1.4.6", diff --git a/packages/suite-build/tsconfig.json b/packages/suite-build/tsconfig.json index ec831118d03..c049babcf11 100644 --- a/packages/suite-build/tsconfig.json +++ b/packages/suite-build/tsconfig.json @@ -6,6 +6,7 @@ { "path": "../../suite-common/suite-config" }, - { "path": "../suite" } + { "path": "../suite" }, + { "path": "../eslint" } ] } diff --git a/packages/suite-build/webpack.config.ts b/packages/suite-build/webpack.config.ts index ce550c90975..78a7e619491 100644 --- a/packages/suite-build/webpack.config.ts +++ b/packages/suite-build/webpack.config.ts @@ -2,7 +2,6 @@ import { merge } from 'webpack-merge'; // Env utils import { project, isDev } from './utils/env'; - // Configs import base from './configs/base.webpack.config'; import dev from './configs/dev.webpack.config'; diff --git a/packages/suite-data/eslint.config.mjs b/packages/suite-data/eslint.config.mjs new file mode 100644 index 00000000000..554dd66742e --- /dev/null +++ b/packages/suite-data/eslint.config.mjs @@ -0,0 +1,23 @@ +import { eslint, globalNoExtraneousDependenciesDevDependencies } from '@trezor/eslint'; + +export default [ + ...eslint, + { + ignores: ['files/**/*'], + }, + { + rules: { + 'no-console': 'off', + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + ...globalNoExtraneousDependenciesDevDependencies, + '**/postcss.config.js', + '**/src/**', // Todo: reconsider, this whole package is probably just "dev" + ], + }, + ], + }, + }, +]; diff --git a/packages/suite-data/files/images/favicons/trayLin.png b/packages/suite-data/files/images/favicons/trayLin.png new file mode 100644 index 00000000000..249eaab702e Binary files /dev/null and b/packages/suite-data/files/images/favicons/trayLin.png differ diff --git a/packages/suite-data/files/images/favicons/trayMacTemplate.png b/packages/suite-data/files/images/favicons/trayMacTemplate.png new file mode 100644 index 00000000000..9acc597d16a Binary files /dev/null and b/packages/suite-data/files/images/favicons/trayMacTemplate.png differ diff --git a/packages/suite-data/files/images/favicons/trayMacTemplate@2x.png b/packages/suite-data/files/images/favicons/trayMacTemplate@2x.png new file mode 100644 index 00000000000..907a493aeaf Binary files /dev/null and b/packages/suite-data/files/images/favicons/trayMacTemplate@2x.png differ diff --git a/packages/suite-data/files/images/favicons/trayWin.ico b/packages/suite-data/files/images/favicons/trayWin.ico new file mode 100644 index 00000000000..c53747140c5 Binary files /dev/null and b/packages/suite-data/files/images/favicons/trayWin.ico differ diff --git a/packages/suite-data/files/images/homescreens/COLOR_520x380/green_t3w1.jpg b/packages/suite-data/files/images/homescreens/COLOR_520x380/green_t3w1.jpg new file mode 100644 index 00000000000..af27950d07f Binary files /dev/null and b/packages/suite-data/files/images/homescreens/COLOR_520x380/green_t3w1.jpg differ diff --git a/packages/suite-data/files/images/homescreens/COLOR_520x380/orange_t3w1.jpg b/packages/suite-data/files/images/homescreens/COLOR_520x380/orange_t3w1.jpg new file mode 100644 index 00000000000..021f180b03e Binary files /dev/null and b/packages/suite-data/files/images/homescreens/COLOR_520x380/orange_t3w1.jpg differ diff --git a/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1.png b/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1.png new file mode 100644 index 00000000000..0ca0f862340 Binary files /dev/null and b/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1.png differ diff --git a/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1@2x.png b/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1@2x.png new file mode 100644 index 00000000000..8faa862198b Binary files /dev/null and b/packages/suite-data/files/images/png/dont-disconnect-trezor-t3w1@2x.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1-ghost.png b/packages/suite-data/files/images/png/trezor-t3w1-ghost.png new file mode 100644 index 00000000000..71fb7869942 Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1-ghost.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1-ghost@2x.png b/packages/suite-data/files/images/png/trezor-t3w1-ghost@2x.png new file mode 100644 index 00000000000..90b85f15f77 Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1-ghost@2x.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1-large.png b/packages/suite-data/files/images/png/trezor-t3w1-large.png new file mode 100644 index 00000000000..fff86d2da2f Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1-large.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1-large@2x.png b/packages/suite-data/files/images/png/trezor-t3w1-large@2x.png new file mode 100644 index 00000000000..efb91cbfa06 Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1-large@2x.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1.png b/packages/suite-data/files/images/png/trezor-t3w1.png new file mode 100644 index 00000000000..ccaaf4c3c20 Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1.png differ diff --git a/packages/suite-data/files/images/png/trezor-t3w1@2x.png b/packages/suite-data/files/images/png/trezor-t3w1@2x.png new file mode 100644 index 00000000000..5f1eab29d7b Binary files /dev/null and b/packages/suite-data/files/images/png/trezor-t3w1@2x.png differ diff --git a/packages/suite-data/files/images/svg/device_confirm_trezor_t3w1.svg b/packages/suite-data/files/images/svg/device_confirm_trezor_t3w1.svg new file mode 100644 index 00000000000..7d2916471aa --- /dev/null +++ b/packages/suite-data/files/images/svg/device_confirm_trezor_t3w1.svg @@ -0,0 +1,11 @@ + + device-confirm-trezor-t2t1-svg + + + + + Layer 1 + + 7 + + \ No newline at end of file diff --git a/packages/suite-data/files/images/svg/early-access-disable.svg b/packages/suite-data/files/images/svg/early-access-disable.svg deleted file mode 100644 index 301d81441ab..00000000000 --- a/packages/suite-data/files/images/svg/early-access-disable.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/suite-data/files/images/svg/early-access.svg b/packages/suite-data/files/images/svg/early-access.svg deleted file mode 100644 index 4930936c1f9..00000000000 --- a/packages/suite-data/files/images/svg/early-access.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/suite-data/files/images/svg/gains-graph.svg b/packages/suite-data/files/images/svg/gains-graph.svg new file mode 100644 index 00000000000..21db6d03fdd --- /dev/null +++ b/packages/suite-data/files/images/svg/gains-graph.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/suite-data/files/translations/cs.json b/packages/suite-data/files/translations/cs.json index 1bb3169712b..f0804f7b413 100644 --- a/packages/suite-data/files/translations/cs.json +++ b/packages/suite-data/files/translations/cs.json @@ -1,5 +1,6 @@ { "AMOUNT": "Částka", + "AMOUNT_EXCEEDS_MAX": "Částka překračuje maximální povolenou hodnotu {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "Částka musí být alespoň {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "Účet příjemce musí mít minimální rezervu {reserve} XRP, aby bylo možné provést aktivaci", "AMOUNT_IS_MORE_THAN_RESERVE": "Částka je vyšší než požadovaná nevyužitá rezerva ({reserve} XRP)", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN kód byl změněn", "TOAST_QR_INCORRECT_ADDRESS": "QR kód obsahuje neplatnou adresu pro tento účet", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "QR kód je pro účet {coin}", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Neznámé schéma protokolu: „{scheme}“. Zkuste to znovu nebo zadejte adresu ručně.", "TOAST_RAW_TX_SENT": "Transakce odeslána. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "Nastavení bylo změněno", "TOAST_SIGN_MESSAGE_ERROR": "Chyba při podepisování zprávy: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Přejít na hlavní panel", "TR_404_TITLE": "Chyba 404: Odkaz nenalezen", "TR_7D_CHANGE": "7d změna", - "TR_ABORT": "Zrušit", "TR_ACCESS_HIDDEN_WALLET": "Otevřít passphrase peněženku", "TR_ACCESS_STANDARD_WALLET": "Otevřít standardní peněženku", "TR_ACCOUNT_DETAILS_HEADER": "Podrobnosti účtu", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot je nový typ adresy, který zlepšuje soukromí a efektivitu sítě. Některé služby zatím nemusí adresy Taproot podporovat.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "Současná a nejrozšířenější metoda generování a správy adres Cardano zajišťuje interoperabilitu, zabezpečení a podporu pro všechny typy tokenů.", "TR_ACCOUNT_TYPE_COINJOIN": "Coinjoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Výchozí", "TR_ACCOUNT_TYPE_IMPORTED": "Importováno", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Účty Ledger jsou kompatibilní s derivačními cestami Ledger Live, což umožňuje snadnou migraci z Ledger do Trezoru.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Legacy účty jsou kompatibilní s derivačními cestami Ledger Legacy, což umožňuje snadnou migraci z Ledger do Trezoru.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "Současná a nejrozšířenější metoda generování a správy adres {value} zajišťuje interoperabilitu, zabezpečení a podporu pro všechny typy tokenů.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "Současná a nejrozšířenější metoda generování a správy adres Solana zajišťuje interoperabilitu, zabezpečení a podporu pro tokeny SOL a SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Není podporováno.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Tento typ účtu není podporován u vašeho modelu Trezoru.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Aktualizujte firmware zařízení, pokud chcete aktivovat tento typ účtu.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP je digitální měna, která umožňuje rychlé a levné přeshraniční platby bez nutnosti spoléhat se na tradiční těžbu, kdy využívá jednotnou účetní knihu k rychlému potvrzení transakcí.", "TR_ACQUIRE_DEVICE": "Použít Trezor tady", "TR_ACQUIRE_DEVICE_TITLE": "Je spuštěna další relace", "TR_ACTIVATED_COINS": "Aktivované kryptoměny", "TR_ACTIVE": "aktivní", + "TR_ADD": "Přidat", "TR_ADDRESSES": "Adresa", "TR_ADDRESSES_CHANGE": "Změnit adresy", "TR_ADDRESSES_FRESH": "Nové adresy", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Kopírovat adresu", "TR_ADDRESS_MODAL_TITLE": "Adresa příjemce {networkName}", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "Adresa příjemce {networkCurrencyName} v síti {networkName}", - "TR_ADDRESS_PHISHING_WARNING": "Abyste zabránili phishingovým útokům, ověřte adresu v Trezoru. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Abyste zabránili phishingovým útokům, ověřte adresu příjemce v Trezoru. {claim}", "TR_ADD_ACCOUNT": "Přidat účet", "TR_ADD_HIDDEN_WALLET": "Passphrase peněženka", "TR_ADD_NETWORK_ACCOUNT": "Přidat účet {network}", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Využití údajů", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Veškeré údaje se uchovávají přísně anonymně. Využívají se pouze ke zlepšení ekosystému Trezoru.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Automatické aktualizace aplikace Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Aplikace Trezor Suite automaticky stáhne nejnovější verzi na pozadí a nainstaluje ji při restartování aplikace. Díky tomu budete mít vždy aktuální verzi s nejnovějšími funkcemi a bezpečnostními opravami. Aktualizace probíhají bez vašeho zásahu.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Automaticky stáhnout nejnovější verzi aplikace Trezor Suite na pozadí a nainstalovat ji při restartování aplikace. Díky tomu budete mít vždy aktuální verzi s nejnovějšími funkcemi a bezpečnostními opravami. Aktualizace probíhají bez vašeho zásahu.", + "TR_ALL_NETWORKS": "Všechny sítě ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Zobrazit tokeny ze všech {networkCount} sítí. Filtrovat podle nejoblíbenějších sítí.", "TR_ALL_TRANSACTIONS": "Transakce", "TR_AMOUNT_SENT": "Odeslaná částka", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Nevhodné pro coinjoin – příliš vysoká částka", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Je to sice nepravděpodobné, ale může se stát, že budete potřebovat zálohu peněženky v případě problému s aktualizací firmwaru.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Před přechodem na {bitcoinOnly} je třeba aktualizovat firmware na nejnovější verzi.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Chápu, že porušuji svou anonymitu", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Chápu, že ohrožuji svou anonymitu.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Spouštění Trezor Bridge na portu 21324", "TR_BRIDGE_DEV_MODE_STOP": "Spouštění Trezor Bridge na výchozím portu", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Jste si jistí? Zařízení můžete v daném okamžiku používat pouze v jedné aplikaci. Pokud zrovna používáte zařízení Trezor v jiné aplikaci, ukončete nejprve tuto relaci.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Váš prohlížeč není podporován. Nejlepší možností je stáhnout si do počítače aplikaci Trezor Suite a spustit ji na pozadí nebo použít podporovaný prohlížeč založený na platformě Chromium, který je kompatibilní s rozhraním WebUSB.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Další aplikace požaduje připojení aplikace Trezor Suite k zařízení Trezor. Nechte aplikaci Trezor Suite spuštěnou na pozadí a zkuste akci provést znovu v druhé aplikaci.", + "TR_BRIDGE_TIP_AUTOSTART": "Tip: Povolte funkci automatického spuštění, aby aplikace Bridge vždy běžela na pozadí.", "TR_BTC_UNITS": "Bitcoinové jednotky", "TR_BUG": "Chyba", "TR_BUMP_FEE": "Urychlit transakci", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "K urychlení transakcí zvyšte poplatek u nejstarší (podle nonce) nevyřízené transakce ve frontě. Transakce musí být potvrzeny v pořadí. Zjistit více", "TR_BUY": "Koupit", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Transakce na účtu", "TR_BUY_BUY": "Koupit", - "TR_BUY_BUY_AGAIN": "Koupit znovu", "TR_BUY_CONFIRMED_ON_TREZOR": "Potvrzeno na Trezoru", "TR_BUY_DETAIL_ERROR_BUTTON": "Zpět na účet", "TR_BUY_DETAIL_ERROR_SUPPORT": "Otevřít stránku podpory poskytovatele", @@ -386,12 +397,12 @@ "TR_BUY_MODAL_FOR_YOUR_SAFETY": "Koupit {cryptocurrency} přes {provider}", "TR_BUY_MODAL_LEGAL_HEADER": "Právní ustanovení", "TR_BUY_MODAL_SECURITY_HEADER": "Bezpečnost je u Trezoru na prvním místě", - "TR_BUY_MODAL_TERMS_1": "Přišli jste si koupit kryptoměnu. Pokud jste byli na tuto stránku přesměrováni z jiného důvodu, obraťte se nejprve na podporu {provider}.", - "TR_BUY_MODAL_TERMS_2": "Tuto funkci používáte k nákupu finančních prostředků, které budou zaslány na účet pod vaší přímou osobní kontrolou.", - "TR_BUY_MODAL_TERMS_3": "Berete na vědomí, že kryptoměnové transakce jsou nevratné a nelze je vrátit. Podvodné nebo náhodné ztráty tak mohou být nenávratné.", - "TR_BUY_MODAL_TERMS_4": "Berete na vědomí, že Invity tuto službu neposkytuje. Služba se řídí podmínkami společnosti {provider}.", - "TR_BUY_MODAL_TERMS_5": "Tuto funkci nepoužíváte k hazardním hrám, podvodům ani k jakémukoli jinému jednání v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými předpisy.", - "TR_BUY_MODAL_TERMS_6": "Chápete, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých zemích lišit. To vás může vystavit vyššímu riziku podvodu, krádeže nebo nestability trhu.", + "TR_BUY_MODAL_TERMS_1": "Chci si koupit kryptoměnu. Pokud mě aplikace na tuto stránku přesměruje z jiného důvodu, obrátím se nejprve na podporu {provider}.", + "TR_BUY_MODAL_TERMS_2": "Tuto funkci používám k nákupu kryptoměn, které budou zaslány na můj vlastní účet.", + "TR_BUY_MODAL_TERMS_3": "Rozumím, že transakce s kryptoměnami jsou konečné a nelze je vrátit. Ztráty způsobené podvodem nebo chybami mohou být nevratné.", + "TR_BUY_MODAL_TERMS_4": "Beru vědomí, že Invity tuto službu neposkytuje. Řídí se smluvními podmínkami společnosti {provider}.", + "TR_BUY_MODAL_TERMS_5": "Tuto funkci nepoužívám k hazardním hrám, podvodům ani k jakékoli jiné činnosti v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými zákony.", + "TR_BUY_MODAL_TERMS_6": "Chápu, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých oblastech lišit. To může zvýšit riziko podvodu, krádeže nebo nestability trhu.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Ověření partneři Invity", "TR_BUY_NETWORK": "Koupit {network}", "TR_BUY_NOT_TRANSACTIONS": "Zatím žádné transakce.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "Čeká na vyřízení", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Čeká na vyřízení", "TR_BUY_STATUS_SUCCESS": "Schváleno", - "TR_BUY_TRANS_ID": "Trans. ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximum je {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum je {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum je {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum je {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Zobrazit podrobnosti", "TR_BYTES": "bajty", "TR_CAMERA_NOT_RECOGNIZED": "Kamera nebyla rozpoznána.", "TR_CAMERA_PERMISSION_DENIED": "Oprávnění pro přístup ke kameře bylo zamítnuto.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Částka v Trezoru", "TR_CHAINED_TXS": "Řetězové transakce", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog na GitHubu", "TR_CHANGE_ADDRESS_TOOLTIP": "Tato adresa vznikla při předchozí transakci jako adresa zpět na peněženku.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Typ firmwaru můžete kdykoli změnit v nastavení.", "TR_CHANGE_HOMESCREEN": "Změnit domovskou obrazovku", "TR_CHANGE_PIN": "Změnit PIN", "TR_CHANGE_WIPE_CODE": "Změnit kód pro vymazání", + "TR_CHECKED_BALANCES_ON": "Zůstatky zkontrolovány", "TR_CHECKING_YOUR_DEVICE": "Kontrola vašeho zařízení", "TR_CHECKSUM_CONVERSION_INFO": "Převedeno na kontrolní součet. Zjistit více", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Ověříme integritu vašeho Trezoru, zkontrolujeme jeho bezpečnost a potvrdíme pravost čipu.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Proveďte simulované zkušební obnovení peněženky za účelem ověření zálohy peněženky.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Zde zadejte slova ze zálohy peněženky v pořadí, jak jsou zobrazena na zařízení. V rámci dalšího bezpečnostního opatření můžete být požádáni o zadání několika slov, která nejsou součástí zálohy peněženky.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Pomocí dvou tlačítek zadejte zálohu peněženky. Ochráníte tak své citlivé údaje mimo nedůvěryhodný nebo nezabezpečený počítač nebo webový prohlížeč.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Zadejte zálohu peněženky pomocí dotykového displeje. Tím se vyhnete vystavení svých citlivých informací potenciálně nezabezpečenému počítači nebo webovému prohlížeči.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Zadejte zálohu peněženky pomocí dotykového displeje. Tím se vyhnete vystavení svých citlivých informací potenciálně nezabezpečenému počítači nebo webovému prohlížeči.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Zadejte zálohu peněženky pomocí dotykového displeje. Tím se vyhnete vystavení svých citlivých informací potenciálně nezabezpečenému počítači nebo webovému prohlížeči.", "TR_CHECK_SEED": "Zkontrolovat zálohu", "TR_CHECK_YOUR_DEVICE": "Zkontrolujte obrazovku Trezoru", "TR_CHOOSE_RECOVERY_TYPE": "Vyberte typ obnovy", @@ -502,12 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Chráněno Trezorem", "TR_COINJOIN_TRANSACTION_BATCH": "Coinjoinové transakce", "TR_COINMARKET_BEST_RATE": "Nejlepší kurz", + "TR_COINMARKET_BUY_AND_SELL": "Koupit a prodat", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} nákupů} one {{totalBuys} nákup} few {{totalBuys} nákupy} other {{totalBuys} nákupů} } • {totalSells, plural, =0 {{totalSells} prodejů} one {{totalSells} prodej} few {{totalBuys} prodeje} other {{totalSells} prodejů} }", "TR_COINMARKET_CEX_TOOLTIP": "Centralizovaná směnárna", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Změňte částku nebo měnu.", "TR_COINMARKET_COMPARE_OFFERS": "Porovnat všechny nabídky", "TR_COINMARKET_COUNTRY": "Země trvalého bydliště", + "TR_COINMARKET_DCA_DOWNLOAD": "Stáhněte si mobilní aplikaci Invity a začněte spořit v Bitcoinech", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Bezpečný a jednoduchý plán pravidelného investování.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Vyvinuto společností SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Výběr do vlastní úchovy bez dalších poplatků.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Výběry bez poplatků", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Přehledné, jednoduché a uživatelsky přívětivé rozhraní.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Snadné používání", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Sledujte historii investicí, jejich výši a frekvenci.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Přehled pravidelného investování", + "TR_COINMARKET_DCA_HEADING": "Spořte si v Bitcoinech s aplikací Invity", "TR_COINMARKET_DEX_TOOLTIP": "Decentralizovaná směnárna", "TR_COINMARKET_ENTER_AMOUNT_IN": "Zadejte částku v {currency}", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Všechny možnosti ověření identity zákazníka", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "Ověření identity zákazníka není vyžadováno", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Všechny nabídky CEX a DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX s pevným kurzem", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX s pohyblivým kurzem", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Decentralizovaná směnárna (DEX) umožňuje obchodovat s kryptoměnami přímo na blockchainu bez nutnosti centrální autority nebo zprostředkovatele.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX s pevným kurzem", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX s pohyblivým kurzem", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Vybrané nabídky", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Platba:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Způsob příjmu:", "TR_COINMARKET_FEES_INCLUDED": "Včetně poplatků", "TR_COINMARKET_FEES_NOT_INCLUDED": "Bez poplatků", "TR_COINMARKET_FEES_ON_WEBSITE": "V zobrazené ceně nejsou zahrnuty některé poplatky. Konečnou cenu najdete na webových stránkách poskytovatele.", @@ -520,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "Ověření identity zákazníka je vyžadováno ve výjimečných případech. Ověření identity zákazníka je vyžadováno při vrácení prostředků. 👈", "TR_COINMARKET_KYC_POLICY": "Zásady ověření identity zákazníka", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "Ověření identity zákazníka není vyžadováno", - "TR_COINMARKET_KYC_YES_REFUND": "Ověření identity zákazníka je vyžadováno ve výjimečných případech. Ověření identity zákazníka není vyžadováno při vrácení prostředků. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "Ověření identity zákazníka je vyžadováno pouze ve výjimečných případech. Není vyžadováno při vrácení prostředků. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Poslední transakce", "TR_COINMARKET_NETWORK_FEE": "Síťový poplatek", "TR_COINMARKET_NETWORK_TOKENS": "Tokeny {networkName}", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Nebyl nalezen žádný poskytovatel CEX", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Nebyl nalezen žádný poskytovatel DEX", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Nejsou k dispozici žádné metody", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Automaticky se obnoví za", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Zpět na obchod", - "TR_COINMARKET_NO_OFFERS_HEADER": "Žádné nabídky", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Kvůli problémům s připojením k serveru nyní bohužel nemáme žádné nabídky.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Momentálně bohužel nemáme žádné nabídky. Zkuste stránku znovu načíst nebo změňte dotaz.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Obnovit stránku", "TR_COINMARKET_OFFERS_EMPTY": "Pro váš požadavek není dostupná žádná nabídka. Změňte zemi nebo nakupované množství", "TR_COINMARKET_OFFERS_REFRESH": "Nabídky se aktualizují za", "TR_COINMARKET_OFFERS_SELECT": "Vybrat", "TR_COINMARKET_OFFER_LOOKING": "Hledáme nejlepší nabídku", - "TR_COINMARKET_OFFER_NO_FOUND": "Pro váš požadavek není dostupná žádná nabídka. Změňte částku nebo měnu.", + "TR_COINMARKET_OFFER_NO_FOUND": "Pro váš požadavek není dostupná žádná nabídka.", "TR_COINMARKET_ON_NETWORK_CHAIN": "Na řetězu {networkName}", "TR_COINMARKET_OTHER_CURRENCIES": "Další měny", "TR_COINMARKET_PAYMENT_METHOD": "Platební metoda", @@ -546,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Způsob příjmu", "TR_COINMARKET_SELL": "Prodat", "TR_COINMARKET_SHOW_OFFERS": "Porovnat nabídky", + "TR_COINMARKET_SWAP": "Směnit", + "TR_COINMARKET_SWAP_AMOUNT": "Částka směny", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} směn} one {{totalSwaps} směna} few {{totalSwaps} směny} other {{totalSwaps} směn} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Chci směnit", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Směnit {fromCrypto} na {toCrypto} u {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Právní ustanovení", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Bezpečnost je u Trezoru na prvním místě", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Chci směnit kryptoměny přes DEX (decentralizovaná směnárna) pomocí kontraktu {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Chci směnit kryptoměny pro svůj vlastní účet. Beru na vědomí, že zásady poskytovatele mohou vyžadovat ověření totožnosti.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Rozumím, že transakce s kryptoměnami jsou konečné a nelze je vrátit. Ztráty způsobené podvodem nebo chybami mohou být nevratné.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Beru vědomí, že Invity tuto službu neposkytuje. Řídí se smluvními podmínkami společnosti {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Tuto funkci nepoužívám k hazardním hrám, podvodům ani k jakékoli jiné činnosti v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými zákony.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Chápu, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých oblastech lišit. To může zvýšit riziko podvodu, krádeže nebo nestability trhu.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Ověření partneři Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Chci směnit", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Směnit {fromCrypto} na {toCrypto} u {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Právní ustanovení", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Bezpečnost je u Trezoru na prvním místě", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Chci směnit kryptoměny. Pokud mě aplikace na tuto stránku přesměruje z jiného důvodu, obrátím se nejprve na Trezor podporu. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Chci směnit kryptoměny pro svůj vlastní účet. Beru na vědomí, že zásady poskytovatele mohou vyžadovat ověření totožnosti.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Rozumím, že transakce s kryptoměnami jsou konečné a nelze je vrátit. Ztráty způsobené podvodem nebo chybami mohou být nevratné.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Beru vědomí, že Invity tuto službu neposkytuje. Řídí se smluvními podmínkami společnosti {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Tuto funkci nepoužívám k hazardním hrám, podvodům ani k jakékoli jiné činnosti v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými zákony.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Chápu, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých oblastech lišit. To může zvýšit riziko podvodu, krádeže nebo nestability trhu.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Ověření partneři Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} v síti {networkName}", "TR_COINMARKET_TRADE_FEE": "Obchodní poplatek", + "TR_COINMARKET_TRANS_ID": "Trans. ID:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Neznámý poskytovatel", + "TR_COINMARKET_VIEW_DETAILS": "Zobrazit podrobnosti", "TR_COINMARKET_YOUR_BEST_OFFER": "Nejlepší nabídka", "TR_COINMARKET_YOU_BUY": "Nakoupíte", "TR_COINMARKET_YOU_GET": "Dostanete", @@ -573,6 +627,7 @@ "TR_CONFIRMED_TX": "Potvrzeno", "TR_CONFIRMING_TX": "Potvrzení transakce", "TR_CONFIRM_ACTION_ON_YOUR": "Postupujte podle pokynů na obrazovce Trezoru", + "TR_CONFIRM_ADDRESS": "Potvrďte adresu", "TR_CONFIRM_BEFORE_COPY": "Před kopírováním potvrďte na Trezoru", "TR_CONFIRM_CONDITIONS": "Než budete pokračovat, potvrďte podmínky.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Potvrďte prázdnou passphrase peněženku na zařízení {deviceLabel}.", @@ -601,7 +656,7 @@ "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Trezor není připojen", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Před odesláním připojte Trezor", "TR_CONNECT_YOUR_DEVICE": "Připojte a odemkněte svůj Trezor", - "TR_CONTACT_SUPPORT": "Kontaktovat podporu", + "TR_CONTACT_SUPPORT": "Kontaktovat Trezor podporu", "TR_CONTACT_TREZOR_SUPPORT": "Kontaktovat Trezor podporu", "TR_CONTINUE": "Pokračovat", "TR_CONTINUE_ANYWAY": "Přesto pokračovat", @@ -621,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Nikdy neposílejte prostředky na adresu ID zásady.", "TR_COPY_AND_CLOSE": "Zkopírovat a zavřít", "TR_COPY_SIGNED_MESSAGE": "Zkopírovat podepsanou zprávu", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Kopírovat", + "TR_COPY_TO_CLIPBOARD": "Kopírovat", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Nepodařilo se načíst changelog", "TR_COULD_NOT_RETRIEVE_DATA": "Nepodařilo se načíst data", "TR_COUNT_WALLETS": "{count} {count, plural, one {peněženka} other {peněženek}}", @@ -632,7 +687,7 @@ "TR_CREATE_SHARES": "Vytvořit zálohy na Trezoru", "TR_CREATE_SHARES_CARD_1": "Vezměte si tužku a papír a vytiskněte karty se zálohou nebo použijte Trezor Keep Metal.", "TR_CREATE_SHARES_CARD_2": "Zálohu peněženky si nefoťte ani nepořizujte její digitální kopii.", - "TR_CREATE_SHARES_CARD_3": "Ujistěte se, že kolem sebe nemáte zvídavé přihlížející.", + "TR_CREATE_SHARES_CARD_3": "Ujistěte se, že kolem sebe nemáte zvídavé přihlížející", "TR_CREATE_SHARES_EXAMPLE": "Například: Celkem 5 dílů, přičemž k obnovení peněženky jsou potřeba 3 libovolné díly", "TR_CREATE_SHARES_EXPLANATION": "Vyberte celkový počet dílů a poté zvolte minimální počet potřebný k obnovení Trezoru.", "TR_CREATE_WALLET": "Vytvořit novou peněženku", @@ -655,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Prostředek nebyl správně načten", "TR_DASHBOARD_DISCOVERY_ERROR": "Chyba kontroly", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Účty nebyly načteny správně {details}", + "TR_DATA": "Data", "TR_DATABASE_UPGRADE_BLOCKED": "Upgrade databáze zablokován jinou instancí aplikace", "TR_DATA_ANALYTICS_CATEGORY_1": "Platforma", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "Operační systém, modelu Trezoru, verze atd.", @@ -708,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "Přepnuli jste zařízení do bootloaderu nechtěně?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Znovu připojte zařízení a nesahejte při tom na tlačítka.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Odpojte a připojte zařízení, aniž byste se dotýkali jeho obrazovky.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Toto zařízení se používá jinde.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "Zařízení možná aktuálně používá aplikace {transportSessionOwner}. V případě potřeby můžete převzít kontrolu nad zařízením.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Zařízení možná aktuálně používá jiná aplikace. V případě potřeby můžete převzít kontrolu nad zařízením.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Zařízení detekováno v nesprávném stavu", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Trezor byl během zálohování odpojen. Důrazně doporučujeme vymazat zařízení pomocí možnosti obnovení továrního nastavení v nastavení zařízení. Poté znovu spusťte proces zálohování.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Nepodařilo se zkontrolovat hash firmwaru. Váš Trezor může být padělek.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Nepodařilo se zkontrolovat hash firmwaru. Váš Trezor může být padělek.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Vypnout", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Zapnout", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Kontrola revize firmwaru je zásadní bezpečnostní funkce. Důrazně doporučujeme nechat ji zapnutou.", @@ -728,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "Zařízení {deviceLabel} nemá zálohu", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "Zařízení {deviceLabel} není připojeno", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "Zařízení {deviceLabel} není dostupné", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Zařízení se nepodařilo ověřit", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Znovu připojte zařízení a zkuste ověření spustit znovu. Pokud problém přetrvává, kontaktujte Trezor podporu a zjistěte, co se s vaším zařízením děje a co dělat dál.", "TR_DEVICE_NOT_CONNECTED": "Zařízení není připojeno", "TR_DEVICE_NOT_INITIALIZED": "Trezor není nastaven", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Provedeme vás celým procesem, abyste mohli Trezor začít používat.", @@ -790,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Stáhnout nejnovější Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Neodpojujte zařízení", "TR_DO_NOT_SHOW_AGAIN": "Znovu nezobrazovat", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Přeskočit tento krok?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Přetáhněte soubor sem nebo kliknutím vyberte soubor", "TR_DROPZONE_ERROR": "Import se nezdařil: {error}", @@ -811,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Připojit se", "TR_EARLY_ACCESS_ENABLED": "Program předběžného přístupu byl povolen", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Připojit se", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Rozumím, že zapojení do tohoto programu mi umožní testovat software před vydáním. Tento software může obsahovat chyby, které mohou ovlivnit normální fungování aplikace Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Rozumím, že zapojení do tohoto programu mi umožní testovat software před vydáním. Tento software může obsahovat chyby, které mohou ovlivnit normální fungování aplikace Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Můžete ho kdykoli vypnout.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Vyzkoušejte nejnovější funkce před jejich vydáním široké veřejnosti.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Nejprve zaškrtněte pole výše", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Beta aktualizace můžete vyhledat teď nebo při příštím spuštění aplikace.", "TR_EARLY_ACCESS_JOINED_TITLE": "Program předběžného přístupu byl povolen", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Chcete-li provést downgrade na nejnovější stabilní verzi Suite, kliknutím na tlačítko „Stáhnout stabilní“ aplikaci přeinstalujte.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Chcete-li provést downgrade na nejnovější stabilní verzi Trezor Suite, kliknutím na tlačítko „Stáhnout stabilní“ aplikaci přeinstalujte.", "TR_EARLY_ACCESS_LEFT_TITLE": "Opustili jste program předběžného přístupu. Již vám nebudeme nabízet beta verze.", "TR_EARLY_ACCESS_MENU": "Program předběžného přístupu", "TR_EARLY_ACCESS_REINSTALL": "Stáhnout stabilní", @@ -872,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Schvalte pouze přesnou částku požadovanou pro tuto směnu. Pokud budete chtít podobnou směnu provést znovu, budete muset zaplatit další poplatek.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Odvolat předchozí schválení", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Proveďte transakci, která zruší předchozí schválení kontraktu s {provider}.", - "TR_EXCHANGE_BUY": "Za", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Potvrdit na Trezoru a odeslat", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Potvrdit a odeslat", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Vytvořit schválení", @@ -894,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Transakce byla úspěšná.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Schváleno", "TR_EXCHANGE_DEX": "Nabídka decentralizované směnárny", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Poplatky za provedení této směny budou přibližně {approvalFee} ({approvalFeeFiat}) za schválení (pokud je vyžadováno) a {swapFee} ({swapFeeFiat}) za směnu.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Na transakční poplatky vám nezbývají žádné prostředky. Snižte částku směny na maximálně {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "Údaj {extraFieldName} je neplatný", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -905,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Pevné kurzy vám přesně ukáží, kolik na konci směny dostanete – částka zůstane stejná mezi výběrem kurzu a dokončením transakce. Uvedenou částku máte garantovanou, ale tyto kurzy jsou obvykle méně výhodné, což znamená, že si za své peníze nekoupíte tolik kryptoměn.", "TR_EXCHANGE_FLOAT": "Nabídka s pohyblivým kurzem", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "U pohyblivých kurzů se konečná částka, kterou získáte, může mírně změnit v důsledku výkyvů na trhu v době mezi výběrem kurzu a dokončením transakce. Tyto kurzy jsou obvykle vyšší, což znamená, že ve výsledku můžete získat více kryptoměn.", - "TR_EXCHANGE_PROVIDER": "Poskytovatel", "TR_EXCHANGE_RATE": "Cena", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Účet příjemce je mimo aplikaci Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Jedná se o konkrétní alfanumerickou adresu, na kterou budou vaše mince doručeny.", @@ -932,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Zadejte číslo.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Zadejte přijatelnou kurzovou odchylku.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Částka nabídky směny", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Souhrn kurzové odchylky", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolerance kurzové odchylky", - "TR_EXCHANGE_TRANS_ID": "Trans. ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Použít účet ({symbol}), který není v Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Adresa příjemce", - "TR_EXCHANGE_VIEW_DETAILS": "Zobrazit podrobnosti", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Automatické aktualizace aplikace Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Aplikace Trezor Suite automaticky stáhne nejnovější verzi na pozadí a nainstaluje ji při restartování aplikace. Díky tomu budete mít vždy aktuální verzi s nejnovějšími funkcemi a bezpečnostními opravami. Aktualizace probíhají bez vašeho zásahu.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Povolit síť BNB Smart Chain bez historických interních transakcí.", "TR_EXPERIMENTAL_FEATURES": "Experimentální", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Experimentální funkce", "TR_EXPERIMENTAL_FEATURES_WARNING": "Pouze pro zkušené uživatele. Používejte na vlastní riziko. Tyto funkce jsou ve fázi testování, mohou být nestabilní a nemusí mít dlouhodobou podporu.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migrovat hesla Dropbox", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Nástroj pro načítání hesel uložených ve službě Dropbox a zabezpečených Trezorem. Navržen pro dřívější uživatele rozšíření Trezor Password Manager pro Chrome.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Tento nástroj slouží k načítání hesel uložených ve službě Dropbox a zabezpečených Trezorem. Je určen pro dřívější uživatele rozšíření Trezor Password Manager pro Chrome.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake je systém, který umožňuje přístup k cenzurovaným webovým stránkám a aplikacím.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Pro přístup k cenzurovaným webům a aplikacím můžete používat Tor Snowflake, systém navržený tak, aby obešel omezení.", "TR_EXPORT_AS": "Exportovat jako {as}", "TR_EXPORT_FAIL": "Export se nezdařil.", "TR_EXPORT_TO_FILE": "Exportovat do souboru", @@ -986,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Je k dispozici nový firmware. Aktualizujte zařízení nyní.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Vaše zařízení je již aktualizováno na nejnovější firmware. V případě potřeby můžete firmware přeinstalovat.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Nepodařilo se zkontrolovat revizi firmwaru. Váš Trezor může být padělek.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Nepodařilo se zkontrolovat revizi firmwaru.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Dokončeno", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Odlehčený firmware podporující pouze operace s Bitcoinem.", "TR_FIRMWARE_SUBHEADING_NONE": "Trezor se dodává bez firmwaru. Chcete-li zařízení bezpečně používat, nainstalujte nejnovější firmware. Pokud používáte pouze bitcoiny, doporučujeme nainstalovat .", @@ -1023,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Rozumím", "TR_GO_TO_ONBOARDING": "Spustit nastavení", "TR_GO_TO_SETTINGS": "Přejít do nastavení", - "TR_GO_TO_SUITE": "Přejít do Suite", + "TR_GO_TO_SUITE": "Přejít do Trezor Suite", "TR_GRAPH_LINEAR": "Lineární", "TR_GRAPH_LOGARITHMIC": "Logaritmický", "TR_GRAPH_MISSING_DATA": "Částky XRP, SOL a všech tokenů jsou zahrnuty v zůstatku portfolia, ale v současné době nejsou podporovány v zobrazení grafu.", @@ -1042,7 +1094,7 @@ "TR_GUIDE_FORUM": "Trezor fórum", "TR_GUIDE_FORUM_LABEL": "Přidejte se do komunity Trezoru", "TR_GUIDE_SUGGESTION_LABEL": "Jak si vedeme?", - "TR_GUIDE_SUPPORT": "Kontaktovat podporu", + "TR_GUIDE_SUPPORT": "Kontaktovat Trezor podporu", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Podpora a zpětná vazba", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Podpora a zpětná vazba", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Pomozte nám vylepšit aplikaci", @@ -1174,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Místní systém souborů", "TR_LOG": "Protokol aplikace", "TR_LOGIN_PROCEED": "Pokračovat", - "TR_LOG_DESCRIPTION": "Protokol obsahuje všechny potřebné technické informace o aplikaci Trezor Suite. Může být potřeba při komunikaci s Trezor podporou.", + "TR_LOG_DESCRIPTION": "Tento protokol obsahuje základní technické informace o aplikaci Trezor Suite a může být zapotřebí při kontaktování Trezor podpory.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Čekání na kolo", "TR_LOW_ANONYMITY_WARNING": "Velmi nízká anonymita. Doporučujeme používat alespoň úroveň 1 z 5, protože cokoli pod 1 není bezpečné.", "TR_LTC_ADDRESS_INFO": "Litecoin změnil formát adresy. Více informací o tom, jak převést adresu, najdete na našem blogu. {TR_LEARN_MORE}", @@ -1225,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portfolio", "TR_NAV_ANONYMIZE": "Anonymizovat mince", "TR_NAV_BUY": "Koupit", + "TR_NAV_DCA": "Pravidelné investování fixní částky", "TR_NAV_DETAILS": "Detaily", "TR_NAV_RECEIVE": "Přijmout", "TR_NAV_SELL": "Prodat", @@ -1266,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimismus", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1278,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nový", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Je dostupná nová verze Trezor Bridge.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Je dostupný nový firmware Trezoru! Aktualizujte zařízení.", "TR_NEXT_UP": "Další", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (s nedoporučeným znakem „{char}“)", + "TR_NON_ASCII_CHARS": "{label} (s nedoporučenými znaky)", "TR_NORMAL_ACCOUNTS": "Výchozí účty", "TR_NORTH": "Sever", "TR_NOTHING_TO_ANONYMIZE": "Není co anonymizovat", @@ -1304,10 +1359,6 @@ "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transakce} other {transakcí}}", "TR_OFF": "vypnuto", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Zvolená částka {amount} je vyšší než povolené maximum {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Zvolená částka {amount} je vyšší než povolené maximum {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Zvolená částka {amount} je nižší než povolené minimum {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Zvolená částka {amount} je nižší než povolené minimum {min}.", "TR_OFFICIAL_LANGUAGES": "Oficiální", "TR_OK": "OK", "TR_ON": "zapnuto", @@ -1335,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Anonymní shromažďování údajů", "TR_ONBOARDING_DEVICE_CHECK": "Bezpečnostní kontrola zařízení", "TR_ONBOARDING_DEVICE_CHECK_1": "Hologram byl neporušený a nepoškozený.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Zařízení bylo zakoupeno v oficiálním Trezor eshopu nebo u důvěryhodného prodejce.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Zařízení bylo zakoupeno v oficiálním Trezor eshopu nebo u důvěryhodného prodejce.", "TR_ONBOARDING_DEVICE_CHECK_3": "Balení bylo neporušené a nepoškozené.", "TR_ONBOARDING_DEVICE_CHECK_4": "V připojeném Trezoru již je nainstalován firmware. V nastavení pokračujte pouze v případě, že jste tento Trezor dříve používali.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Stáhnout aplikaci pro počítače", @@ -1366,30 +1417,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Další vstupy a výstupy", "TR_OUTGOING": "Odchozí", "TR_OUTPUTS": "Výstupy", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Minimální a maximální částka, za kterou je tento uživatel ochoten prodat {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Transakci je třeba zahájit na stránkách {providerName} – postupujte podle níže uvedených kroků.", - "TR_P2P_GET_STARTED_ITEM_1": "Kliknutím na „Přejít na stránky {providerName}“ budete přesměrováni na stránky našeho partnera.", - "TR_P2P_GET_STARTED_ITEM_3": "Jakmile vás stránka {providerName} požádá o „adresu vrácení zástavy“, vraťte se sem a pokračujte.", - "TR_P2P_GET_STARTED_ITEM_4": "Už to bude! Odhalte a zkopírujte svou adresu, vložte ji do pole „Adresa vrácení zástavy“ na stránkách {providerName} a dokončete transakci.", - "TR_P2P_GO_TO_PROVIDER": "Přejít na stránky {providerName}", - "TR_P2P_INFO": "S technologií {peerToPeer} (P2P) není na straně kupujícího ani prodávajícího vyžadováno ověření identity. Všechny strany jsou chráněny před podvody zabezpečeným {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "Chci koupit", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Koupit Peer-to-Peer {cryptocurrency} přes {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Právní ustanovení", - "TR_P2P_MODAL_SECURITY_HEADER": "Bezpečnost je u Trezoru na prvním místě", - "TR_P2P_MODAL_TERMS_1": "Chcete koupit kryptoměnu od jiné osoby, kterou si vyberete, pomocí technologie P2P (Peer-to-Peer) bez ověření totožnosti. Pokud jste byli na tuto stránku přesměrováni z jiného důvodu, obraťte se nejprve na podporu.", - "TR_P2P_MODAL_TERMS_2": "Berete na vědomí, že kryptoměnové transakce jsou nevratné a nelze je vrátit. Podvodné nebo náhodné ztráty tak mohou být nenávratné.", - "TR_P2P_MODAL_TERMS_4": "Berete na vědomí, že Invity tuto službu neposkytuje. Služba se řídí podmínkami společnosti {provider}.", - "TR_P2P_MODAL_TERMS_5": "Tuto funkci nepoužíváte k hazardním hrám, podvodům ani k jakémukoli jinému jednání v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými předpisy.", - "TR_P2P_MODAL_TERMS_6": "Chápete, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých zemích lišit. To vás může vystavit vyššímu riziku podvodu, krádeže nebo nestability trhu.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Ověření partneři Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "Transakce musí být dokončena v této lhůtě, počínaje vytvořením kontraktu na webu {providerName}.", - "TR_P2P_PRICE": "Cena za 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "Cena {symbol} nabízená tímto uživatelem.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Ahoj, používám {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Zvolená částka {amount} je vyšší než povolené maximum {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Zvolená částka {amount} je nižší než povolené minimum {minimum}.", "TR_PAGINATION_NEWER": "Novější", "TR_PAGINATION_OLDER": "Starší", "TR_PASSPHRASE_CASE_SENSITIVE": "Poznámka: Passphrase rozlišuje velká a malá písmena.", @@ -1400,6 +1427,8 @@ "TR_PASSPHRASE_MISMATCH": "Passphrase se neshoduje", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Passphrase se neshodují. Z bezpečnostních důvodů začněte znovu a zadejte je správně.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Začít znovu", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Doporučujeme používat ABC, abc, 123, mezery nebo tyto speciální znaky", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "Při použití speciálních znaků, které zde nejsou uvedeny, riskujete budoucí nekompatibilitu", "TR_PASSPHRASE_TOO_LONG": "Délka passphrase překračuje povolený limit.", "TR_PASSPHRASE_WALLET": "Passphrase peněženka #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Zjistěte, jak funguje passphrase", @@ -1441,15 +1470,30 @@ "TR_PIN_SUBHEADING": "Chraňte zařízení před neoprávněným fyzickým přístupem pomocí silného PIN kódu.", "TR_PLAY_IT_SAFE": "Neriskujte", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Povolte kameru, abyste mohli naskenovat QR kód.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Připojte zařízení a pokračujte v procesu ověření.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Připojte Trezor a pokračujte v procesu ověření.", "TR_PLEASE_ENABLE_PASSPHRASE": "Chcete-li pokračovat v procesu ověřování, zapněte použití passphrase.", "TR_POLICY_ID_ADDRESS": "ID zásady:", "TR_PRIMARY_FIAT": "Fiat měna", "TR_PRIVATE": "Soukromé", "TR_PRIVATE_DESCRIPTION": "Anonymita alespoň {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Pokračovat s neověřenou adresou", "TR_PROMO_BANNER_DASHBOARD": "Nejpohodlnější hardwarová peněženka pro bezpečnou správu vašich kryptoměn", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Před skenováním potvrďte na Trezoru", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Nejprve potvrďte adresu příjemce v Trezoru, protože jeho zabezpečený displej nelze nabourat.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Zapnuto", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Právě aktualizováno ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Restartovat a aktualizovat", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Zařízení Trezor", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Je dostupná aktualizace ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Aktuální ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Aplikace Trezor Suite stáhla novou aktualizaci.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Aplikace Trezor Suite byla aktualizována.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Je dostupná aktualizace aplikace Trezor Suite", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Restartovat a aktualizovat", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Spustit aktualizaci", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Je dostupná aktualizace Trezoru", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Co je nového?", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Můžete být požádáni o zadání některých slov, která nejsou součástí zálohy peněženky", "TR_RANGE": "rozsah", "TR_READ_AND_UNDERSTOOD": "Četl/a jsem a pochopil/a výše uvedené", @@ -1501,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Bezpečnostní kontroly", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "Na výzvu", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Povolit potenciálně nebezpečné akce, například neshody klíčů nebo extrémní poplatky, ručním schválením Trezoru.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Neměňte toto nastavení, pokud nevíte, co děláte!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Toto nastavení měňte, pouze pokud víte, co děláte!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Striktní", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Kompletní zabezpečení Trezoru.", "TR_SCAN_QR_CODE": "Naskenujte QR kód", @@ -1513,7 +1557,7 @@ "TR_SECURITY_CHECKPOINT_GOT_SEED": "Máte zálohu peněženky?", "TR_SECURITY_CHECK_HOLOGRAM": "Upozorňujeme, že balení zařízení včetně hologramů a bezpečnostních samolepek se postupem času měnilo. Podrobnosti o balení si můžete ověřit tady. Ujistěte se, že zařízení pochází z oficiálního Trezor eshopu nebo od některého z našich důvěryhodných prodejců. V opačném případě hrozí riziko, že zařízení je padělek. Pokud máte podezření, že zařízení není pravé, obraťte se na Trezor podporu.", "TR_SECURITY_FEATURES_COMPLETED_N": "Zabezpečení ({n} z {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Zařízení nastavená v režimu bez seedu nemají přístup k aplikaci Trezor Suite. Tím se zabrání nevratné ztrátě mincí při použití nevhodně nastaveného zařízení k nesprávnému účelu.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Zařízení nastavená v režimu bez seedu nemají přístup k aplikaci Trezor Suite, aby nemohlo dojít k nevratné ztrátě mincí při nesprávném použití zařízení.", "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Nastavení bez seedu není v Trezor Suite podporováno", "TR_SEED_BACKUP_LENGTH": "Záloha peněženky může obsahovat 12, 18 nebo 24 slov.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Záloha peněženky může obsahovat 12, 18, 20, 24 nebo 33 slov.", @@ -1524,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Zadejte slova ze zálohy peněženky v pořadí, jak jsou zobrazena na Trezoru.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Pomocí dotykového displeje zadejte všechna slova ve správném pořadí.", "TR_SEE_DETAILS": "Zobrazit detaily", + "TR_SEE_IF_ISSUE_PERSISTS": "Zkontrolujte, zda problém přetrvává.", "TR_SELECTED": "Vybrána částka {amount}", "TR_SELECT_COIN_FOR_SETTINGS": "Vyberte aktivní kryptoměnu, jejíž nastavení chcete změnit", "TR_SELECT_DEVICE": "Vyberte zařízení", + "TR_SELECT_NAME_OR_ADDRESS": "Zkuste hledat podle názvu, symbolu, sítě nebo adresy kontraktu", "TR_SELECT_NUMBER_OF_WORDS": "Vyberte počet slov v záloze peněženky", "TR_SELECT_PASSPHRASE_SOURCE": "Vyberte, kam chcete zadat passphrase na zařízení {deviceLabel}.", "TR_SELECT_RECOVERY_METHOD": "Vyberte metodu obnovení", + "TR_SELECT_TOKEN": "Vyberte token", "TR_SELECT_TREZOR": "Vyberte Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Chcete-li pokračovat, vyberte svůj Trezor.", "TR_SELECT_TYPE": "Vyberte typ", @@ -1560,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Prodat {cryptocurrency} přes {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Právní ustanovení", "TR_SELL_MODAL_SECURITY_HEADER": "Bezpečnost je u Trezoru na prvním místě", - "TR_SELL_MODAL_TERMS_1": "Přišli jste si koupit kryptoměnu. Pokud jste byli na tuto stránku přesměrováni z jiného důvodu, obraťte se nejprve na podporu.", + "TR_SELL_MODAL_TERMS_1": "Přišli jste si koupit kryptoměnu. Pokud jste byli na tuto stránku přesměrováni z jiného důvodu, obraťte se nejprve na Trezor podporu.", "TR_SELL_MODAL_TERMS_2": "Kupujete kryptoměnu pro svůj vlastní účet. Berete na vědomí, že zásady poskytovatele mohou vyžadovat ověření totožnosti.", "TR_SELL_MODAL_TERMS_3": "Berete na vědomí, že kryptoměnové transakce jsou nevratné a nelze je vrátit. Podvodné nebo náhodné ztráty tak mohou být nenávratné.", "TR_SELL_MODAL_TERMS_4": "Berete na vědomí, že Invity tuto službu neposkytuje. Služba se řídí podmínkami společnosti {provider}.", - "TR_SELL_MODAL_TERMS_5": "Tuto funkci nepoužíváte k hazardním hrám, podvodům ani k jakémukoli jinému jednání v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými předpisy.", + "TR_SELL_MODAL_TERMS_5": "Tuto funkci nepoužívám k hazardním hrám, podvodům ani k jakékoli jiné činnosti v rozporu s podmínkami služby Invity nebo poskytovatele nebo v rozporu s platnými zákony.", "TR_SELL_MODAL_TERMS_6": "Chápete, že kryptoměny jsou nově vznikajícím finančním nástrojem a že právní předpisy se mohou v různých zemích lišit. To vás může vystavit vyššímu riziku podvodu, krádeže nebo nestability trhu.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Ověření partneři Invity", "TR_SELL_REGISTER": "Registrovat", @@ -1573,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Odmítnuto", "TR_SELL_STATUS_PENDING": "Čeká na vyřízení", "TR_SELL_STATUS_SUCCESS": "Schváleno", - "TR_SELL_TRANS_ID": "Trans. ID:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum je {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum je {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Zobrazit podrobnosti", "TR_SENDFORM_LABELING_EXAMPLE_1": "Spoření", "TR_SENDFORM_LABELING_EXAMPLE_2": "Nájemné", "TR_SENDING_SYMBOL": "Odesílání {multiple, select, true {několika tokenů} false {{symbol}} other {{symbol}}}", @@ -1631,6 +1674,8 @@ "TR_SHOW_LOG": "Zobrazit protokol", "TR_SHOW_MORE": "Zobrazit více", "TR_SHOW_MORE_ADDRESSES": "Zobrazit více ({count})", + "TR_SHOW_ON_TRAY": "Zobrazit ikonu na hlavním panelu", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Sledujte, zda je aplikace Trezor Suite spuštěná na pozadí.", "TR_SHOW_UNVERIFIED_ADDRESS": "Zobrazit neověřenou adresu", "TR_SHOW_UNVERIFIED_XPUB": "Zobrazit neověřený veřejný klíč", "TR_SIDEBAR_ADD_COIN": "Přidat minci", @@ -1643,7 +1688,9 @@ "TR_SIZE": "Velikost", "TR_SKIP": "Přeskočit", "TR_SKIP_BACKUP": "Přeskočit zálohování", + "TR_SKIP_BACKUP_DESCRIPTION": "Záloha peněženky vám umožní obnovit prostředky v případě ztráty, odcizení nebo poškození Trezoru. Bez zálohy můžete trvale ztratit přístup ke svým kryptoměnám.", "TR_SKIP_PIN": "Přeskočit PIN kód", + "TR_SKIP_PIN_DESCRIPTION": "PIN kód zařízení zabraňuje neoprávněnému přístupu k vašemu Trezoru. Bez něj bude mít přístup k vašim prostředkům každý, kdo bude mít vaše zařízení.", "TR_SKIP_ROUNDS": "Vynechání kol", "TR_SKIP_ROUNDS_DESCRIPTION": "Když povolíte vynechání kol, ztížíte prokázání jakéhokoli vztahu mezi vstupy. To znamená, že můžete dále zamaskovat původ prostředků.", "TR_SKIP_ROUNDS_HEADING": "Povolit Trezoru vynechávat kola", @@ -1656,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Přidání do stake poolu", "TR_STAKE_ANY_AMOUNT_ETH": "Stakujte alespoň {amount} {symbol} a začněte získávat odměny. S naší aktuální sazbou APY {apyPercent} % vydělávají i vaše odměny!", "TR_STAKE_APY": "Roční procentní výnos", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Roční procentní výnos", "TR_STAKE_AVAILABLE": "K dispozici", "TR_STAKE_CAN_CLAIM_WARNING": "Již si můžete vybrat {amount} {symbol}. {br}Vyberte nebo počkejte, dokud se nezpracuje konec stakingu.", @@ -1665,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Můžete vybrat, jakmile skončí období konce stakingu.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "v dalším bloku", "TR_STAKE_CLAIM_PENDING": "Čeká na výběr", + "TR_STAKE_CLAIM_UNSTAKED": "Vybrat {symbol} ze zrušeného stakingu", "TR_STAKE_CONFIRM_AND_STAKE": "Potvrdit a stakovat", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Potvrdit období vstupu", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Beru na vědomí a souhlasím se stakingem na Everstake", "TR_STAKE_DAYS": "{count, plural, one {# den} few {# dny} other {# dní}}", "TR_STAKE_DELEGATED": "Delegace staku", "TR_STAKE_DEREGISTERED": "Zrušení registrace stake adresy", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Získávejte odměny každý týden", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "Vstup do stake poolu může trvat až {count, plural, one {# den} few {# dny} other {# dní}}", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Zapojte se do stake poolu", "TR_STAKE_ETH": "Stake Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "Nejsnazší způsob, jak získat {symbol}.", + "TR_STAKE_ETH_CARD_TITLE": "Nejsnazší způsob, jak získat {symbol}", "TR_STAKE_ETH_EARN_REPEAT": "Zapojte se. Získejte odměny. Opakujte proces.", "TR_STAKE_ETH_EVERSTAKE": "Trezor a Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake je přední světový vývojář a dodavatel technologie stakingu", @@ -1684,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Vaše odměny také vydělávají. Nechte je stakovat a sledujte, jak vaše odměny za {symbol} stoupají.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Vaše odměny {symbol} také vydělávají se sazbou APY. Stakujte své prostředky nebo přidejte další, abyste zvýšili své odměny.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Sledujte, jak se peníze sypou", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Vydělávejte {apyPercent} % APY* stakingem svého Etherea pomocí Trezoru.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Vydělávejte {apyPercent} % APY stakingem svého Etherea pomocí Trezoru.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Během tohoto období bude vaše {symbol} zablokováno a toto blokování nelze zrušit. Zjistit více", "TR_STAKE_EVERSTAKE_MANAGES": "Everstake spravuje a chrání vaše stakované {symbol} pomocí svých smart kontraktů, infrastruktury a technologie.", "TR_STAKE_INSTANT": "Okamžité", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Obdrželi jste {amount} {symbol} „okamžitě“. {days, plural, =0 {} one {Zbytek vám bude vyplacen do # dne.} other { Zbytek vám bude vyplacen do # dnů.}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} na účtu", "TR_STAKE_LEARN_MORE": "Zjistit více", + "TR_STAKE_LEAVE_STAKING_POOL": "Opustit stake pool", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Nechali jsme vám {amount} {symbol}, abyste mohli zaplatit poplatky za výběr.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Nechali jsme vám malé množství {symbol}, abyste mohli zaplatit poplatky za výběr.", "TR_STAKE_MAX": "Maximální", "TR_STAKE_MAX_FEE_DESC": "Maximální poplatek je transakční poplatek sítě, který jste ochotni zaplatit v síti, abyste zajistili, že vaše transakce bude zpracována.", "TR_STAKE_MAX_REWARD_DAYS": "Max. {count, plural, one {# den} few {# dny} other {# dní}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "Minimální vklad do staku je {amount} {symbol}", + "TR_STAKE_MONTHLY": "Měsíčně", "TR_STAKE_NEXT_PAYOUT": "Příští výplata odměn", "TR_STAKE_NOT_ENOUGH_FUNDS": "Nedostatek {symbol} na zaplacení síťových poplatků", "TR_STAKE_ONLY_REWARDS": "Pouze odměny", @@ -1706,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Registrace stake adresy", "TR_STAKE_RESTAKED_BADGE": "Znovu stakováno", "TR_STAKE_REWARDS": "Odměny", + "TR_STAKE_SIGN_TRANSACTION": "Podepsat transakci", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Podepsat transakci zrušení stakingu", "TR_STAKE_STAKE": "Stake", "TR_STAKE_STAKED_AMOUNT": "Stakovaná částka", "TR_STAKE_STAKED_AND_EARNING": "Staking a získávání odměn", @@ -1713,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Stakovat více", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Staking v kostce", "TR_STAKE_STAKING_IS": "Při stakingu dočasně uzamknete své Ethereum, abyste podpořili fungování blockchainu. Na oplátku získáte další Ethereum jako odměnu.", + "TR_STAKE_STAKING_PROCESS": "Proces stakingu", "TR_STAKE_START_STAKING": "Začněte stakovat", "TR_STAKE_TIME_TO_CLAIM": "Čas na výběr", "TR_STAKE_TOTAL_PENDING": "Celková výše staku čekající na vyřízení:", @@ -1721,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Staking zrušen a připraveno na výběr", "TR_STAKE_UNSTAKE_TO_CLAIM": "Před výběrem zrušte staking", "TR_STAKE_UNSTAKING": "Zrušení stakingu", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Přibližné množství {symbol}, které je okamžitě k dispozici", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "Likvidita stake poolu umožňuje okamžité zrušení stakingu některých prostředků. Pro zbývající prostředky bude platit doba pro zrušení stakingu.", "TR_STAKE_UNSTAKING_PERIOD": "Doba pro zrušení stakingu", + "TR_STAKE_UNSTAKING_PROCESS": "Proces zrušení stakingu", "TR_STAKE_UNSTAKING_TAKES": "Zrušení stakingu aktuálně trvá {count, plural, one {# den} few {# dny} other {# dní}}. Po dokončení můžete s prostředky obchodovat nebo je odeslat.", + "TR_STAKE_WEEKLY": "Týdně", "TR_STAKE_WHAT_IS_STAKING": "Co je staking?", + "TR_STAKE_YEARLY": "Ročně", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Vaše stakované prostředky spravuje Everstake", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} bylo okamžitě stakováno!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "Staking {amount} {symbol} byl okamžitě zrušen!", + "TR_STAKING_CONSOLIDATING_FUNDS": "Konsolidujeme vaše {symbol}", "TR_STAKING_DELEGATE": "Delegovat", "TR_STAKING_DEPOSIT": "Vratná záloha", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "Poplatek za vklad je {feeAmount} ADA a je vyžadován k registraci vaší adresy pro zahájení stakingu. Pokud se rozhodnete staking Cardana zrušit, dostanete vklad zpět.", + "TR_STAKING_ESTIMATED_GAINS": "Odhadované zisky", "TR_STAKING_FEE": "Poplatek", + "TR_STAKING_GETTING_READY": "{symbol} vám začne vydělávat", "TR_STAKING_INSTANTLY_STAKED": "Okamžitě jste stakovali {amount} {symbol}. {days, plural, =0 {} one {Zbytek {symbol} se stakuje do # dne.} other { Zbytek {symbol} se stakuje do # dnů.}}", "TR_STAKING_IS_NOT_SUPPORTED": "Staking není v této síti podporován.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Na účtu nemáte dostatek prostředků.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Jakmile potvrdíte", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Stakingem na stake pool Trezor přímo podporujete ekosystém Trezor a Cardano v rámci Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Delegujete na stake pool třetí strany", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "Stake pool, na který delegujete, je přesycen. Delegujte svůj vklad znovu, abyste maximalizovali své odměny za staking", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Stake pool je přesycený", "TR_STAKING_REDELEGATE": "Znovu delegovat", "TR_STAKING_REWARDS": "Dostupné odměny", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Odměny jsou automaticky znovu stakovány", "TR_STAKING_REWARDS_DESCRIPTION": "Upozorňujeme, že po počáteční registraci a delegaci stakingu může trvat až 20 dní, než začnete dostávat odměny. Po uplynutí této doby budete dostávat odměny každých 5 dní.", "TR_STAKING_REWARDS_TITLE": "Staking Cardana je aktivní", "TR_STAKING_STAKE_ADDRESS": "Vaše stake adresa", @@ -1746,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Nepodařilo se nám připojit ke stake poolu Trezor, na který by bylo možné delegovat.", "TR_STAKING_TX_PENDING": "Vaše transakce {txid} byla odeslána do blockchainu a čeká na potvrzení.", "TR_STAKING_WITHDRAW": "Vybrat", + "TR_STAKING_YOUR_EARNINGS": "Vaše odměny jsou automaticky znovu stakovány, díky čemuž získáte složený úrok.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "{symbol} ze zrušeného stakingu je k dispozici", + "TR_STAKING_YOU_ARE_HERE": "Jste tady", "TR_STANDARD_WALLET_DESCRIPTION": "Bez passphrase", "TR_START": "Začít", "TR_START_AGAIN": "Začít znovu", @@ -1754,6 +1827,7 @@ "TR_START_COINJOIN": "Spustit coinjoin", "TR_START_RECOVERY": "Spustit obnovení", "TR_STEP": "Krok {number}", + "TR_STEP_OF_TOTAL": "Krok {index} z {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Stále nevidíte svůj Trezor?", "TR_STOP": "Zastavit", "TR_STOPPING": "Zastavuji", @@ -1805,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Zatím žádné tokeny…", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Žádné tokeny. Možná jsou skryté.", "TR_TOKENS_SEARCH_TOOLTIP": "Zkuste hledat podle tokenu, symbolu nebo adresy kontraktu.", + "TR_TOKEN_NOT_FOUND": "Token nebyl nalezen", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Token nebyl nalezen v síti {networkName}.", "TR_TOKEN_TRANSFERS": "{standard} převody tokenů", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Zkuste upravit vyhledávání.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Zkuste upravit vyhledávání nebo přepněte na jinou síť.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Nerozpoznané tokeny", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Nerozpoznané tokeny představují potenciální riziko. Buďte opatrní.", "TR_TOO_LONG": "Zpráva je příliš dlouhá", @@ -1817,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Aktualizovat cestu", "TR_TOR_DESCRIPTION": "Směrujte veškerý provoz Trezor Suite přes síť Tor, čímž se zvýší vaše soukromí a zabezpečení. Může chvíli trvat, než se Tor načte a naváže spojení.", "TR_TOR_DISABLE": "Vypnout Tor", + "TR_TOR_DISABLED": "Vypnuto", "TR_TOR_DISABLE_ONIONS_ONLY": "Chybí vlastní backendy, které nejsou onion", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Přidejte vlastní adresy backendu, které nejsou na bázi onion, aby nedocházelo k tomuto chování.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Nyní můžete Tor bezpečně vypnout.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Vlastní backendy již nepoužívají pouze adresy onion.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Vypnout Tor", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Vypnutím Toru se všechny onion backendy přenastaví na výchozí servery Trezoru.", + "TR_TOR_DISABLING": "Vypínání", "TR_TOR_ENABLE": "Zapnout Tor", + "TR_TOR_ENABLED": "Zapnuto", "TR_TOR_ENABLE_AND_CONFIRM": "Zapnout Tor a potvrdit", "TR_TOR_ENABLE_TITLE": "Zapnout Tor", + "TR_TOR_ENABLING": "Zapínání", + "TR_TOR_ERROR": "Chyba", "TR_TOR_IS_SLOW_MESSAGE": "Tor se připojuje k síti.

Čekejte prosím.", "TR_TOR_KEEP_RUNNING": "Nechat Tor běžet", - "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Chcete-li pokračovat, vyberte možnost „Nechat Tor běžet“ nebo „Zastavit Tor“.", + "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Pokračujte výběrem možnosti „Nechat Tor běžet“ nebo výběrem „Zastavit Tor“ proces coinjoinu ukončete.", + "TR_TOR_MISBEHAVING": "Nesprávné chování", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Vypnout Tor a přepnout na výchozí backendy", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Opustit", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Pokračujte výběrem možnosti „Povolit Tor“ nebo výběrem „Odejít“ proces ukončete.", @@ -1843,11 +1927,7 @@ "TR_TO_BTC": "Na BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Chcete-li, aby vaše popisky byly konzistentní a dostupné na všech zařízeních, připojte se ke cloudovému úložišti.", "TR_TO_SATOSHIS": "Na satoshi", - "TR_TRADE_BUYS": "kupuje", - "TR_TRADE_ENTER_COIN": "Zadejte název nebo symbol kryptoměny…", - "TR_TRADE_EXCHANGES": "směňuje", "TR_TRADE_REDIRECTING": "Přesměrování…", - "TR_TRADE_SELLS": "prodává", "TR_TRANSACTIONS_NOT_AVAILABLE": "Historie transakcí není k dispozici", "TR_TRANSACTIONS_SEARCH_TIP_1": "Tip: Můžete vyhledávat ID transakcí, adresy, tokeny, popisky, částky a data.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Tip: Kombinací operátorů AND (&) a OR (|) můžete získat podrobnější výsledky. Například > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 zobrazí všechny transakce z ledna nebo prosince {lastYear}.", @@ -1862,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Vyhledávejte podle ID transakce, popisku nebo částky nebo použijte operátory jako < > | & = !=.", "TR_TRANSACTION_DETAILS": "Detaily", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor Bridge ve verzi {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutoriál zrušen", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutoriál dokončen", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Naučte se používat zařízení pomocí krátkého tutoriálu", @@ -1869,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Zavřete ostatní panely a okna, které mohou používat váš Trezor", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Po zavření ostatních panelů a oken zkuste tuto stránku obnovit.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Po zavření ostatních panelů a oken prohlížeče zkuste aplikaci Trezor Suite zavřít a znovu otevřít.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Kroky potřebné k povolení komunikace", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Zkuste problém vyřešit pomocí těchto kroků.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Navštivte stránku se stavem Trezor Bridge", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Ujistěte se, že běží proces Trezor Bridge", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Přímou komunikaci se zařízeními USB v současné době umožňují pouze prohlížeče založené na platformě Chromium", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Přímou komunikaci se zařízeními USB v současné době umožňují pouze prohlížeče založené na platformě Chromium.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Použijte prohlížeč založený na platformě Chromium", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "Kabel musí být zcela zasunutý. Pokud máte zařízení s USB-C, měl by kabel zapadnout na místo.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Vyzkoušejte jiný kabel", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "S nainstalovanou aplikací Trezor Bridge.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Zkuste použít jiný počítač, pokud je to možné", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Pro všechny případy", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Zkuste restartovat počítač", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Restartování počítače může vyřešit problém s komunikací mezi prohlížečem a zařízením.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Restartujte počítač", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Spustit desktopovou aplikaci Trezor Suite", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Použijte desktopovou aplikaci Trezor Suite", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Kliknutím můžete přepínat mezi různými implementaci bridge. Aktuální verze: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Použijte jinou verzi Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Zkuste nainstalovat pravidla udev. Před otevřením zkontrolujte, že se uložila do počítače.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Pokud jste firmware zařízení naposledy aktualizovali v roce 2019 nebo dříve, postupujte podle pokynů v databázi znalostí", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Zdá se, že používáte starší model Trezoru.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Připojte jej přímo k počítači (ne do rozbočovače USB).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Vyzkoušejte jiný port USB", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Instalovat pravidla automaticky", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Chybějící pravidla udev", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Neočekávaný stav: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Zařízení je správně připojeno, ale prohlížeč s ním momentálně nemůže komunikovat. Je potřeba nainstalovat aplikaci Trezor Bridge.", "TR_TRY_AGAIN": "Zkusit znovu", "TR_TXID": "TX ID", "TR_TXID_RBF": "Původní TX ID, které má být nahrazeno", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transakce potvrzena", "TR_TX_CONFIRMING": "Potvrzení transakce", + "TR_TX_DATA_FUNCTION": "Funkce", + "TR_TX_DATA_INPUT_DATA": "Vstupní data", + "TR_TX_DATA_METHOD": "Vstupní data", + "TR_TX_DATA_METHOD_NAME": "Název metody", + "TR_TX_DATA_PARAMS": "Parametry", "TR_TX_DEPOSIT": "Vklad", "TR_TX_FEE": "Poplatek", "TR_TX_TAB_AMOUNT": "Částka", @@ -1934,13 +2023,18 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Je vyžadována aktualizace firmwaru. Domovskou obrazovku můžete později změnit v nastavení", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Aktualizujte firmware, abyste mohli změnit domovskou obrazovku", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Je dostupná aktualizace", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Povolit automatické aktualizace", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Nové", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Aktualizovat a restartovat", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Nainstalovat aktualizaci?", "TR_UPDATE_MODAL_NOT_NOW": "Teď ne", - "TR_UPDATE_MODAL_RESTART_NEEDED": "Dojde k restartování aplikace Trezor Suite", + "TR_UPDATE_MODAL_RESTART_NEEDED": "Dojde k restartování aplikace Trezor Suite.", "TR_UPDATE_MODAL_START_DOWNLOAD": "Stáhnout", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Aktualizace stažena", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Aktualizovat po ukončení", + "TR_UPDATE_MODAL_WHATS_NEW": "Co je nového?", + "TR_UPDATE_MODAL_YOUR_VERSION": "Vaše verze: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Přístup k tomuto účtu získáte po upgradu firmwaru. Viz modrý pruh nahoře.", "TR_UP_TO": "až", "TR_UP_TO_DATE": "Aktuální", "TR_UP_TO_DAYS": "až {count, plural, one {# den} few {# dny} other {# dní}}", @@ -1994,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Otevřít passphrase peněženku", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Passphrase peněženka", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Vytvořte si novou peněženku nebo ji obnovte ze zálohy peněženky.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Vždy se snažíme vám vylepšit používání Trezoru. Zde najdete popis novinek:", "TR_WEST": "Západ", "TR_WHAT_DATA_WE_COLLECT": "Jaké údaje shromažďujeme?", "TR_WHAT_IS_PASSPHRASE": "V čem je rozdíl", @@ -2004,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Beru na vědomí, že musím mít zálohu peněženky, pokud chci získat přístup ke svým prostředkům", "TR_WIPE_DEVICE_TEXT": "Resetováním zařízení z něj odstraníte všechna data. Zařízení resetujte pouze v případě, že máte zálohu peněženky, která vám umožní obnovit přístup k prostředkům.", "TR_WIPE_OR_UPDATE": "Resetování zařízení nebo aktualizace firmwaru", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Přejděte do nastavení zařízení", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Přejděte do nastavení zařízení.", "TR_WIPING_YOUR_DEVICE": "Obnovení továrního nastavení vymaže paměť zařízení, čímž se vymažou všechny informace včetně zálohy peněženky a PIN kódu. Tovární nastavení obnovte pouze v případě, že máte zálohu peněženky, která je potřeba k obnovení přístupu k prostředkům.", "TR_WORDS": "Slova: {count}", "TR_WORD_DOES_NOT_EXIST": "Slovo „{word}“ neexistuje v seznamu slov BIP39.", diff --git a/packages/suite-data/files/translations/de.json b/packages/suite-data/files/translations/de.json index 656f347c3d7..1799c0204cd 100644 --- a/packages/suite-data/files/translations/de.json +++ b/packages/suite-data/files/translations/de.json @@ -1,5 +1,6 @@ { "AMOUNT": "Betrag", + "AMOUNT_EXCEEDS_MAX": "Der Betrag übersteigt den zulässigen Höchstwert von {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "Betrag muss mindestens {dust} sein", "AMOUNT_IS_LESS_THAN_RESERVE": "Das Empfängerkonto erfordert zur Aktivierung eine Mindestreserve von {reserve} XRP", "AMOUNT_IS_MORE_THAN_RESERVE": "Betrag liegt über der erforderlichen nicht abhebbaren Reserve ({reserve} XRP)", @@ -31,7 +32,7 @@ "DATA_NOT_VALID_HEX": "Ungültiger Hex", "DESTINATION_TAG": "Destination-Tag", "DESTINATION_TAG_IS_NOT_NUMBER": "Destination-Tag ist keine Zahl", - "DESTINATION_TAG_IS_NOT_VALID": "Destination-Tag ist ungültig", + "DESTINATION_TAG_IS_NOT_VALID": "Destination-Tag ist nicht gültig", "DESTINATION_TAG_NOT_SET": "Destination-Tag ist nicht festgelegt", "DESTINATION_TAG_TOOLTIP": "Ein Destination-Tag ist ein eindeutiger Code zur Identifizierung des Empfängers einer Transaktion.", "DISCONNECT_DEVICE_DESCRIPTION": "Dein Gerät wurde gelöscht und verwahrt keine Private Keys mehr.", @@ -67,7 +68,7 @@ "LOCKTIME_ADD_TOOLTIP": "Die Sperrzeit legt den frühesten Zeitpunkt fest, zu dem eine Transaktion in einen Block verarbeitet werden kann.", "LOCKTIME_BLOCKHEIGHT": "Sperrzeit Blockhöhe", "LOCKTIME_IS_NOT_INTEGER": "Sperrzeit ist keine ganze Zahl", - "LOCKTIME_IS_NOT_SET": "Sperrzeit nicht festgelegt", + "LOCKTIME_IS_NOT_SET": "Sperrzeit ist nicht festgelegt", "LOCKTIME_IS_TOO_BIG": "Zeitstempel ist zu groß", "LOCKTIME_IS_TOO_LOW": "Sperrzeit ist zu niedrig", "LOCKTIME_SCHEDULE_SEND": "Sperrzeit", @@ -124,7 +125,7 @@ "RECIPIENT_ADDRESS": "Adresse", "RECIPIENT_CANNOT_SEND_TO_MYSELF": "Kann nicht an mich selbst gesendet werden", "RECIPIENT_IS_NOT_SET": "Adresse ist nicht festgelegt", - "RECIPIENT_IS_NOT_VALID": "Adresse ist ungültig", + "RECIPIENT_IS_NOT_VALID": "Adresse ist nicht gültig", "RECIPIENT_REQUIRES_UPDATE": "Taproot wird von deiner Firmware-Version nicht unterstützt. Aktualisiere deine Geräte-Firmware.", "RECIPIENT_SCAN": "Scannen", "REFRESH": "Aktualisieren", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN erfolgreich geändert", "TOAST_QR_INCORRECT_ADDRESS": "Der QR-Code enthält eine ungültige Adresse für dieses Konto", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "QR-Code ist für {coin}-Konto definiert", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Unbekanntes Protokollschema: „{scheme}“. Versuche es erneut, oder gib die Adresse manuell ein.", "TOAST_RAW_TX_SENT": "Transaktion gesendet. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "Einstellungen erfolgreich geändert", "TOAST_SIGN_MESSAGE_ERROR": "Fehler in der Nachrichten-Signatur: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Zum Dashboard", "TR_404_TITLE": "Fehler 404: Link nicht gefunden", "TR_7D_CHANGE": "7T-Änderung", - "TR_ABORT": "Abbrechen", "TR_ACCESS_HIDDEN_WALLET": "Zugriff auf Passphrase Wallet", "TR_ACCESS_STANDARD_WALLET": "Zugriff auf Standard-Wallet", "TR_ACCOUNT_DETAILS_HEADER": "Kontoinformationen", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot ist ein neuer Adresstyp, der Privatsphäre und Netzwerkeffizienz verbessern kann. Beachte, dass einige Dienste möglicherweise noch keine Taproot-Adressen unterstützen.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "Mit der aktuellen und am weitesten verbreiteten Methode zur Generierung und Verwaltung von Cardano-Adressen wird Interoperabilität, Sicherheit und die Unterstützung aller Arten von Tokens gewährleistet.", "TR_ACCOUNT_TYPE_COINJOIN": "CoinJoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Standard", "TR_ACCOUNT_TYPE_IMPORTED": "Importiert", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Ledger-Konten sind kompatibel mit Ableitungspfaden von Ledger Live und gewährleisten so die reibungslose Migration von Ledger auf Trezor.", "TR_ACCOUNT_TYPE_LEGACY": "Veraltet", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Veraltete Konten sind kompatibel mit Ableitungspfaden von Ledger Legacy und gewährleisten so die reibungslose Migration von Ledger auf Trezor.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "Mit der aktuellen und am weitesten verbreiteten Methode zur Generierung und Verwaltung von {value}-Adressen wird Interoperabilität, Sicherheit und die Unterstützung aller Arten von Tokens gewährleistet.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "Mit der aktuellen und am weitesten verbreiteten Methode zur Generierung und Verwaltung von Solana-Adressen wird Interoperabilität, Sicherheit und die Unterstützung von SOL und SPL-Tokens gewährleistet.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Nicht unterstützt.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Diese Kontoart wird von diesem Trezor Modell nicht unterstützt.", "TR_ACCOUNT_TYPE_SEGWIT": "Veraltetes SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Bitte aktualisiere die Firmware des Geräts, um diese Kontoart zu aktivieren.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP ist eine digitale Währung, die schnelle, kostengünstige internationale Zahlungen ermöglicht, ohne auf herkömmliches Mining angewiesen zu sein. Dabei wird ein Konsens-Ledger zur schnellen Bestätigung von Transaktionen genutzt.", "TR_ACQUIRE_DEVICE": "Trezor hier verwenden", "TR_ACQUIRE_DEVICE_TITLE": "Weitere Sitzung aktiv", "TR_ACTIVATED_COINS": "Aktivierte Coins", "TR_ACTIVE": "aktiv", + "TR_ADD": "Hinzufügen", "TR_ADDRESSES": "Adresse", "TR_ADDRESSES_CHANGE": "Adressen ändern", "TR_ADDRESSES_FRESH": "Neue Adressen", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Adresse kopieren", "TR_ADDRESS_MODAL_TITLE": "Empfängeradresse für {networkName}", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkCurrencyName} Empfängeradresse auf dem {networkName} Netzwerk", - "TR_ADDRESS_PHISHING_WARNING": "Um Phishing-Angriffe zu verhindern, solltest du die Adresse auf deinem Trezor verifizieren. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Um Phishing-Angriffe zu verhindern, verifiziere die Empfängeradresse auf deinem Trezor. {claim}", "TR_ADD_ACCOUNT": "Konto hinzufügen", "TR_ADD_HIDDEN_WALLET": "Passphrase Wallet", "TR_ADD_NETWORK_ACCOUNT": "{network}-Konto hinzufügen", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Datennutzung", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Alle Daten werden streng anonym gehalten. Sie werden ausschließlich zur Verbesserung des Trezor Ecosystem genutzt.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Automatische Updates für Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite lädt im Hintergrund automatisch die neueste Version herunter. Beim Neustart der App wird diese installiert. So bleibst du bei den neuesten Funktionen und Sicherheits-Patches immer auf dem Laufenden. Updates werden ausgeführt, ohne dass du deine Zustimmung erteilen musst.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Lade im Hintergrund automatisch die neueste Version von Trezor Suite herunter und installiere sie beim Neustart der App. So bleibst du bei den neuesten Funktionen und Sicherheits-Patches immer auf dem Laufenden. Updates werden ausgeführt, ohne dass du deine Zustimmung erteilen musst.", + "TR_ALL_NETWORKS": "Alle Netzwerke ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Zeige Token aus allen {networkCount} Netzwerken an. Filtere die Ergebnisse nach den beliebtesten Netzwerken.", "TR_ALL_TRANSACTIONS": "Transaktionen", "TR_AMOUNT_SENT": "Gesendeter Betrag", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Nicht für CoinJoin geeignet – Betrag zu hoch", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Es ist zwar unwahrscheinlich, doch im Falle eines Problems beim Firmware-Update musst du möglicherweise auf dein Wallet-Backup zugreifen.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Bevor du zu {bitcoinOnly} wechseln kannst, musst du deine Firmware auf die neueste Version aktualisieren.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Ich weiß, dass ich meiner Anonymität schade", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Mir ist bewusst, dass ich meine Anonymität beeinträchtige.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Starte Trezor Bridge auf Port 21324", "TR_BRIDGE_DEV_MODE_STOP": "Starte Trezor Bridge auf dem Standardport", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Bist du sicher? Das Gerät kann nur von einer einzigen Anwendung gleichzeitig verwendet werden. Wenn du gerade eine andere Anwendung mit deinem Trezor Gerät verwendest, stelle sicher, dass du diese zuerst beendest.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Wir empfehlen dir, die Trezor Suite Desktop-App herunterzuladen und im Hintergrund laufen zu lassen, um ein optimales Ergebnis zu erzielen. Alternativ verwende einen unterstützten Browser, der mit WebUSB kompatibel ist.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Die Trezor Suite wurde von einer anderen Anwendung angefordert, um die Verbindung zu deinem Trezor-Gerät zu ermöglichen. Lass diese im Hintergrund laufen und alles wird gut.", + "TR_BRIDGE_TIP_AUTOSTART": "Tipp: Aktiviere die automatische Startfunktion, und lass Bridge immer im Hintergrund laufen.", "TR_BTC_UNITS": "Bitcoin-Einheiten", "TR_BUG": "Fehler", "TR_BUMP_FEE": "Transaktion beschleunigen", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "Erhöhe die Gebühr für die älteste (nach Nonce) ausstehende Transaktion in der Warteschlange, um deine Transaktionen zu beschleunigen. Transaktionen müssen der Reihe nach bestätigt werden. Mehr erfahren", "TR_BUY": "Kaufen", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Handelstransaktionen", "TR_BUY_BUY": "Kaufen", - "TR_BUY_BUY_AGAIN": "Erneut kaufen", "TR_BUY_CONFIRMED_ON_TREZOR": "Auf Trezor bestätigt", "TR_BUY_DETAIL_ERROR_BUTTON": "Zurück zum Konto", "TR_BUY_DETAIL_ERROR_SUPPORT": "Zum Anbieter-Support", @@ -386,12 +397,12 @@ "TR_BUY_MODAL_FOR_YOUR_SAFETY": "{cryptocurrency} mit {provider} kaufen", "TR_BUY_MODAL_LEGAL_HEADER": "Rechtlicher Hinweis", "TR_BUY_MODAL_SECURITY_HEADER": "Sicherheit geht bei Trezor vor", - "TR_BUY_MODAL_TERMS_1": "Du bist hier, um Kryptowährung zu kaufen. Wenn du aus einem anderen Grund auf diese Seite weitergeleitet wurdest, kontaktiere den {provider}-Support, bevor du fortfährst.", - "TR_BUY_MODAL_TERMS_2": "Du nutzt diese Funktion, um Assets zu kaufen, die an ein Konto unter deiner direkten persönlichen Kontrolle gesendet werden.", - "TR_BUY_MODAL_TERMS_3": "Dir ist klar, dass Krypto-Transaktionen unwiderruflich sind und nicht zurückerstattet werden können. Daher können betrügerische oder versehentliche Verluste permanent sein.", - "TR_BUY_MODAL_TERMS_4": "Dir ist klar, dass Invity diesen Dienst nicht anbietet. Der Dienst unterliegt den Bedingungen von {provider}.", - "TR_BUY_MODAL_TERMS_5": "Du nutzt diese Funktion nicht für Glücksspiele, Betrug oder andere Verstöße gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften.", - "TR_BUY_MODAL_TERMS_6": "Dir ist klar, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften in verschiedenen Ländern unterschiedlich sein können. Daher kannst du einem höheren Risiko von Betrug, Diebstahl oder Marktunsicherheit ausgesetzt sein.", + "TR_BUY_MODAL_TERMS_1": "Ich bin hier, um Kryptowährungen zu kaufen. Wenn ich aus einem anderen Grund auf diese Seite weitergeleitet wurde, kontaktiere ich den {provider} Support, bevor ich fortfahre.", + "TR_BUY_MODAL_TERMS_2": "Ich nutze diese Funktion, um Kryptowährungen zu kaufen, die an mein eigenes Konto gesendet werden.", + "TR_BUY_MODAL_TERMS_3": "Mir ist bewusst, dass Krypto-Transaktionen endgültig sind und nicht rückgängig gemacht oder zurückerstattet werden können. Durch Betrug oder Fehler entstandene Verluste können möglicherweise nicht wiederhergestellt werden.", + "TR_BUY_MODAL_TERMS_4": "Mir ist bewusst, dass Invity diesen Dienst nicht anbietet. Er unterliegt den Geschäftsbedingungen von {provider}.", + "TR_BUY_MODAL_TERMS_5": "Ich nutze diese Funktion nicht für Spekulation, Betrug oder jegliche Aktivitäten, die gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften verstoßen.", + "TR_BUY_MODAL_TERMS_6": "Mir ist bewusst, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften je nach Region unterschiedlich sein können. Dadurch kann das Risiko von Betrug, Diebstahl oder Marktunsicherheit erhöht sein.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Verifizierte Partner von Invity", "TR_BUY_NETWORK": "{network} kaufen", "TR_BUY_NOT_TRANSACTIONS": "Noch keine Transaktionen.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "In Bearbeitung", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "In Bearbeitung", "TR_BUY_STATUS_SUCCESS": "Bestätigt", - "TR_BUY_TRANS_ID": "Trans. ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximum liegt bei {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum liegt bei {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum liegt bei {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum liegt bei {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Details anzeigen", "TR_BYTES": "Bytes", "TR_CAMERA_NOT_RECOGNIZED": "Die Kamera wurde nicht erkannt.", "TR_CAMERA_PERMISSION_DENIED": "Der Zugriff auf die Kamera wurde verweigert.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor Betrag", "TR_CHAINED_TXS": "Verkettete Transaktionen", "TR_CHANGELOG": "Änderungsprotokoll", - "TR_CHANGELOG_ON_GITHUB": "Änderungsprotokoll auf GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Dies ist eine Wechselgeldadresse, die aus einer früheren Sendung stammt.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Du kannst deinen Firmware-Typ jederzeit in den Einstellungen ändern.", "TR_CHANGE_HOMESCREEN": "Startbildschirm ändern", "TR_CHANGE_PIN": "PIN ändern", "TR_CHANGE_WIPE_CODE": "Löschcode ändern", + "TR_CHECKED_BALANCES_ON": "Guthaben geprüft auf", "TR_CHECKING_YOUR_DEVICE": "Dein Gerät wird überprüft", "TR_CHECKSUM_CONVERSION_INFO": "In Prüfsumme umgewandelt. Mehr erfahren", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Wir überprüfen die Integrität deines Trezor Geräts, um dessen Sicherheit zu gewährleisten und die Echtheit des Chips zu bestätigen.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Führe eine simulierte Wiederherstellung durch, um dein Wallet-Backup zu überprüfen.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Gib hier die Wörter aus deinem Wallet-Backup in der auf dem Gerät angezeigten Reihenfolge ein. Du wirst möglicherweise aufgefordert, als zusätzliche Sicherheitsmaßnahme einige Wörter einzugeben, die nicht Teil deines Wallet-Backups sind.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Verwende die Zwei-Tasten-Tastatur, um dein Wallet-Backup einzugeben. Dadurch hältst du all deine sensiblen Daten sicher und geschützt, fern von zwielichtigen oder unsicheren Computern oder Webbrowsern.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Dein Wallet-Backup wird über den Touchscreen eingegeben. So werden deine sensiblen Daten vor unsicheren Computern oder Webbrowsern geschützt.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Dein Wallet-Backup wird über den Touchscreen eingegeben. So werden deine sensiblen Daten vor unsicheren Computern oder Webbrowsern geschützt.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Dein Wallet-Backup wird über den Touchscreen eingegeben. So werden deine sensiblen Daten vor unsicheren Computern oder Webbrowsern geschützt.", "TR_CHECK_SEED": "Backup überprüfen", "TR_CHECK_YOUR_DEVICE": "Deinen Trezor Bildschirm überprüfen", "TR_CHOOSE_RECOVERY_TYPE": "Wiederherstellungstyp wählen", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Geschützt durch deinen Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "CoinJoin-Transaktionen", "TR_COINMARKET_BEST_RATE": "Bester Preis", + "TR_COINMARKET_BUY_AND_SELL": "Kaufen und verkaufen", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} Kauf} one {{totalBuys} Kauf} other {{totalBuys} Käufe} } • {totalSells, plural, =0 {{totalSells} Verkauf} one {{totalSells} Verkauf} other {{totalSells} Verkäufe} }", + "TR_COINMARKET_CEX_TOOLTIP": "Zentrale Börse", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Ändere den Betrag oder die Währung.", "TR_COINMARKET_COMPARE_OFFERS": "Alle Angebote vergleichen", "TR_COINMARKET_COUNTRY": "Wohnsitzland", + "TR_COINMARKET_DCA_DOWNLOAD": "Lade die mobile App von Invity herunter und spare in Bitcoin", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Ein sicherer und einfacher verwalteter DCA-Sparplan.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Entwickelt von SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Lass dir ohne zusätzliche Gebühren Mittel zur Selbstverwaltung auszahlen.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Kostenlose Auszahlung", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Eine schnelle, effiziente und benutzerfreundliche Oberfläche.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Benutzerfreundlich", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Überwache den Verlauf, die Beträge und die Häufigkeit deiner Investitionen.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "DCA-Übersicht", + "TR_COINMARKET_DCA_HEADING": "Spare in Bitcoin mit der Invity App", + "TR_COINMARKET_DEX_TOOLTIP": "Dezentrale Börse", "TR_COINMARKET_ENTER_AMOUNT_IN": "Betrag in {currency} eingeben", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Alle KYC-Optionen", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC niemals erforderlich", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "All CEX- und DEX-Angebote", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX mit Festpreis", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX mit variablem Preis", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "An einer dezentralen Börse (DEX) kann direkt auf der Blockchain mit Krypto gehandelt werden, ohne dass eine zentrale Behörde oder Vermittlung nötig ist.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX mit Festpreis", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX mit variablem Preis", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Aktuelle Angebote", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Zahlung:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Empfangsmethode:", "TR_COINMARKET_FEES_INCLUDED": "Gebühren inbegriffen", "TR_COINMARKET_FEES_NOT_INCLUDED": "Gebühren nicht inbegriffen", "TR_COINMARKET_FEES_ON_WEBSITE": "Einige Gebühren sind nicht im angezeigten Preis inbegriffen. Du kannst den endgültigen Preis auf der Website des Anbieters überprüfen.", @@ -518,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "KYC in Ausnahmefällen angefragt. KYC zur Rückerstattung erforderlich. 👈", "TR_COINMARKET_KYC_POLICY": "KYC-Richtlinie", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC niemals erforderlich", - "TR_COINMARKET_KYC_YES_REFUND": "KYC in Ausnahmefällen angefragt. KYC zur Rückerstattung nicht erforderlich. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "KYC wird nur in Ausnahmefällen angefragt. Es ist zur Rückerstattung nicht erforderlich. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Letzte Transaktionen", "TR_COINMARKET_NETWORK_FEE": "Netzwerkgebühr", "TR_COINMARKET_NETWORK_TOKENS": "{networkName}-Token", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Kein CEX-Anbieter gefunden", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Kein DEX-Anbieter gefunden", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Keine Methoden verfügbar", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Automatische Aktualisierung in", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Zurück zum Trade", - "TR_COINMARKET_NO_OFFERS_HEADER": "Keine Angebote", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Leider haben wir aufgrund eines Problems mit der Serverkonnektivität zur Zeit keine Angebote.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Leider haben wir im Moment keine Angebote. Aktualisiere die Seite oder ändern deine Anfrage.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Seite aktualisieren", "TR_COINMARKET_OFFERS_EMPTY": "Kein Angebot für deine Anfrage vorhanden. Bitte ändere das Land oder den Kaufbetrag.", "TR_COINMARKET_OFFERS_REFRESH": "Aktualisierung der Angebote in", "TR_COINMARKET_OFFERS_SELECT": "Auswählen", "TR_COINMARKET_OFFER_LOOKING": "Das beste Angebot wird gesucht", - "TR_COINMARKET_OFFER_NO_FOUND": "Keine Angebote für deine Anfrage verfügbar. Ändere den Betrag oder die Währung.", + "TR_COINMARKET_OFFER_NO_FOUND": "Keine Angebote für deine Anfrage verfügbar.", "TR_COINMARKET_ON_NETWORK_CHAIN": "Auf {networkName}-Chain", "TR_COINMARKET_OTHER_CURRENCIES": "Weitere Währungen", "TR_COINMARKET_PAYMENT_METHOD": "Zahlungsmethode", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Empfangsmethode", "TR_COINMARKET_SELL": "Verkaufen", "TR_COINMARKET_SHOW_OFFERS": "Angebote vergleichen", + "TR_COINMARKET_SWAP": "Swap", + "TR_COINMARKET_SWAP_AMOUNT": "Swap-Betrag", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} Swap} one {{totalSwaps} Swap} other {{totalSwaps} Swaps} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Ich bin bereit zum Swap", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Swap von {fromCrypto} zu {toCrypto} mit {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Rechtlicher Hinweis", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Sicherheit geht bei Trezor vor", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Ich möchte mit dem Kontrakt von {provider} Swaps für Kryptowährungen bei einer DEX (Decentralized Exchange, dezentrale Börse) durchführen.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Ich möchte für mein eigenes Konto Swaps für Kryptowährungen durchführen. Mir ist bewusst, dass die Richtlinien des Anbieters eine Identitätsüberprüfung erfordern können.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Mir ist bewusst, dass Krypto-Transaktionen endgültig sind und nicht rückgängig gemacht oder zurückerstattet werden können. Durch Betrug oder Fehler entstandene Verluste können möglicherweise nicht wiederhergestellt werden.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Mir ist bewusst, dass Invity diesen Dienst nicht anbietet. Er unterliegt den Geschäftsbedingungen von {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Ich nutze diese Funktion nicht für Spekulation, Betrug oder jegliche Aktivitäten, die gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften verstoßen.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Mir ist bewusst, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften je nach Region unterschiedlich sein können. Dadurch kann das Risiko von Betrug, Diebstahl oder Marktunsicherheit erhöht sein.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Verifizierte Partner von Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Ich bin bereit zum Swap", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Swap von {fromCrypto} zu {toCrypto} mit {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Rechtlicher Hinweis", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Sicherheit geht bei Trezor vor", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Ich bin hier, um Swaps für Kryptowährungen durchzuführen. Wenn ich aus einem anderen Grund auf diese Seite weitergeleitet wurde, kontaktiere ich den Trezor Support, bevor ich fortfahre. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Ich möchte für mein eigenes Konto Swaps für Kryptowährungen durchführen. Mir ist bewusst, dass die Richtlinien des Anbieters eine Identitätsüberprüfung erfordern können.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Mir ist bewusst, dass Krypto-Transaktionen endgültig sind und nicht rückgängig gemacht oder zurückerstattet werden können. Durch Betrug oder Fehler entstandene Verluste können möglicherweise nicht wiederhergestellt werden.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Mir ist bewusst, dass Invity diesen Dienst nicht anbietet. Er unterliegt den Geschäftsbedingungen von {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Ich nutze diese Funktion nicht für Spekulation, Betrug oder jegliche Aktivitäten, die gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften verstoßen.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Mir ist bewusst, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften je nach Region unterschiedlich sein können. Dadurch kann das Risiko von Betrug, Diebstahl oder Marktunsicherheit erhöht sein.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Verifizierte Partner von Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} im {networkName}-Netzwerk", "TR_COINMARKET_TRADE_FEE": "Trade-Gebühr", + "TR_COINMARKET_TRANS_ID": "Trans. ID:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Unbekannter Anbieter", + "TR_COINMARKET_VIEW_DETAILS": "Details anzeigen", "TR_COINMARKET_YOUR_BEST_OFFER": "Dein bestes Angebot", "TR_COINMARKET_YOU_BUY": "Du kaufst", "TR_COINMARKET_YOU_GET": "Du erhältst", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Bestätigt", "TR_CONFIRMING_TX": "Transaktion wird bestätigt", "TR_CONFIRM_ACTION_ON_YOUR": "Folge den Anweisungen auf deinem Trezor Bildschirm", + "TR_CONFIRM_ADDRESS": "Adresse bestätigen", "TR_CONFIRM_BEFORE_COPY": "Vor dem Kopieren auf Trezor bestätigen", "TR_CONFIRM_CONDITIONS": "Bestätige die Bedingungen, bevor du fortfährst.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Bestätige das leere Passphrase Wallet auf dem Gerät „{deviceLabel}“.", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Verwalten", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Aktiviere den Dialog zur Eingabe der Passphrase beim Öffnen von Trezor Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Verwendest du vorrangig eine Passphrase?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verifiziere auf deinem Trezor, um die Empfängeradresse zu bestätigen. Es wird nicht empfohlen, ohne die Bestätigung fortzufahren.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Schließe die Verifizierung auf deinem Trezor ab, um die Empfängeradresse zu bestätigen. Es wird nicht empfohlen, ohne eine Bestätigung fortzufahren.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "Empfängeradresse kann nicht verifiziert werden", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Verbinde deinen Trezor, um Coins zu senden.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Dein Trezor ist nicht verbunden", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Zum Senden Trezor verbinden", "TR_CONNECT_YOUR_DEVICE": "Deinen Trezor verbinden und entsperren", - "TR_CONTACT_SUPPORT": "Support kontaktieren", + "TR_CONTACT_SUPPORT": "Trezor Support kontaktieren", "TR_CONTACT_TREZOR_SUPPORT": "Trezor Support kontaktieren", "TR_CONTINUE": "Weiter", "TR_CONTINUE_ANYWAY": "Trotzdem fortfahren", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Sende niemals Assets an eine Richtlinien-ID-Adresse.", "TR_COPY_AND_CLOSE": "Kopieren und schließen", "TR_COPY_SIGNED_MESSAGE": "Signierte Nachricht kopieren", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Kopieren", + "TR_COPY_TO_CLIPBOARD": "Kopieren", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Änderungsprotokoll konnte nicht abgerufen werden", "TR_COULD_NOT_RETRIEVE_DATA": "Daten konnten nicht abgerufen werden", "TR_COUNT_WALLETS": "{count} {count, plural, one {Wallet} other {Wallets}}", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Assets nicht ordnungsgemäß geladen", "TR_DASHBOARD_DISCOVERY_ERROR": "Entdeckungsfehler", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Konten wurden nicht ordnungsgemäß geladen {details}", + "TR_DATA": "Daten", "TR_DATABASE_UPGRADE_BLOCKED": "Datenbank-Upgrade wurde von einer anderen App-Instanz blockiert", "TR_DATA_ANALYTICS_CATEGORY_1": "Plattform", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "OS, Trezor-Modell, Version usw.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "Du bist versehentlich im Bootloader-Modus?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Verbinde das Gerät erneut, ohne dabei Tasten zu berühren.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Verbinde das Gerät erneut, ohne den Bildschirm zu berühren.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Dieses Gerät wird gerade anderweitig verwendet.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "Dieses Gerät wird möglicherweise gerade von der App {transportSessionOwner} verwendet. Du kannst bei Bedarf die Kontrolle über das Gerät übernehmen.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Dieses Gerät wird möglicherweise gerade von einer anderen App verwendet. Du kannst bei Bedarf die Kontrolle über das Gerät übernehmen.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Gerät in falschem Zustand erkannt", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Dein Trezor wurde während des Backup-Prozesses vom Netz getrennt. Wir empfehlen dir dringend, die Option „Werksreset“ in den Geräteeinstellungen zu verwenden, um deine Gerätedaten zu löschen und das Wallet-Backup zu wiederholen.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Firmware-Hash-Prüfung fehlgeschlagen. Dein Trezor könnte eine Fälschung sein.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Die Firmware-Hash-Prüfung konnte nicht durchgeführt werden. Dein Trezor könnte eine Fälschung sein.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Ausschalten", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Einschalten", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Die Prüfung der Firmware-Version ist eine sehr wichtige Sicherheitsfunktion. Wir empfehlen dringend, sie eingeschaltet zu lassen.", @@ -726,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "Backup für Gerät „{deviceLabel}“ wurde nicht durchgeführt", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "Gerät „{deviceLabel}“ ist nicht verbunden", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "Gerät „{deviceLabel}“ ist nicht verfügbar", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Verifizierung des Geräts war nicht erfolgreich", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Verbinde dein Gerät neu und wiederhole die Verifizierung. Wenn das Problem weiterhin besteht, kontaktiere den Trezor Support, um herauszufinden, was mit deinem Gerät los ist und was du als nächstes tun kannst.", "TR_DEVICE_NOT_CONNECTED": "Gerät nicht verbunden", "TR_DEVICE_NOT_INITIALIZED": "Trezor ist nicht eingerichtet", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Wir begleiten dich durch den Prozess und sorgen dafür, dass du sofort loslegen kannst.", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Aktuelle Bridge {version} herunterladen", "TR_DO_NOT_DISCONNECT_DEVICE": "Trenne dein Gerät nicht", "TR_DO_NOT_SHOW_AGAIN": "Nicht erneut anzeigen", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Diesen Schritt überspringen?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Ziehe die Datei hierher oder klicke, um eine Datei auszuwählen.", "TR_DROPZONE_ERROR": "Import fehlgeschlagen: {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Teilnehmen", "TR_EARLY_ACCESS_ENABLED": "Early Access-Programm aktiviert", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Teilnehmen", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Ich habe verstanden, dass ich damit Vorabversionen von Software testen kann, die Fehler enthalten können, die den normalen Betrieb von Suite beeinträchtigen.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Ich habe verstanden, dass ich damit Vorabversionen von Software testen kann, die Fehler enthalten können, die den normalen Betrieb von Trezor Suite beeinträchtigen.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Du kannst diese Funktion jederzeit deaktivieren.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Probiere die neuesten Produktfunktionen aus, bevor sie allgemein verfügbar sind.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Aktiviere zuerst das Feld oben", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Du kannst entweder jetzt oder beim nächsten Start nach Beta-Updates suchen.", "TR_EARLY_ACCESS_JOINED_TITLE": "Early Access-Programm aktiviert", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Um ein Downgrade auf die neueste stabile Version von Suite durchzuführen, klicke auf „Stabile Version herunterladen“ und installiere die App neu.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Um ein Downgrade auf die neueste stabile Version von Trezor Suite durchzuführen, klicke auf „Stabile Version herunterladen“ und installiere die App neu.", "TR_EARLY_ACCESS_LEFT_TITLE": "Du hast das Early Access-Programm verlassen. Beta-Versionen werden nicht mehr angeboten.", "TR_EARLY_ACCESS_MENU": "Early Access-Programm", "TR_EARLY_ACCESS_REINSTALL": "Stabile Version herunterladen", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Genehmige nur den Betrag, der für diesen Swap erforderlich ist. Du musst eine zusätzliche Gebühr zahlen, wenn du einen ähnlichen Swap erneut durchführen möchtest.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Vorherige Genehmigung widerrufen", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Führe eine Transaktion durch, die die vorherige Genehmigung des Kontrakts mit {provider} entfernt.", - "TR_EXCHANGE_BUY": "Für", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Auf Trezor bestätigen und senden", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Bestätigen und senden", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Genehmigung erstellen", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Deine Transaktion war erfolgreich.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Bestätigt", "TR_EXCHANGE_DEX": "Angebot einer dezentralen Börse", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Die Gebühren für diesen Swap werden auf {approvalFee} ({approvalFeeFiat}) für die Genehmigung (falls erforderlich) und {swapFee} ({swapFeeFiat}) für den Swap geschätzt.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Du hast nicht genügend Assets für die Transaktionsgebühren. Reduziere den Tauschbetrag auf max. {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} ist ungültig", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Feste Preise zeigen dir genau an, wie viel du am Ende des Kaufs erhältst. Der Betrag ändert sich zwischen Auswahl des Preises und Abschluss der Transaktion nicht. Der angezeigte Betrag wird dir garantiert. Diese Preise sind in der Regel aber weniger großzügig, was bedeutet, dass du mit deinem Geld nicht so viel Krypto kaufen kannst.", "TR_EXCHANGE_FLOAT": "Angebot mit variablem Preis", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Variable Preise bedeuten, dass sich der endgültige Betrag, den du erhältst, aufgrund von Marktschwankungen zwischen dem Zeitpunkt der Auswahl des Preises und dem Abschluss der Transaktion leicht ändern kann. Diese Preise sind in der Regel höher, was bedeutet, dass du am Ende mehr Krypto erhalten könntest.", - "TR_EXCHANGE_PROVIDER": "Anbieter", "TR_EXCHANGE_RATE": "Preis", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Empfängerkonto ist außerhalb von Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Dies ist die alphanumerische Adresse, die deine Coins empfängt.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Gib eine Zahl ein.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Gib die gewünschte Slippage ein.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Betrag des Swap-Angebots", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Slippage-Zusammenfassung", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Slippage-Toleranz", - "TR_EXCHANGE_TRANS_ID": "Trans. ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Konto ({symbol}) außerhalb von Suite verwenden", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Empfängeradresse", - "TR_EXCHANGE_VIEW_DETAILS": "Details anzeigen", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Automatische Updates für Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite lädt im Hintergrund automatisch die neueste Version herunter. Beim Neustart der App wird diese installiert. So bleibst du bei den neuesten Funktionen und Sicherheits-Patches immer auf dem Laufenden. Updates werden ausgeführt, ohne dass du deine Zustimmung erteilen musst.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Aktiviere die BNB Smart Chain ohne vergangene interne Transaktionen.", "TR_EXPERIMENTAL_FEATURES": "Experimentell", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Experimentelle Funktionen", "TR_EXPERIMENTAL_FEATURES_WARNING": "Der langfristige Support dieser Funktionen ist von der Garantie ausgeschlossen.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Dropbox-Passwörter migrieren", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Trezor Password Manager ist eine Erweiterung der Trezor Suite, mit der Sie Ihr Trezor-Gerät anstelle eines Master-Passworts verwenden können.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Nutze diesen Dienst zum Abrufen von Passwörtern, die in Dropbox gespeichert und von Trezor geschützt werden. Entwickelt für ehemalige Benutzer der Chrome-Erweiterung von Trezor Password Manager.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake ist ein System für den Zugriff auf zensierte Websites und Apps.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Erhalte Zugriff auf zensierte Websites und Apps mit Tor Snowflake, einem System zur Umgehung von Einschränkungen.", "TR_EXPORT_AS": "Exportieren als {as}", "TR_EXPORT_FAIL": "Export fehlgeschlagen.", "TR_EXPORT_TO_FILE": "In Datei exportieren", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Neue Firmware ist jetzt verfügbar. Aktualisiere jetzt dein Gerät.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Dein Gerät ist bereits auf die neueste Firmware aktualisiert. Du kannst die Firmware bei Bedarf neu installieren.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Firmware-Versionsprüfung fehlgeschlagen. Dein Trezor könnte eine Fälschung sein.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Firmware-Versionsprüfung konnte nicht durchgeführt werden.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Abgeschlossen", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Leichte Firmware-Version unterstützt nur Bitcoin-Operationen.", "TR_FIRMWARE_SUBHEADING_NONE": "Dein Trezor wurde ohne Firmware geliefert. Installiere die neueste Firmware, um dein Gerät sicher verwenden zu können. Für reine Bitcoin-Benutzer empfehlen wir die Installation der .", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Verstanden", "TR_GO_TO_ONBOARDING": "Einrichtung beginnen", "TR_GO_TO_SETTINGS": "Zu den Einstellungen", - "TR_GO_TO_SUITE": "Auf Suite zugreifen", + "TR_GO_TO_SUITE": "Zu Trezor Suite", "TR_GRAPH_LINEAR": "Linear", "TR_GRAPH_LOGARITHMIC": "Logarithmisch", "TR_GRAPH_MISSING_DATA": "XRP-, SOL- und alle Token-Beträge sind im Portfolioguthaben enthalten, werden aber derzeit in der Grafikansicht nicht unterstützt.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Trezor Forum", "TR_GUIDE_FORUM_LABEL": "Mit der Trezor Community verbinden", "TR_GUIDE_SUGGESTION_LABEL": "Wie machen wir uns?", - "TR_GUIDE_SUPPORT": "Support kontaktieren", + "TR_GUIDE_SUPPORT": "Trezor Support kontaktieren", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Support und Feedback", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Support und Feedback", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Hilf uns, uns zu verbessern", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Lokales Dateisystem", "TR_LOG": "Anwendungsprotokoll", "TR_LOGIN_PROCEED": "Fortfahren", - "TR_LOG_DESCRIPTION": "Das Protokoll enthält alle nötigen technischen Informationen über Trezor Suite. Möglicherweise wird es beim Kontakt zum Trezor Support benötigt.", + "TR_LOG_DESCRIPTION": "Dieses Protokoll enthält wichtige technische Informationen zu Trezor Suite und wird möglicherweise bei der Kontaktaufnahme mit dem Trezor Support benötigt.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Wartet auf eine Runde", "TR_LOW_ANONYMITY_WARNING": "Sehr geringe Privatsphäre. Wir empfehlen, mindestens 1 von 5 zu verwenden, da alles, was unter diesem Wert liegt, nicht privat ist.", "TR_LTC_ADDRESS_INFO": "Litecoin hat das Adressformat geändert. Weitere Informationen zur Konvertierung deiner Adresse findest du in unserem Blog. {TR_LEARN_MORE}", @@ -1223,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portfolio", "TR_NAV_ANONYMIZE": "Coins als privat kennzeichnen", "TR_NAV_BUY": "Kaufen", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Details", "TR_NAV_RECEIVE": "Empfangen", "TR_NAV_SELL": "Verkaufen", @@ -1264,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimism", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1276,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Neu", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Es gibt eine neue Trezor Bridge.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Es gibt eine neue Trezor Firmware. Aktualisiere dein Gerät.", "TR_NEXT_UP": "Weiter", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (mit nicht empfohlenen „{char}“)", + "TR_NON_ASCII_CHARS": "{label} (mit nicht empfohlenen Zeichen)", "TR_NORMAL_ACCOUNTS": "Standardkonten", "TR_NORTH": "Norden", "TR_NOTHING_TO_ANONYMIZE": "Keine Assets zum Kennzeichnen als privat", @@ -1296,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Standard-Wallet", "TR_NO_SEARCH_RESULTS": "Keine Ergebnisse für dein Suchkriterium", "TR_NO_SPENDABLE_UTXOS": "Es gibt keine auszahlbaren UTXOs in deinem Konto.", - "TR_NO_TRANSPORT": "Kommunikation zwischen Browser und Gerät fehlgeschlagen", + "TR_NO_TRANSPORT": "Kommunikation zwischen deinem Browser und deinem Gerät fehlgeschlagen", "TR_NO_TRANSPORT_DESKTOP": "Kommunikation zwischen App und Gerät fehlgeschlagen", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {Konto} other {Konten}} • {fiatValue}", "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {Transaktion} other {Transaktionen}}", "TR_OFF": "aus", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Der ausgewählte Betrag {amount} ist höher als das zulässige Maximum von {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Der ausgewählte Betrag {amount} ist höher als das zulässige Maximum von {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Der ausgewählte Betrag {amount} ist niedriger als das zulässige Minimum von {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Der ausgewählte Betrag {amount} ist niedriger als das zulässige Minimum von {min}.", "TR_OFFICIAL_LANGUAGES": "Offiziell", "TR_OK": "OK", "TR_ON": "ein", @@ -1333,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Anonyme Datenerfassung", "TR_ONBOARDING_DEVICE_CHECK": "Gerätesicherheitscheck", "TR_ONBOARDING_DEVICE_CHECK_1": "Mein Hologramm war unversehrt und nicht manipuliert.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Mein Gerät wurde im offiziellen Trezor Shop oder bei einem vertrauenswürdigen Reseller gekauft.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Mein Gerät wurde im offiziellen Trezor Shop oder bei einem vertrauenswürdigen Reseller gekauft.", "TR_ONBOARDING_DEVICE_CHECK_3": "Die Geräteverpackung war unversehrt und nicht manipuliert.", "TR_ONBOARDING_DEVICE_CHECK_4": "Die Firmware ist bereits auf dem verbundenen Trezor installiert. Fahre nur mit der Einrichtung fort, wenn du diesen Trezor bereits verwendet hast.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Desktop-App herunterladen", @@ -1364,40 +1417,18 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Andere Ein- und Ausgaben", "TR_OUTGOING": "Ausgehend", "TR_OUTPUTS": "Ausgaben", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Der Mindest- und Höchstbetrag, für den dieser Benutzer bereit ist, {symbol} zu verkaufen", - "TR_P2P_GET_STARTED_INTRO": "Du musst die Transaktion bei {providerName} starten – befolge alle Schritte sorgfältig.", - "TR_P2P_GET_STARTED_ITEM_1": "Wähle „Gehe zu {providerName}“ aus, um zur Website unseres Partners weitergeleitet zu werden.", - "TR_P2P_GET_STARTED_ITEM_3": "Sobald {providerName} nach einer Freigabeadresse fragt, kehre hierher zurück und fahre fort.", - "TR_P2P_GET_STARTED_ITEM_4": "Du hast es fast geschafft! Kopiere deine Adresse, füge sie in das Feld „Freigabeadresse“ bei {providerName} ein und schließe die Transaktion ab.", - "TR_P2P_GO_TO_PROVIDER": "Zu {providerName}", - "TR_P2P_INFO": "Bei der P2P-Technologie ({peerToPeer}) entfällt die KYC-Verifizierung auf der Käufer- und der Verkäuferseite. Alle Parteien sind durch ein sicheres {multisigEscrow} vor Betrug geschützt.", - "TR_P2P_MODAL_CONFIRM": "Ich bin bereit, zu kaufen", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Kauf von {cryptocurrency} per Peer-to-Peer mit {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Rechtlicher Hinweis", - "TR_P2P_MODAL_SECURITY_HEADER": "Sicherheit geht bei Trezor vor", - "TR_P2P_MODAL_TERMS_1": "Du bist hier, um Kryptowährung von einer anderen Person deiner Wahl per P2P-Technologie (Peer-to-Peer) ohne ID-Verifizierung zu kaufen. Wenn du aus einem anderen Grund auf diese Seite weitergeleitet wurdest, kontaktiere den Support, bevor du fortfährst.", - "TR_P2P_MODAL_TERMS_2": "Dir ist klar, dass Krypto-Transaktionen unwiderruflich sind und nicht zurückerstattet werden können. Daher können betrügerische oder versehentliche Verluste permanent sein.", - "TR_P2P_MODAL_TERMS_4": "Dir ist klar, dass Invity diesen Dienst nicht anbietet. Der Dienst unterliegt den Bedingungen von {provider}.", - "TR_P2P_MODAL_TERMS_5": "Du nutzt diese Funktion nicht für Glücksspiele, Betrug oder andere Verstöße gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften.", - "TR_P2P_MODAL_TERMS_6": "Dir ist klar, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften in verschiedenen Ländern unterschiedlich sein können. Daher kannst du einem höheren Risiko von Betrug, Diebstahl oder Marktunsicherheit ausgesetzt sein.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Verifizierte Partner von Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "Die Transaktion muss innerhalb dieser Frist abgeschlossen werden, gerechnet ab der Erstellung eines Kontrakts auf der Website von {providerName}.", - "TR_P2P_PRICE": "Preis für 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "Von diesem Benutzer angebotener {symbol}-Preis.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Hallo, ich nutze {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Der ausgewählte Betrag {amount} ist höher als das zulässige Maximum von {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Der ausgewählte Betrag {amount} ist niedriger als das zulässige Minimum von {minimum}.", "TR_PAGINATION_NEWER": "Neuer", "TR_PAGINATION_OLDER": "Älter", "TR_PASSPHRASE_CASE_SENSITIVE": "Hinweis: Bei der Passphrase wird zwischen Groß- und Kleinschreibung unterschieden.", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "Zuerst ist es wichtig, die Funktionsweise einer Passphrase kennenzulernen.", "TR_PASSPHRASE_DESCRIPTION_ITEM2": "Eine Passphrase öffnet ein Wallet, welches von dieser Phrase geschützt wird.", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Nicht einmal der Trezor Support kann es dir wiederbeschaffen.", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Nicht einmal der Trezor Support kann es dir wiederbeschaffen", "TR_PASSPHRASE_HIDDEN_WALLET": "Passphrase Wallet", "TR_PASSPHRASE_MISMATCH": "Passphrase stimmt nicht überein", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Die Passphrasen stimmen nicht überein. Bitte beginne aus Sicherheitsgründen erneut und gib sie richtig ein.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Erneut beginnen", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Wir empfehlen die Verwendung von ABC, abc, 123, Leerzeichen oder diesen Sonderzeichen", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "Die Verwendung nicht aufgelisteter Sonderzeichen ist ein Risiko für die zukünftige Kompatibilität", "TR_PASSPHRASE_TOO_LONG": "Passphrase überschreitet die zulässige Länge", "TR_PASSPHRASE_WALLET": "Passphrase Wallet Nr. {id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "So funktioniert eine Passphrase", @@ -1439,15 +1470,30 @@ "TR_PIN_SUBHEADING": "Eine starke PIN schützt deinen Trezor vor unbefugtem physischen Zugriff.", "TR_PLAY_IT_SAFE": "Gehen wir auf Nummer sicher", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Aktiviere deine Kamera zum Scannen von QR-Codes.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Verbinde dein Gerät, um mit dem Verifizierungsprozess fortzufahren.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Verbinde deinen Trezor, um mit dem Verifizierungsprozess fortzufahren.", "TR_PLEASE_ENABLE_PASSPHRASE": "Aktiviere die Passphrase-Funktion, um mit dem Verifizierungsprozess fortzufahren.", "TR_POLICY_ID_ADDRESS": "Richtlinien-ID:", "TR_PRIMARY_FIAT": "Fiat-Währung", "TR_PRIVATE": "Privat", "TR_PRIVATE_DESCRIPTION": "Privatsphäre-Level mindestens {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Mit nicht geprüfter Adresse fortfahren", "TR_PROMO_BANNER_DASHBOARD": "Das komfortabelste Hardware Wallet zur sicheren Verwaltung deiner Krypto-Assets", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Vor dem Scannen auf Trezor bestätigen", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Bestätige zunächst die Empfängeradresse auf deinem Trezor Gerät, da dessen vertrauenswürdiges Display nicht gehackt werden kann.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Aktiviert", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Gerade aktualisiert ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Zum Aktualisieren neu starten", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Trezor Gerät", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Update verfügbar ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Auf dem neuesten Stand ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite hat ein neues Update heruntergeladen.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite wurde aktualisiert.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Trezor Suite Update jetzt verfügbar", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Neu starten und aktualisieren", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Update starten", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Trezor Update jetzt verfügbar", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Was gibt es Neues?", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Du wirst möglicherweise aufgefordert, einige Wörter einzugeben, die nicht Teil deines Wallet-Backups sind.", "TR_RANGE": "Bereich", "TR_READ_AND_UNDERSTOOD": "Ich habe die vorstehenden Angaben gelesen und verstanden", @@ -1522,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Gib die Wörter aus deinem Wallet-Backup in der auf dem Trezor angezeigten Reihenfolge ein.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Gib über das Touchscreen-Display alle Wörter in der richtigen Reihenfolge ein.", "TR_SEE_DETAILS": "Siehe Details", + "TR_SEE_IF_ISSUE_PERSISTS": "Prüfe, ob das Problem weiterhin besteht.", "TR_SELECTED": "{amount} ausgewählt", "TR_SELECT_COIN_FOR_SETTINGS": "Aktiven Coin auswählen, um Einstellungen zu ändern", "TR_SELECT_DEVICE": "Gerät auswählen", + "TR_SELECT_NAME_OR_ADDRESS": "Nach Name, Symbol, Netzwerk oder Kontraktadresse suchen", "TR_SELECT_NUMBER_OF_WORDS": "Wörteranzahl in deinem Wallet-Backup auswählen", "TR_SELECT_PASSPHRASE_SOURCE": "Wähle, wo die Passphrase auf „{deviceLabel}“ eingegeben werden muss.", "TR_SELECT_RECOVERY_METHOD": "Wiederherstellungsmethode auswählen", + "TR_SELECT_TOKEN": "Token auswählen", "TR_SELECT_TREZOR": "Trezor auswählen", "TR_SELECT_TREZOR_TO_CONTINUE": "Wähle deinen Trezor aus, um fortzufahren.", "TR_SELECT_TYPE": "Typ auswählen", @@ -1558,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "{cryptocurrency} mit {provider} verkaufen", "TR_SELL_MODAL_LEGAL_HEADER": "Rechtlicher Hinweis", "TR_SELL_MODAL_SECURITY_HEADER": "Sicherheit geht bei Trezor vor", - "TR_SELL_MODAL_TERMS_1": "Du bist hier, um Kryptowährung zu verkaufen. Wenn du aus einem anderen Grund auf diese Seite weitergeleitet wurdest, kontaktiere den Support, bevor du fortfährst.", + "TR_SELL_MODAL_TERMS_1": "Du bist hier, um Kryptowährung zu verkaufen. Wenn du aus einem anderen Grund auf diese Seite weitergeleitet wurdest, kontaktiere den Trezor Support, bevor du fortfährst.", "TR_SELL_MODAL_TERMS_2": "Du verkaufst Kryptowährung für dein eigenes Konto. Du erkennst an, dass die Richtlinien des Anbieters eine Identitätsüberprüfung erfordern können.", "TR_SELL_MODAL_TERMS_3": "Dir ist klar, dass Krypto-Transaktionen unwiderruflich sind und nicht zurückerstattet werden können. Daher können betrügerische oder versehentliche Verluste permanent sein.", "TR_SELL_MODAL_TERMS_4": "Dir ist klar, dass Invity diesen Dienst nicht anbietet. Der Dienst unterliegt den Bedingungen von {provider}.", - "TR_SELL_MODAL_TERMS_5": "Du nutzt diese Funktion nicht für Glücksspiele, Betrug oder andere Verstöße gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften.", + "TR_SELL_MODAL_TERMS_5": "Ich nutze diese Funktion nicht für Spekulation, Betrug oder jegliche Aktivitäten, die gegen die Nutzungsbedingungen von Invity oder des Anbieters oder gegen geltende Vorschriften verstoßen.", "TR_SELL_MODAL_TERMS_6": "Dir ist klar, dass Kryptowährungen ein aufstrebendes Finanzinstrument sind und dass die Vorschriften in verschiedenen Ländern unterschiedlich sein können. Daher kannst du einem höheren Risiko von Betrug, Diebstahl oder Marktunsicherheit ausgesetzt sein.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Verifizierte Partner von Invity", "TR_SELL_REGISTER": "Registrieren", @@ -1571,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Abgelehnt", "TR_SELL_STATUS_PENDING": "In Bearbeitung", "TR_SELL_STATUS_SUCCESS": "Bestätigt", - "TR_SELL_TRANS_ID": "Trans. ID:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum liegt bei {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum liegt bei {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Details anzeigen", "TR_SENDFORM_LABELING_EXAMPLE_1": "Ersparnisse", "TR_SENDFORM_LABELING_EXAMPLE_2": "Miete", "TR_SENDING_SYMBOL": "{multiple, select, true {Mehrere Tokens} false {{symbol}} other {{symbol}}} werden gesendet", @@ -1629,6 +1674,8 @@ "TR_SHOW_LOG": "Protokoll anzeigen", "TR_SHOW_MORE": "Mehr anzeigen", "TR_SHOW_MORE_ADDRESSES": "Mehr ({count}) anzeigen", + "TR_SHOW_ON_TRAY": "Symbol in Symbolleiste anzeigen", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Verfolge, ob Trezor Suite im Hintergrund läuft.", "TR_SHOW_UNVERIFIED_ADDRESS": "Nicht verifizierte Adresse anzeigen", "TR_SHOW_UNVERIFIED_XPUB": "Nicht verifizierten Public Key anzeigen", "TR_SIDEBAR_ADD_COIN": "Coin hinzufügen", @@ -1641,7 +1688,9 @@ "TR_SIZE": "Größe", "TR_SKIP": "Überspringen", "TR_SKIP_BACKUP": "Backup überspringen", + "TR_SKIP_BACKUP_DESCRIPTION": "Mit einem Wallet-Backup kannst du deine Assets wiederherstellen, wenn dein Trezor verloren geht, gestohlen oder beschädigt wird. Ohne ein Backup können deine Krypto-Assets dauerhaft verloren gehen.", "TR_SKIP_PIN": "PIN überspringen", + "TR_SKIP_PIN_DESCRIPTION": "Eine Geräte-PIN verhindert unbefugten Zugriff auf deinen Trezor. Ohne eine Geräte-PIN kann jeder, der Zugriff auf dein Gerät hat, auch auf deine Assets zugreifen.", "TR_SKIP_ROUNDS": "Runden überspringen", "TR_SKIP_ROUNDS_DESCRIPTION": "Wenn Runden übersprungen werden können, sind Zusammenhänge zwischen deinen Eingaben noch schwerer nachzuweisen. Das bedeutet, dass du die Herkunft der Assets weiter verschleiern kannst.", "TR_SKIP_ROUNDS_HEADING": "Trezor kann Runden überspringen", @@ -1654,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Zum Staking-Pool hinzufügen", "TR_STAKE_ANY_AMOUNT_ETH": "Stake einen Mindestbetrag von {amount} {symbol} und verdiene Belohnungen. Mit unserem aktuellen APY-Satz von {apyPercent} % verdienen auch deine Belohnungen mit!", "TR_STAKE_APY": "Jährliche Rendite in Prozent", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Jährliche Rendite in Prozent", "TR_STAKE_AVAILABLE": "Verfügbar", "TR_STAKE_CAN_CLAIM_WARNING": "Du kannst bereits {amount} {symbol} beanspruchen. {br}Bitte erhebe einen Anspruch oder warte, bis ein neues Unstaking bearbeitet wird.", @@ -1663,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Du kannst einen Anspruch erheben, sobald der Unstaking-Zeitraum beendet ist.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "im nächsten Block", "TR_STAKE_CLAIM_PENDING": "Ausstehender Anspruch", + "TR_STAKE_CLAIM_UNSTAKED": "Nicht gestakete {symbol} beanspruchen", "TR_STAKE_CONFIRM_AND_STAKE": "Bestätigen & Staken", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Eingabezeitraum bestätigen", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Ich erkenne das Staking mit Everstake an und stimme diesem zu", "TR_STAKE_DAYS": "{count, plural, one {# Tag} other {# Tage}}", "TR_STAKE_DELEGATED": "Stake-Delegierung", "TR_STAKE_DEREGISTERED": "Deregistrierung einer Stake-Adresse", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Wöchentlich Belohnungen verdienen", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "Die Eingabe eines Staking-Pools kann bis zu {count, plural, one {# Tag} other {# Tage}} dauern", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Staking-Pool beitreten", "TR_STAKE_ETH": "Staking von Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "Die leichteste Art, {symbol} zu verdienen.", + "TR_STAKE_ETH_CARD_TITLE": "Die leichteste Art, {symbol} zu verdienen", "TR_STAKE_ETH_EARN_REPEAT": "Betreibe Staking. Verdiene Belohnungen. Und von vorn.", "TR_STAKE_ETH_EVERSTAKE": "Trezor & Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake ist ein weltweit führender Anbieter von Staking-Technologie", @@ -1682,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Deine Belohnungen verdienen auch. Lasse sie weiter im Staking und sieh dabei zu, wie deine {symbol}-Belohnungen ansteigen.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Deine {symbol}-Belohnungen profitieren auch vom APY-Satz. Lasse deine Assets weiter im Staking oder füge noch welche hinzu, um deine Belohnungen zu erhöhen.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Sieh zu, wie dein Geld sich vermehrt", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Verdiene {apyPercent} % APY*, indem du Ethereum-Staking mit Trezor betreibst.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Verdiene {apyPercent} % APY, indem du Ethereum-Staking mit Trezor betreibst.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Deine {symbol} werden in diesem Zeitraum geblockt, und du kannst dies nicht rückgängig machen. Mehr erfahren", "TR_STAKE_EVERSTAKE_MANAGES": "Everstake hält und schützt deine {symbol} im Staking mit ihren Smart Contracts, ihrer Infrastruktur und ihrer Technologie.", "TR_STAKE_INSTANT": "Sofort", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Du hast „sofort“ {amount} {symbol} erhalten. {days, plural, =0 {} one {Der Restbetrag wird innerhalb von # Tag gezahlt.} other { Der Restbetrag wird innerhalb von # Tagen gezahlt}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} auf Konto", "TR_STAKE_LEARN_MORE": "Mehr erfahren", + "TR_STAKE_LEAVE_STAKING_POOL": "Staking-Pool verlassen", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Wir haben {amount} {symbol} ausgelassen, sodass du deine Abhebungsgebühren bezahlen kannst.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Wir haben einen kleinen Betrag an {symbol} ausgelassen, sodass du deine Abhebungsgebühren bezahlen kannst.", "TR_STAKE_MAX": "Max", "TR_STAKE_MAX_FEE_DESC": "Die maximale Gebühr ist die Netzwerk-Transaktionsgebühr, die du im Netzwerk bezahlen willst, um sicherzustellen, dass deine Transaktion durchgeführt wird.", "TR_STAKE_MAX_REWARD_DAYS": "Max. {count, plural, one {# Tag} other {# Tage}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "Der Mindestbetrag für das Staking liegt bei {amount} {symbol}", + "TR_STAKE_MONTHLY": "Monatlich", "TR_STAKE_NEXT_PAYOUT": "Nächste Auszahlung der Belohnung", "TR_STAKE_NOT_ENOUGH_FUNDS": "Nicht genügend {symbol} zur Zahlung der Netzwerkgebühren", "TR_STAKE_ONLY_REWARDS": "Nur Belohnungen", @@ -1704,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Registrierung einer Staking-Adresse", "TR_STAKE_RESTAKED_BADGE": "Restaking durchgeführt", "TR_STAKE_REWARDS": "Belohnungen", + "TR_STAKE_SIGN_TRANSACTION": "Transaktion signieren", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Unstaking-Transaktion signieren", "TR_STAKE_STAKE": "Staking", "TR_STAKE_STAKED_AMOUNT": "Staking-Betrag", "TR_STAKE_STAKED_AND_EARNING": "Belohnungen aus Staking und Verdiensten", @@ -1711,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Mehr Staking", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Staking in Kürze", "TR_STAKE_STAKING_IS": "Beim Staking werden deine Ethereum-Assets vorübergehend gesperrt, um den Betrieb der Blockchain zu unterstützen. Im Gegenzug erhältst du zusätzliches Ethereum als Belohnung.", + "TR_STAKE_STAKING_PROCESS": "Staking-Prozess", "TR_STAKE_START_STAKING": "Staking beginnen", "TR_STAKE_TIME_TO_CLAIM": "Zeit für einen Anspruch", "TR_STAKE_TOTAL_PENDING": "Ausstehendes Staking insgesamt:", @@ -1719,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Anspruchsberechtigung bei Unstaking", "TR_STAKE_UNSTAKE_TO_CLAIM": "Unstaking zur Erhebung eines Anspruchs", "TR_STAKE_UNSTAKING": "Unstaking", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Ungefähre {symbol} sofort verfügbar", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "Die Liquidität des Staking-Pools kann ein sofortiges Unstaking einiger Assets ermöglichen. Die restlichen Assets unterliegen dem Unstaking-Zeitraum.", "TR_STAKE_UNSTAKING_PERIOD": "Unstaking-Zeitraum", + "TR_STAKE_UNSTAKING_PROCESS": "Unstaking-Prozess", "TR_STAKE_UNSTAKING_TAKES": "Unstaking dauert derzeit {count, plural, one {# Tag} other {# Tage}}. Nach Ablauf kannst du deine Assets traden oder versenden.", + "TR_STAKE_WEEKLY": "Wöchentlich", "TR_STAKE_WHAT_IS_STAKING": "Was ist Staking?", + "TR_STAKE_YEARLY": "Jährlich", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Deine Assets im Staking werden von Everstake gehalten", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "Staking für {amount} {symbol} sofort durchgeführt", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "Unstaking für {amount} {symbol} sofort durchgeführt", + "TR_STAKING_CONSOLIDATING_FUNDS": "Konsolidierung deiner {symbol} für dich", "TR_STAKING_DELEGATE": "Delegieren", "TR_STAKING_DEPOSIT": "Erstattungsfähiges Pfand", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "Die Einzahlungsgebühr beträgt {feeAmount} ADA und wird benötigt, um deine Adresse zu registrieren und mit dem Staking zu beginnen. Wenn du dein Staking von Cardano beendest, erhältst du eine Gebühr zurück.", + "TR_STAKING_ESTIMATED_GAINS": "Geschätzte Gewinne", "TR_STAKING_FEE": "Gebühr", + "TR_STAKING_GETTING_READY": "Deine {symbol} ist bereit für Einnahmen", "TR_STAKING_INSTANTLY_STAKED": "Für {amount} {symbol} wurde Staking durchgeführt. {days, plural, =0 {} one {Für die restlichen {symbol} wird Staking innerhalb von # Tag durchgeführt.} other { Für die restlichen {symbol} wird Staking innerhalb von # Tagen durchgeführt.}}", "TR_STAKING_IS_NOT_SUPPORTED": "Staking wird in diesem Netzwerk nicht unterstützt.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Du hast nicht genügend Assets auf deinem Konto.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Nach der Bestätigung", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Durch das Staking in einem Trezor Staking-Pool unterstützt du direkt Trezor und das Cardano-Ökosystem innerhalb von Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Du delegierst an einen Staking-Pool eines Drittanbieters", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "Der Staking-Pool, an den du delegierst, ist voll. Delegiere deinen Stake neu, um deine Staking-Belohnungen zu maximieren", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Staking-Pool ist voll", "TR_STAKING_REDELEGATE": "Neu delegieren", "TR_STAKING_REWARDS": "Verfügbare Belohnungen", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Für Belohnungen wird automatisch ein Restaking durchgeführt", "TR_STAKING_REWARDS_DESCRIPTION": "Beachte, dass es bis zu 20 Tage dauern kann, bis du deine Belohnung nach der ersten Registrierung und Delegierung erhältst. Nach Ablauf dieser Periode erhältst du deine Belohnung alle fünf Tage.", "TR_STAKING_REWARDS_TITLE": "Cardano-Staking ist aktiv", "TR_STAKING_STAKE_ADDRESS": "Deine Staking-Adresse", @@ -1744,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Der Staking-Pool von Trezor konnte nicht erreicht werden, um an ihn zu delegieren.", "TR_STAKING_TX_PENDING": "Deine Transaktions-{txid} wurde erfolgreich an die Blockchain gesendet und wartet auf Bestätigung.", "TR_STAKING_WITHDRAW": "Auszahlen", + "TR_STAKING_YOUR_EARNINGS": "Für deine Einnahmen wird automatisch ein Restaking durchgeführt, sodass du Zinseszinsen verdienst.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Deine nicht gestaketen {symbol} sind bereit", + "TR_STAKING_YOU_ARE_HERE": "Dein aktueller Stand", "TR_STANDARD_WALLET_DESCRIPTION": "Keine Passphrase", "TR_START": "Starten", "TR_START_AGAIN": "Erneut starten", @@ -1752,6 +1827,7 @@ "TR_START_COINJOIN": "CoinJoin starten", "TR_START_RECOVERY": "Wiederherstellung starten", "TR_STEP": "Schritt {number}", + "TR_STEP_OF_TOTAL": "Schritt {index} von {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Du siehst deinen Trezor noch immer nicht?", "TR_STOP": "Stoppen", "TR_STOPPING": "Wird gestoppt", @@ -1803,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Noch keine Token.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Keine Tokens. Möglicherweise sind sie ausgeblendet.", "TR_TOKENS_SEARCH_TOOLTIP": "Suche nach Token, Symbol oder Kontraktadresse.", + "TR_TOKEN_NOT_FOUND": "Token nicht gefunden", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Token auf dem {networkName} Netzwerk nicht gefunden.", "TR_TOKEN_TRANSFERS": "{standard} Token-Transfers", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Versuche es mit einem anderen Suchbegriff.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Versuche es mit einem anderen Suchbegriff oder wechsle auf ein anderes Netzwerk.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Nicht erkannte Token", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Nicht erkannte Token können ein Risiko darstellen. Sei vorsichtig.", "TR_TOO_LONG": "Nachricht ist zu lang", @@ -1815,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Pfad aktualisieren", "TR_TOR_DESCRIPTION": "Leite den gesamten Datenverkehr von Suite über das Tor-Netzwerk, um Privatsphäre und Sicherheit zu erhöhen. Es kann einige Zeit dauern, bis Tor geladen ist und eine Verbindung herstellt.", "TR_TOR_DISABLE": "Tor deaktivieren", + "TR_TOR_DISABLED": "Deaktiviert", "TR_TOR_DISABLE_ONIONS_ONLY": "Benutzerdefinierte Nicht-Onion-Backends fehlen", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Füge benutzerdefinierte Nicht-Onion-Backend-Adressen hinzu, um dieses Verhalten zu vermeiden.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Du kannst Tor jetzt sicher deaktivieren.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Benutzerdefinierte Backends verwenden nicht mehr nur Onion-Adressen.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Tor deaktivieren", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Das Deaktivieren von Tor setzt alle Onion-Backends auf Standard-Tor-Server zurück.", + "TR_TOR_DISABLING": "Deaktivierung läuft ...", "TR_TOR_ENABLE": "Tor aktivieren", + "TR_TOR_ENABLED": "Aktiviert", "TR_TOR_ENABLE_AND_CONFIRM": "Tor aktivieren und bestätigen", "TR_TOR_ENABLE_TITLE": "Tor aktivieren", + "TR_TOR_ENABLING": "Aktivierung läuft ...", + "TR_TOR_ERROR": "Fehler", "TR_TOR_IS_SLOW_MESSAGE": "Tor stellt eine Verbindung zum Netzwerk her.

Hab etwas Geduld.", "TR_TOR_KEEP_RUNNING": "Tor weiterhin ausführen", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Wähle „Tor weiterhin ausführen“, um fortzufahren, oder „Tor beenden“, um den CoinJoin-Prozess zu beenden.", + "TR_TOR_MISBEHAVING": "Fehlverhalten", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Tor deaktivieren und zu Standard-Backends wechseln", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Verlassen", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Wähle „Tor aktivieren“, um fortzufahren, oder „Verlassen“, um den Prozess zu beenden.", @@ -1841,11 +1927,7 @@ "TR_TO_BTC": "In BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Um deine Labels konsistent und auf verschiedenen Geräten verfügbar zu machen, stelle eine Verbindung zu einem Cloud-Speicher-Provider her.", "TR_TO_SATOSHIS": "Zu Sats", - "TR_TRADE_BUYS": "kauft", - "TR_TRADE_ENTER_COIN": "Kryptoname oder -symbol eingeben ...", - "TR_TRADE_EXCHANGES": "tauscht", "TR_TRADE_REDIRECTING": "Wird umgeleitet ...", - "TR_TRADE_SELLS": "verkauft", "TR_TRANSACTIONS_NOT_AVAILABLE": "Transaktionsverlauf nicht verfügbar", "TR_TRANSACTIONS_SEARCH_TIP_1": "Tipp: Du kannst nach Transaktions-IDs, Adressen, Tokens, Labels, Beträgen und Datumsangaben suchen.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Tipp: Für komplexeres Suchen kannst du die Operatoren AND (&) und OR (|) kombinieren. > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 zeigt zum Beispiel alle Transaktionen im Januar oder Dezember {lastYear} an.", @@ -1860,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Suche nach Transaktions-ID, Label oder Betrag oder verwende Operatoren wie < > | & = !=.", "TR_TRANSACTION_DETAILS": "Details", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor Bridge läuft unter Version {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutorial abgebrochen", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutorial beendet", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Lerne mithilfe eines kurzen Tutorials, wie du dein Gerät verwendest", @@ -1867,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Schließe andere Registerkarten und Fenster, die deinen Trezor verwenden könnten.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Nachdem du andere Registerkarten und Fenster geschlossen hast, aktualisiere diese Seite.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Nachdem du andere Browser-Registerkarten und -Fenster geschlossen hast, beende Trezor Suite und öffne die App wieder.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Notwendige Schritte, um Kommunikation zu ermöglichen", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Versuche es mit diesen Schritten, um das Problem zu lösen.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Statusseite von Trezor Bridge besuchen", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Stelle sicher, dass der Trezor Bridge-Prozess läuft", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Verwende einen Chromium-basierten Browser, der eine direkte Kommunikation mit USB-Geräten ermöglicht", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Derzeit ist die direkte Kommunikation mit USB-Geräten nur über Chromium-basierte Browser möglich.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Versuche einen Browser mit WebUSB-Unterstützung zu verwenden", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "Das Kabel muss vollständig eingesteckt sein. Bei einem über USB-C verbundenen Gerät sollte das Kabel einrasten.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Verwende ein anderes Kabel", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Mit installierter Trezor Bridge.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Verwende einen anderen Computer, wenn du kannst", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Nur für den Fall", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Starte deinen Computer neu", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Möglicherweise kannst du das Kommunikationsproblem zwischen deinem Browser und deinem Gerät mit einem Neustart deines Computers beheben.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Computer neu starten", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Starten Sie die Trezor Suite Desktop-Anwendung", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Verwende die Suite-Desktop-Anwendung", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Klicke zum Umschalten auf eine andere Bridge-Implementierung. Aktuelle Version: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Andere Version von Trezor Bridge verwenden", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Installiere udev-Regeln. Speichere sie vor dem Öffnen auf dem Desktop.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Wenn du deine Geräte-Firmware das letzte Mal im Jahr 2019 oder früher aktualisiert hast, folge bitte den Anweisungen in der Wissensdatenbank.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Du verwendest offenbar ein älteres Modell von Trezor.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Verbinde das Gerät direkt mit deinem Computer (ohne USB-Hub).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Verwende einen anderen USB-Anschluss", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Regeln automatisch installieren", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Fehlende udev-Regeln", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Unerwarteter Zustand: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Dein Gerät ist ordnungsgemäß angeschlossen, aber dein Browser kann im Moment nicht damit kommunizieren. Du musst Trezor Bridge installieren.", "TR_TRY_AGAIN": "Wiederholen", "TR_TXID": "TX-ID", "TR_TXID_RBF": "Zu ersetzende Original-TX-ID", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transaktion bestätigt", "TR_TX_CONFIRMING": "Transaktion wird bestätigt", + "TR_TX_DATA_FUNCTION": "Funktion", + "TR_TX_DATA_INPUT_DATA": "Eingabedaten", + "TR_TX_DATA_METHOD": "Eingabedaten", + "TR_TX_DATA_METHOD_NAME": "Methodenname", + "TR_TX_DATA_PARAMS": "Parameter", "TR_TX_DEPOSIT": "Einzahlung", "TR_TX_FEE": "Gebühr", "TR_TX_TAB_AMOUNT": "Betrag", @@ -1932,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Firmware-Update erforderlich. Du kannst deinen Startbildschirm später auf der Einstellungsseite ändern", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Aktualisiere deine Firmware, um deinen Startbildschirm zu ändern", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Update verfügbar", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Automatische Updates aktivieren", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Neu", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Aktualisieren und neu starten", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Update jetzt installieren?", "TR_UPDATE_MODAL_NOT_NOW": "Nicht jetzt", @@ -1939,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "Herunterladen", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Update heruntergeladen", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Update nach Beenden", + "TR_UPDATE_MODAL_WHATS_NEW": "Was gibt es Neues?", + "TR_UPDATE_MODAL_YOUR_VERSION": "Deine Version: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Aktualisiere deine Firmware, um auf dieses Konto zuzugreifen. Weitere Informationen erhältst du oben auf dem blauen Banner.", "TR_UP_TO": "bis zu", "TR_UP_TO_DATE": "Auf dem neuesten Stand", "TR_UP_TO_DAYS": "bis zu {count, plural, one {# Tag} other {# Tage}}", @@ -1962,7 +2058,7 @@ "TR_VERIFY_TREZOR_OWNERSHIP_CARD_2": "Keine Fotos und keine digitalen Kopien deines Wallet-Backups", "TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION": "Gib dein aktuelles Wallet-Backup auf deinem Trezor ein und bestätige damit, dass du der Eigentümer dieses Wallets bist.", "TR_VERSION": "Version {version}", - "TR_VERSION_HAS_BEEN_RELEASED": "Version {version} wurde veröffentlicht.", + "TR_VERSION_HAS_BEEN_RELEASED": "Version {version} wurde veröffentlicht!", "TR_VIEW": "Anzeigen", "TR_VIEW_ACCOUNT": "Konto anzeigen", "TR_VIEW_ALL": "Alle anzeigen", @@ -1992,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Zugriff auf Passphrase Wallet", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Passphrase Wallet", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Erstelle ein neues Wallet oder stelle ein Wallet mit deinem Wallet-Backup wieder her.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Wir arbeiten stets daran, deine Benutzererfahrung mit Trezor zu verbessern. Das gibt es Neues:", "TR_WEST": "Westen", "TR_WHAT_DATA_WE_COLLECT": "Welche Daten erfassen wir?", "TR_WHAT_IS_PASSPHRASE": "Erfahre mehr über den Unterschied", @@ -2002,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Mir ist klar, dass ich ein Backup meines Wallet-Backups benötige, um den Zugang zu meinen Assets wiederherzustellen.", "TR_WIPE_DEVICE_TEXT": "Beim Zurücksetzen des Geräts werden alle Daten gelöscht. Setze dein Gerät nur zurück, wenn du dein Wallet-Backup hast, das den Zugang zu deinen Assets wiederherstellen kann.", "TR_WIPE_OR_UPDATE": "Gerät zurücksetzen oder Firmware aktualisieren", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Zu Geräteeinstellungen", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Gehe zu den Geräteeinstellungen.", "TR_WIPING_YOUR_DEVICE": "Beim Werksreset wird der Speicher des Geräts gelöscht, wobei alle Informationen, einschließlich des Wallet-Backups und der PIN, entfernt werden. Führe das Werksreset nur durch, wenn du dein Wallet-Backup hast, das zum Wiederherstellen des Zugangs zu deinen Assets erforderlich ist.", "TR_WORDS": "{count} Wörter", "TR_WORD_DOES_NOT_EXIST": "Das Wort „{word}“ befindet sich nicht in der BIP39-Wörterliste.", diff --git a/packages/suite-data/files/translations/en.json b/packages/suite-data/files/translations/en.json index 7f7434b395e..3f4d0afcac2 100644 --- a/packages/suite-data/files/translations/en.json +++ b/packages/suite-data/files/translations/en.json @@ -1,19 +1,20 @@ { "AMOUNT": "Amount", + "AMOUNT_EXCEEDS_MAX": "The amount exceeds the maximum allowed value of {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "Amount must be at least {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "Recipient account requires minimum reserve {reserve} XRP to activate", "AMOUNT_IS_MORE_THAN_RESERVE": "Amount is above the required unspendable reserve ({reserve} XRP)", "AMOUNT_IS_NOT_ENOUGH": "Not enough funds", - "AMOUNT_IS_NOT_INTEGER": "Amount is not an integer", + "AMOUNT_IS_NOT_INTEGER": "Amount isn't an integer", "AMOUNT_IS_NOT_IN_RANGE_DECIMALS": "Maximum {decimals} decimals allowed", - "AMOUNT_IS_NOT_SET": "Amount is not set", + "AMOUNT_IS_NOT_SET": "Amount isn't set", "AMOUNT_IS_TOO_LOW": "Amount is too low", "AMOUNT_NOT_ENOUGH_CURRENCY_FEE": "Not enough {symbol} to cover transaction fee", "AMOUNT_SEND_MAX": "Send max", - "BACKUP_BACKUP_ALREADY_FAILED_DESCRIPTION": "A previous attempt to backup this device failed. Device backup may be done only once.", - "BACKUP_BACKUP_ALREADY_FAILED_HEADING": "Backup failed", - "BACKUP_BACKUP_ALREADY_FINISHED_DESCRIPTION": "Connected device has already been backed up. You should have the wallet backup written down and hidden in a safe place.", - "BACKUP_BACKUP_ALREADY_FINISHED_HEADING": "Backup already finished", + "BACKUP_BACKUP_ALREADY_FAILED_DESCRIPTION": "A previous attempt to backup this device failed. Wallet backup may be done only once.", + "BACKUP_BACKUP_ALREADY_FAILED_HEADING": "Wallet backup failed", + "BACKUP_BACKUP_ALREADY_FINISHED_DESCRIPTION": "The connected device already has a wallet backup. You should have the wallet backup written down and hidden in a safe place.", + "BACKUP_BACKUP_ALREADY_FINISHED_HEADING": "Wallet backup already finished", "BROADCAST": "Broadcast", "BROADCAST_TOOLTIP": "Broadcast the transaction to the network.", "BROADCAST_TOOLTIP_DISABLED_LOCKTIME": "A transaction with a locktime set beyond the current block or timestamp will be rejected by the network.", @@ -30,9 +31,9 @@ "DATA_NOT_SET": "Data not set", "DATA_NOT_VALID_HEX": "Not a valid hex", "DESTINATION_TAG": "Destination tag", - "DESTINATION_TAG_IS_NOT_NUMBER": "Destination tag is not a number", - "DESTINATION_TAG_IS_NOT_VALID": "Destination tag is not valid", - "DESTINATION_TAG_NOT_SET": "Destination tag is not set", + "DESTINATION_TAG_IS_NOT_NUMBER": "Destination tag isn't a number", + "DESTINATION_TAG_IS_NOT_VALID": "Destination tag isn't valid", + "DESTINATION_TAG_NOT_SET": "Destination tag isn't set", "DESTINATION_TAG_TOOLTIP": "Destination tag is a unique code to identify the receiver of a transaction.", "DISCONNECT_DEVICE_DESCRIPTION": "Your device was wiped and no longer holds any private keys.", "DOWNLOAD_TRANSACTION": "Download as .txt", @@ -58,7 +59,7 @@ "IMAGE_VALIDATION_ERROR_INVALID_FORMAT_ONLY_JPG": "Invalid file selected. Must be .jpg", "IMAGE_VALIDATION_ERROR_INVALID_FORMAT_ONLY_PNG_JPG": "Invalid file selected. Must be .jpg or .png", "IMAGE_VALIDATION_ERROR_INVALID_SIZE_JPG": "Invalid size (Image must be less than 16KB)", - "IMAGE_VALIDATION_ERROR_PROGRESSIVE_JPG": "Progressive JPG image format is not supported.", + "IMAGE_VALIDATION_ERROR_PROGRESSIVE_JPG": "Progressive JPG image format isn't supported.", "IMAGE_VALIDATION_ERROR_UNEXPECTED_ALPHA": "Invalid image format. It must not contain transparencies.", "IMPORT_CSV": "Import", "INCLUDING_FEE": "Incl. fee", @@ -66,8 +67,8 @@ "LOCKTIME_ADD": "Add Locktime", "LOCKTIME_ADD_TOOLTIP": "Locktime sets the earliest time a transaction can be mined into a block.", "LOCKTIME_BLOCKHEIGHT": "Locktime blockheight", - "LOCKTIME_IS_NOT_INTEGER": "Locktime is not an integer", - "LOCKTIME_IS_NOT_SET": "Locktime not set", + "LOCKTIME_IS_NOT_INTEGER": "Locktime isn't an integer", + "LOCKTIME_IS_NOT_SET": "Locktime isn't set", "LOCKTIME_IS_TOO_BIG": "Timestamp is too big", "LOCKTIME_IS_TOO_LOW": "Locktime is too low", "LOCKTIME_SCHEDULE_SEND": "Locktime", @@ -112,7 +113,7 @@ "RECEIVE_ADDRESS_LIMIT_REACHED": "You've reached the maximum limit of 21 fresh, unused addresses", "RECEIVE_ADDRESS_REVEAL": "Show full address", "RECEIVE_ADDRESS_UNAVAILABLE": "Unavailable", - "RECEIVE_DESC_BITCOIN": "To receive any funds you need to get a fresh receive address. It is advised to always use a fresh address, as this prevents anyone else from tracking your transactions. You can reuse an address, but we recommend not doing so unless absolutely necessary.", + "RECEIVE_DESC_BITCOIN": "To receive any funds you need to get a fresh receive address. It's advised to always use a fresh address, as this prevents anyone else from tracking your transactions. You can reuse an address, but we recommend not doing so unless absolutely necessary.", "RECEIVE_DESC_ETHEREUM": "Use this address to receive tokens as well.", "RECEIVE_TABLE_ADDRESS": "Address", "RECEIVE_TABLE_NOT_USED": "Unused", @@ -123,13 +124,13 @@ "RECIPIENT_ADD": "Add Recipient", "RECIPIENT_ADDRESS": "Address", "RECIPIENT_CANNOT_SEND_TO_MYSELF": "Can't send to myself", - "RECIPIENT_IS_NOT_SET": "Address is not set", - "RECIPIENT_IS_NOT_VALID": "Address is not valid", + "RECIPIENT_IS_NOT_SET": "Address isn't set", + "RECIPIENT_IS_NOT_VALID": "Address isn't valid", "RECIPIENT_REQUIRES_UPDATE": "Taproot is not supported by your firmware version. Please update your device firmware.", "RECIPIENT_SCAN": "Scan", "REFRESH": "Refresh", "REMAINING_BALANCE_LESS_THAN_RENT": "After sending this amount, your account will have {remainingSolBalance} SOL remaining. A non-empty account must maintain a balance of more than {rent} SOL.", - "REVIEW_AND_SEND_TRANSACTION": "Review & Send", + "REVIEW_AND_SEND_TRANSACTION": "Review & send", "SEND_RAW": "Send raw", "SEND_RAW_TRANSACTION_TOOLTIP": "You can provide all the raw data for your transaction by yourself.", "SEND_TRANSACTION": "Send", @@ -155,8 +156,8 @@ "TOAST_AUTO_UPDATER_ERROR": "Auto updater error ({state})", "TOAST_AUTO_UPDATER_NEW_VERSION_FIRST_RUN": "New version ({version}) installed successfully", "TOAST_AUTO_UPDATER_NO_NEW": "No new updates available.", - "TOAST_BACKUP_FAILED": "Backup failed", - "TOAST_BACKUP_SUCCESS": "Backup successful", + "TOAST_BACKUP_FAILED": "Wallet backup failed", + "TOAST_BACKUP_SUCCESS": "Wallet backup successful", "TOAST_COIN_SCHEME_PROTOCOL": "{header}{body}", "TOAST_COIN_SCHEME_PROTOCOL_ACTION": "Autofill send form", "TOAST_COIN_SCHEME_PROTOCOL_HEADER": "Go to an account to send", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN changed successfully", "TOAST_QR_INCORRECT_ADDRESS": "QR code contains invalid address for this account", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "QR code is defined for {coin} account", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Unknown protocol scheme: \"{scheme}\". Try again or enter the address manually.", "TOAST_RAW_TX_SENT": "Transaction sent. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "Settings changed successfully", "TOAST_SIGN_MESSAGE_ERROR": "Message signing error: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Go to Dashboard", "TR_404_TITLE": "Error 404: Link not found", "TR_7D_CHANGE": "7d change", - "TR_ABORT": "Abort", "TR_ACCESS_HIDDEN_WALLET": "Access Passphrase wallet", "TR_ACCESS_STANDARD_WALLET": "Access standard wallet", "TR_ACCOUNT_DETAILS_HEADER": "Account details", @@ -211,7 +212,7 @@ "TR_ACCOUNT_EXCEPTION_DISCOVERY_EMPTY_DESC": "All coins are currently disabled. Please enable in Settings.", "TR_ACCOUNT_EXCEPTION_DISCOVERY_ERROR": "Account discovery error", "TR_ACCOUNT_EXCEPTION_NOT_ENABLED": "{networkName} not enabled in Settings.", - "TR_ACCOUNT_EXCEPTION_NOT_EXIST": "Account does not exist", + "TR_ACCOUNT_EXCEPTION_NOT_EXIST": "Account doesn't exist", "TR_ACCOUNT_IMPORTED_ANNOUNCEMENT": "A watch-only account is a public address you’ve imported into your wallet, allowing the wallet to watch for outputs but not spend them.", "TR_ACCOUNT_IS_EMPTY_DESCRIPTION": "Get started by receiving or buying {network}.", "TR_ACCOUNT_IS_EMPTY_TITLE": "No transactions... yet.", @@ -260,6 +261,7 @@ "TR_ACQUIRE_DEVICE_TITLE": "Another session is running", "TR_ACTIVATED_COINS": "Activated coins", "TR_ACTIVE": "active", + "TR_ADD": "Add", "TR_ADDRESSES": "Address", "TR_ADDRESSES_CHANGE": "Change addresses", "TR_ADDRESSES_FRESH": "Fresh addresses", @@ -269,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Copy address", "TR_ADDRESS_MODAL_TITLE": "{networkName} receive address", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkCurrencyName} receive address on {networkName} network", - "TR_ADDRESS_PHISHING_WARNING": "To prevent phishing attacks, you should verify the address on your Trezor. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "To prevent phishing attacks, verify the receive address on your Trezor. {claim}", "TR_ADD_ACCOUNT": "Add account", "TR_ADD_HIDDEN_WALLET": "Passphrase wallet", "TR_ADD_NETWORK_ACCOUNT": "Add {network} account", @@ -296,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Data usage", "TR_ALLOW_ANALYTICS_DESCRIPTION": "All data is kept strictly anonymous. It's only used to improve the Trezor ecosystem.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Automatic Trezor Suite updates", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite automatically downloads the latest version in the background and installs it when restarting the app. This ensures you're always up-to-date with the latest features and security patches. Updates occur without requiring your permission.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Automatically download the latest version of Trezor Suite in the background and install it when restarting the app. This ensures you're always up-to-date with the latest features and security patches. Updates occur without requiring your permission.", + "TR_ALL_NETWORKS": "All networks ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "View tokens from all {networkCount} networks. Filter by the most popular networks.", "TR_ALL_TRANSACTIONS": "Transactions", "TR_AMOUNT_SENT": "Amount sent", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Not suitable for coinjoin - amount too high", @@ -355,31 +359,29 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Although unlikely, you may need to access your wallet backup in case of a firmware update issue.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Before switching to {bitcoinOnly}, you need to upgrade your firmware to the latest version.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "I understand I'm damaging my anonymity", + "TR_BREAKING_ANONYMITY_CHECKBOX": "I understand that I'm compromising my anonymity.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Starting Trezor Bridge on port 21324", "TR_BRIDGE_DEV_MODE_STOP": "Starting Trezor Bridge on default port", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Are you sure? Your device can only be used by one app at a time. If you're currently using another app with your Trezor device, finish that session first.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Your browser isn't supported. For the best experience, download and run the Trezor Suite desktop app in the background, or use a supported Chromium-based browser that is compatible with WebUSB.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Another app requested Trezor Suite to connect with your Trezor device. Keep Trezor Suite running in the background and retry the action in the other app.", + "TR_BRIDGE_TIP_AUTOSTART": "Tip: Enable the auto-start feature and have Trezor Bridge always running in the background.", "TR_BTC_UNITS": "Bitcoin units", "TR_BUG": "Bug", "TR_BUMP_FEE": "Bump fee", "TR_BUMP_FEE_DISABLED_TOOLTIP": "To speed up your transactions, increase the fee on the oldest (by nonce) pending transaction in the queue. Transactions must be confirmed in order. Learn more", "TR_BUY": "Buy", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Trade transactions", "TR_BUY_BUY": "Buy", - "TR_BUY_BUY_AGAIN": "Buy again", "TR_BUY_CONFIRMED_ON_TREZOR": "Confirmed on Trezor", - "TR_BUY_CONFIRM_ON_TREZOR": "Confirm on Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Back to Account", "TR_BUY_DETAIL_ERROR_SUPPORT": "Go to provider support", "TR_BUY_DETAIL_ERROR_TEXT": "Sorry, your transaction failed or was rejected. Your payment method was not charged.", "TR_BUY_DETAIL_ERROR_TITLE": "The transaction failed", "TR_BUY_DETAIL_PENDING_SUPPORT": "Go to provider support", "TR_BUY_DETAIL_PENDING_TITLE": "Processing your transaction...", - "TR_BUY_DETAIL_SUBMITTED_GATE": "Go to payment gateway", - "TR_BUY_DETAIL_SUBMITTED_TEXT": "Click the button below to finish entering your details on the provider's site.", + "TR_BUY_DETAIL_SUBMITTED_GATE": "Proceed to pay", + "TR_BUY_DETAIL_SUBMITTED_TEXT": "Click to complete your details on the provider's site.", "TR_BUY_DETAIL_SUBMITTED_TITLE": "Waiting for your payment...", "TR_BUY_DETAIL_SUCCESS_BUTTON": "Back to Account", "TR_BUY_DETAIL_SUCCESS_TEXT": "Your transaction was approved; please wait for it to finish.", @@ -388,7 +390,7 @@ "TR_BUY_DETAIL_WAITING_FOR_USER_TEXT": "{providerName} needs some final details to finish this transaction. Visit their site to proceed.", "TR_BUY_DETAIL_WAITING_FOR_USER_TITLE": "Complete your transaction", "TR_BUY_FOOTER_TEXT_1": "Invity is a comparison tool that connects you to the best exchange providers. They only use location in order to show the most relevant offers.", - "TR_BUY_FOOTER_TEXT_2": "Invity does not see any of your payment or KYC information; you share this only with the exchange provider if you choose to finish the transaction.", + "TR_BUY_FOOTER_TEXT_2": "Invity doesn't see any of your payment or KYC information; you share this only with the exchange provider if you choose to finish the transaction.", "TR_BUY_GO_TO_PAYMENT": "Finish transaction", "TR_BUY_LEARN_MORE": "Learn more", "TR_BUY_MODAL_CONFIRM": "I’m ready to buy", @@ -398,7 +400,7 @@ "TR_BUY_MODAL_TERMS_1": "I’m here to buy cryptocurrencies. If I were directed to this site for any other reason, I'll contact {provider} support before proceeding.", "TR_BUY_MODAL_TERMS_2": "I’m using this feature to buy cryptocurrencies that'll be sent to my own account.", "TR_BUY_MODAL_TERMS_3": "I understand that cryptocurrency transactions are final and can't be reversed or refunded. Losses due to fraud or mistakes may not be recoverable.", - "TR_BUY_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s terms and conditions.", + "TR_BUY_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s Terms and Conditions.", "TR_BUY_MODAL_TERMS_5": "I'm not using this feature for gambling, fraud, or any activity that violates Invity’s or the provider's Terms of Service, or any applicable laws.", "TR_BUY_MODAL_TERMS_6": "I understand that cryptocurrencies are an emerging financial tool and that regulations can differ by region. This may increase the risk of fraud, theft, or market instability.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Verified partners by Invity", @@ -415,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "Pending", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Pending", "TR_BUY_STATUS_SUCCESS": "Approved", - "TR_BUY_TRANS_ID": "Trans. ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximum is {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum is {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum is {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum is {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "View details", "TR_BYTES": "bytes", "TR_CAMERA_NOT_RECOGNIZED": "The camera was not recognized.", "TR_CAMERA_PERMISSION_DENIED": "Permission to access the camera was denied.", @@ -439,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor amount", "TR_CHAINED_TXS": "Chained transactions", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog on GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "This is a change address created from a previous send.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "You can change your firmware type in Settings anytime.", "TR_CHANGE_HOMESCREEN": "Change homescreen", "TR_CHANGE_PIN": "Change PIN", "TR_CHANGE_WIPE_CODE": "Change wipe code", + "TR_CHECKED_BALANCES_ON": "Checked balances on", "TR_CHECKING_YOUR_DEVICE": "Checking your device", "TR_CHECKSUM_CONVERSION_INFO": "Converted to checksum. Learn more", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "We'll verify the integrity of your Trezor device, ensuring its safety and confirming the authenticity of the chip.", @@ -452,13 +452,12 @@ "TR_CHECK_FINGERPRINT": "Check fingerprint", "TR_CHECK_FOR_DEVICES": "Find Trezor", "TR_CHECK_ORIGIN": "Check device", - "TR_CHECK_RECOVERY_SEED": "Check backup", + "TR_CHECK_RECOVERY_SEED": "Check wallet backup", "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Perform a simulated recovery to verify your wallet backup.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Enter the words from your wallet backup here in the order displayed on your device. You may be asked to type some words that aren't part of your wallet backup as an additional security measure.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Use the two-button pad to enter your wallet backup. By doing this, you're keeping all your sensitive info safe and sound, away from any shady or insecure computer or web browser.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Your wallet backup is entered using the touchscreen. This avoids exposing any of your sensitive information to a potentially insecure computer or web browser.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Your wallet backup is entered using the touchscreen. This avoids exposing any of your sensitive information to a potentially insecure computer or web browser.", - "TR_CHECK_SEED": "Check backup", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Your wallet backup is entered using the touchscreen. This avoids exposing any of your sensitive information to a potentially insecure computer or web browser.", + "TR_CHECK_SEED": "Check wallet backup", "TR_CHECK_YOUR_DEVICE": "Check your Trezor's screen", "TR_CHOOSE_RECOVERY_TYPE": "Choose recovery type", "TR_CHUNKED_ADDRESS": "Spaced", @@ -511,7 +510,8 @@ "TR_COINJOIN_TILE_3_TITLE": "Protected by your Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Coinjoin transactions", "TR_COINMARKET_BEST_RATE": "Best rate", - "TR_COINMARKET_BUY_AND_SELL": "Buy & sell", + "TR_COINMARKET_BUY_AND_SELL": "Buy & Sell", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} buys} one {{totalBuys} buy} other {{totalBuys} buys} } • {totalSells, plural, =0 {{totalSells} sells} one {{totalSells} sell} other {{totalSells} sells} }", "TR_COINMARKET_CEX_TOOLTIP": "Centralized exchange", "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Change amount or currency.", "TR_COINMARKET_COMPARE_OFFERS": "Compare all offers", @@ -553,19 +553,13 @@ "TR_COINMARKET_KYC_NO_REFUND": "KYC requested in exceptional cases. KYC required for refunds. 👈", "TR_COINMARKET_KYC_POLICY": "KYC policy", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC never required", - "TR_COINMARKET_KYC_YES_REFUND": "KYC requested in exceptional cases. KYC not required for refunds. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "KYC is only requested in exceptional cases. It's not required for refunds. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Last transactions", "TR_COINMARKET_NETWORK_FEE": "Network fee", "TR_COINMARKET_NETWORK_TOKENS": "{networkName} tokens", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "No CEX provider found", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "No DEX provider found", "TR_COINMARKET_NO_METHODS_AVAILABLE": "No methods available", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Auto-reloading in", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Back to Trade", - "TR_COINMARKET_NO_OFFERS_HEADER": "No offers", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Sorry, we don't have any offers at the moment due to a server connectivity issue.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Sorry, we don't have any offers at the moment. Try to reload the page or change your query.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Reload page", "TR_COINMARKET_OFFERS_EMPTY": "No offers available for your request. Change country or buy amount.", "TR_COINMARKET_OFFERS_REFRESH": "Offers refresh in", "TR_COINMARKET_OFFERS_SELECT": "Select", @@ -581,6 +575,7 @@ "TR_COINMARKET_SHOW_OFFERS": "Compare offers", "TR_COINMARKET_SWAP": "Swap", "TR_COINMARKET_SWAP_AMOUNT": "Swap amount", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} swaps} one {{totalSwaps} swap} other {{totalSwaps} swaps} }", "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "I’m ready to swap", "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Swap {fromCrypto} to {toCrypto} with {provider}", "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Legal notice", @@ -588,7 +583,7 @@ "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "I want to swap cryptocurrencies using DEX (decentralized exchange) by using {provider}'s contract.", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "I want to swap cryptocurrencies for my own account. I understand that the provider's policies may require identity verification.", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "I understand that cryptocurrency transactions are final and can't be reversed or refunded. Losses due to fraud or mistakes may not be recoverable.", - "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s terms and conditions.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s Terms and Conditions.", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "I'm not using this feature for gambling, fraud, or any activity that violates Invity’s or the provider's Terms of Service, or any applicable laws.", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "I understand that cryptocurrencies are an emerging financial tool and that regulations can differ by region. This may increase the risk of fraud, theft, or market instability.", "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Verified partners by Invity", @@ -599,13 +594,15 @@ "TR_COINMARKET_SWAP_MODAL_TERMS_1": "I’m here to swap cryptocurrencies. If I were directed to this site for any other reason, I'll contact Trezor Support before proceeding. ", "TR_COINMARKET_SWAP_MODAL_TERMS_2": "I want to swap cryptocurrencies for my own account. I understand that the provider's policies may require identity verification.", "TR_COINMARKET_SWAP_MODAL_TERMS_3": "I understand that cryptocurrency transactions are final and can't be reversed or refunded. Losses due to fraud or mistakes may not be recoverable.", - "TR_COINMARKET_SWAP_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s terms and conditions.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "I understand that Invity doesn't provide this service. It's governed by {provider}’s Terms and Conditions.", "TR_COINMARKET_SWAP_MODAL_TERMS_5": "I'm not using this feature for gambling, fraud, or any activity that violates Invity’s or the provider's Terms of Service, or any applicable laws.", "TR_COINMARKET_SWAP_MODAL_TERMS_6": "I understand that cryptocurrencies are an emerging financial tool and that regulations can differ by region. This may increase the risk of fraud, theft, or market instability.", "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Verified partners by Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} on {networkName} network", "TR_COINMARKET_TRADE_FEE": "Trade fee", + "TR_COINMARKET_TRANS_ID": "Trans. ID:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Unknown provider", + "TR_COINMARKET_VIEW_DETAILS": "View details", "TR_COINMARKET_YOUR_BEST_OFFER": "Your best offer", "TR_COINMARKET_YOU_BUY": "You buy", "TR_COINMARKET_YOU_GET": "You get", @@ -653,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Manage", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Enable the passphrase entry dialog to open when you open Trezor Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Do you primarily use a passphrase?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verify on Trezor to confirm receive address. Continuing without confirming isn't recommended.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verify on your Trezor to confirm the receive address. It's not recommended to continue without confirming.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "Receive address can't be verified", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "To send coins, connect your Trezor.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Your Trezor isn't connected", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Connect Trezor to Send", "TR_CONNECT_YOUR_DEVICE": "Connect & unlock your Trezor", - "TR_CONTACT_SUPPORT": "Contact support", + "TR_CONTACT_SUPPORT": "Contact Trezor Support", "TR_CONTACT_TREZOR_SUPPORT": "Contact Trezor Support", "TR_CONTINUE": "Continue", "TR_CONTINUE_ANYWAY": "Continue anyway", @@ -679,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Never send funds to a policy ID address.", "TR_COPY_AND_CLOSE": "Copy & Close", "TR_COPY_SIGNED_MESSAGE": "Copy signed message", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copy", + "TR_COPY_TO_CLIPBOARD": "Copy", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Could not retrieve the changelog", "TR_COULD_NOT_RETRIEVE_DATA": "Could not retrieve data", "TR_COUNT_WALLETS": "{count} {count, plural, one {wallet} other {wallets}}", @@ -713,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Asset not loaded correctly", "TR_DASHBOARD_DISCOVERY_ERROR": "Discovery error", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Accounts were not loaded properly {details}", + "TR_DATA": "Data", "TR_DATABASE_UPGRADE_BLOCKED": "Database upgrade blocked by another app instance", "TR_DATA_ANALYTICS_CATEGORY_1": "Platform", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "OS, Trezor model, version etc.", @@ -766,19 +764,24 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "In bootloader by mistake?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Reconnect the device without touching any buttons.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Reconnect the device without touching the screen.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "This device is being used elsewhere.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "The app {transportSessionOwner} may currently be using this device. You can take control of the device if needed.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Another app may currently be using this device. You can take control of the device if needed.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Device detected in incorrect state", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Your Trezor was disconnected during the backup process. We strongly recommend that you use the factory reset option in Device settings to wipe your device and start the wallet backup process again.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Firmware hash check failed. Your Trezor might be counterfeit.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Firmware hash check couldn't be performed. Your Trezor might be counterfeit.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Turn off", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Turn on", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Firmware revision check is a crucial security feature. We strongly recommend keeping it turned on.", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION_DISABLED": "Firmware revision check is a crucial security feature. We strongly recommend keeping it turned on.", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Firmware authenticity check is a crucial security feature. We strongly recommend keeping it turned on.", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION_DISABLED": "Firmware authenticity check is a crucial security feature. We strongly recommend keeping it turned on.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_BUTTON": "Turn off", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_1": "Turn off the firmware revision check only if you fully understand the risks and have a valid reason. If unsure, contact Trezor Support for help.", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_1": "Turn off the firmware authenticity check only if you fully understand the risks and have a valid reason. If unsure, contact Trezor Support for help.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_2": "Only turn off this feature if your device has successfully passed the check before. Using an unverified device could result in the loss of your funds.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_3": "Trezor Support will never ask you to turn off the firmware revision check. This feature is designed to protect your security.", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE": "Turn off firmware revision check", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE_DISABLED": "Turn on firmware revision check", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_UNABLE_TO_PERFORM": "Firmware revision check couldn't be performed. Go online to verify your firmware version.", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE": "Turn off firmware authenticity check", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE_DISABLED": "Turn on firmware authenticity check", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_UNABLE_TO_PERFORM": "Firmware authenticity check couldn't be performed. Go online to verify your firmware version.", "TR_DEVICE_FW_UNKNOWN": "Unknown", "TR_DEVICE_IN_BOOTLOADER": "The device is in bootloader mode.", "TR_DEVICE_IN_RECOVERY_MODE": "Your device is in recovery mode.", @@ -786,8 +789,10 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "Device \"{deviceLabel}\" isn't backed up", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "Device \"{deviceLabel}\" isn't connected", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "Device \"{deviceLabel}\" is unavailable", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Device verification unsuccessful", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Reconnect your device and try verifying again. If the issue continues, contact Trezor Support to figure out what's going on with your device and what to do next.", "TR_DEVICE_NOT_CONNECTED": "Device not connected", - "TR_DEVICE_NOT_INITIALIZED": "Trezor is not set up", + "TR_DEVICE_NOT_INITIALIZED": "Trezor isn't set up", "TR_DEVICE_NOT_INITIALIZED_TEXT": "We'll guide you through the process and get you started right away.", "TR_DEVICE_SECURITY": "Security", "TR_DEVICE_SETTINGS_AFTER_DELAY": "After delay", @@ -848,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Download latest Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Don't disconnect your device", "TR_DO_NOT_SHOW_AGAIN": "Don't show again", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Skip this step?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Drag and drop file here or click to select from files", "TR_DROPZONE_ERROR": "Import failed: {error}", @@ -869,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Join", "TR_EARLY_ACCESS_ENABLED": "Early Access Program enabled", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Join", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "I understand this allows me to test pre-release software, which may contain errors that affect the normal operation of Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "I understand this allows me to test pre-release software, which may contain errors that affect the normal operation of Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "You can turn it off anytime.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Try out the latest product features before release to the general public.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Check the field above first", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "You can either check for beta updates now or on the next launch.", "TR_EARLY_ACCESS_JOINED_TITLE": "Early Access Program enabled", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "To downgrade to the latest stable release of Suite, please click \"Download stable\" and reinstall the app.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "To downgrade to the latest stable release of Trezor Suite, click \"Download stable\" and reinstall the app.", "TR_EARLY_ACCESS_LEFT_TITLE": "You've left the Early Access Program. Beta releases are no longer offered.", "TR_EARLY_ACCESS_MENU": "Early Access Program", "TR_EARLY_ACCESS_REINSTALL": "Download stable", @@ -902,8 +906,8 @@ "TR_ENTER_SEED_WORDS_ON_DEVICE": "The words are entered on the device for security reasons. Please enter the words in the correct order.", "TR_ENTER_WIPECODE": "Enter Wipe Code", "TR_ERROR": "Error", - "TR_ERROR_CARDANO_DELEGATE": "Amount is not enough", - "TR_ERROR_CARDANO_WITHDRAWAL": "Amount is not enough", + "TR_ERROR_CARDANO_DELEGATE": "Amount isn't enough", + "TR_ERROR_CARDANO_WITHDRAWAL": "Amount isn't enough", "TR_ETH_ADDRESS_CANT_VERIFY_HISTORY": "Unable to verify address history. Check that the address is correct.", "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "Address has no transaction history and isn't checksummed. Check that the address is correct.", "TR_EVM_EXPLANATION_DESCRIPTION": "It shares the same address style as Ethereum but has its own unique coins and tokens that can't be used on other networks.", @@ -930,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Approve only the exact amount required for this swap. You will need to pay an additional fee if you want to make a similar swap again.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Revoke previous approval", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Perform a transaction that will remove previous approval of contract with {provider}.", - "TR_EXCHANGE_BUY": "For", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirm on Trezor & send", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirm & Send", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Create approval", @@ -952,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Your transaction was successful.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Approved", "TR_EXCHANGE_DEX": "Decentralized exchange offer", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "The fees to perform this swap are estimated at {approvalFee} ({approvalFeeFiat}) for approval (if required) and {swapFee} ({swapFeeFiat}) for the swap.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "No funds remaining for the transaction fees. Please lower the exchange amount to max {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} is invalid", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -963,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Fixed rates show you exactly how much you'll end up with at the end of the exchange—the amount won't change between when you select the rate and when your transaction is complete. You're guaranteed the amount shown, but these rates are usually less generous, meaning your money won't buy as much crypto.", "TR_EXCHANGE_FLOAT": "Floating-rate offer", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Floating rates mean that the final amount you'll get may change slightly due to fluctuations in the market between when you select the rate and when your transaction is complete. These rates are usually higher, meaning you could end up with more crypto in the end.", - "TR_EXCHANGE_PROVIDER": "Provider", "TR_EXCHANGE_RATE": "Price", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Receive account is outside of Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "This is the specific alphanumeric address that will receive your coins.", @@ -990,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Please enter a number.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Enter your desired slippage.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Swap offer amount", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Slippage summary", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Slippage tolerance", - "TR_EXCHANGE_TRANS_ID": "Trans. ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Use an account ({symbol}) that isn't in Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Receive address", - "TR_EXCHANGE_VIEW_DETAILS": "View details", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Automatic Trezor Suite updates", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite automatically downloads the latest version in the background and installs it when restarting the app. This ensures you're always up-to-date with the latest features and security patches. Updates occur without requiring your permission.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Enables the BNB Smart Chain network but without internal transaction data history.", "TR_EXPERIMENTAL_FEATURES": "Experimental", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Experimental features", "TR_EXPERIMENTAL_FEATURES_WARNING": "For experienced users only. Use at your own risk. These features are in testing, may be unstable, and might not have long-term support.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migrate Dropbox passwords", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "A utility for retrieving passwords stored on Dropbox and secured by Trezor. Designed for previous Chrome extension users of Trezor Password Manager.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Use this utility to retrieve passwords stored on Dropbox and secured by Trezor. Designed for former users of the Trezor Password Manager Chrome extension.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake is a system that allows access to censored websites and apps.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Access censored websites and apps using Tor Snowflake, a system designed to bypass restrictions.", "TR_EXPORT_AS": "Export as {as}", "TR_EXPORT_FAIL": "Export failed.", "TR_EXPORT_TO_FILE": "Export to file", @@ -1043,8 +1036,8 @@ "TR_FIRMWARE_LANGUAGE_FETCH_ERROR": "Translation download failed", "TR_FIRMWARE_NEW_FW_DESCRIPTION": "New firmware is now available. Update your device now.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Your device is already updated to the latest firmware. You may reinstall the firmware if needed.", - "TR_FIRMWARE_REVISION_CHECK_FAILED": "Firmware revision check failed. Your Trezor may be counterfeit.", - "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Couldn't perform firmware revision check.", + "TR_FIRMWARE_REVISION_CHECK_FAILED": "Firmware authenticity check failed. Your Trezor may be counterfeit.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Couldn't perform firmware authenticity check.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Completed", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Lightweight firmware supporting only Bitcoin operations.", "TR_FIRMWARE_SUBHEADING_NONE": "Your Trezor is shipped without firmware. Install the latest firmware in order to use your device safely. For Bitcoin-only users, we recommend installing .", @@ -1082,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Got it\n", "TR_GO_TO_ONBOARDING": "Begin setup", "TR_GO_TO_SETTINGS": "Go to settings", - "TR_GO_TO_SUITE": "Access Suite", + "TR_GO_TO_SUITE": "Go to Trezor Suite", "TR_GRAPH_LINEAR": "Linear", "TR_GRAPH_LOGARITHMIC": "Logarithmic", "TR_GRAPH_MISSING_DATA": "XRP, SOL, and all token amounts are in the portfolio balance but aren't currently supported in graph view.", @@ -1101,7 +1094,7 @@ "TR_GUIDE_FORUM": "Trezor Forum", "TR_GUIDE_FORUM_LABEL": "Connect with the Trezor community", "TR_GUIDE_SUGGESTION_LABEL": "How are we doing?", - "TR_GUIDE_SUPPORT": "Contact support", + "TR_GUIDE_SUPPORT": "Contact Trezor Support", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Support & Feedback", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Support & Feedback", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Help us improve", @@ -1233,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Local file system", "TR_LOG": "Application log", "TR_LOGIN_PROCEED": "Proceed", - "TR_LOG_DESCRIPTION": "The log contains all necessary technical information about Trezor Suite. It may be needed when connecting with Trezor Support.", + "TR_LOG_DESCRIPTION": "This log contains essential technical information about Trezor Suite and may be needed when contacting Trezor Support.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Waiting for a round", "TR_LOW_ANONYMITY_WARNING": "Very low privacy. We recommend using at least 1 in 5, as anything below this threshold isn't private.", "TR_LTC_ADDRESS_INFO": "Litecoin changed the address format. Find more info about how to convert your address on our blog. {TR_LEARN_MORE}", @@ -1326,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimism", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1338,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "New", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "New Trezor Bridge is available.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "New Trezor firmware is available! Update your device now.", "TR_NEXT_UP": "Next", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (with non-recommended \"{char}\")", + "TR_NON_ASCII_CHARS": "{label} (with non-recommended characters)", "TR_NORMAL_ACCOUNTS": "Default accounts", "TR_NORTH": "North", "TR_NOTHING_TO_ANONYMIZE": "Nothing to make private", @@ -1358,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Standard wallet", "TR_NO_SEARCH_RESULTS": "No results for your search criterion", "TR_NO_SPENDABLE_UTXOS": "There are no spendable UTXOs in your account.", - "TR_NO_TRANSPORT": "Browser can't communicate with device", + "TR_NO_TRANSPORT": "Your browser can't communicate with your device", "TR_NO_TRANSPORT_DESKTOP": "App can't communicate with device", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {account} other {accounts}} • {fiatValue}", "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transaction} other {transactions}}", "TR_OFF": "off", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "The chosen amount of {amount} is higher than the accepted maximum of {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "The chosen amount of {amount} is higher than the accepted maximum of {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "The chosen amount of {amount} is lower than the accepted minimum of {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "The chosen amount of {amount} is lower than the accepted minimum of {min}.", "TR_OFFICIAL_LANGUAGES": "Official", "TR_OK": "OK", "TR_ON": "on", @@ -1426,40 +1417,18 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Other inputs and outputs", "TR_OUTGOING": "Outgoing", "TR_OUTPUTS": "Outputs", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "The minimum and maximum amount for which this user is willing to sell {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "You need to initiate the transaction on {providerName} – make sure to follow the steps below carefully.", - "TR_P2P_GET_STARTED_ITEM_1": "Select “Go to {providerName}” to be redirected to our partner’s website.", - "TR_P2P_GET_STARTED_ITEM_3": "Once {providerName} asks you for a release address, return here and continue.", - "TR_P2P_GET_STARTED_ITEM_4": "Almost there! Reveal and copy your address, paste it into the “Release address” field back on {providerName}, and finalize the transaction.", - "TR_P2P_GO_TO_PROVIDER": "Go to {providerName}", - "TR_P2P_INFO": "With {peerToPeer} (P2P) technology, there is no KYC verification involved neither on the side of the buyer nor the seller. All parties are protected against fraud by secure {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "I’m ready to buy", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Peer-to-Peer Buy {cryptocurrency} with {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Legal notice", - "TR_P2P_MODAL_SECURITY_HEADER": "Security first with your Trezor", - "TR_P2P_MODAL_TERMS_1": "You’re here to buy cryptocurrency from another person you choose using Peer-to-Peer (P2P) technology without ID verification. If you were directed to this site for any other reason, please contact support before proceeding.", - "TR_P2P_MODAL_TERMS_2": "You understand that cryptocurrency transactions are irreversible and may not be refunded. Thus, fraudulent or accidental losses may be unrecoverable.", - "TR_P2P_MODAL_TERMS_4": "You understand that Invity does not provide this service. {provider}’s terms govern the service.", - "TR_P2P_MODAL_TERMS_5": "I'm not using this feature for gambling, fraud, or any activity that violates Invity’s or the provider's Terms of Service, or any applicable laws.", - "TR_P2P_MODAL_TERMS_6": "You understand that cryptocurrencies are an emerging financial tool and that regulations may vary in different jurisdictions. This may put you at a higher risk of fraud, theft, or market instability.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Verified partners by Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "The transaction needs to be completed within this time limit, counting from creating a contract on the {providerName} site.", - "TR_P2P_PRICE": "Price for 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "{symbol} price offered by this user.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Hey there, I’m using {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "The chosen amount of {amount} is higher than the accepted maximum of {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "The chosen amount of {amount} is lower than the accepted minimum of {minimum}.", "TR_PAGINATION_NEWER": "Newer", "TR_PAGINATION_OLDER": "Older", "TR_PASSPHRASE_CASE_SENSITIVE": "Note: Passphrase is case-sensitive.", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "It's important to first learn how a passphrase works", "TR_PASSPHRASE_DESCRIPTION_ITEM2": "A passphrase opens a wallet secured by that phrase", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "No one can recover it, not even Trezor support", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "No one can recover it, not even Trezor Support", "TR_PASSPHRASE_HIDDEN_WALLET": "Passphrase wallet", "TR_PASSPHRASE_MISMATCH": "Passphrase mismatch", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "The passphrases didn't match. For security, start over and enter them correctly.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Start over", + "TR_PASSPHRASE_NON_ASCII_CHARS": "We recommend using ABC, abc, 123, spaces or these special characters", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "Use of unlisted special characters may impact future compatibility.", "TR_PASSPHRASE_TOO_LONG": "Passphrase length exceeds the allowed limit.", "TR_PASSPHRASE_WALLET": "Passphrase wallet #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Learn how a passphrase works", @@ -1501,7 +1470,7 @@ "TR_PIN_SUBHEADING": "Using a strong PIN protects your Trezor from unauthorized physical access.", "TR_PLAY_IT_SAFE": "Let's play it safe", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Please enable your camera to scan QR codes.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Please connect your device to continue with the verification process.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Connect your Trezor to continue with the verification process.", "TR_PLEASE_ENABLE_PASSPHRASE": "Please enable the passphrase feature to continue with the verification process.", "TR_POLICY_ID_ADDRESS": "Policy ID:", "TR_PRIMARY_FIAT": "Fiat currency", @@ -1509,7 +1478,6 @@ "TR_PRIVATE_DESCRIPTION": "Privacy at least {targetAnonymity}", "TR_PROCEED_UNVERIFIED_ADDRESS": "Proceed with unverified address", "TR_PROMO_BANNER_DASHBOARD": "The most convenient hardware wallet to securely manage your crypto", - "TR_QR_CODE": "QR code", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Confirm on Trezor before scanning", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Please confirm the receiving address on your Trezor device first, as its trusted display can't be hacked.", "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Enabled", @@ -1519,6 +1487,13 @@ "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Update available ({newVersion})", "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Up to date ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite downloaded a new update.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite's been updated.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Trezor Suite update now available", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Restart & update", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Start update", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Trezor update now available", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "What’s new?", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "You may be asked to type some words that aren't part of your wallet backup", "TR_RANGE": "range", "TR_READ_AND_UNDERSTOOD": "I've read and understood the above", @@ -1570,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Safety checks", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "Prompt", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Allow potentially unsafe actions, such as mismatching keys or allowing extreme fees, by manually approving them on your Trezor.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Do not change this unless you know what you're doing!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Only change this if you know what you're doing!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Strict", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Full Trezor security.", "TR_SCAN_QR_CODE": "Scan QR code", @@ -1580,10 +1555,10 @@ "TR_SEARCH_TRANSACTIONS": "Search transactions", "TR_SEARCH_UTXOS": "Search for a specific address, transaction ID, or label", "TR_SECURITY_CHECKPOINT_GOT_SEED": "Do you have your wallet backup?", - "TR_SECURITY_CHECK_HOLOGRAM": "Please note that device packaging, including holograms and security seals, have been updated over time. You can verify packaging details here. Ensure that your device was purchased from either the official Trezor Shop or from one of our trusted sellers. Otherwise, there's a risk that your device might be a counterfeit. If you suspect that your device is not genuine, please contact Trezor support.", + "TR_SECURITY_CHECK_HOLOGRAM": "Please note that device packaging, including holograms and security seals, have been updated over time. You can verify packaging details here. Ensure that your device was purchased from either the official Trezor Shop or from one of our trusted sellers. Otherwise, there's a risk that your device might be a counterfeit. If you suspect that your device is not genuine, please contact Trezor Support.", "TR_SECURITY_FEATURES_COMPLETED_N": "Security ({n} of {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Devices set up in seedless mode can't access Trezor Suite. This is to avoid irreversible coin loss, which happens when using an improperly set up device for the wrong purpose.", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Seedless setup is not supported by Trezor Suite", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Devices set up in seedless mode can't access Trezor Suite to prevent irreversible coin loss, which can occur if a device is used incorrectly.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Seedless setup isn't supported in Trezor Suite", "TR_SEED_BACKUP_LENGTH": "Your wallet backup may contain 12, 18, or 24 words.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Your wallet backup may contain 12, 18, 20, 24, or 33 words.", "TR_SEED_CHECK_FAIL_TITLE": "Wallet backup check failed", @@ -1593,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Enter the words from your wallet backup in the order displayed on your Trezor.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Using the touchscreen display, enter all the words in the correct order until completed.", "TR_SEE_DETAILS": "See details", + "TR_SEE_IF_ISSUE_PERSISTS": "See if issue persists.", "TR_SELECTED": "{amount} selected", "TR_SELECT_COIN_FOR_SETTINGS": "Select active coin to change settings", "TR_SELECT_DEVICE": "Select device", + "TR_SELECT_NAME_OR_ADDRESS": "Search by name, symbol, network, or contract address", "TR_SELECT_NUMBER_OF_WORDS": "Select the number of words in your wallet backup", "TR_SELECT_PASSPHRASE_SOURCE": "Select where to enter passphrase on \"{deviceLabel}\" .", "TR_SELECT_RECOVERY_METHOD": "Select recovery method", + "TR_SELECT_TOKEN": "Select a token", "TR_SELECT_TREZOR": "Select Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Select your Trezor to continue.", "TR_SELECT_TYPE": "Select type", @@ -1629,7 +1607,7 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Sell {cryptocurrency} with {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Legal notice", "TR_SELL_MODAL_SECURITY_HEADER": "Security first with your Trezor", - "TR_SELL_MODAL_TERMS_1": "You're here to sell cryptocurrency. If you were directed to this site for any other reason, please contact support before proceeding.", + "TR_SELL_MODAL_TERMS_1": "You're here to sell cryptocurrency. If you were directed to this site for any other reason, please contact Trezor Support before proceeding.", "TR_SELL_MODAL_TERMS_2": "You're selling cryptocurrency for your own account. You acknowledge that the provider's policies may require identity verification.", "TR_SELL_MODAL_TERMS_3": "You understand that cryptocurrency transactions are irreversible and may not be refunded. Thus, fraudulent or accidental losses may be unrecoverable.", "TR_SELL_MODAL_TERMS_4": "You understand that Invity does not provide this service. {provider}’s terms govern the service.", @@ -1642,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Rejected", "TR_SELL_STATUS_PENDING": "Pending", "TR_SELL_STATUS_SUCCESS": "Approved", - "TR_SELL_TRANS_ID": "Trans. ID:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum is {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum is {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "View details", "TR_SENDFORM_LABELING_EXAMPLE_1": "Savings", "TR_SENDFORM_LABELING_EXAMPLE_2": "Rent", "TR_SENDING_SYMBOL": "Sending {multiple, select, true {multiple tokens} false {{symbol}} other {{symbol}}}", @@ -1700,6 +1674,8 @@ "TR_SHOW_LOG": "Show log", "TR_SHOW_MORE": "Show more", "TR_SHOW_MORE_ADDRESSES": "Show more ({count})", + "TR_SHOW_ON_TRAY": "Show icon in tray", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Monitor if Trezor Suite is running in the background.", "TR_SHOW_UNVERIFIED_ADDRESS": "Show unverified address", "TR_SHOW_UNVERIFIED_XPUB": "Show unverified public key", "TR_SIDEBAR_ADD_COIN": "Add a coin", @@ -1712,7 +1688,9 @@ "TR_SIZE": "Size", "TR_SKIP": "Skip", "TR_SKIP_BACKUP": "Skip Backup", + "TR_SKIP_BACKUP_DESCRIPTION": "A wallet backup lets you recover your funds if your Trezor is lost, stolen, or damaged. Without a backup, you could lose access to your crypto permanently.", "TR_SKIP_PIN": "Skip PIN", + "TR_SKIP_PIN_DESCRIPTION": "A device PIN prevents unauthorized access to your Trezor. Without it, anyone with your device can access your funds.", "TR_SKIP_ROUNDS": "Round skipping", "TR_SKIP_ROUNDS_DESCRIPTION": "By allowing rounds to be skipped, you make it more difficult to prove any relation between your inputs. This means you can further obfuscate the origin of the funds.", "TR_SKIP_ROUNDS_HEADING": "Allow Trezor to skip rounds", @@ -1725,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Adding to staking pool", "TR_STAKE_ANY_AMOUNT_ETH": "Stake a minimum amount of {amount} {symbol} and start earning rewards. With our current APY rate of {apyPercent}%, your rewards earn too!", "TR_STAKE_APY": "Annual Percentage Yield", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Annual Percentage Yield", "TR_STAKE_AVAILABLE": "Available", "TR_STAKE_CAN_CLAIM_WARNING": "You can already claim {amount} {symbol}. {br}Please claim or wait until new unstake is processed.", @@ -1765,10 +1744,12 @@ "TR_STAKE_LEARN_MORE": "Learn more", "TR_STAKE_LEAVE_STAKING_POOL": "Leave staking pool", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "We’ve left {amount} {symbol} out so you can pay for withdrawal fees.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "We’ve left a small amount of {symbol} out so you can pay for withdrawal fees.", "TR_STAKE_MAX": "Max", "TR_STAKE_MAX_FEE_DESC": "Maximum fee is the network transaction fee that you’re willing to pay on the network to ensure your transaction gets processed.", "TR_STAKE_MAX_REWARD_DAYS": "Max {count, plural, one {# day} other {# days}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "Minimum amount to stake is {amount} {symbol}", + "TR_STAKE_MONTHLY": "Monthly", "TR_STAKE_NEXT_PAYOUT": "Next reward payout", "TR_STAKE_NOT_ENOUGH_FUNDS": "Not enough {symbol} to pay network fees", "TR_STAKE_ONLY_REWARDS": "Only rewards", @@ -1798,26 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Unstaked and ready to claim", "TR_STAKE_UNSTAKE_TO_CLAIM": "Unstake to claim", "TR_STAKE_UNSTAKING": "Unstaking", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Approximate amount of {symbol} available instantly.", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "The liquidity of the staking pool can allow for instant unstake of some funds. The remaining funds will follow the unstaking period.", "TR_STAKE_UNSTAKING_PERIOD": "Unstaking period", "TR_STAKE_UNSTAKING_PROCESS": "Unstaking process", "TR_STAKE_UNSTAKING_TAKES": "Unstaking currently takes {count, plural, one {# day} other {# days}}. Once completed, you can trade or send your funds.", + "TR_STAKE_WEEKLY": "Weekly", "TR_STAKE_WHAT_IS_STAKING": "What is staking?", + "TR_STAKE_YEARLY": "Yearly", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Your staked funds are maintained by Everstake", "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} staked instantly!", "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} unstaked instantly!", + "TR_STAKING_CONSOLIDATING_FUNDS": "Consolidating your {symbol} for you", "TR_STAKING_DELEGATE": "Delegate", "TR_STAKING_DEPOSIT": "Refundable Deposit", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "The deposit fee is {feeAmount} ADA and is required to register your address to start staking. If you choose to unstake your Cardano you will get the deposit back.", + "TR_STAKING_ESTIMATED_GAINS": "Estimated gains", "TR_STAKING_FEE": "Fee", + "TR_STAKING_GETTING_READY": "Your {symbol} is getting ready to work", "TR_STAKING_INSTANTLY_STAKED": "You've instantly staked {amount} {symbol}. {days, plural, =0 {} one {The remaining {symbol} will be staked within # day.} other { The remaining {symbol} will be staked within # days}}", "TR_STAKING_IS_NOT_SUPPORTED": "Staking is not supported on this network.", "TR_STAKING_NOT_ENOUGH_FUNDS": "You don't have enough funds on your account.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Once you confirm", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "By staking on a Trezor stake pool you are directly supporting Trezor and the Cardano ecosystem within Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "You are delegating on a third-party stake pool", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "The stake pool you are delegating on is oversaturated. Please redelegate your stake to maximize your staking rewards", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Stake pool is oversaturated", "TR_STAKING_REDELEGATE": "Redelegate", "TR_STAKING_REWARDS": "Available Rewards", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Rewards are automatically restaked", "TR_STAKING_REWARDS_DESCRIPTION": "Please note that it can take up to 20 days until you start receiving your rewards after initial stake registration and delegation. After this period is completed you will receive your reward every 5 days.", "TR_STAKING_REWARDS_TITLE": "Cardano Staking is Active", "TR_STAKING_STAKE_ADDRESS": "Your stake address", @@ -1826,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Couldn't reach Trezor stake pool to delegate on.", "TR_STAKING_TX_PENDING": "Your transaction {txid} was successfully sent to the blockchain and is waiting for confirmation.", "TR_STAKING_WITHDRAW": "Withdraw", + "TR_STAKING_YOUR_EARNINGS": "Your earnings are automatically restaked, allowing you to earn compound interest.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Your unstaked {symbol} is ready", + "TR_STAKING_YOU_ARE_HERE": "You're here", "TR_STANDARD_WALLET_DESCRIPTION": "No passphrase", "TR_START": "Start", "TR_START_AGAIN": "Start again", @@ -1834,6 +1827,7 @@ "TR_START_COINJOIN": "Start coinjoin", "TR_START_RECOVERY": "Start recovery", "TR_STEP": "Step {number}", + "TR_STEP_OF_TOTAL": "Step {index} of {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Still don’t see your Trezor?", "TR_STOP": "Stop", "TR_STOPPING": "Stopping", @@ -1842,7 +1836,7 @@ "TR_SUITE_META_DESCRIPTION": "New desktop & browser app for Trezor hardware wallets. Trezor Suite brings significant improvements across our three key pillars of usability, security, and privacy.", "TR_SUITE_STORAGE": "App storage", "TR_SUITE_VERSION": "Trezor Suite version", - "TR_SWITCH_DEVICE": "Switch Device", + "TR_SWITCH_DEVICE": "Switch device", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_CANCEL_BUTTON": "Cancel", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DESCRIPTION": "Your funds and transactions won’t be visible until you reconnect your device.", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_DESCRIPTION": "Your funds and transactions won't be visible until you reconnect your device.", @@ -1885,7 +1879,11 @@ "TR_TOKENS_EMPTY": "No tokens... yet.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "No tokens. They may be hidden.", "TR_TOKENS_SEARCH_TOOLTIP": "Search by token, symbol, or contract address.", + "TR_TOKEN_NOT_FOUND": "No token found", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Token not found on the {networkName} network.", "TR_TOKEN_TRANSFERS": "{standard} Token Transfers", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Try a different search.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Try a different search or switch to another network.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Unrecognized tokens", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Unrecognized tokens pose potential risks. Use caution.", "TR_TOO_LONG": "Message is too long", @@ -1913,7 +1911,7 @@ "TR_TOR_ERROR": "Error", "TR_TOR_IS_SLOW_MESSAGE": "Tor is connecting to the network.

Hang in there.", "TR_TOR_KEEP_RUNNING": "Keep running Tor", - "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Please select 'Keep running Tor' to continue or 'Stop Tor' to quit the coinjoin process.", + "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Please select \"Keep running Tor\" to continue or \"Stop Tor\" to quit the coinjoin process.", "TR_TOR_MISBEHAVING": "Misbehaving", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Disable Tor and switch to default backends", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Leave", @@ -1929,11 +1927,7 @@ "TR_TO_BTC": "To BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "To make your labels consistent and available on different devices, connect to a cloud storage provider.", "TR_TO_SATOSHIS": "To sats", - "TR_TRADE_BUYS": "buys", - "TR_TRADE_ENTER_COIN": "Enter crypto name or symbol...", - "TR_TRADE_EXCHANGES": "exchanges", "TR_TRADE_REDIRECTING": "Redirecting ...", - "TR_TRADE_SELLS": "sells", "TR_TRANSACTIONS_NOT_AVAILABLE": "Transaction history not available", "TR_TRANSACTIONS_SEARCH_TIP_1": "Tip: You can search for transaction IDs, addresses, tokens, labels, amounts, and dates.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Tip: Combine AND (&) and OR (|) operators for more complex searches. For example > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 will show all transactions in January or December {lastYear}.", @@ -1948,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Search by transaction ID, label or amount or use operators such as < > | & = !=.", "TR_TRANSACTION_DETAILS": "Details", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor Bridge running version {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutorial canceled", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutorial completed", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Learn how to use your device with the help of a short tutorial", @@ -1955,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Close other tabs and windows that might be using your Trezor", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "After closing other tabs and windows, try refreshing this page.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "After closing other browser tabs and windows, try quitting and reopening Trezor Suite.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Steps required to enable communication", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Try these steps to solve this issue.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Visit Trezor Bridge status page", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Ensure the Trezor Bridge process is running", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Only Chromium-based browsers currently allow direct communication with USB devices", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Only Chromium-based browsers currently allow direct communication with USB devices.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Use a Chromium-based browser", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "The cable must be fully inserted. In case of a USB-C connected device, the cable should click into place.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Try a different cable", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "With Trezor Bridge installed.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Try using a different computer, if you can", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Just in case", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Try restarting your computer", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Restarting your computer may fix the communication issue between your browser and device.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Restart your computer", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Run the Trezor Suite desktop app", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Use the Trezor Suite desktop app", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Click to toggle an alternative bridge implementation. Current version: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Use another version of Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Try installing udev rules. Make sure they are saved to the desktop before opening.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "If you last updated your device firmware in 2019 or earlier, please follow the instructions in the Knowledge Base", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "It appears that you may be using an older Trezor model.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Connect it directly to your computer (without a USB hub).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Try a different USB port", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Install rules automatically", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Missing udev rules", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Unexpected state: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Your device is connected properly, but your browser can't communicate with it at the moment. You need to install Trezor Bridge.", "TR_TRY_AGAIN": "Try again", "TR_TXID": "TX ID", "TR_TXID_RBF": "Original TX ID to be replaced", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transaction confirmed", "TR_TX_CONFIRMING": "Confirming transaction", + "TR_TX_DATA_FUNCTION": "Function", + "TR_TX_DATA_INPUT_DATA": "Input data", + "TR_TX_DATA_METHOD": "Input data", + "TR_TX_DATA_METHOD_NAME": "Method name", + "TR_TX_DATA_PARAMS": "Params", "TR_TX_DEPOSIT": "Deposit", "TR_TX_FEE": "Fee", "TR_TX_TAB_AMOUNT": "Amount", @@ -2020,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Firmware update required. You can change your homescreen in the settings page later", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Update your firmware to change your homescreen", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Update available", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Enable automatic updates", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "New", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Update & restart", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Install update now?", "TR_UPDATE_MODAL_NOT_NOW": "Not now", @@ -2027,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "Download", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Update downloaded", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Update on quit", + "TR_UPDATE_MODAL_WHATS_NEW": "What’s new?", + "TR_UPDATE_MODAL_YOUR_VERSION": "Your version: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Upgrade your firmware to access this account. See the blue banner above.", "TR_UP_TO": "up to", "TR_UP_TO_DATE": "Up to date", "TR_UP_TO_DAYS": "up to {count, plural, one {# day} other {# days}}", @@ -2080,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Access Passphrase wallet", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Hidden wallet", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Create a new wallet or recover one using your wallet backup.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "We’re always striving to enhance your Trezor experience. Here’s what’s new:", "TR_WEST": "West", "TR_WHAT_DATA_WE_COLLECT": "What data do we collect?", "TR_WHAT_IS_PASSPHRASE": "Learn more about the difference", diff --git a/packages/suite-data/files/translations/es.json b/packages/suite-data/files/translations/es.json index 02887b2f154..9059706acff 100644 --- a/packages/suite-data/files/translations/es.json +++ b/packages/suite-data/files/translations/es.json @@ -1,5 +1,6 @@ { "AMOUNT": "Importe", + "AMOUNT_EXCEEDS_MAX": "El importe excede el valor máximo permitido de {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "El importe debe ser al menos {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "La cuenta del destinatario requiere una reserva mínima de {reserve} XRP para activarse.", "AMOUNT_IS_MORE_THAN_RESERVE": "El importe es superior a la reserva no utilizable requerida ({reserve} XRP).", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "El PIN se ha cambiado correctamente.", "TOAST_QR_INCORRECT_ADDRESS": "El código QR contiene una dirección no válida para esta cuenta.", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "El código QR está definido para la cuenta de {coin}.", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Esquema de protocolo desconocido: \"{scheme}\". Inténtalo de nuevo o introduce la dirección manualmente.", "TOAST_RAW_TX_SENT": "Transacción enviada. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "La configuración se ha modificado correctamente.", "TOAST_SIGN_MESSAGE_ERROR": "Error al firmar el mensaje: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Ir al panel de control", "TR_404_TITLE": "Error 404: No se ha encontrado el enlace.", "TR_7D_CHANGE": "Cambio 7 días", - "TR_ABORT": "Abortar", "TR_ACCESS_HIDDEN_WALLET": "Acceder al monedero protegido por frase de contraseña", "TR_ACCESS_STANDARD_WALLET": "Acceder al monedero estándar", "TR_ACCOUNT_DETAILS_HEADER": "Detalles de la cuenta", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot es un nuevo tipo de dirección que puede mejorar la privacidad y la eficiencia de la red. Ten en cuenta que es posible que algunos servicios aún no sean compatibles con las direcciones Taproot.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "El método actual y comúnmente aceptado para generar y gestionar direcciones de Cardano garantiza la interoperabilidad, la seguridad y la gestión de todo tipo de tokens.", "TR_ACCOUNT_TYPE_COINJOIN": "CoinJoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Predeterminado", "TR_ACCOUNT_TYPE_IMPORTED": "Importado", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Las cuentas Ledger son compatibles con las rutas de derivación de Ledger Live, lo que permite una migración fluida de Ledger a Trezor.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Las cuentas Legacy son compatibles con las rutas de derivación de Ledger Legacy, lo que permite una migración fluida de Ledger a Trezor.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "El método actual y comúnmente aceptado para generar y gestionar direcciones de {value} garantiza la interoperabilidad, la seguridad y la gestión de todo tipo de tokens.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "El método actual y comúnmente aceptado para generar y gestionar direcciones de Solana garantiza la interoperabilidad, la seguridad y la gestión de tokens SOL y SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "No compatible.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Este tipo de cuenta no es compatible con este modelo de Trezor.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Actualiza el firmware del dispositivo para habilitar este tipo de cuenta.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP es una moneda digital con la que se pueden realizar pagos transfronterizos con rapidez y por poco precio sin recurrir a la minería tradicional, utilizando un libro contable de consenso para confirmar rápidamente las transacciones.", "TR_ACQUIRE_DEVICE": "Utilizar el Trezor aquí", "TR_ACQUIRE_DEVICE_TITLE": "Hay otra sesión en curso.", "TR_ACTIVATED_COINS": "Monedas activadas", "TR_ACTIVE": "activada", + "TR_ADD": "Añadir", "TR_ADDRESSES": "Dirección", "TR_ADDRESSES_CHANGE": "Cambiar direcciones", "TR_ADDRESSES_FRESH": "Nuevas direcciones", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Copiar dirección", "TR_ADDRESS_MODAL_TITLE": "Dirección de destino de {networkName}", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "Dirección de destino de {networkCurrencyName} en la red {networkName}", - "TR_ADDRESS_PHISHING_WARNING": "Para evitar ataques de phishing, deberías verificar la dirección en tu Trezor. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Para evitar ataques de phishing, verifica la dirección de destino en tu Trezor. {claim}", "TR_ADD_ACCOUNT": "Añadir cuenta", "TR_ADD_HIDDEN_WALLET": "Monedero protegido por frase de contraseña", "TR_ADD_NETWORK_ACCOUNT": "Añadir cuenta {network}", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Uso de datos", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Todos los datos se almacenan en el más estricto anonimato. Solo los usamos para mejorar el ecosistema Trezor.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Actualizaciones automáticas de Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite descarga automáticamente la versión más reciente en segundo plano y la instala al reiniciar la aplicación. De este modo, siempre estarás al día de las últimas funciones y medidas de seguridad. Las actualizaciones se producen sin solicitar tu permiso.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Descarga automáticamente la versión más reciente de Trezor Suite en segundo plano e instálala al reiniciar la aplicación. De este modo, siempre estarás al día de las últimas funciones y medidas de seguridad. Las actualizaciones se producen sin solicitar tu permiso.", + "TR_ALL_NETWORKS": "Todas las redes ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Ve los tokens de todas las {networkCount} redes. Filtra por las redes más populares.", "TR_ALL_TRANSACTIONS": "Transacciones", "TR_AMOUNT_SENT": "Importe enviado", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "No es adecuado para CoinJoin. El importe es demasiado grande.", @@ -355,13 +366,13 @@ "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "¿Seguro? Tu dispositivo solo se puede utilizar con una aplicación a la vez. Si estás utilizando otra aplicación con tu dispositivo Trezor, cierra primero esa sesión.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Tu navegador no es compatible. Para disfrutar de la mejor experiencia, descarga y ejecuta la aplicación de escritorio Trezor Suite en segundo plano o utiliza un navegador compatible con WebUSB que se base en Chromium.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Otra aplicación ha solicitado Trezor Suite para conectarse con tu dispositivo Trezor. Mantén Trezor Suite ejecutándose en segundo plano y vuelve a intentarlo en la otra aplicación.", + "TR_BRIDGE_TIP_AUTOSTART": "Sugerencia: Activa la función de inicio automático y permite que Bridge se ejecute siempre en segundo plano.", "TR_BTC_UNITS": "Unidades de bitcoin", "TR_BUG": "Error", "TR_BUMP_FEE": "Aumentar comisión", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "Para acelerar las transacciones, aumenta la comisión en la transacción pendiente más antigua (por nonce) de la cola. Las transacciones deben confirmarse en orden. Más información", "TR_BUY": "Comprar", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Transacciones de trading", "TR_BUY_BUY": "Comprar", - "TR_BUY_BUY_AGAIN": "Comprar de nuevo", "TR_BUY_CONFIRMED_ON_TREZOR": "Se ha confirmado en Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Volver a Cuenta", "TR_BUY_DETAIL_ERROR_SUPPORT": "Visitar la página de atención al cliente del proveedor", @@ -386,12 +397,12 @@ "TR_BUY_MODAL_FOR_YOUR_SAFETY": "Comprar {cryptocurrency} con {provider}", "TR_BUY_MODAL_LEGAL_HEADER": "Aviso legal", "TR_BUY_MODAL_SECURITY_HEADER": "La seguridad es lo primero con tu Trezor.", - "TR_BUY_MODAL_TERMS_1": "Estás aquí para comprar criptomonedas. Si has llegado a este sitio por cualquier otra razón, ponte en contacto con el servicio de atención al cliente {provider} antes de continuar.", - "TR_BUY_MODAL_TERMS_2": "Estás utilizando esta función para comprar fondos que se enviarán a una cuenta bajo tu control personal directo.", - "TR_BUY_MODAL_TERMS_3": "Las transacciones con criptomonedas son irreversibles y no se pueden reembolsar. Por lo tanto, podrías no recuperar las pérdidas, ya se deban a error o a tácticas fraudulentas.", - "TR_BUY_MODAL_TERMS_4": "Ten en cuenta Invity no proporciona este servicio, que en cambio se rige por los términos de {provider}.", - "TR_BUY_MODAL_TERMS_5": "No utilices esta función para apostar, cometer un fraude o infringir de cualquier otro modo los términos de servicio de Invity o del proveedor, ni cualquier normativa aplicable.", - "TR_BUY_MODAL_TERMS_6": "Ten presente que las criptomonedas son una herramienta financiera emergente y que las regulaciones pueden variar en distintas jurisdicciones, por lo que el mercado es más inestable y puedes exponerte a un mayor riesgo de fraude o robo.", + "TR_BUY_MODAL_TERMS_1": "Estoy aquí para comprar criptomonedas. Si hubiera llegado a este sitio por cualquier otra razón, me pondría en contacto con el servicio de atención al cliente de {provider} antes de continuar.", + "TR_BUY_MODAL_TERMS_2": "Estoy utilizando esta función para comprar criptomonedas y recibirlas en mi cuenta.", + "TR_BUY_MODAL_TERMS_3": "Entiendo que las transacciones con criptomonedas son definitivas y que no pueden deshacerse ni reembolsarse. Las pérdidas ocasionadas por fraude o por equivocaciones podrían no ser recuperables.", + "TR_BUY_MODAL_TERMS_4": "Entiendo que Invity no facilita este servicio. Está sujeto a los términos y condiciones de {provider}.", + "TR_BUY_MODAL_TERMS_5": "No voy a utilizar esta función para apostar, cometer fraude o llevar a cabo cualquier actividad que infrinja los términos de servicio de Invity o del proveedor ni cualquier normativa de aplicación.", + "TR_BUY_MODAL_TERMS_6": "Entiendo que las criptomonedas son un instrumento financiero emergente y que la normativa puede variar en función de la región, por lo que puede aumentar el riesgo de fraude, de robo o de inestabilidad del mercado.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Socios verificados por Invity", "TR_BUY_NETWORK": "Comprar {network}", "TR_BUY_NOT_TRANSACTIONS": "Aún no hay transacciones.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "Pendiente", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Pendiente", "TR_BUY_STATUS_SUCCESS": "Se ha aprobado", - "TR_BUY_TRANS_ID": "ID de transacción:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "El máximo es {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "El máximo es {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "El mínimo es {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "El mínimo es {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Ver detalles", "TR_BYTES": "bytes", "TR_CAMERA_NOT_RECOGNIZED": "No se ha reconocido la cámara.", "TR_CAMERA_PERMISSION_DENIED": "Se ha denegado el permiso para acceder a la cámara.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Importe de Trezor", "TR_CHAINED_TXS": "Transacciones encadenadas", "TR_CHANGELOG": "Registro de cambios", - "TR_CHANGELOG_ON_GITHUB": "Registro de cambios en GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Se trata de una dirección de cambio creada a partir de un envío anterior.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Puedes cambiar el tipo de firmware en Configuración en cualquier momento.", "TR_CHANGE_HOMESCREEN": "Cambiar pantalla de inicio", "TR_CHANGE_PIN": "Cambiar PIN", "TR_CHANGE_WIPE_CODE": "Cambiar el código de borrar", + "TR_CHECKED_BALANCES_ON": "Saldos comprobados en", "TR_CHECKING_YOUR_DEVICE": "Comprobación del dispositivo", "TR_CHECKSUM_CONVERSION_INFO": "Convertido a suma de comprobación. Más información", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Verificaremos la integridad de tu dispositivo Trezor, garantizando su seguridad y confirmando la autenticidad del chip.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Realiza una recuperación simulada para verificar tu copia de seguridad del monedero.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Introduce aquí las palabras de la copia de seguridad del monedero en el orden en el que aparecen en tu dispositivo. Es posible que se te pida que escribas algunas palabras que no son parte de tu copia de seguridad del monedero como medida de seguridad adicional.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Utiliza el teclado de 2 botones para introducir tu copia de seguridad del monedero. Así, tu información sensible se mantiene a salvo de navegadores o sistemas poco seguros.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Tu copia de seguridad del monedero se introduce utilizando la pantalla táctil. Así se evita introducir cualquier información sensible en un navegador o un sistema potencialmente inseguro.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Tu copia de seguridad del monedero se introduce utilizando la pantalla táctil. Así se evita introducir cualquier información sensible en un navegador o un sistema potencialmente inseguro.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Tu copia de seguridad del monedero se introduce utilizando la pantalla táctil. Así se evita introducir cualquier información sensible en un navegador o un sistema potencialmente inseguro.", "TR_CHECK_SEED": "Revisar copia de seguridad", "TR_CHECK_YOUR_DEVICE": "Comprueba la pantalla de tu Trezor.", "TR_CHOOSE_RECOVERY_TYPE": "Selecciona el tipo de recuperación.", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Protegido por tu Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Transacciones CoinJoin", "TR_COINMARKET_BEST_RATE": "Mejor tipo de cambio", + "TR_COINMARKET_BUY_AND_SELL": "Comprar y vender", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} compra} one {{totalBuys} compra} other {{totalBuys} compras} } • {totalSells, plural, =0 {{totalSells} venta} one {{totalSells} venta} other {{totalSells} ventas} }", + "TR_COINMARKET_CEX_TOOLTIP": "Exchange Centralizado", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Cambia el importe o la moneda.", "TR_COINMARKET_COMPARE_OFFERS": "Comparar todas las ofertas", "TR_COINMARKET_COUNTRY": "País de residencia", + "TR_COINMARKET_DCA_DOWNLOAD": "Descarga la aplicación móvil de Invity para empezar a ahorrar bitcoins", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Un plan de ahorros de custodia seguro y simple basado en el coste medio de adquisición (DCA).", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Desarrollado por SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Elige la autocustodia sin pagar comisiones adicionales.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Retiros gratuitos", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Una interfaz rápida, simplificada e intuitiva.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Fácil de usar", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Haz un seguimiento del historial, la cantidad y la frecuencia de las inversiones.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Resumen del DCA", + "TR_COINMARKET_DCA_HEADING": "Ahorra bitcoins con la aplicación de Invity", + "TR_COINMARKET_DEX_TOOLTIP": "Exchanges descentralizados (DEX)", "TR_COINMARKET_ENTER_AMOUNT_IN": "Introduce el importe en {currency}", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Todas las opciones de KYC", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "Nunca se requiere la verificación KYC", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Todas las ofertas de exchange centralizados (CEX) y descentralizados (DEX)", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX de interés fijo", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX de interés flotante", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Mediante un exchange descentralizado (DEX) puedes negociar con criptos directamente en la cadena de bloques sin necesidad de contar con una autoridad central o un intermediario.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX de interés fijo", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX de interés flotante", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Ofertas incluidas", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Pago:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Método de recepción", "TR_COINMARKET_FEES_INCLUDED": "Comisiones incluidas", "TR_COINMARKET_FEES_NOT_INCLUDED": "Comisiones no incluidas", "TR_COINMARKET_FEES_ON_WEBSITE": "El precio indicado no incluye algunas comisiones. Podrás consultar el precio final en el sitio web del proveedor.", @@ -518,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "Se solicitará la verificación KYC en casos excepcionales. Se exige la verificación KYC para reembolsos. 👈", "TR_COINMARKET_KYC_POLICY": "Política de KYC", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "Nunca se requiere la verificación KYC", - "TR_COINMARKET_KYC_YES_REFUND": "Se solicitará la verificación KYC en casos excepcionales. No se exige la verificación KYC para reembolsos. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "Solo se solicitará la verificación KYC en casos excepcionales. No se exige para reembolsos. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Últimas transacciones", "TR_COINMARKET_NETWORK_FEE": "Comisión de red", "TR_COINMARKET_NETWORK_TOKENS": "{networkName} tokens", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "No se ha encontrado ningún proveedor de exchange centralizado (CEX)", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "No se ha encontrado ningún proveedor de exchange descentralizado (DEX)", "TR_COINMARKET_NO_METHODS_AVAILABLE": "No hay métodos disponibles", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Recarga automática en", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Volver a la vista de trading", - "TR_COINMARKET_NO_OFFERS_HEADER": "No hay ofertas", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Lo sentimos, no tenemos ninguna oferta en este momento debido a problemas de conectividad con el servidor.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Lo sentimos, no tenemos ninguna oferta en este momento. Intenta recargar la página o cambiar tu consulta.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Recargar la página", "TR_COINMARKET_OFFERS_EMPTY": "No se han encontrado ofertas para tu solicitud. Cambia el país o el importe de la compra", "TR_COINMARKET_OFFERS_REFRESH": "La ofertas se actualizan en", "TR_COINMARKET_OFFERS_SELECT": "Seleccionar", "TR_COINMARKET_OFFER_LOOKING": "Buscando la mejor oferta para ti", - "TR_COINMARKET_OFFER_NO_FOUND": "No hay ofertas disponibles para tu solicitud. Cambia el importe o la moneda.", + "TR_COINMARKET_OFFER_NO_FOUND": "No hay ofertas disponibles para tu solicitud.", "TR_COINMARKET_ON_NETWORK_CHAIN": "En la cadena {networkName}", "TR_COINMARKET_OTHER_CURRENCIES": "Otras monedas", "TR_COINMARKET_PAYMENT_METHOD": "Método de pago", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Método de recepción", "TR_COINMARKET_SELL": "Vender", "TR_COINMARKET_SHOW_OFFERS": "Comparar ofertas", + "TR_COINMARKET_SWAP": "Intercambio", + "TR_COINMARKET_SWAP_AMOUNT": "Cantidad del intercambio", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} intercambio} one {{totalSwaps} intercambio} other {{totalSwaps} intercambios} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Todo listo para el intercambio", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Intercambiar {fromCrypto} por {toCrypto} con {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Aviso legal", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "La seguridad es lo primero con tu Trezor.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Quiero intercambiar criptomonedas usando un exchange descentralizado (DEX) mediante el contrato de {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Quiero intercambiar criptomonedas para mi propia cuenta. Entiendo que las políticas del proveedor pueden requerir la verificación de identidad.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Entiendo que las transacciones con criptomonedas son definitivas y que no pueden deshacerse ni reembolsarse. Las pérdidas ocasionadas por fraude o por equivocaciones podrían no ser recuperables.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Entiendo que Invity no facilita este servicio. Está sujeto a los términos y condiciones de {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "No voy a utilizar esta función para apostar, cometer fraude o llevar a cabo cualquier actividad que infrinja los términos de servicio de Invity o del proveedor ni cualquier normativa de aplicación.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Entiendo que las criptomonedas son un instrumento financiero emergente y que la normativa puede variar en función de la región, por lo que puede aumentar el riesgo de fraude, de robo o de inestabilidad del mercado.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Socios verificados por Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Todo listo para el intercambio", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Intercambiar {fromCrypto} por {toCrypto} con {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Aviso legal", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "La seguridad es lo primero con tu Trezor.", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Estoy aquí para intercambiar criptomonedas. Si hubiera llegado a este sitio por cualquier otra razón, me pondría en contacto con el servicio de atención al cliente de Trezor antes de continuar. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Quiero intercambiar criptomonedas para mi propia cuenta. Entiendo que las políticas del proveedor pueden requerir la verificación de identidad.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Entiendo que las transacciones con criptomonedas son definitivas y que no pueden deshacerse ni reembolsarse. Las pérdidas ocasionadas por fraude o por equivocaciones podrían no ser recuperables.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Entiendo que Invity no facilita este servicio. Está sujeto a los términos y condiciones de {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "No voy a utilizar esta función para apostar, cometer fraude o llevar a cabo cualquier actividad que infrinja los términos de servicio de Invity o del proveedor ni cualquier normativa de aplicación.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Entiendo que las criptomonedas son un instrumento financiero emergente y que la normativa puede variar en función de la región, por lo que puede aumentar el riesgo de fraude, de robo o de inestabilidad del mercado.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Socios verificados por Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} en la red {networkName}", "TR_COINMARKET_TRADE_FEE": "Comisión por operación", + "TR_COINMARKET_TRANS_ID": "ID de transacción:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Proveedor desconocido", + "TR_COINMARKET_VIEW_DETAILS": "Ver detalles", "TR_COINMARKET_YOUR_BEST_OFFER": "Tu mejor oferta", "TR_COINMARKET_YOU_BUY": "Compras", "TR_COINMARKET_YOU_GET": "Obtienes", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Confirmado", "TR_CONFIRMING_TX": "Confirmando la transacción", "TR_CONFIRM_ACTION_ON_YOUR": "Sigue las instrucciones que aparecen en la pantalla de tu Trezor.", + "TR_CONFIRM_ADDRESS": "Confirmar dirección", "TR_CONFIRM_BEFORE_COPY": "Confirmar en Trezor antes de copiar", "TR_CONFIRM_CONDITIONS": "Confirma las condiciones antes de continuar.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Confirmar el monedero protegido por frase de contraseña vacío en el dispositivo {deviceLabel}.", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Gestionar", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Activa el cuadro de diálogo para introducir la frase de contraseña al abrir Trezor Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "¿Utilizas principalmente una frase de contraseña?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Completa la verificación del Trezor para confirmar la dirección de destino. No se recomienda continuar sin confirmar.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Completa la verificación de tu Trezor para confirmar la dirección de destino. No es recomendable continuar sin confirmar.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "No se puede verificar la dirección de destino", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Conecta tu Trezor para enviar monedas.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Tu Trezor no está conectado", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Conecta tu Trezor para hacer envíos", "TR_CONNECT_YOUR_DEVICE": "Conecta y desbloquea tu Trezor", - "TR_CONTACT_SUPPORT": "Ponte en contacto con el servicio de atención al cliente.", + "TR_CONTACT_SUPPORT": "Ponte en contacto con el servicio de atención al cliente de Trezor.", "TR_CONTACT_TREZOR_SUPPORT": "Ponte en contacto con el servicio de atención al cliente de Trezor.", "TR_CONTINUE": "Continuar", "TR_CONTINUE_ANYWAY": "Continuar de todos modos", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Nunca envíes fondos a la dirección de un ID de política.", "TR_COPY_AND_CLOSE": "Copiar y cerrar", "TR_COPY_SIGNED_MESSAGE": "Copiar el mensaje firmado", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copiar", + "TR_COPY_TO_CLIPBOARD": "Copiar", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "No se ha podido recuperar el registro de cambios.", "TR_COULD_NOT_RETRIEVE_DATA": "No se han podido recuperar los datos.", "TR_COUNT_WALLETS": "{count} {count, plural, one {monedero} other {monederos}}", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "El activo no se ha cargado correctamente.", "TR_DASHBOARD_DISCOVERY_ERROR": "Error de descubrimiento", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Las cuentas no se han cargado correctamente {details}.", + "TR_DATA": "Datos", "TR_DATABASE_UPGRADE_BLOCKED": "La actualización de la base de datos está bloqueada por otra instancia de la aplicación.", "TR_DATA_ANALYTICS_CATEGORY_1": "Plataforma", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "SO, modelo de Trezor, versión, etc.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "¿Te has conectado en modo bootloader por error?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Vuelve a conectar el dispositivo sin tocar ningún botón.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Vuelve a conectar el dispositivo sin tocar la pantalla.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Este dispositivo se está utilizando en otro lugar.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "Es posible que la aplicación {transportSessionOwner} esté utilizando este dispositivo actualmente. Puedes asumir el control del dispositivo si es necesario.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Es posible que otra aplicación esté utilizando este dispositivo actualmente. Puedes asumir el control del dispositivo si es necesario.", "TR_DEVICE_CONNECTED_WRONG_STATE": "El dispositivo se ha detectado en estado incorrecto.", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Tu Trezor se desconectó durante el proceso de copia de seguridad. Te recomendamos encarecidamente que utilices la opción de restablecimiento de fábrica en Configuración del dispositivo para borrar todos los datos e inicies de nuevo la copia de seguridad.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Error al comprobar el hash del firmware. Tu Trezor puede ser una falsificación.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "No se ha podido realizar la comprobación del hash del firmware. Tu Trezor puede ser una falsificación.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Desactivar", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Activar", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "La comprobación de la revisión del firmware es una función de seguridad fundamental. Te recomendamos encarecidamente mantenerla activada.", @@ -726,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "El dispositivo {deviceLabel} no tiene copia de seguridad.", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "El dispositivo {deviceLabel} no está conectado.", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "El dispositivo {deviceLabel} no está disponible.", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "La verificación del dispositivo ha fallado.", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Vuelve a conectar tu dispositivo y reintenta la verificación. Si el problema persiste, ponte en contacto con el servicio de atención al cliente de Trezor para averiguar qué está pasando y qué hacer a continuación.", "TR_DEVICE_NOT_CONNECTED": "El dispositivo no está conectado.", "TR_DEVICE_NOT_INITIALIZED": "El Trezor no está configurado.", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Te guiaremos en el proceso para que empieces de inmediato.", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Descargar la versión más reciente: Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "No desconectes tu dispositivo.", "TR_DO_NOT_SHOW_AGAIN": "No mostrar de nuevo", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "¿Omitir este paso?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Arrastra y suelta el archivo aquí o haz clic para seleccionar el archivo.", "TR_DROPZONE_ERROR": "Se ha producido un error durante la importación: {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Unirse", "TR_EARLY_ACCESS_ENABLED": "Programa de acceso anticipado activado", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Unirse", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Entiendo que así puedo probar software en fase de prelanzamiento, que puede contener errores que afecten al funcionamiento normal de Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Entiendo que así puedo probar software en fase de prelanzamiento, que puede contener errores que afecten al funcionamiento normal de Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Puedes desactivarlo en cualquier momento.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Prueba las últimas funciones del producto antes de que se lancen al público general.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Comprueba primero el campo de arriba.", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Puedes comprobar si hay actualizaciones beta ahora o en el próximo lanzamiento.", "TR_EARLY_ACCESS_JOINED_TITLE": "Programa de acceso anticipado activado", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Para volver a la última versión estable de Suite, haz clic en «Descargar versión estable» y vuelve a instalar la aplicación.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Para volver a la última versión estable de Trezor Suite, haz clic en «Descargar versión estable» y vuelve a instalar la aplicación.", "TR_EARLY_ACCESS_LEFT_TITLE": "Has abandonado el programa de acceso anticipado. Ya no se te ofrecerán nuevas versiones beta.", "TR_EARLY_ACCESS_MENU": "Programa de acceso anticipado", "TR_EARLY_ACCESS_REINSTALL": "Descargar versión estable", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Aprueba solo el importe exacto necesario para este intercambio. Tendrás que pagar una cuota adicional si quieres volver a realizar un intercambio similar.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Revocar la aprobación anterior", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Realiza una transacción que eliminará la aprobación previa del contrato con {provider}.", - "TR_EXCHANGE_BUY": "Para", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirmar en Trezor y enviar", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirmar y enviar", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Crear aprobación", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Tu transacción se ha completado correctamente.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Se ha aprobado", "TR_EXCHANGE_DEX": "Oferta de exchange descentralizado", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Las comisiones para realizar este intercambio se estiman en {approvalFee} ({approvalFeeFiat}) para la aprobación (si se requiere) y {swapFee} ({swapFeeFiat}) para el intercambio.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "No quedan fondos para las comisiones de transacción. Reduce el importe de intercambio a un máximo de {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} no es válido.", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Las comisiones fijas te muestran exactamente el importe que recibirás al final del intercambio, por lo que no cambiará entre que seleccionas la comisión y se completa la transacción. Se te garantiza el importe mostrado, pero a cambio de comisiones menos generosas, de modo que podrás comprar menos criptomonedas con el mismo dinero.", "TR_EXCHANGE_FLOAT": "Oferta de comisión variable", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Las comisiones variables implican que el importe final que recibirás puede variar ligeramente por las fluctuaciones del mercado entre que seleccionas la comisión y se completa la transacción. Estas comisiones suelen ser más altas, de modo el importe final en criptomonedas podría ser superior.", - "TR_EXCHANGE_PROVIDER": "proveedor", "TR_EXCHANGE_RATE": "Precio", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "La cuenta de destino está fuera de Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Esta es la dirección alfanumérica específica en la que recibirás tus monedas.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Introduce un número.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Introduce el deslizamiento deseado.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Importe de la oferta de intercambio", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Resumen del deslizamiento", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolerancia de deslizamiento", - "TR_EXCHANGE_TRANS_ID": "ID de transacción:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Utiliza una cuenta ({symbol}) que no esté en Suite.", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Dirección de destino", - "TR_EXCHANGE_VIEW_DETAILS": "Ver detalles", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Actualizaciones automáticas de Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite descarga automáticamente la versión más reciente en segundo plano y la instala al reiniciar la aplicación. De este modo, siempre estarás al día de las últimas funciones y medidas de seguridad. Las actualizaciones se producen sin solicitar tu permiso.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Activar la red BNB Smart Chain sin transacciones internas pasadas.", "TR_EXPERIMENTAL_FEATURES": "Experimental", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Funciones experimentales", "TR_EXPERIMENTAL_FEATURES_WARNING": "Solo para usuarios con experiencia. Utilízala bajo tu propia responsabilidad. Estas funciones están en fase de prueba, pueden ser inestables y podrían dejar de recibir soporte a largo plazo.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migrar contraseñas de Dropbox", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Una herramienta para recuperar contraseñas almacenadas en Dropbox y protegidas por Trezor. Diseñada para antiguos usuarios de la extensión de Chrome del gestor de contraseñas de Trezor.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Usa esta herramienta para recuperar contraseñas almacenadas en Dropbox y protegidas por Trezor. Diseñada para antiguos usuarios de la extensión de Chrome del gestor de contraseñas de Trezor.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake es un sistema que permite acceder a sitios web y aplicaciones censurados.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Accede a sitios web y aplicaciones censurados utilizando Tor Snowflake, un sistema diseñado para eludir restricciones.", "TR_EXPORT_AS": "Exportar como {as}", "TR_EXPORT_FAIL": "Se ha producido un error durante la exportación.", "TR_EXPORT_TO_FILE": "Exportar a archivo", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "El nuevo firmware ya está disponible. Actualiza tu dispositivo ahora.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Tu dispositivo ya está actualizado a la última versión del firmware. Puedes reinstalar el firmware si es necesario.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Error al comprobar la revisión del firmware. Tu Trezor puede ser una falsificación.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "No se pudo realizar la comprobación de firmware.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Completado", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Firmware ligero que solo admite operaciones con bitcoins.", "TR_FIRMWARE_SUBHEADING_NONE": "Tu Trezor se envía sin firmware. Instala el firmware más reciente para utilizar el dispositivo de forma segura. Si solo usas bitcoins, te recomendamos que instales el .", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Listo", "TR_GO_TO_ONBOARDING": "Comenzar la configuración", "TR_GO_TO_SETTINGS": "Ir a Configuración", - "TR_GO_TO_SUITE": "Acceder a Suite", + "TR_GO_TO_SUITE": "Ir a Trezor Suite", "TR_GRAPH_LINEAR": "Lineal", "TR_GRAPH_LOGARITHMIC": "Logarítmica", "TR_GRAPH_MISSING_DATA": "Los importes de tokens XRP y SOL, así como del resto de tokens, se incluyen en el saldo de la cartera, pero por ahora no se pueden visualizar en gráfico.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Foro de Trezor", "TR_GUIDE_FORUM_LABEL": "Conectar con la comunidad de Trezor", "TR_GUIDE_SUGGESTION_LABEL": "¿Qué te parece?", - "TR_GUIDE_SUPPORT": "Ponte en contacto con el servicio de atención al cliente.", + "TR_GUIDE_SUPPORT": "Ponte en contacto con el servicio de atención al cliente de Trezor.", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Atención al cliente y comentarios", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Atención al cliente y comentarios", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Ayúdanos a mejorar", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Sistema de archivos local", "TR_LOG": "Registro de la aplicación", "TR_LOGIN_PROCEED": "Continuar", - "TR_LOG_DESCRIPTION": "El registro contiene toda la información técnica necesaria sobre Trezor Suite. Puede que te haga falta al contactar con el servicio de atención al cliente de Trezor.", + "TR_LOG_DESCRIPTION": "Este registro contiene información técnica esencial sobre Trezor Suite y puede ser necesaria al contactar con el servicio de atención al cliente de Trezor.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Esperando una ronda", "TR_LOW_ANONYMITY_WARNING": "Privacidad muy baja. Recomendamos usar al menos 1 de cada 5, ya que por debajo de este umbral no hay privacidad.", "TR_LTC_ADDRESS_INFO": "Litecoin ha cambiado el formato de dirección. Visita nuestro blog para obtener más información sobre cómo convertir tu dirección. {TR_LEARN_MORE}", @@ -1223,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Cartera", "TR_NAV_ANONYMIZE": "Anonimizar monedas", "TR_NAV_BUY": "Comprar", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Detalles", "TR_NAV_RECEIVE": "Recibir", "TR_NAV_SELL": "Vender", @@ -1264,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimismo", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1276,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nuevo", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Está disponible un nuevo Trezor Bridge.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Nuevo firmware de Trezor disponible. Actualiza tu dispositivo.", "TR_NEXT_UP": "Siguiente", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (con \"{char}\", no recomendado)", + "TR_NON_ASCII_CHARS": "{label} (con caracteres no recomendados)", "TR_NORMAL_ACCOUNTS": "Cuentas predeterminadas", "TR_NORTH": "Norte", "TR_NOTHING_TO_ANONYMIZE": "No hay nada que anonimizar.", @@ -1296,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Monedero estándar", "TR_NO_SEARCH_RESULTS": "No hay resultados para tu criterio de búsqueda.", "TR_NO_SPENDABLE_UTXOS": "No hay transacciones de salida que puedas gastar en tu cuenta.", - "TR_NO_TRANSPORT": "El navegador no puede comunicarse con el dispositivo", + "TR_NO_TRANSPORT": "Tu navegador no puede comunicarse con tu dispositivo.", "TR_NO_TRANSPORT_DESKTOP": "La aplicación no puede comunicarse con el dispositivo", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {cuenta} other {cuentas}} • {fiatValue}", "TR_N_MIN": "{n} min.", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transacción} other {transacciones}}", "TR_OFF": "apagado", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "El importe elegido de {amount} es superior al máximo aceptado de {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "El importe elegido de {amount} es superior al máximo aceptado de {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "El importe elegido de {amount} es inferior al mínimo aceptado de {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "El importe elegido de {amount} es inferior al mínimo aceptado de {min}.", "TR_OFFICIAL_LANGUAGES": "Oficial", "TR_OK": "OK", "TR_ON": "activado", @@ -1333,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Recogida de datos anónimos", "TR_ONBOARDING_DEVICE_CHECK": "Comprobación de seguridad del dispositivo", "TR_ONBOARDING_DEVICE_CHECK_1": "Mi sello holográfico estaba intacto y no se había manipulado.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Compré el dispositivo en la tienda oficial de Trezor o en un distribuidor de confianza.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Compré el dispositivo en la tienda oficial de Trezor o en un distribuidor de confianza.", "TR_ONBOARDING_DEVICE_CHECK_3": "El paquete del dispositivo estaba intacto y no había sido manipulado.", "TR_ONBOARDING_DEVICE_CHECK_4": "Ya hay un firmware instalado en el Trezor conectado. Continua con la configuración solo si has utilizado este Trezor con anterioridad.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Descargar la aplicación de escritorio", @@ -1364,30 +1417,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Otras entradas y salidas", "TR_OUTGOING": "Saliente", "TR_OUTPUTS": "Salidas", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Los importes mínimo y máximo por los que este usuario está dispuesto a vender {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Necesitas iniciar la transacción en {providerName}. Sigue atentamente los siguientes pasos.", - "TR_P2P_GET_STARTED_ITEM_1": "Selecciona «Ir a {providerName}» para llegar al sitio web de nuestro socio.", - "TR_P2P_GET_STARTED_ITEM_3": "Cuando {providerName} te pida una dirección de envío, vuelve aquí y continúa.", - "TR_P2P_GET_STARTED_ITEM_4": "¡Ya casi está! Revela tu dirección y cópiala, pégala en el campo «Dirección de envío» en {providerName} y finaliza la transacción.", - "TR_P2P_GO_TO_PROVIDER": "Ir a {providerName}", - "TR_P2P_INFO": "Con la tecnología {peerToPeer} (P2P), ni el comprador ni el vendedor participan en un proceso de verificación KYC, ya que todas las partes están protegidas contra el fraude gracias a la seguridad del {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "Todo listo para comprar", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Compra {cryptocurrency} en modo P2P con {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Aviso legal", - "TR_P2P_MODAL_SECURITY_HEADER": "La seguridad es lo primero con tu Trezor.", - "TR_P2P_MODAL_TERMS_1": "Estás aquí para comprar criptomonedas de otra persona que elijas utilizando la tecnología Peer-to-Peer (P2P) sin verificación de ID. Si has llegado a este sitio por cualquier otra razón, ponte en contacto con el servicio de atención al cliente antes de continuar.", - "TR_P2P_MODAL_TERMS_2": "Las transacciones con criptomonedas son irreversibles y no se pueden reembolsar. Por lo tanto, podrías no recuperar las pérdidas, ya se deban a error o a tácticas fraudulentas.", - "TR_P2P_MODAL_TERMS_4": "Ten en cuenta Invity no proporciona este servicio, que en cambio se rige por los términos de {provider}.", - "TR_P2P_MODAL_TERMS_5": "No utilices esta función para apostar, cometer un fraude o infringir de cualquier otro modo los términos de servicio de Invity o del proveedor, ni cualquier normativa aplicable.", - "TR_P2P_MODAL_TERMS_6": "Ten presente que las criptomonedas son una herramienta financiera emergente y que las regulaciones pueden variar en distintas jurisdicciones, por lo que el mercado es más inestable y puedes exponerte a un mayor riesgo de fraude o robo.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Socios verificados por Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "La transacción debe completarse dentro de este límite de tiempo, a contar desde la creación de un contrato en el sitio de {providerName}.", - "TR_P2P_PRICE": "Precio para 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "Precio para {symbol} ofrecido por este usuario.", - "TR_P2P_TITLE_NOT_AVAILABLE": "¡Hola! Estoy usando {providerName}.", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "El importe elegido de {amount} es superior al máximo aceptado de {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "El importe elegido de {amount} es inferior al mínimo aceptado de {minimum}.", "TR_PAGINATION_NEWER": "Más reciente", "TR_PAGINATION_OLDER": "Más antiguo", "TR_PASSPHRASE_CASE_SENSITIVE": "Nota: La frase de contraseña distingue entre mayúsculas y minúsculas.", @@ -1398,6 +1427,8 @@ "TR_PASSPHRASE_MISMATCH": "La frase de contraseña no coincide", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Las frases de contraseña no coinciden. Por motivos de seguridad, empieza de nuevo e introdúcelas correctamente.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Empezar de nuevo", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Recomendamos usar ABC, abc, 123, espacios o estos caracteres especiales.", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "El uso de caracteres especiales distintos a los enumerados pone en riesgo la compatibilidad futura.", "TR_PASSPHRASE_TOO_LONG": "La longitud de la frase de contraseña ha superado el límite permitido.", "TR_PASSPHRASE_WALLET": "Monedero protegido por frase de contraseña #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Aprende cómo funciona una frase de contraseña", @@ -1439,15 +1470,30 @@ "TR_PIN_SUBHEADING": "Con un PIN fuerte, proteges tu Trezor del acceso físico no autorizado.", "TR_PLAY_IT_SAFE": "Vayamos a lo seguro", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Da permiso a la cámara para escanear códigos QR.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Conecta tu dispositivo para continuar con el proceso de verificación.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Conecta tu Trezor para continuar con el proceso de verificación.", "TR_PLEASE_ENABLE_PASSPHRASE": "Activa la función de frase de contraseña para continuar con el proceso de verificación.", "TR_POLICY_ID_ADDRESS": "ID de política:", "TR_PRIMARY_FIAT": "Moneda fiduciaria", "TR_PRIVATE": "Privado", "TR_PRIVATE_DESCRIPTION": "Privacidad de al menos {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Continuar sin verificar la dirección", "TR_PROMO_BANNER_DASHBOARD": "El monedero físico más cómodo para gestionar tus cripto de forma segura", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Confirma en Trezor antes de escanear.", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Confirma primero la dirección de destino en tu dispositivo Trezor, ya que su pantalla no se puede hackear.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Activado", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Actualización reciente ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Reinicia para actualizar", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Dispositivo Trezor", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Hay una actualización disponible ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Actualizado ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite ha descargado una nueva actualización.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite se ha actualizado.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Hay una actualización de Trezor Suite disponible.", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Reiniciar y actualizar", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Iniciar actualización", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Hay una actualización de Trezor disponible.", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Novedades", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Es posible que se te pida que escribas algunas palabras que no son parte de tu copia de seguridad del monedero.", "TR_RANGE": "rango", "TR_READ_AND_UNDERSTOOD": "He leído y entendido lo anterior.", @@ -1499,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Comprobaciones de seguridad", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "Preguntar", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Puedes aprobar manualmente acciones potencialmente inseguras en tu Trezor, como la aplicación de comisiones extremas o la admisión de claves distintas entre sí.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "No lo cambies salvo que tengas los conocimientos necesarios.", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Cambia esto solo si tienes los conocimientos necesarios.", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Estricto", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Seguridad total de Trezor.", "TR_SCAN_QR_CODE": "Escanea el código QR", @@ -1511,7 +1557,7 @@ "TR_SECURITY_CHECKPOINT_GOT_SEED": "¿Tienes tu copia de seguridad del monedero?", "TR_SECURITY_CHECK_HOLOGRAM": "Ten en cuenta que el paquete del dispositivo, incluidos los hologramas y los sellos de seguridad, ha ido cambiando con el tiempo. Puedes verificar los detalles del paquete aquí. Asegúrate de haber adquirido tu dispositivo en la tienda oficial de Trezor o en uno de nuestros vendedores de confianza. De lo contrario, existe el riesgo de que tu dispositivo sea falso. Si sospechas que tu dispositivo no es original, ponte en contacto con el servicio de atención al cliente de Trezor.", "TR_SECURITY_FEATURES_COMPLETED_N": "Seguridad ({n} de {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Los dispositivos que están configurados sin semilla no pueden acceder a Trezor Suite. Esta norma impide que pierdas monedas irreversiblemente, en caso de que el dispositivo esté mal configurado y se use erróneamente.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Los dispositivos que están configurados sin semilla no pueden acceder a Trezor Suite para evitar que pierdas monedas irreversiblemente, lo que puede ocurrir si un dispositivo se usa incorrectamente.", "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "La configuración sin semilla no es compatible con Trezor Suite.", "TR_SEED_BACKUP_LENGTH": "Tu copia de seguridad del monedero puede contener 12, 18 o 24 palabras.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Tu copia de seguridad del monedero puede contener 12, 18, 20, 24 o 33 palabras.", @@ -1522,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Introduce las palabras de tu copia de seguridad del monedero en el orden en el que aparecen en tu Trezor.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Utilizando la pantalla táctil, introduce todas las palabras en el orden correcto.", "TR_SEE_DETAILS": "Ver detalles", + "TR_SEE_IF_ISSUE_PERSISTS": "Comprueba si el problema persiste.", "TR_SELECTED": "{amount} seleccionado", "TR_SELECT_COIN_FOR_SETTINGS": "Selecciona una moneda activa para cambiar la configuración.", "TR_SELECT_DEVICE": "Seleccionar un dispositivo", + "TR_SELECT_NAME_OR_ADDRESS": "Búsqueda por nombre, símbolo, red o dirección del contrato.", "TR_SELECT_NUMBER_OF_WORDS": "Selecciona el número de palabras de la copia de seguridad del monedero.", "TR_SELECT_PASSPHRASE_SOURCE": "Selecciona dónde introducir la frase de contraseña para {deviceLabel}.", "TR_SELECT_RECOVERY_METHOD": "Seleccionar el método de recuperación", + "TR_SELECT_TOKEN": "Seleccionar un token", "TR_SELECT_TREZOR": "Selecciona Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Selecciona tu Trezor para continuar.", "TR_SELECT_TYPE": "Selecciona el tipo", @@ -1558,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Vender {cryptocurrency} con {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Aviso legal", "TR_SELL_MODAL_SECURITY_HEADER": "La seguridad es lo primero con tu Trezor.", - "TR_SELL_MODAL_TERMS_1": "Estás aquí para vender criptomonedas. Si has llegado a este sitio por cualquier otra razón, ponte en contacto con el servicio de atención al cliente antes de continuar.", + "TR_SELL_MODAL_TERMS_1": "Estás aquí para vender criptomonedas. Si has llegado a este sitio por cualquier otra razón, ponte en contacto con el servicio de atención al cliente de Trezor antes de continuar.", "TR_SELL_MODAL_TERMS_2": "Estás vendiendo criptomonedas por tu propia cuenta. Ten presente que las políticas del proveedor pueden requerir la verificación de identidad.", "TR_SELL_MODAL_TERMS_3": "Las transacciones con criptomonedas son irreversibles y no se pueden reembolsar. Por lo tanto, podrías no recuperar las pérdidas, ya se deban a error o a tácticas fraudulentas.", "TR_SELL_MODAL_TERMS_4": "Ten en cuenta Invity no proporciona este servicio, que en cambio se rige por los términos de {provider}.", - "TR_SELL_MODAL_TERMS_5": "No utilices esta función para apostar, cometer un fraude o infringir de cualquier otro modo los términos de servicio de Invity o del proveedor, ni cualquier normativa aplicable.", + "TR_SELL_MODAL_TERMS_5": "No voy a utilizar esta función para apostar, cometer fraude o llevar a cabo cualquier actividad que infrinja los términos de servicio de Invity o del proveedor ni cualquier normativa de aplicación.", "TR_SELL_MODAL_TERMS_6": "Ten presente que las criptomonedas son una herramienta financiera emergente y que las regulaciones pueden variar en distintas jurisdicciones, por lo que el mercado es más inestable y puedes exponerte a un mayor riesgo de fraude o robo.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Socios verificados por Invity", "TR_SELL_REGISTER": "Registrarse", @@ -1571,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Se ha rechazado", "TR_SELL_STATUS_PENDING": "Pendiente", "TR_SELL_STATUS_SUCCESS": "Se ha aprobado", - "TR_SELL_TRANS_ID": "ID de transacción:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "El máximo es {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "El mínimo es {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Ver detalles", "TR_SENDFORM_LABELING_EXAMPLE_1": "Ahorros", "TR_SENDFORM_LABELING_EXAMPLE_2": "Alquiler", "TR_SENDING_SYMBOL": "Enviando {multiple, select, true {multiple tokens} false {{symbol}} other {{symbol}}}", @@ -1629,6 +1674,8 @@ "TR_SHOW_LOG": "Mostrar registro", "TR_SHOW_MORE": "Mostrar más", "TR_SHOW_MORE_ADDRESSES": "Mostrar más ({count})", + "TR_SHOW_ON_TRAY": "Mostrar icono en la bandeja", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Monitoriza si Trezor Suite se ejecuta en segundo plano.", "TR_SHOW_UNVERIFIED_ADDRESS": "Mostrar dirección no verificada", "TR_SHOW_UNVERIFIED_XPUB": "Mostrar clave pública no verificada", "TR_SIDEBAR_ADD_COIN": "Añadir una moneda", @@ -1641,7 +1688,9 @@ "TR_SIZE": "Tamaño", "TR_SKIP": "Omitir", "TR_SKIP_BACKUP": "Omitir copia de seguridad", + "TR_SKIP_BACKUP_DESCRIPTION": "Una copia de seguridad del monedero te permite recuperar tus fondos si tu Trezor se pierde, se daña o te lo roban. Sin una copia de seguridad, podrías perder el acceso a tus cripto de manera permanente.", "TR_SKIP_PIN": "Omitir PIN", + "TR_SKIP_PIN_DESCRIPTION": "Un PIN de dispositivo evita el acceso no autorizado a tu Trezor. Sin esta medida, cualquier persona que tenga tu dispositivo puede acceder a tus fondos.", "TR_SKIP_ROUNDS": "Saltar rondas", "TR_SKIP_ROUNDS_DESCRIPTION": "Si te saltas rondas, dificultas aún más que se establezca ninguna relación entre tus entradas. Así puedes anonimizar más el origen de los fondos.", "TR_SKIP_ROUNDS_HEADING": "Permitir que Trezor se salte rondas", @@ -1654,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Añadiendo al staking pool", "TR_STAKE_ANY_AMOUNT_ETH": "Haz stake con un importe mínimo de {amount} {symbol} y empieza a ganar recompensas. A nuestra actual tasa de rendimiento porcentual anual (RPA) del {apyPercent}%, ¡tus recompensas también aportan!", "TR_STAKE_APY": "Rendimiento porcentual anual", + "TR_STAKE_APY_ABBR": "RPA", "TR_STAKE_APY_DESC": "* Rendimiento porcentual anual", "TR_STAKE_AVAILABLE": "Disponible", "TR_STAKE_CAN_CLAIM_WARNING": "Ya puedes reclamar {amount} {symbol}. {br}Reclama o espera hasta que se procese el nuevo unstake.", @@ -1663,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Podrás reclamar una vez finalizado el periodo de unstaking.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "en el siguiente bloque", "TR_STAKE_CLAIM_PENDING": "Reclamación pendiente", + "TR_STAKE_CLAIM_UNSTAKED": "Solicitar retirada de staking de {symbol}", "TR_STAKE_CONFIRM_AND_STAKE": "Confirmar y hacer stake", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Confirmar periodo de presentación", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Acepto y doy mi consentimiento para hacer staking con Everstake", "TR_STAKE_DAYS": "{count, plural, one {# day} other {# days}}", "TR_STAKE_DELEGATED": "Delegación de stake", "TR_STAKE_DEREGISTERED": "Cancelación del registro de una dirección de stake", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Gana recompensas cada semana", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "Entrar en el staking pool puede llevar hasta {count, plural, one {# day} other {# days}}", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Entrar en el staking pool", "TR_STAKE_ETH": "Haz stake con Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "La manera más fácil de ganar {symbol}.", + "TR_STAKE_ETH_CARD_TITLE": "La manera más fácil de ganar {symbol}", "TR_STAKE_ETH_EARN_REPEAT": "Haz staking. Gana recompensas. Repite el proceso.", "TR_STAKE_ETH_EVERSTAKE": "Trezor y Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake es un proveedor y líder mundial de tecnología de staking", @@ -1682,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Tus recompensas también aportan. Mantenlas en stake y observa cómo se disparan tus recompensas de {symbol}.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Tus recompensas de {symbol} también obtienen el rendimiento porcentual anual. Mantén tus fondos en stake o añade más para aumentar tus recompensas.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Verás el efecto de tu dinero", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Gana {apyPercent}% de RPA* haciendo staking de tu Ethereum con Trezor.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Gana un {apyPercent}% de RPA haciendo staking de Ethereum con Trezor.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Tu {symbol} se bloqueará durante este periodo, y no podrás cancelarlo. Más información", "TR_STAKE_EVERSTAKE_MANAGES": "Everstake mantiene y protege tu {symbol} en stake con su infraestructura, su tecnología y sus contratos inteligentes.", "TR_STAKE_INSTANT": "Instantáneo", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Has recibido {amount} {symbol} \"al instante\". {days, plural, =0 {} one {El resto se pagará en # día.} other { El resto se pagará en # días.}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} en cuenta", "TR_STAKE_LEARN_MORE": "Más información", + "TR_STAKE_LEAVE_STAKING_POOL": "Abandonar el staking pool", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Hemos dejado {amount} {symbol} para que puedas pagar las comisiones de retirada.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Hemos dejado un pequeño importe de {symbol} para que puedas pagar las comisiones de retirada.", "TR_STAKE_MAX": "Máx.", "TR_STAKE_MAX_FEE_DESC": "La comisión máxima es la comisión de transacción de red que estás dispuesto a pagar en la red para garantizar que tu transacción se procese.", "TR_STAKE_MAX_REWARD_DAYS": "Máx. {count, plural, one {# day} other {# days}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "El importe mínimo para el stake es {amount} {symbol}", + "TR_STAKE_MONTHLY": "Mensual", "TR_STAKE_NEXT_PAYOUT": "Próximo pago de recompensa", "TR_STAKE_NOT_ENOUGH_FUNDS": "No hay suficiente {symbol} para pagar las comisiones de red", "TR_STAKE_ONLY_REWARDS": "Solo recompensas", @@ -1704,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Registro de una dirección de stake", "TR_STAKE_RESTAKED_BADGE": "En restake", "TR_STAKE_REWARDS": "Recompensas", + "TR_STAKE_SIGN_TRANSACTION": "Firmar transacción", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Firmar transacción de retirada de staking", "TR_STAKE_STAKE": "Stake", "TR_STAKE_STAKED_AMOUNT": "Cantidad en stake", "TR_STAKE_STAKED_AND_EARNING": "En stake y obteniendo recompensas", @@ -1711,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Añadir más al stake", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Una breve descripción del staking", "TR_STAKE_STAKING_IS": "El staking implica bloquear temporalmente tus activos de Ethereum para apoyar el funcionamiento de la cadena de bloques. A cambio, ganarás más Ethereum como recompensa.", + "TR_STAKE_STAKING_PROCESS": "Proceso de staking", "TR_STAKE_START_STAKING": "Empieza a hacer staking", "TR_STAKE_TIME_TO_CLAIM": "Es el momento de reclamar", "TR_STAKE_TOTAL_PENDING": "Cantidad de stake pendiente:", @@ -1719,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Sin stake y listo para reclamar", "TR_STAKE_UNSTAKE_TO_CLAIM": "Quita el stake para reclamar", "TR_STAKE_UNSTAKING": "Quitando el stake", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Importe aproximado en {symbol} disponible de inmediato", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "La liquidez del staking pool puede permitir el unstake instantáneo de algunos fondos. Los fondos restantes cumplirán el periodo para quitar el stake.", "TR_STAKE_UNSTAKING_PERIOD": "Periodo para quitar el stake", + "TR_STAKE_UNSTAKING_PROCESS": "Proceso de retirada de staking", "TR_STAKE_UNSTAKING_TAKES": "Quitar el stake lleva {count, plural, one {# day} other {# days}}. Una vez completado, puedes operar con él o enviar tus fondos.", + "TR_STAKE_WEEKLY": "Semanal", "TR_STAKE_WHAT_IS_STAKING": "¿Qué es hacer staking?", + "TR_STAKE_YEARLY": "Anual", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Everstake mantiene tus fondos en stake", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "¡Has realizado staking de {amount} {symbol} al instante!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "¡Has quitado el staking de {amount} {symbol} al instante!", + "TR_STAKING_CONSOLIDATING_FUNDS": "Consolidando tus {symbol} por ti", "TR_STAKING_DELEGATE": "Delegar", "TR_STAKING_DEPOSIT": "Depósito reembolsable", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "La comisión de depósito es de {feeAmount} ADA y es necesario registrar tu dirección para empezar con el staking. Si decides quitar tu staking de cardano recuperarás el depósito.", + "TR_STAKING_ESTIMATED_GAINS": "Ganancias estimadas", "TR_STAKING_FEE": "Comisión", - "TR_STAKING_INSTANTLY_STAKED": "Tu importe en stake al instante es de {amount} {symbol}. {days, plural, =0 {} one {El importe en {symbol} restante aparecerá en stake en # día.} other { El importe en {symbol} restante aparecerá en stake en # días.}}", + "TR_STAKING_GETTING_READY": "Tu {symbol} está casi a punto", + "TR_STAKING_INSTANTLY_STAKED": "Tu importe en stake al instante es de {amount} {symbol}. {days, plural, =0 {} one {El importe restante en {symbol} restante aparecerá en stake en # día.} other { El importe restante en {symbol} aparecerá en stake en # días.}}", "TR_STAKING_IS_NOT_SUPPORTED": "El staking no es compatible con esta red.", "TR_STAKING_NOT_ENOUGH_FUNDS": "No tienes suficientes fondos en tu cuenta.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Una vez que confirmes", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Si haces staking en un stake pool de Trezor, apoyas directamente a Trezor y al ecosistema de Cardano dentro de Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Estás delegando en un stake pool de terceros.", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "El stake pool de delegación está saturado. Vuelve a delegar tu stake para maximizar tus recompensas de staking.", "TR_STAKING_POOL_OVERSATURATED_TITLE": "El stake pool está saturado.", "TR_STAKING_REDELEGATE": "Volver a delegar", "TR_STAKING_REWARDS": "Recompensas disponibles", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Las recompensas se ponen en restake automáticamente", "TR_STAKING_REWARDS_DESCRIPTION": "Ten en cuenta que pueden pasar hasta 20 días para que empieces a recibir recompensas después del registro y la delegación iniciales de stake. Tras dicho período, recibirás tu recompensa cada 5 días.", "TR_STAKING_REWARDS_TITLE": "El staking de cardano está activado.", "TR_STAKING_STAKE_ADDRESS": "Tu dirección de stake", @@ -1744,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "No se ha podido conectar con el stake pool de Trezor para hacer una delegación.", "TR_STAKING_TX_PENDING": "Tu transacción {txid} se ha enviado correctamente a la cadena de bloques y está pendiente de confirmación.", "TR_STAKING_WITHDRAW": "Retirar", + "TR_STAKING_YOUR_EARNINGS": "Tus ganancias se ponen en restake automáticamente, lo que te permite ganar intereses compuestos.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Tu {symbol} en unstake está listo", + "TR_STAKING_YOU_ARE_HERE": "Estás aquí", "TR_STANDARD_WALLET_DESCRIPTION": "Sin frase de contraseña", "TR_START": "Comenzar", "TR_START_AGAIN": "Comenzar de nuevo", @@ -1752,6 +1827,7 @@ "TR_START_COINJOIN": "Iniciar CoinJoin", "TR_START_RECOVERY": "Iniciar recuperación", "TR_STEP": "Paso {number}", + "TR_STEP_OF_TOTAL": "Paso {index} de {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "¿Todavía no ves tu Trezor?", "TR_STOP": "Detener", "TR_STOPPING": "Deteniendo", @@ -1803,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Todavía no hay tokens...", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "No hay tokens. Podrían estar ocultos.", "TR_TOKENS_SEARCH_TOOLTIP": "Búsqueda por token, símbolo o dirección del contrato.", + "TR_TOKEN_NOT_FOUND": "No se ha encontrado el token.", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "No se ha encontrado el token en la red {networkName}.", "TR_TOKEN_TRANSFERS": "Transferencias de tokens {standard}", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Prueba con una búsqueda diferente.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Prueba con una búsqueda diferente o cambia a otra red.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Tokens no reconocidos", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Los tokens no reconocidos suponen posibles riesgos. Ten cuidado.", "TR_TOO_LONG": "El mensaje es demasiado largo.", @@ -1815,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Actualizar ruta", "TR_TOR_DESCRIPTION": "Dirige el tráfico de Trezor Suite a través de la red Tor para aumentar tu privacidad y tu seguridad. Tor puede tardar algún tiempo en cargar y establecer una conexión.", "TR_TOR_DISABLE": "Desactivar Tor", + "TR_TOR_DISABLED": "Desactivado", "TR_TOR_DISABLE_ONIONS_ONLY": "Faltan backends personalizados no .onion.", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Añade direcciones de backend personalizadas no .onion para evitar este comportamiento.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Ahora puedes desactivar Tor con seguridad.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Los backends personalizados ya no usan solo direcciones .onion.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Desactivar Tor", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Si desactivas Tor ahora, se resetearán todos los backends .onion a los servidores Trezor predeterminados.", + "TR_TOR_DISABLING": "Desactivación", "TR_TOR_ENABLE": "Activar Tor", + "TR_TOR_ENABLED": "Activado", "TR_TOR_ENABLE_AND_CONFIRM": "Activar Tor y confirmar", "TR_TOR_ENABLE_TITLE": "Activar Tor", + "TR_TOR_ENABLING": "Activación", + "TR_TOR_ERROR": "Error", "TR_TOR_IS_SLOW_MESSAGE": "Tor se está conectando a la red.

Espera.", "TR_TOR_KEEP_RUNNING": "Seguir ejecutando Tor", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Selecciona «Seguir ejecutando Tor» para continuar o «Detener Tor» para salir del proceso de CoinJoin.", + "TR_TOR_MISBEHAVING": "Mal comportamiento", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Desactivar Tor y cambiar a los backends predeterminados", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Abandonar", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Selecciona «Activar Tor» para continuar o «Salir» para abandonar el proceso.", @@ -1841,11 +1927,7 @@ "TR_TO_BTC": "A BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Para que tus etiquetas se mantengan y estén disponibles en diferentes dispositivos, conéctate al proveedor de almacenamiento en la nube.", "TR_TO_SATOSHIS": "A sats", - "TR_TRADE_BUYS": "compras", - "TR_TRADE_ENTER_COIN": "Introduce el nombre o el símbolo de la criptomoneda...", - "TR_TRADE_EXCHANGES": "intercambios", "TR_TRADE_REDIRECTING": "Redirigiendo...", - "TR_TRADE_SELLS": "ventas", "TR_TRANSACTIONS_NOT_AVAILABLE": "El historial de transacciones no está disponible.", "TR_TRANSACTIONS_SEARCH_TIP_1": "Sugerencia: Puedes buscar ID de transacción, direcciones, tokens, etiquetas, importes y fechas.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Sugerencia: Combina los operadores AND (&) y OR (|) para hacer búsquedas más complejas. Por ejemplo, con > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 verás todas las transacciones en enero o diciembre de {lastYear}.", @@ -1860,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Busca por ID de transacción, etiqueta o cantidad, o usa operadores como < > | & = !=.", "TR_TRANSACTION_DETAILS": "Detalles", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Estás ejecutando la versión {version} de Trezor Bridge", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutorial cancelado", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutorial completado", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Aprende a utilizar tu dispositivo con la ayuda de un breve tutorial", @@ -1867,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Cierra otras pestañas y ventanas que puedan estar utilizando tu Trezor.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Después de cerrar otras pestañas y ventanas, prueba a refrescar esta página.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Después de cerrar otras pestañas y ventanas del navegador, prueba a salir de Trezor Suite y volver a abrirlo.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Pasos necesarios para permitir la comunicación", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Prueba estos pasos para resolver este problema.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Visita la página de estado de Trezor Bridge.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Asegúrate de que el proceso Trezor Bridge se está ejecutando.", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Solo los navegadores basados en Chromium permiten actualmente la comunicación directa con dispositivos USB", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Solo los navegadores basados en Chromium permiten actualmente la comunicación directa con dispositivos USB.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Utiliza un navegador basado en Chromium", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "El cable debe estar completamente insertado. Si tienes un dispositivo USB-C conectado, el cable debe hacer clic al introducirlo.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Prueba con un cable diferente.", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Con Trezor Bridge instalado.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Si es posible, inténtalo con un ordenador diferente.", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Por si acaso.", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Prueba a reiniciar tu ordenador.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Reiniciar tu ordenador puede solucionar el problema de comunicación entre tu navegador y tu dispositivo.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Reiniciar tu ordenador", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Ejecuta la aplicación de escritorio Trezor Suite", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Utiliza la aplicación de escritorio Trezor Suite", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Haz clic para cambiar a una implementación de Bridge alternativa. Versión actual: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Usar otra versión de Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Prueba a instalar las reglas udev. Asegúrate de guardarlas primero en el escritorio antes de abrirlas.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Si actualizaste el firmware de tu dispositivo por última vez en 2019 o antes, sigue las instrucciones de la base de conocimientos.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Parece que estás usando un modelo de Trezor antiguo.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Conéctalo directamente a tu ordenador (sin un concentrador USB).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Prueba un puerto USB diferente.", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Instalar las reglas automáticamente", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Faltan las reglas udev.", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Estado inesperado: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Tu dispositivo está conectado correctamente, pero tu navegador no puede comunicarse con él en este momento. Debes instalar Trezor Bridge.", "TR_TRY_AGAIN": "Inténtalo de nuevo.", "TR_TXID": "TX ID", "TR_TXID_RBF": "Se debe sustituir el TX ID inicial", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transacción confirmada", "TR_TX_CONFIRMING": "Confirmando la transacción", + "TR_TX_DATA_FUNCTION": "Función", + "TR_TX_DATA_INPUT_DATA": "Introducir datos", + "TR_TX_DATA_METHOD": "Introducir datos", + "TR_TX_DATA_METHOD_NAME": "Nombre de método", + "TR_TX_DATA_PARAMS": "Parámetros", "TR_TX_DEPOSIT": "Depósito", "TR_TX_FEE": "Comisión", "TR_TX_TAB_AMOUNT": "Importe", @@ -1932,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Se requiere una actualización de firmware. Después puedes cambiar la pantalla de inicio desde Configuración.", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Actualiza el firmware del dispositivo para cambiar la pantalla de inicio.", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Hay una actualización disponible.", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Activar actualizaciones automáticas", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Nuevo", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Actualizar y reiniciar", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "¿Instalar actualización ahora?", "TR_UPDATE_MODAL_NOT_NOW": "Ahora no", @@ -1939,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "Descargar", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Actualización descargada", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Actualizar al salir", + "TR_UPDATE_MODAL_WHATS_NEW": "Novedades", + "TR_UPDATE_MODAL_YOUR_VERSION": "Tu versión: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Actualiza el firmware para tener acceso a esta cuenta. Consulta el banner azul de arriba.", "TR_UP_TO": "hasta", "TR_UP_TO_DATE": "Actualizado", "TR_UP_TO_DAYS": "hasta {count, plural, one {# day} other {# days}}", @@ -1992,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Acceder al monedero protegido por frase de contraseña", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Monedero oculto", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Crear un nuevo monedero o restaurar uno con la copia de seguridad del monedero.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Nos esforzamos continuamente por mejorar tu experiencia en Trezor. Estas son las novedades:", "TR_WEST": "Oeste", "TR_WHAT_DATA_WE_COLLECT": "¿Qué datos recopilamos?", "TR_WHAT_IS_PASSPHRASE": "Más información sobre la diferencia", @@ -2002,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Entiendo que debo guardar una copia de seguridad de mi copia de seguridad del monedero para volver a tener acceso a mis fondos.", "TR_WIPE_DEVICE_TEXT": "Resetear el dispositivo elimina todos sus datos. Reinicia tu dispositivo solo si tienes una copia de seguridad de tu monedero, que puede restaurar el acceso a tus fondos.", "TR_WIPE_OR_UPDATE": "Resetear el dispositivo o actualizar el firmware", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Ir a Configuración del dispositivo", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Ve a la configuración del dispositivo.", "TR_WIPING_YOUR_DEVICE": "El restablecimiento de fábrica elimina toda la información de la memoria del dispositivo, incluyendo la copia de seguridad del monedero y el PIN. Realiza un restablecimiento de fábrica solo si tienes una copia de seguridad de tu monedero, ya que es necesaria para restaurar el acceso a tus fondos.", "TR_WORDS": "{count} palabras", "TR_WORD_DOES_NOT_EXIST": "La palabra «{word}» no existe en la lista de palabras BIP39.", diff --git a/packages/suite-data/files/translations/fr.json b/packages/suite-data/files/translations/fr.json index 709613f37f3..c386b5ae3e9 100644 --- a/packages/suite-data/files/translations/fr.json +++ b/packages/suite-data/files/translations/fr.json @@ -1,5 +1,6 @@ { "AMOUNT": "Montant", + "AMOUNT_EXCEEDS_MAX": "Le montant dépasse la valeur maximale autorisée de {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "Le montant doit être au minimum de {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "Le compte du destinataire nécessite une réserve minimale {reserve} de XRP pour être activé", "AMOUNT_IS_MORE_THAN_RESERVE": "Le montant est supérieur à la réserve non utilisable requise ({reserve} de XRP)", @@ -66,7 +67,7 @@ "LOCKTIME_ADD": "Ajouter un temps de verrouillage", "LOCKTIME_ADD_TOOLTIP": "Le temps de verrouillage définit le moment le plus précoce où une transaction peut être minée dans un bloc.", "LOCKTIME_BLOCKHEIGHT": "Temps de verrouillage hauteur de bloc", - "LOCKTIME_IS_NOT_INTEGER": "Le temps de verrouillage n’est pas un entier", + "LOCKTIME_IS_NOT_INTEGER": "Le temps de verrouillage n’est pas un nombre entier", "LOCKTIME_IS_NOT_SET": "Le temps de verrouillage n’est pas défini", "LOCKTIME_IS_TOO_BIG": "L’horodatage est trop grand", "LOCKTIME_IS_TOO_LOW": "Le temps de verrouillage est trop bas", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "Le code PIN a été modifié avec succès", "TOAST_QR_INCORRECT_ADDRESS": "Le code QR contient une adresse non valide pour ce compte", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "Le code QR est défini pour le compte {coin}", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Schéma de protocole inconnu : « {scheme} ». Réessayez ou saisissez l’adresse manuellement.", "TOAST_RAW_TX_SENT": "Transaction envoyée. TxID : {txid}", "TOAST_SETTINGS_APPLIED": "Paramètres modifiés avec succès", "TOAST_SIGN_MESSAGE_ERROR": "Erreur de signature du message : {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Accéder au tableau de bord", "TR_404_TITLE": "Erreur 404 : lien introuvable", "TR_7D_CHANGE": "Variation 7 j", - "TR_ABORT": "Annuler", "TR_ACCESS_HIDDEN_WALLET": "Accéder au portefeuille protégé par une phrase secrète", "TR_ACCESS_STANDARD_WALLET": "Accéder au portefeuille standard", "TR_ACCOUNT_DETAILS_HEADER": "Détails du compte", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot est un nouveau type d’adresse qui peut améliorer la confidentialité et l’efficacité du réseau. Notez que certains services ne prennent pas encore en charge les adresses Taproot.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "La méthode actuelle et la plus répandue de génération et de gestion des adresses Cardano garantit l’interopérabilité, la sécurité et la prise en charge de tous les types de jetons.", "TR_ACCOUNT_TYPE_COINJOIN": "Coinjoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Par défaut", "TR_ACCOUNT_TYPE_IMPORTED": "Importé", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Les comptes Ledger sont compatibles avec les chemins de dérivation de Ledger Live, facilitant ainsi la migration de Ledger vers Trezor.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Les comptes Legacy sont compatibles avec les chemins de dérivation de Ledger Legacy, facilitant ainsi la migration de Ledger vers Trezor.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "La méthode actuelle et la plus répandue de génération et de gestion des adresses {value} garantit l’interopérabilité, la sécurité et la prise en charge de tous les types de jetons.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "La méthode actuelle et la plus répandue de génération et de gestion des adresses Solana garantit l’interopérabilité, la sécurité et la prise en charge des jetons SOL et SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Non pris en charge.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Ce type de compte n’est pas pris en charge sur ce modèle Trezor.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Veuillez mettre à jour le micrologiciel du dispositif pour activer ce type de compte.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP est une cryptomonnaie qui permet des paiements transfrontaliers rapides et à faible coût sans dépendre du minage traditionnel et qui utilise un grand livre basé sur un mécanisme de consensus pour des confirmations de transactions rapides.", "TR_ACQUIRE_DEVICE": "Utiliser Trezor ici", "TR_ACQUIRE_DEVICE_TITLE": "Une autre session est en cours", "TR_ACTIVATED_COINS": "Coins activés", "TR_ACTIVE": "active", + "TR_ADD": "Ajouter", "TR_ADDRESSES": "Adresse", "TR_ADDRESSES_CHANGE": "Modifier les adresses", "TR_ADDRESSES_FRESH": "Nouvelles adresses", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Copier l’adresse", "TR_ADDRESS_MODAL_TITLE": "{networkName} adresse de réception", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkCurrencyName} adresse de réception sur le réseau {networkName}", - "TR_ADDRESS_PHISHING_WARNING": "Pour éviter les attaques par hameçonnage, vous devez vérifier l’adresse sur votre Trezor. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Pour éviter les attaques par hameçonnage, vérifiez l’adresse de réception sur votre Trezor. {claim}", "TR_ADD_ACCOUNT": "Ajouter un compte", "TR_ADD_HIDDEN_WALLET": "Portefeuille protégé par une phrase secrète", "TR_ADD_NETWORK_ACCOUNT": "Ajouter un compte {network}", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Utilisation des données", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Toutes les données sont strictement anonymes. Permet uniquement d’améliorer l’écosystème Trezor.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Mises à jour automatiques pour Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite télécharge automatiquement la dernière version en arrière-plan et l’installe lors du redémarrage de l’application. Vous bénéficiez ainsi en permanence de la mise à jour des dernières fonctionnalités et des correctifs de sécurité. Les mises à jour ont lieu sans nécessiter votre autorisation.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Téléchargez automatiquement la dernière version de Trezor Suite en arrière-plan et installez-la au redémarrage de l’application. Vous bénéficiez ainsi en permanence de la mise à jour des dernières fonctionnalités et des correctifs de sécurité. Les mises à jour ont lieu sans nécessiter votre autorisation.", + "TR_ALL_NETWORKS": "Tous les réseaux ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Affichez les jetons de tous les réseaux {networkCount}. Filtrez par réseaux les plus populaires.", "TR_ALL_TRANSACTIONS": "Transactions", "TR_AMOUNT_SENT": "Montant envoyé", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Ne convient pas aux Coinjoin - montant trop élevé", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Bien que cela soit peu probable, vous devrez peut-être accéder à votre sauvegarde du portefeuille en cas de problème de mise à jour du micrologiciel.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Avant de passer à {bitcoinOnly}, vous devez installer la dernière version du micrologiciel.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Je comprends que je porte atteinte à mon anonymat", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Je comprends que je porte atteinte à mon anonymat.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Démarrage de Trezor Bridge sur le port 21324", "TR_BRIDGE_DEV_MODE_STOP": "Démarrage de Trezor Bridge sur le port par défaut", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Êtes-vous sûr ? Votre dispositif peut uniquement être utilisé par une application à la fois. Si vous utilisez actuellement une autre application avec votre dispositif Trezor, terminez d’abord cette session.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Votre navigateur n’est pas pris en charge. Pour bénéficier d’une expérience optimale, téléchargez et exécutez l’application de bureau Trezor Suite en arrière-plan, ou utilisez un navigateur basé sur Chromium compatible avec WebUSB.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Une autre application a demandé à Trezor Suite de se connecter à votre dispositif Trezor. Laissez Trezor Suite en cours d’exécution en arrière-plan et réessayez l’action dans l’autre application.", + "TR_BRIDGE_TIP_AUTOSTART": "Astuce : Activez la fonction de démarrage automatique pour que Bridge fonctionne toujours en arrière-plan.", "TR_BTC_UNITS": "Unités Bitcoin", "TR_BUG": "Bug", "TR_BUMP_FEE": "Frais de bump", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "Pour accélérer vos transactions, majorez les frais de la transaction la plus ancienne en attente (par nonce) dans la file d’attente. Les transactions doivent être confirmées dans l’ordre. En savoir plus", "TR_BUY": "Acheter", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Effectuer des transactions", "TR_BUY_BUY": "Acheter", - "TR_BUY_BUY_AGAIN": "Acheter à nouveau", "TR_BUY_CONFIRMED_ON_TREZOR": "Confirmé sur Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Retour au compte", "TR_BUY_DETAIL_ERROR_SUPPORT": "Accéder à la page d’assistance du fournisseur", @@ -386,12 +397,12 @@ "TR_BUY_MODAL_FOR_YOUR_SAFETY": "Achetez {cryptocurrency} avec {provider}", "TR_BUY_MODAL_LEGAL_HEADER": "Mentions légales", "TR_BUY_MODAL_SECURITY_HEADER": "La sécurité avant tout avec votre Trezor", - "TR_BUY_MODAL_TERMS_1": "Vous êtes ici pour acheter des cryptomonnaies. Si vous avez été dirigé vers ce site pour toute autre raison, veuillez contacter l’assistance {provider} avant de poursuivre.", - "TR_BUY_MODAL_TERMS_2": "Vous utilisez cette fonctionnalité pour acheter des fonds qui seront envoyés à un compte sous votre contrôle personnel direct.", - "TR_BUY_MODAL_TERMS_3": "Vous comprenez que les transactions de cryptomonnaies sont irréversibles et ne peuvent être remboursées. Par conséquent, les fonds perdus de manière frauduleuse ou accidentelle peuvent être irrécupérables.", - "TR_BUY_MODAL_TERMS_4": "Vous comprenez qu’Invity ne fournit pas ce service. Les conditions de {provider} régissent le service.", - "TR_BUY_MODAL_TERMS_5": "Vous n’utilisez pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre violation des conditions de service d’Invity ou du fournisseur, ou de toute réglementation applicable.", - "TR_BUY_MODAL_TERMS_6": "Vous comprenez que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une juridiction à l’autre. Cela peut vous exposer à un risque plus élevé de fraude, de vol ou d’instabilité du marché.", + "TR_BUY_MODAL_TERMS_1": "Je souhaite acheter des cryptomonnaies. Si je suis redirigé vers ce site pour toute autre raison, je dois contacter l’assistance {provider} avant de continuer.", + "TR_BUY_MODAL_TERMS_2": "J’utilise cette fonctionnalité pour acheter des cryptomonnaies qui seront transférées sur mon propre compte.", + "TR_BUY_MODAL_TERMS_3": "Je comprends que les transactions de cryptomonnaies sont définitives et ne peuvent être ni annulées ni remboursées. Les pertes dues à une fraude ou à une erreur peuvent être impossibles à récupérer.", + "TR_BUY_MODAL_TERMS_4": "Je comprends qu’Invity ne fournit pas ce service. Son fonctionnement est régi par les Conditions générales de {provider}.", + "TR_BUY_MODAL_TERMS_5": "Je n’utilise pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre activité qui violerait les conditions de service d’Invity ou du fournisseur, ou toute loi applicable.", + "TR_BUY_MODAL_TERMS_6": "Je comprends que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une région à une autre. Cela peut augmenter le risque de fraude, de vol ou d’instabilité du marché.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Partenaires vérifiés par Invity", "TR_BUY_NETWORK": "Acheter {network}", "TR_BUY_NOT_TRANSACTIONS": "Aucune transaction pour le moment.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "En attente", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "En attente", "TR_BUY_STATUS_SUCCESS": "Approuvé", - "TR_BUY_TRANS_ID": "Identifiant trans. :", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Le maximum est {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Le maximum est {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Le minimum est {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Le minimum est {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Afficher les détails", "TR_BYTES": "octets", "TR_CAMERA_NOT_RECOGNIZED": "La caméra n’a pas été reconnue.", "TR_CAMERA_PERMISSION_DENIED": "L’autorisation d’accéder à la caméra a été refusée.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Montant du Trezor", "TR_CHAINED_TXS": "Transactions enchaînées", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog sur GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Il s’agit d’une adresse de modification créée à partir d’un envoi précédent.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Vous pouvez modifier votre type de micrologiciel à tout moment dans les paramètres.", "TR_CHANGE_HOMESCREEN": "Changer l’écran d’accueil", "TR_CHANGE_PIN": "Modifier le code PIN", "TR_CHANGE_WIPE_CODE": "Modifier le code d’effacement", + "TR_CHECKED_BALANCES_ON": "Soldes vérifiés sur", "TR_CHECKING_YOUR_DEVICE": "Vérification de votre dispositif", "TR_CHECKSUM_CONVERSION_INFO": "Converti en somme de contrôle. En savoir plus", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Nous vérifierons l’intégrité de votre dispositif Trezor, afin d’assurer sa sécurité et de confirmer l’authenticité de la puce.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Effectuez une simulation de récupération pour vérifier votre sauvegarde du portefeuille.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Saisissez les mots de votre sauvegarde du portefeuille dans l’ordre affiché sur votre dispositif. Il se peut qu’on vous demande de saisir certains mots qui ne font pas partie de votre sauvegarde du portefeuille, à titre de mesure de sécurité supplémentaire.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Utilisez le pavé à 2 boutons pour saisir votre sauvegarde du portefeuille. Ce faisant, vous conservez toutes vos informations sensibles en toute sécurité, à l’écart de tout ordinateur ou navigateur Web louche ou non sécurisé.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "La saisie de votre sauvegarde du portefeuille s’effectue à l’aide de l’écran tactile. Vous évitez ainsi d’exposer vos informations sensibles à un ordinateur ou à un navigateur Web potentiellement non sécurisé.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "La saisie de votre sauvegarde du portefeuille s’effectue à l’aide de l’écran tactile. Vous évitez ainsi d’exposer vos informations sensibles à un ordinateur ou à un navigateur Web potentiellement non sécurisé.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "La saisie de votre sauvegarde du portefeuille s’effectue à l’aide de l’écran tactile. Vous évitez ainsi d’exposer vos informations sensibles à un ordinateur ou à un navigateur Web potentiellement non sécurisé.", "TR_CHECK_SEED": "Vérifier la sauvegarde", "TR_CHECK_YOUR_DEVICE": "Vérifiez votre écran Trezor", "TR_CHOOSE_RECOVERY_TYPE": "Choisir le type de récupération", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Protégé par votre Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Transactions Coinjoin", "TR_COINMARKET_BEST_RATE": "Meilleur taux", + "TR_COINMARKET_BUY_AND_SELL": "Acheter et vendre", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} achat} one {{totalBuys} achat} other {{totalBuys} achats} } • {totalSells, plural, =0 {{totalSells} vente} one {{totalSells} vente} other {{totalSells} ventes} }", + "TR_COINMARKET_CEX_TOOLTIP": "Plateforme centralisée", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Changez le montant ou la devise.", "TR_COINMARKET_COMPARE_OFFERS": "Comparer toutes les offres", "TR_COINMARKET_COUNTRY": "Pays de résidence", + "TR_COINMARKET_DCA_DOWNLOAD": "Téléchargez l’application mobile Invity pour commencer à épargner en Bitcoins", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Un plan d’épargne en DCA avec garde simple et sécurisé.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Développé par SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Effectuez des retraits vers un portefeuille en auto-garde sans frais supplémentaires.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Retraits sans frais", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Une interface rapide, rationalisée et conviviale.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Facile à utiliser", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Suivez l’historique, le montant et la fréquence de vos investissements.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Aperçu du DCA", + "TR_COINMARKET_DCA_HEADING": "Épargner en Bitcoins avec l’application Invity", + "TR_COINMARKET_DEX_TOOLTIP": "Plateforme décentralisée", "TR_COINMARKET_ENTER_AMOUNT_IN": "Saisir un montant en {currency}", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Toutes les options KYC", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC jamais requis", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Toutes les offres CEX & DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX à taux fixe", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX à taux variable", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Une plateforme d’échange décentralisée (DEX) vous permet d’échanger des cryptomonnaies directement sur la blockchain, sans qu’une autorité centrale ou un intermédiaire ne soit nécessaire.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX à taux fixe", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX à taux variable", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Offres spéciales", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Paiement :", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Méthode de réception :", "TR_COINMARKET_FEES_INCLUDED": "Frais inclus", "TR_COINMARKET_FEES_NOT_INCLUDED": "Frais non inclus", "TR_COINMARKET_FEES_ON_WEBSITE": "Certains frais ne sont pas inclus dans le prix affiché. Vous pourrez consulter le prix final sur le site Web du fournisseur.", @@ -518,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "KYC requis dans des cas exceptionnels. KYC requis pour les remboursements. 👈", "TR_COINMARKET_KYC_POLICY": "Politique en matière de KYC", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC jamais requis", - "TR_COINMARKET_KYC_YES_REFUND": "KYC requis dans des cas exceptionnels. KYC non requis pour les remboursements. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "KYC n’est requis que dans des cas exceptionnels. Il n’est pas requis pour les remboursements. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Dernières transactions", "TR_COINMARKET_NETWORK_FEE": "Frais de réseau", "TR_COINMARKET_NETWORK_TOKENS": "Jetons {networkName}", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Aucun fournisseur de plateforme centralisée trouvé", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Aucun fournisseur de plateforme décentralisée trouvé", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Aucune méthode disponible", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Rechargement automatique dans", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Retour aux transactions", - "TR_COINMARKET_NO_OFFERS_HEADER": "Aucune offre", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Désolé, nous n’avons aucune offre pour le moment en raison d’un problème de connectivité du serveur.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Désolé, nous n’avons aucune offre pour le moment. Essayez de recharger la page ou de modifier votre requête.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Actualiser la page", "TR_COINMARKET_OFFERS_EMPTY": "Aucune offre ne correspond à votre demande. Veuillez changer de pays ou de montant d’achat.", "TR_COINMARKET_OFFERS_REFRESH": "Offres à actualiser dans", "TR_COINMARKET_OFFERS_SELECT": "Sélectionner", "TR_COINMARKET_OFFER_LOOKING": "Nous recherchons la meilleure offre pour vous", - "TR_COINMARKET_OFFER_NO_FOUND": "Aucune offre correspondant à votre demande n’est disponible. Changez le montant ou la devise.", + "TR_COINMARKET_OFFER_NO_FOUND": "Aucune offre correspondant à votre demande n’est disponible.", "TR_COINMARKET_ON_NETWORK_CHAIN": "Sur la chaîne du réseau {networkName}", "TR_COINMARKET_OTHER_CURRENCIES": "Autres devises", "TR_COINMARKET_PAYMENT_METHOD": "Méthode de paiement", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Méthode de réception", "TR_COINMARKET_SELL": "Vendre", "TR_COINMARKET_SHOW_OFFERS": "Comparer les offres", + "TR_COINMARKET_SWAP": "Échange", + "TR_COINMARKET_SWAP_AMOUNT": "Montant de l’échange", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} échange} one {{totalSwaps} échange} other {{totalSwaps} échanges} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Je suis prêt à échanger", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Échanger des actifs {fromCrypto} contre des actifs {toCrypto} sur {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Mentions légales", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "La sécurité avant tout avec votre Trezor", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Je souhaite échanger des cryptomonnaies à l’aide d’une DEX (plateforme d’échange décentralisée) en utilisant le contrat de {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Je souhaite échanger des cryptomonnaies pour mon propre compte. Je comprends que les politiques du fournisseur peuvent nécessiter une vérification d’identité.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Je comprends que les transactions de cryptomonnaies sont définitives et ne peuvent être ni annulées ni remboursées. Les pertes dues à une fraude ou à une erreur peuvent être impossibles à récupérer.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Je comprends qu’Invity ne fournit pas ce service. Son fonctionnement est régi par les Conditions générales de {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Je n’utilise pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre activité qui violerait les conditions de service d’Invity ou du fournisseur, ou toute loi applicable.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Je comprends que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une région à une autre. Cela peut augmenter le risque de fraude, de vol ou d’instabilité du marché.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Partenaires vérifiés par Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Je suis prêt à échanger", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Échanger des actifs {fromCrypto} contre des actifs {toCrypto} sur {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Mentions légales", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "La sécurité avant tout avec votre Trezor", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Je souhaite échanger des cryptomonnaies. Si je suis redirigé vers ce site pour toute autre raison, je dois contacter l’assistance Trezor avant de continuer. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Je souhaite échanger des cryptomonnaies pour mon propre compte. Je comprends que les politiques du fournisseur peuvent nécessiter une vérification d’identité.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Je comprends que les transactions de cryptomonnaies sont définitives et ne peuvent être ni annulées ni remboursées. Les pertes dues à une fraude ou à une erreur peuvent être impossibles à récupérer.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Je comprends qu’Invity ne fournit pas ce service. Son fonctionnement est régi par les Conditions générales de {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Je n’utilise pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre activité qui violerait les conditions de service d’Invity ou du fournisseur, ou toute loi applicable.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Je comprends que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une région à une autre. Cela peut augmenter le risque de fraude, de vol ou d’instabilité du marché.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Partenaires vérifiés par Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} sur le réseau {networkName}", "TR_COINMARKET_TRADE_FEE": "Frais de trading", + "TR_COINMARKET_TRANS_ID": "Identifiant trans. :", "TR_COINMARKET_UNKNOWN_PROVIDER": "Fournisseur inconnu", + "TR_COINMARKET_VIEW_DETAILS": "Afficher les détails", "TR_COINMARKET_YOUR_BEST_OFFER": "Votre meilleure offre", "TR_COINMARKET_YOU_BUY": "Vous achetez", "TR_COINMARKET_YOU_GET": "Vous obtenez", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Confirmé", "TR_CONFIRMING_TX": "Confirmation de la transaction", "TR_CONFIRM_ACTION_ON_YOUR": "Suivez les instructions sur l’écran de votre Trezor", + "TR_CONFIRM_ADDRESS": "Confirmer l’adresse", "TR_CONFIRM_BEFORE_COPY": "Confirmez sur Trezor avant de copier", "TR_CONFIRM_CONDITIONS": "Confirmez les conditions avant de poursuivre.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Confirmer que le portefeuille protégé par une phrase secrète est vide sur le dispositif « {deviceLabel} ».", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Gérer", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Activez la boîte de dialogue de saisie de la phrase secrète lorsque vous ouvrez Trezor Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Utilisez-vous principalement une phrase secrète ?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Vérifiez sur Trezor pour confirmer l’adresse de réception. Il n’est pas recommandé de poursuivre sans confirmer.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Vérifiez votre Trezor pour confirmer l’adresse de réception. Il n’est pas recommandé de continuer sans avoir confirmé.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "L’adresse de réception ne peut pas être vérifiée", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Pour envoyer des coins, connectez votre Trezor.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Votre Trezor n’est pas connecté", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Connecter Trezor pour envoyer", "TR_CONNECT_YOUR_DEVICE": "Connectez-vous et déverrouillez votre Trezor", - "TR_CONTACT_SUPPORT": "Contacter l’assistance", + "TR_CONTACT_SUPPORT": "Contacter l’assistance Trezor", "TR_CONTACT_TREZOR_SUPPORT": "Contacter l’assistance Trezor", "TR_CONTINUE": "Continuer", "TR_CONTINUE_ANYWAY": "Continuer quand même", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "N’envoyez jamais de fonds à une adresse relative à un identifiant de politique.", "TR_COPY_AND_CLOSE": "Copier et fermer", "TR_COPY_SIGNED_MESSAGE": "Copier le message signé", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copier", + "TR_COPY_TO_CLIPBOARD": "Copier", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Impossible de récupérer le journal des modifications", "TR_COULD_NOT_RETRIEVE_DATA": "Impossible de récupérer les données", "TR_COUNT_WALLETS": "{count} {count, plural, one {portefeuille} other {portefeuilles}}", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "L’actif n’a pas été chargé correctement", "TR_DASHBOARD_DISCOVERY_ERROR": "Erreur de découverte", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Les comptes n’ont pas été chargés correctement {details}", + "TR_DATA": "Données", "TR_DATABASE_UPGRADE_BLOCKED": "Mise à niveau de la base de données bloquée par une autre instance d’application", "TR_DATA_ANALYTICS_CATEGORY_1": "Plateforme", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "Système d’exploitation, modèle Trezor, version, etc.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "En mode bootloader par erreur ?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Reconnecter le dispositif sans toucher à aucun bouton.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Reconnectez le dispositif sans toucher l’écran.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Ce dispositif est utilisé ailleurs.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "L’application {transportSessionOwner} peut être en train d’utiliser ce dispositif. En cas de besoin, vous pouvez prendre le contrôle de ce dispositif.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Une autre application peut être en train d’utiliser ce dispositif. En cas de besoin, vous pouvez prendre le contrôle de ce dispositif.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Dispositif détecté dans un état incorrect", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Votre Trezor a été déconnecté pendant le processus de sauvegarde. Nous vous recommandons vivement d’utiliser l’option de réinitialisation aux paramètres d’usine dans les paramètres du dispositif pour effacer votre dispositif et recommencer le processus de sauvegarde du portefeuille.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "La vérification du hachage du micrologiciel a échoué. Votre Trezor est peut-être une contrefaçon.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "La vérification du hachage du micrologiciel n’a pas pu être effectuée. Votre Trezor est peut-être une contrefaçon.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Désactiver", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Activer", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "La vérification de la révision du micrologiciel est une fonction de sécurité cruciale. Nous vous recommandons de l’activer en permanence.", @@ -726,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "Le dispositif « {deviceLabel} » n’est pas sauvegardé", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "Le dispositif « {deviceLabel} » n’est pas connecté", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "Le dispositif « {deviceLabel} » n’est pas disponible", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Échec de la vérification du dispositif", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Reconnectez votre dispositif et réessayez la vérification. Si le problème persiste, contactez l’assistance Trezor pour savoir ce qui se passe avec votre dispositif et ce qu’il faut faire ensuite.", "TR_DEVICE_NOT_CONNECTED": "Dispositif non connecté", "TR_DEVICE_NOT_INITIALIZED": "Trezor n’est pas configuré", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Nous vous guiderons tout au long du processus et vous aiderons à démarrer immédiatement.", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Télécharger le dernier Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Ne déconnectez pas votre dispositif", "TR_DO_NOT_SHOW_AGAIN": "Ne plus afficher", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Passer cette étape ?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Glisser-déposer un fichier ici ou cliquer pour sélectionner des fichiers", "TR_DROPZONE_ERROR": "Échec de l’importation : {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Rejoindre", "TR_EARLY_ACCESS_ENABLED": "Programme Early Access activé", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Rejoindre", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Je comprends que cela me permet de tester la version préliminaire du logiciel, qui peut contenir des erreurs qui affectent le fonctionnement normal de Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Je comprends que cela me permet de tester la version préliminaire du logiciel, qui peut contenir des erreurs qui affectent le fonctionnement normal de Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Vous pouvez le désactiver à tout moment.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Tester les dernières fonctionnalités du produit avant leur mise à disposition du grand public.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Vérifiez d’abord le champ ci-dessus", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Vous pouvez rechercher des mises à jour bêta dès maintenant ou lors du prochain lancement.", "TR_EARLY_ACCESS_JOINED_TITLE": "Programme Early Access activé", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Pour revenir à la dernière version stable de Suite, cliquez sur « Télécharger version stable » et réinstallez l’application.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Pour revenir à la dernière version stable de Trezor Suite, cliquez sur « Télécharger version stable » et réinstallez l’application.", "TR_EARLY_ACCESS_LEFT_TITLE": "Vous avez quitté le programme Early Access. Les versions bêta ne sont plus proposées.", "TR_EARLY_ACCESS_MENU": "Programme Early Access", "TR_EARLY_ACCESS_REINSTALL": "Télécharger version stable", @@ -842,8 +906,8 @@ "TR_ENTER_SEED_WORDS_ON_DEVICE": "Les mots sont saisis sur le dispositif pour des raisons de sécurité. Veuillez saisir les mots dans le bon ordre.", "TR_ENTER_WIPECODE": "Saisir le code d’effacement", "TR_ERROR": "Erreur", - "TR_ERROR_CARDANO_DELEGATE": "Le montant est insuffisant", - "TR_ERROR_CARDANO_WITHDRAWAL": "Le montant est insuffisant", + "TR_ERROR_CARDANO_DELEGATE": "Le montant n’est pas suffisant", + "TR_ERROR_CARDANO_WITHDRAWAL": "Le montant n’est pas suffisant", "TR_ETH_ADDRESS_CANT_VERIFY_HISTORY": "Impossible de vérifier l’historique de l’adresse. Vérifiez que l’adresse est correcte.", "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "L’adresse n’est associée à aucun historique de transaction et n’est pas une adresse de somme de contrôle. Vérifiez que l’adresse est correcte.", "TR_EVM_EXPLANATION_DESCRIPTION": "Il partage le même style d’adresse qu’Ethereum, mais possède ses propres coins et jetons uniques qui ne peuvent pas être utilisés sur d’autres réseaux.", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Approuvez uniquement le montant exact requis pour cet échange. Vous devrez payer des frais supplémentaires si vous souhaitez effectuer à nouveau un échange similaire.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Révoquer l’approbation précédente", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Effectuer une transaction qui supprimera l’approbation préalable du contrat avec {provider}.", - "TR_EXCHANGE_BUY": "Pour", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirmer sur Trezor et envoyer", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirmer et envoyer", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Créer une approbation", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "La transaction a été effectuée avec succès.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Approuvé", "TR_EXCHANGE_DEX": "Offre d’échange décentralisé", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Les frais d’exécution de ce swap sont estimés à {approvalFee} ({approvalFeeFiat}) pour approbation (si nécessaire) et {swapFee} ({swapFeeFiat}) pour l’échange.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Il ne reste plus de fonds pour les frais de transaction. Veuillez réduire le montant de l’échange à {max} {symbol} maximum.", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} n’est pas valide", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Les taux fixes vous indiquent exactement le montant que vous obtiendrez à la fin de l’échange : le montant ne variera pas entre le moment où vous sélectionnez le taux et celui où votre transaction est finalisée. Le montant indiqué vous est garanti, mais ces taux sont généralement moins avantageux, ce qui signifie que vous ne pourrez pas acheter autant de crypto avec les mêmes fonds.", "TR_EXCHANGE_FLOAT": "Offre à taux variable", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Les taux variables signifient que le montant final obtenu peut légèrement varier en raison des fluctuations du marché entre le moment où vous sélectionnez le taux et le moment où votre transaction est finalisée. Ces taux sont généralement plus élevés, ce qui signifie que vous pourriez obtenir plus de crypto.", - "TR_EXCHANGE_PROVIDER": "Fournisseur", "TR_EXCHANGE_RATE": "Prix", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Le compte de réception est en dehors de Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Il s’agit de l’adresse alphanumérique spécifique qui recevra vos coins.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Veuillez saisir un nombre.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Saisir le glissement souhaité.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Montant de l’offre d’échange", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Synthèse du glissement", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolérance au glissement", - "TR_EXCHANGE_TRANS_ID": "Identifiant trans. :", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Utiliser un compte ({symbol}) qui n’est pas dans Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Adresse de réception", - "TR_EXCHANGE_VIEW_DETAILS": "Afficher les détails", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Mises à jour automatiques pour Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite télécharge automatiquement la dernière version en arrière-plan et l’installe lors du redémarrage de l’application. Vous bénéficiez ainsi en permanence de la mise à jour des dernières fonctionnalités et des correctifs de sécurité. Les mises à jour ont lieu sans nécessiter votre autorisation.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Activez la BNB Smart Chain sans transactions internes antérieures.", "TR_EXPERIMENTAL_FEATURES": "Expérimental", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Fonctionnalités expérimentales", "TR_EXPERIMENTAL_FEATURES_WARNING": "Destiné uniquement aux utilisateurs expérimentés. Cette utilisation se fait à vos risques et périls. Ces fonctionnalités sont en cours de test, peuvent être instables et ne pas être prises en charge à long terme.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migration des mots de passe Dropbox", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Un outil permettant de récupérer les mots de passe stockés sur Dropbox et sécurisés par Trezor. Conçu pour les anciens utilisateurs de l’extension Chrome du Gestionnaire de mots de passe Trezor.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Utilisez cet outil pour récupérer les mots de passe stockés sur Dropbox et sécurisés par Trezor. Conçu pour les anciens utilisateurs de l’extension Chrome du Gestionnaire de mots de passe Trezor.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake est un système qui permet d’accéder à des sites Web et à des applications soumis à la censure.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Accédez aux sites Web et applications censurés en utilisant Tor Snowflake, un système conçu pour contourner les restrictions.", "TR_EXPORT_AS": "Exporter en tant que {as}", "TR_EXPORT_FAIL": "L’exportation a échoué.", "TR_EXPORT_TO_FILE": "Exporter vers un fichier", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Un nouveau micrologiciel est désormais disponible. Mettez votre dispositif à jour dès maintenant.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Le micrologiciel le plus récent est déjà installé sur votre dispositif. Vous pouvez réinstaller le micrologiciel si nécessaire.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "La vérification de la révision du micrologiciel a échoué. Votre Trezor est peut-être une contrefaçon.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "La vérification de la révision du micrologiciel n’a pas pu être effectuée.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Terminé", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Micrologiciel léger prenant uniquement en charge les opérations en Bitcoin.", "TR_FIRMWARE_SUBHEADING_NONE": "Votre Trezor est livré sans micrologiciel. Installez la dernière version du micrologiciel pour utiliser votre dispositif en toute sécurité. Pour les utilisateurs de Bitcoin uniquement, nous recommandons d’installer .", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "J’ai compris", "TR_GO_TO_ONBOARDING": "Commencer la configuration", "TR_GO_TO_SETTINGS": "Accéder aux paramètres", - "TR_GO_TO_SUITE": "Accéder à Suite", + "TR_GO_TO_SUITE": "Accéder à Trezor Suite", "TR_GRAPH_LINEAR": "Linéaire", "TR_GRAPH_LOGARITHMIC": "Logarithmique", "TR_GRAPH_MISSING_DATA": "Tous les montants de jetons XRP, SOL et autres sont inclus dans le solde du portefeuille, mais ne sont actuellement pas pris en charge dans le graphique.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Forum Trezor", "TR_GUIDE_FORUM_LABEL": "Rejoindre la communauté Trezor", "TR_GUIDE_SUGGESTION_LABEL": "Comment nous débrouillons-nous ?", - "TR_GUIDE_SUPPORT": "Contacter l’assistance", + "TR_GUIDE_SUPPORT": "Contacter l’assistance Trezor", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Assistance et commentaires", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Assistance et commentaires", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Aidez-nous à nous améliorer", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Système de fichiers local", "TR_LOG": "Journal de bord de l’application", "TR_LOGIN_PROCEED": "Procéder", - "TR_LOG_DESCRIPTION": "Le journal contient toutes les informations techniques nécessaires sur Trezor Suite. Il peut s’avérer nécessaire lors de la connexion à l’assistance Trezor.", + "TR_LOG_DESCRIPTION": "Ce journal contient des informations techniques essentielles sur Trezor Suite et peut être requis si vous contactez l’assistance Trezor.", "TR_LOOKING_FOR_COINJOIN_ROUND": "En attente d’un tour", "TR_LOW_ANONYMITY_WARNING": "Très faible niveau de confidentialité. Nous vous recommandons d’utiliser un niveau d’au moins 1 sur 5, car tout ce qui est inférieur à ce seuil n’est pas confidentiel.", "TR_LTC_ADDRESS_INFO": "Litecoin a changé le format d’adresse. Vous trouverez plus d’informations sur la façon de convertir votre adresse sur notre blog. {TR_LEARN_MORE}", @@ -1223,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portfolio", "TR_NAV_ANONYMIZE": "Rendre les coins confidentiels", "TR_NAV_BUY": "Acheter", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Détails", "TR_NAV_RECEIVE": "Recevoir", "TR_NAV_SELL": "Vendre", @@ -1264,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimism", "TR_NETWORK_POLYGON": "Polygone PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana DevNet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1276,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nouveau", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Nouveau Trezor Bridge disponible.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Le nouveau micrologiciel Trezor est disponible ! Veuillez mettre\n à jour votre dispositif.", "TR_NEXT_UP": "Suivant", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (avec le caractère non recommandé « {char} »)", + "TR_NON_ASCII_CHARS": "{label} (avec des caractères non recommandés)", "TR_NORMAL_ACCOUNTS": "Comptes par défaut", "TR_NORTH": "Nord", "TR_NOTHING_TO_ANONYMIZE": "Aucun élément à rendre confidentiel", @@ -1296,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Portefeuille standard", "TR_NO_SEARCH_RESULTS": "Aucun résultat pour votre critère de recherche", "TR_NO_SPENDABLE_UTXOS": "Il n’y a pas d’UTXO pouvant être dépensé dans votre compte.", - "TR_NO_TRANSPORT": "Le navigateur ne peut pas communiquer avec le dispositif", + "TR_NO_TRANSPORT": "Votre navigateur ne peut pas communiquer avec votre dispositif", "TR_NO_TRANSPORT_DESKTOP": "L’application ne peut pas communiquer avec le dispositif", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {compte} other {comptes}} • {fiatValue}", "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transaction} other {transactions}}", "TR_OFF": "désactivé", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Le montant choisi de {amount} est supérieur au maximum accepté de {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Le montant choisi de {amount} est supérieur au maximum accepté de {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Le montant choisi de {amount} est inférieur au minimum accepté de {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Le montant choisi de {amount} est inférieur au minimum accepté de {min}.", "TR_OFFICIAL_LANGUAGES": "Officiel", "TR_OK": "OK", "TR_ON": "activé", @@ -1333,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Collecte de données anonymes", "TR_ONBOARDING_DEVICE_CHECK": "Vérification de la sécurité du dispositif", "TR_ONBOARDING_DEVICE_CHECK_1": "Mon hologramme était intact et n’a pas été altéré.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Mon dispositif a été acheté auprès de la boutique officielle Trezor Shop ou d’un revendeur de confiance.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Mon dispositif a été acheté auprès de la boutique officielle Trezor Shop ou d’un revendeur de confiance.", "TR_ONBOARDING_DEVICE_CHECK_3": "L’emballage du dispositif était intact et n’a pas été altéré.", "TR_ONBOARDING_DEVICE_CHECK_4": "Le micrologiciel est déjà installé sur le Trezor connecté. Poursuivez la configuration uniquement si vous avez déjà utilisé ce Trezor.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Télécharger l’application de bureau", @@ -1364,30 +1417,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Autres entrées et sorties", "TR_OUTGOING": "Sortant", "TR_OUTPUTS": "Sorties", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Les montants minimum et maximum pour lesquels cet utilisateur est disposé à vendre {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Vous devez initier la transaction sur {providerName} - assurez-vous de suivre attentivement les étapes ci-dessous.", - "TR_P2P_GET_STARTED_ITEM_1": "Sélectionnez « Accéder à {providerName} » pour être redirigé vers le site Web de notre partenaire.", - "TR_P2P_GET_STARTED_ITEM_3": "Une fois que {providerName} vous demande une adresse de diffusion, revenez ici et continuez.", - "TR_P2P_GET_STARTED_ITEM_4": "Vous y êtes presque ! Révélez et copiez votre adresse, collez-la à nouveau dans le champ « Adresse de diffusion » sur {providerName}, et finalisez la transaction.", - "TR_P2P_GO_TO_PROVIDER": "Accéder à {providerName}", - "TR_P2P_INFO": "Avec la technologie {peerToPeer} (P2P), aucune vérification KYC n’est impliquée ni du côté de l’acheteur ni du côté du vendeur. Toutes les parties sont protégées contre la fraude par {multisigEscrow} sécurisées.", - "TR_P2P_MODAL_CONFIRM": "Je suis prêt à acheter", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Acheter des {cryptocurrency} en pair à pair avec {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Mentions légales", - "TR_P2P_MODAL_SECURITY_HEADER": "La sécurité avant tout avec votre Trezor", - "TR_P2P_MODAL_TERMS_1": "Vous êtes ici pour acheter des cryptomonnaies à une autre personne de votre choix en utilisant la technologie Peer-to-Peer (P2P) sans vérification d’identité. Si vous avez été dirigé vers ce site pour toute autre raison, veuillez contacter l’assistance avant de poursuivre.", - "TR_P2P_MODAL_TERMS_2": "Vous comprenez que les transactions de cryptomonnaies sont irréversibles et ne peuvent être remboursées. Par conséquent, les fonds perdus de manière frauduleuse ou accidentelle peuvent être irrécupérables.", - "TR_P2P_MODAL_TERMS_4": "Vous comprenez qu’Invity ne fournit pas ce service. Les conditions de {provider} régissent le service.", - "TR_P2P_MODAL_TERMS_5": "Vous n’utilisez pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre violation des conditions de service d’Invity ou du fournisseur, ou de toute réglementation applicable.", - "TR_P2P_MODAL_TERMS_6": "Vous comprenez que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une juridiction à l’autre. Cela peut vous exposer à un risque plus élevé de fraude, de vol ou d’instabilité du marché.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Partenaires vérifiés par Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "La transaction doit être effectuée dans ce délai, à compter de la création d’un contrat sur le site de {providerName}.", - "TR_P2P_PRICE": "Prix pour 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "Prix en {symbol} proposé par cet utilisateur.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Bonjour, j’utilise {providerName} !", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Le montant choisi de {amount} est supérieur au maximum accepté de {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Le montant choisi de {amount} est inférieur au minimum accepté de {minimum}.", "TR_PAGINATION_NEWER": "Plus récent", "TR_PAGINATION_OLDER": "Ancien", "TR_PASSPHRASE_CASE_SENSITIVE": "Remarque : la phrase secrète est sensible à la casse.", @@ -1398,6 +1427,8 @@ "TR_PASSPHRASE_MISMATCH": "Erreur de phrases secrètes", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Les phrases secrètes ne sont pas identiques. Pour des raisons de sécurité, recommencez et saisissez-les correctement.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Recommencer", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Nous vous recommandons d’utiliser les caractères suivants : ABC, abc, 123, espaces ou ces caractères spéciaux", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "L’utilisation de caractères spéciaux non répertoriés risque de compromettre la compatibilité future", "TR_PASSPHRASE_TOO_LONG": "La longueur de la phrase secrète a dépassé la limite autorisée.", "TR_PASSPHRASE_WALLET": "Portefeuille protégé par une phrase secrète n° {id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Découvrir comment fonctionne une phrase secrète", @@ -1439,15 +1470,30 @@ "TR_PIN_SUBHEADING": "L’utilisation d’un code PIN fort protège votre Trezor contre tout accès physique non autorisé.", "TR_PLAY_IT_SAFE": "Jouons la carte de la sécurité", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Veuillez autoriser votre appareil photo à scanner un code QR.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Veuillez connecter votre dispositif pour continuer le processus de vérification.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Connectez votre Trezor pour continuer le processus de vérification.", "TR_PLEASE_ENABLE_PASSPHRASE": "Veuillez activer la fonctionnalité de phrase secrète pour continuer le processus de vérification.", "TR_POLICY_ID_ADDRESS": "Identifiant de politique :", "TR_PRIMARY_FIAT": "Devise Fiat", "TR_PRIVATE": "Confidentiel", "TR_PRIVATE_DESCRIPTION": "Confidentialité au moins au niveau {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Poursuivre avec une adresse non vérifiée", "TR_PROMO_BANNER_DASHBOARD": "Le portefeuille matériel le plus pratique pour gérer vos cryptomonnaies en toute sécurité", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Confirmez sur Trezor avant de procéder à la numérisation", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Veuillez tout d’abord confirmer l’adresse de réception sur votre dispositif Trezor, car son affichage fiable ne peut pas être piraté.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Activé", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Mise à jour terminée ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Redémarrer pour mettre à jour", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Dispositif Trezor", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Mise à jour disponible ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "À jour ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite a téléchargé une nouvelle mise à jour.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite a été mis à jour.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Une mise à jour de Trezor Suite est désormais disponible", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Redémarrer et mettre à jour", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Lancer la mise à jour", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Une mise à jour de Trezor est désormais disponible", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Nouveautés", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Il se peut qu’on vous demande de saisir certains mots qui ne font pas partie de la sauvegarde de votre portefeuille", "TR_RANGE": "plage", "TR_READ_AND_UNDERSTOOD": "J’ai lu et compris ce qui précède", @@ -1511,7 +1557,7 @@ "TR_SECURITY_CHECKPOINT_GOT_SEED": "Avez-vous la sauvegarde de votre portefeuille ?", "TR_SECURITY_CHECK_HOLOGRAM": "Veuillez noter que l’emballage des dispositifs, y compris les hologrammes et les sceaux de sécurité, a été mis à jour au fil du temps. Vous pouvez vérifier les détails de l’emballage ici. Assurez-vous que votre dispositif a été acheté auprès de la boutique officielle Trezor Shop ou de l’un de nos revendeurs de confiance. Dans le cas contraire, il y a un risque que votre dispositif soit une contrefaçon. Si vous avez un doute sur l’authenticité de votre dispositif, veuillez contacter l’assistance Trezor.", "TR_SECURITY_FEATURES_COMPLETED_N": "Sécurité ({n} sur {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Les dispositifs configurés en mode sans seed de récupération ne peuvent pas accéder à Trezor Suite. Ceci permet d’éviter la perte irréversible de coins, qui se produit lorsqu’un dispositif mal configuré est utilisé à des fins inappropriées.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Les dispositifs configurés en mode sans seed de récupération ne peuvent pas accéder à Trezor Suite pour éviter la perte irréversible de coins, qui peut se produire si un dispositif est utilisé de manière incorrecte.", "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "La configuration sans seed de récupération n’est pas prise en charge par Trezor Suite", "TR_SEED_BACKUP_LENGTH": "Votre sauvegarde du portefeuille peut contenir 12, 18 ou 24 mots.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Votre sauvegarde du portefeuille peut contenir 12, 18, 20, 24 ou 33 mots.", @@ -1522,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Entrez les mots de votre sauvegarde du portefeuille dans l’ordre affiché sur votre Trezor.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "À l’aide de l’écran tactile, saisissez tous les mots dans le bon ordre jusqu’à ce que vous ayez terminé.", "TR_SEE_DETAILS": "Voir les détails", + "TR_SEE_IF_ISSUE_PERSISTS": "Vérifiez si le problème persiste.", "TR_SELECTED": "{amount} sélectionné", "TR_SELECT_COIN_FOR_SETTINGS": "Sélectionnez le coin actif pour modifier les paramètres", "TR_SELECT_DEVICE": "Sélectionner un dispositif", + "TR_SELECT_NAME_OR_ADDRESS": "Rechercher par nom, symbole, réseau ou adresse de contrat", "TR_SELECT_NUMBER_OF_WORDS": "Sélectionnez le nombre de mots dans votre sauvegarde du portefeuille", "TR_SELECT_PASSPHRASE_SOURCE": "Sélectionnez l’endroit où vous souhaitez entrer la phrase secrète en cliquant sur « {deviceLabel} ».", "TR_SELECT_RECOVERY_METHOD": "Sélectionner la méthode de récupération", + "TR_SELECT_TOKEN": "Sélectionner un jeton", "TR_SELECT_TREZOR": "Sélectionner Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Sélectionnez votre Trezor pour continuer.", "TR_SELECT_TYPE": "Sélectionnez le type", @@ -1558,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Vendez des {cryptocurrency} avec {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Mentions légales", "TR_SELL_MODAL_SECURITY_HEADER": "La sécurité avant tout avec votre Trezor", - "TR_SELL_MODAL_TERMS_1": "Vous êtes ici pour vendre des cryptomonnaies. Si vous avez été dirigé vers ce site pour toute autre raison, veuillez contacter l’assistance avant de poursuivre.", + "TR_SELL_MODAL_TERMS_1": "Vous êtes ici pour vendre des cryptomonnaies. Si vous avez été dirigé vers ce site pour toute autre raison, veuillez contacter l’assistance Trezor avant de poursuivre.", "TR_SELL_MODAL_TERMS_2": "Vous vendez des cryptomonnaies pour votre propre compte. Vous reconnaissez que les politiques du fournisseur peuvent nécessiter une vérification d’identité.", "TR_SELL_MODAL_TERMS_3": "Vous comprenez que les transactions de cryptomonnaies sont irréversibles et ne peuvent être remboursées. Par conséquent, les fonds perdus de manière frauduleuse ou accidentelle peuvent être irrécupérables.", "TR_SELL_MODAL_TERMS_4": "Vous comprenez qu’Invity ne fournit pas ce service. Les conditions de {provider} régissent le service.", - "TR_SELL_MODAL_TERMS_5": "Vous n’utilisez pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre violation des conditions de service d’Invity ou du fournisseur, ou de toute réglementation applicable.", + "TR_SELL_MODAL_TERMS_5": "Je n’utilise pas cette fonctionnalité à des fins de jeux d’argent, de fraude ou de toute autre activité qui violerait les conditions de service d’Invity ou du fournisseur, ou toute loi applicable.", "TR_SELL_MODAL_TERMS_6": "Vous comprenez que les cryptomonnaies représentent un instrument financier émergent et que les réglementations peuvent varier d’une juridiction à l’autre. Cela peut vous exposer à un risque plus élevé de fraude, de vol ou d’instabilité du marché.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Partenaires vérifiés par Invity", "TR_SELL_REGISTER": "Inscription", @@ -1571,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Rejeté", "TR_SELL_STATUS_PENDING": "En attente", "TR_SELL_STATUS_SUCCESS": "Approuvé", - "TR_SELL_TRANS_ID": "Identifiant trans. :", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Le maximum est {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Le minimum est {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Afficher les détails", "TR_SENDFORM_LABELING_EXAMPLE_1": "Épargne", "TR_SENDFORM_LABELING_EXAMPLE_2": "Location", "TR_SENDING_SYMBOL": "Envoi de {multiple, select, true {plusieurs jetons} false {{symbol}} other {{symbol}}}", @@ -1629,6 +1674,8 @@ "TR_SHOW_LOG": "Afficher le journal", "TR_SHOW_MORE": "Afficher plus", "TR_SHOW_MORE_ADDRESSES": "Afficher plus ({count})", + "TR_SHOW_ON_TRAY": "Afficher l’icône dans la barre d’état", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Vérifiez si Trezor Suite fonctionne en arrière-plan.", "TR_SHOW_UNVERIFIED_ADDRESS": "Afficher l’adresse non vérifiée", "TR_SHOW_UNVERIFIED_XPUB": "Afficher la clé publique non vérifiée", "TR_SIDEBAR_ADD_COIN": "Ajouter un coin", @@ -1641,7 +1688,9 @@ "TR_SIZE": "Taille", "TR_SKIP": "Ignorer", "TR_SKIP_BACKUP": "Ignorer la sauvegarde", + "TR_SKIP_BACKUP_DESCRIPTION": "Une sauvegarde de portefeuille vous permet de récupérer vos fonds si votre Trezor est perdu, volé ou endommagé. Sans sauvegarde, vous pourriez perdre définitivement l’accès à vos cryptos.", "TR_SKIP_PIN": "Ignorer le code PIN", + "TR_SKIP_PIN_DESCRIPTION": "Un code PIN sur le dispositif empêche l’accès non autorisé à votre Trezor. Sans lui, toute personne possédant votre dispositif peut accéder à vos fonds.", "TR_SKIP_ROUNDS": "Tour ignoré", "TR_SKIP_ROUNDS_DESCRIPTION": "En permettant d’ignorer les tours, il est plus difficile de prouver une quelconque relation entre vos entrées. Cela signifie que vous pouvez encore mieux dissimuler l’origine des fonds.", "TR_SKIP_ROUNDS_HEADING": "Autoriser Trezor à ignorer des tours", @@ -1654,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Ajout au pool de staking", "TR_STAKE_ANY_AMOUNT_ETH": "Stakez un montant minimum de {amount} {symbol} et commencez à obtenir des récompenses. Avec notre taux APY actuel de {apyPercent} %, vos récompenses gagnent également !", "TR_STAKE_APY": "Rendement annuel en pourcentage (APY)", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "* Rendement annuel en pourcentage (APY)", "TR_STAKE_AVAILABLE": "Disponible", "TR_STAKE_CAN_CLAIM_WARNING": "Vous pouvez déjà demander {amount} {symbol}. {br}Veuillez demander ou attendre le traitement d’un nouvel unstake.", @@ -1663,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Vous pouvez faire une demande une fois la période d’unstaking terminée.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "dans le bloc suivant", "TR_STAKE_CLAIM_PENDING": "Demande en cours", + "TR_STAKE_CLAIM_UNSTAKED": "Réclamer des actifs {symbol} unstakés", "TR_STAKE_CONFIRM_AND_STAKE": "Confirmer et staker", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Confirmez la période d’entrée", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Je reconnais et j’accepte de staker avec Everstake", "TR_STAKE_DAYS": "{count, plural, one {# jour} other {# jours}}", "TR_STAKE_DELEGATED": "Délégation du staking", "TR_STAKE_DEREGISTERED": "Annulation de l’enregistrement d’une adresse de staking", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Gagnez des récompenses chaque semaine", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "L’entrée dans le pool de staking peut prendre jusqu’à {count, plural, one {# jour} other {# jours}}", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Entrer dans le pool de staking", "TR_STAKE_ETH": "Staker de l’Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "Le moyen le plus simple de gagner des {symbol}.", + "TR_STAKE_ETH_CARD_TITLE": "Le moyen le plus simple de gagner des {symbol}", "TR_STAKE_ETH_EARN_REPEAT": "Stakez. Gagnez des récompenses. Recommencez.", "TR_STAKE_ETH_EVERSTAKE": "Trezor et Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake est un leader et fournisseur mondial de technologie de staking", @@ -1682,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Vos récompenses aussi ont des récompenses. Continuez à les staker et voyez vos récompenses {symbol} affluer.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Vos récompenses {symbol} bénéficient également du taux d’APY. Gardez vos fonds stakés ou ajoutez-en davantage pour augmenter vos récompenses.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Regardez votre argent se déchaîner", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Gagnez {apyPercent} % d’APY* en stakant votre Ethereum avec Trezor.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Gagnez {apyPercent} % d’APY en stakant votre Ethereum avec Trezor.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Votre {symbol} sera bloqué pendant cette période et vous ne pourrez pas l’annuler. En savoir plus", "TR_STAKE_EVERSTAKE_MANAGES": "Everstake gère et protège votre {symbol} staké grâce à son infrastructure, sa technologie et ses contrats intelligents.", "TR_STAKE_INSTANT": "Instantané", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Vous recevrez {amount} {symbol} « instantanément ». {days, plural, =0 {} one {Le montant restant vous sera payé d’ici # jour.} other { Le montant restant vous sera payé d’ici # jours}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} sur le compte", "TR_STAKE_LEARN_MORE": "En savoir plus", + "TR_STAKE_LEAVE_STAKING_POOL": "Quitter le pool de staking", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Nous avons laissé {amount} {symbol} à votre disposition pour que vous puissiez payer vos frais de retrait.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Nous avons laissé une petite quantité d’actifs {symbol} à votre disposition pour que vous puissiez payer vos frais de retrait.", "TR_STAKE_MAX": "Max", "TR_STAKE_MAX_FEE_DESC": "Le tarif maximum correspond aux frais de transaction du réseau que vous êtes prêt à payer sur le réseau pour garantir le traitement de votre transaction.", "TR_STAKE_MAX_REWARD_DAYS": "{count, plural, one {# jour} other {# jours}} max.", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "Le montant minimum à staker est de {amount} {symbol}", + "TR_STAKE_MONTHLY": "Mensuel", "TR_STAKE_NEXT_PAYOUT": "Prochain versement de récompenses", "TR_STAKE_NOT_ENOUGH_FUNDS": "{symbol} insuffisants pour payer les frais de réseau", "TR_STAKE_ONLY_REWARDS": "Uniquement des récompenses", @@ -1704,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Enregistrement d’une adresse de staking", "TR_STAKE_RESTAKED_BADGE": "Restaké", "TR_STAKE_REWARDS": "Récompenses", + "TR_STAKE_SIGN_TRANSACTION": "Signer la transaction", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Signer la transaction d’unstaking", "TR_STAKE_STAKE": "Staker", "TR_STAKE_STAKED_AMOUNT": "Montant staké", "TR_STAKE_STAKED_AND_EARNING": "Récompenses stakées et gains", @@ -1711,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Staker davantage", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Le staking en bref", "TR_STAKE_STAKING_IS": "Le staking consiste à verrouiller temporairement vos actifs Ethereum pour prendre en charge le fonctionnement de la blockchain. En guise de récompense, vous gagnerez un Ethereum supplémentaire.", + "TR_STAKE_STAKING_PROCESS": "Processus de staking", "TR_STAKE_START_STAKING": "Commencer à staker", "TR_STAKE_TIME_TO_CLAIM": "Délai de demande", "TR_STAKE_TOTAL_PENDING": "Total du staking en cours :", @@ -1719,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Unstaké et prêt à être demandé", "TR_STAKE_UNSTAKE_TO_CLAIM": "Unstaker pour demander", "TR_STAKE_UNSTAKING": "Unstaking", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Montant approximatif d’actifs {symbol} disponibles instantanément", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "La liquidité du pool de staking peut permettre de débloquer instantanément certains fonds. Les fonds restants suivront la période d’unstaking.", "TR_STAKE_UNSTAKING_PERIOD": "Période d’unstaking", + "TR_STAKE_UNSTAKING_PROCESS": "Processus d’unstaking", "TR_STAKE_UNSTAKING_TAKES": "Actuellement, l’unskating prend {count, plural, one {# jour} other {# jours}}. Une fois l’opération terminée, vous pourrez l’échanger ou envoyer vos fonds.", + "TR_STAKE_WEEKLY": "Hebdomadaire", "TR_STAKE_WHAT_IS_STAKING": "Qu’est-ce que le staking ?", + "TR_STAKE_YEARLY": "Annuel", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Vos fonds stakés sont gérés par Everstake", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} staké(s) instantanément !", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} unstaké(s) instantanément !", + "TR_STAKING_CONSOLIDATING_FUNDS": "Consolidation de vos {symbol} pour vous", "TR_STAKING_DELEGATE": "Déléguer", "TR_STAKING_DEPOSIT": "Dépôt remboursable", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "Les frais de dépôt sont de {feeAmount} ADA et sont nécessaires pour enregistrer votre adresse afin de commencer à staker. Si vous décidez d’unstaker votre Cardano, vous récupérerez le dépôt.", + "TR_STAKING_ESTIMATED_GAINS": "Gains estimés", "TR_STAKING_FEE": "Frais", + "TR_STAKING_GETTING_READY": "Vos {symbol} se préparent", "TR_STAKING_INSTANTLY_STAKED": "Vous avez staké {amount} {symbol} instantanément. {days, plural, =0 {} one {Les {symbol} restants seront stakés d’ici # jour.} other { Les {symbol} restants seront stakés d’ici # jours}}", "TR_STAKING_IS_NOT_SUPPORTED": "Le staking n’est pas pris en charge sur ce réseau.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Vous n’avez pas assez de fonds sur votre compte.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Une fois que vous confirmez", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "En stakant dans un pool de staking de Trezor, vous soutenez directement l’écosystème Trezor et l’écosystème Cardano au sein de Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Vous déléguez dans un pool de staking tiers", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "Le pool de staking dans lequel vous déléguez est saturé. Veuillez déléguer à nouveau votre staking pour maximiser vos récompenses de staking", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Le pool de staking est saturé", "TR_STAKING_REDELEGATE": "Déléguer à nouveau", "TR_STAKING_REWARDS": "Récompenses disponibles", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Les récompenses sont automatiquement restakées", "TR_STAKING_REWARDS_DESCRIPTION": "Veuillez noter qu’il peut s’écouler jusqu’à 20 jours avant que vous ne receviez vos récompenses après l’enregistrement initial et la délégation du staking. Une fois cette période passée, vous recevrez votre récompense tous les 5 jours.", "TR_STAKING_REWARDS_TITLE": "Le staking du Cardano est actif", "TR_STAKING_STAKE_ADDRESS": "Votre adresse de staking", @@ -1744,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Impossible d’atteindre le pool de staking de Trezor dans lequel déléguer.", "TR_STAKING_TX_PENDING": "Votre transaction {txid} a bien été envoyée à la blockchain et attend une confirmation.", "TR_STAKING_WITHDRAW": "Retirer", + "TR_STAKING_YOUR_EARNINGS": "Vos gains sont automatiquement restakés, ce qui vous permet de gagner des intérêts composés.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Vos {symbol} unstakés sont prêts", + "TR_STAKING_YOU_ARE_HERE": "Vous êtes là", "TR_STANDARD_WALLET_DESCRIPTION": "Aucune phrase secrète", "TR_START": "Début", "TR_START_AGAIN": "Redémarrer", @@ -1752,6 +1827,7 @@ "TR_START_COINJOIN": "Démarrer le Coinjoin", "TR_START_RECOVERY": "Démarrer la récupération", "TR_STEP": "Étape {number}", + "TR_STEP_OF_TOTAL": "Étape {index} sur {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Vous ne voyez toujours pas votre Trezor ?", "TR_STOP": "Arrêter", "TR_STOPPING": "Arrêt en cours", @@ -1803,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Aucun jeton... pour l’instant.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Aucun jeton. Les jetons peuvent être masqués.", "TR_TOKENS_SEARCH_TOOLTIP": "Rechercher par jeton, symbole ou adresse de contrat.", + "TR_TOKEN_NOT_FOUND": "Jeton introuvable", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Jeton introuvable sur le réseau {networkName}.", "TR_TOKEN_TRANSFERS": "{standard} Transferts de jetons", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Essayez une autre recherche.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Essayez une autre recherche ou changez de réseau.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Jetons non reconnus", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Les jetons non reconnus présentent des risques potentiels. Faites preuve de prudence.", "TR_TOO_LONG": "Le message est trop long", @@ -1815,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Mettre à jour le chemin d’accès", "TR_TOR_DESCRIPTION": "Acheminez l’ensemble du trafic de Trezor Suite via le réseau Tor, ce qui renforce votre confidentialité et votre sécurité. Le chargement de Tor et l’établissement d’une connexion peuvent prendre un certain temps.", "TR_TOR_DISABLE": "Désactiver Tor", + "TR_TOR_DISABLED": "Désactivé", "TR_TOR_DISABLE_ONIONS_ONLY": "Serveurs principaux personnalisés non onion manquants", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Veuillez ajouter des adresses de serveur principal personnalisé non-onion pour éviter ceci.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Vous pouvez maintenant désactiver Tor en toute sécurité.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Les serveurs principaux personnalisés n’utilisent plus uniquement des adresses onion.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Désactiver Tor", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Désactiver Tor maintenant réinitialise tous les serveurs principaux Onion aux serveurs Trezor par défaut.", + "TR_TOR_DISABLING": "Désactivation", "TR_TOR_ENABLE": "Activer Tor", + "TR_TOR_ENABLED": "Activé", "TR_TOR_ENABLE_AND_CONFIRM": "Activer Tor et confirmer", "TR_TOR_ENABLE_TITLE": "Activer Tor", + "TR_TOR_ENABLING": "Activation", + "TR_TOR_ERROR": "Erreur", "TR_TOR_IS_SLOW_MESSAGE": "Tor se connecte au réseau.

Patientez.", "TR_TOR_KEEP_RUNNING": "Continuer à exécuter Tor", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Veuillez sélectionner « Continuer à exécuter Tor » pour continuer ou « Arrêter Tor » pour quitter le processus Coinjoin.", + "TR_TOR_MISBEHAVING": "Mauvais comportement", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Désactiver Tor et basculer vers les serveurs principaux par défaut", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Quitter", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Sélectionnez « Activer Tor » pour continuer ou « Quitter » pour quitter le processus.", @@ -1841,11 +1927,7 @@ "TR_TO_BTC": "En BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Pour rendre vos étiquettes cohérentes et disponibles sur différents dispositifs, connectez-vous au fournisseur de services de stockage cloud.", "TR_TO_SATOSHIS": "En sats", - "TR_TRADE_BUYS": "achète", - "TR_TRADE_ENTER_COIN": "Saisissez le nom ou le symbole de la crypto…", - "TR_TRADE_EXCHANGES": "échange", "TR_TRADE_REDIRECTING": "Redirection...", - "TR_TRADE_SELLS": "vend", "TR_TRANSACTIONS_NOT_AVAILABLE": "L’historique des transactions n’est pas disponible", "TR_TRANSACTIONS_SEARCH_TIP_1": "Astuce : vous pouvez rechercher des ID de transaction, des adresses, des jetons, des étiquettes, des montants et des dates.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Astuce : combinez les opérateurs AND (&) et OR (|) pour des recherches plus complexes. Par exemple, > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 afficheront toutes les transactions effectuées en janvier ou décembre {lastYear}.", @@ -1860,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Recherchez par identifiant de transaction, étiquette ou montant ou utilisez des opérateurs tels que < > | & = !=.", "TR_TRANSACTION_DETAILS": "Détails", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Version actuelle de Trezor Bridge : {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutoriel annulé", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutoriel terminé", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Découvrez comment utiliser votre dispositif à l’aide d’un court tutoriel", @@ -1867,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Fermer les autres onglets et fenêtres qui pourraient utiliser votre Trezor", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Après la fermeture d’autres onglets et fenêtres, essayez de rafraîchir cette page.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Après avoir fermé d’autres onglets et fenêtres du navigateur, essayez de quitter et de rouvrir l’application Trezor Suite.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Étapes requises pour permettre la communication", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Essayez ces étapes pour résoudre le problème.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Consultez la page d’état de Trezor Bridge", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Assurez-vous que le processus Trezor Bridge est en cours d’exécution", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Seuls les navigateurs basés sur Chromium permettent actuellement une communication directe avec les dispositifs USB", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Seuls les navigateurs basés sur Chromium permettent actuellement une communication directe avec les dispositifs USB.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Utilisez un navigateur basé sur Chromium", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "Le câble doit être complètement inséré. Dans le cas d’un dispositif connecté en USB-C, le câble doit « se clipser ».", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Essayez un autre câble", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Avec Trezor Bridge installé.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Si possible, essayez d’utiliser un autre ordinateur", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Juste au cas où", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Essayez de redémarrer votre ordinateur", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Le redémarrage de votre ordinateur peut résoudre le problème de communication entre votre navigateur et votre dispositif.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Redémarrez votre ordinateur", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Lancez l’application de bureau Trezor Suite", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Utilisez l’application de bureau Trezor Suite", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Cliquez pour accéder à une autre implémentation du bridge. Version actuelle : ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Utilisez une autre version de Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Essayez d’installer les règles udev. Assurez-vous d’abord de les enregistrer sur le bureau avant d’ouvrir.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Si la dernière mise à jour du micrologiciel de votre dispositif date de 2019 ou d’avant, veuillez suivre les instructions de la base de connaissances.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Il semble que vous utilisiez une ancienne version de Trezor.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Connectez-le directement à l’ordinateur (sans hub USB).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Essayez un port USB différent", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Installer les règles automatiquement", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Règles udev manquantes", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "État inattendu : {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Votre dispositif est connecté correctement, mais votre navigateur ne peut pas communiquer avec celui-ci pour le moment. Vous devez installer Trezor Bridge.", "TR_TRY_AGAIN": "Réessayez", "TR_TXID": "TX ID", "TR_TXID_RBF": "Identifiant TX d’origine à remplacer", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transaction confirmée", "TR_TX_CONFIRMING": "Confirmation de la transaction", + "TR_TX_DATA_FUNCTION": "Fonction", + "TR_TX_DATA_INPUT_DATA": "Données d’entrée", + "TR_TX_DATA_METHOD": "Données d’entrée", + "TR_TX_DATA_METHOD_NAME": "Nom de la méthode", + "TR_TX_DATA_PARAMS": "Paramètres", "TR_TX_DEPOSIT": "Dépôt", "TR_TX_FEE": "Frais", "TR_TX_TAB_AMOUNT": "Montant", @@ -1932,13 +2023,18 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Mise à jour du micrologiciel requise. Vous pouvez modifier votre écran d’accueil ultérieurement dans la page des paramètres", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Mettez à jour votre micrologiciel pour modifier votre écran d’accueil", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Mise à jour disponible", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Activer les mises à jour automatiques", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Nouveau", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Mettre à jour et redémarrer", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Installer la mise à jour maintenant ?", "TR_UPDATE_MODAL_NOT_NOW": "Pas maintenant", - "TR_UPDATE_MODAL_RESTART_NEEDED": "Cela nécessite un redémarrage de Trezor Suite", + "TR_UPDATE_MODAL_RESTART_NEEDED": "Cela nécessite un redémarrage de Trezor Suite.", "TR_UPDATE_MODAL_START_DOWNLOAD": "Télécharger", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Mise à jour téléchargée", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Mettre à jour lors de la fermeture", + "TR_UPDATE_MODAL_WHATS_NEW": "Nouveautés", + "TR_UPDATE_MODAL_YOUR_VERSION": "Votre version : v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Mettez à jour votre micrologiciel pour accéder à ce compte. Voir la bannière bleue ci-dessus.", "TR_UP_TO": "jusqu’à", "TR_UP_TO_DATE": "À jour", "TR_UP_TO_DAYS": "jusqu’à {count, plural, one {# jour} other {# jours}}", @@ -1992,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Accéder au portefeuille protégé par une phrase secrète", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Portefeuille masqué", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Créez un nouveau portefeuille ou récupérez-en un en utilisant votre sauvegarde du portefeuille.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Nous nous efforçons toujours d’améliorer votre expérience Trezor. Découvrez les nouveautés :", "TR_WEST": "Ouest", "TR_WHAT_DATA_WE_COLLECT": "Quelles données recueillons-nous ?", "TR_WHAT_IS_PASSPHRASE": "En savoir plus sur la différence", @@ -2002,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Je comprends que je dois avoir une sauvegarde de ma sauvegarde du portefeuille afin de retrouver l’accès à mes fonds", "TR_WIPE_DEVICE_TEXT": "La réinitialisation du dispositif supprime toutes ses données. Réinitialisez votre dispositif uniquement si vous disposez d’une sauvegarde de votre portefeuille, laquelle vous permet de restaurer l’accès à vos fonds.", "TR_WIPE_OR_UPDATE": "Réinitialiser le dispositif ou mettre à jour le micrologiciel", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Accéder aux paramètres du dispositif", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Accédez aux paramètres du dispositif.", "TR_WIPING_YOUR_DEVICE": "La réinitialisation aux paramètres d’usine efface la mémoire du dispositif et toutes les informations, y compris la sauvegarde du portefeuille et le code PIN. Procédez à une réinitialisation d’usine uniquement si vous disposez d’une sauvegarde de votre portefeuille, laquelle est nécessaire pour restaurer l’accès à vos fonds.", "TR_WORDS": "{count} mots", "TR_WORD_DOES_NOT_EXIST": "Le mot « {word} » n’existe pas dans la liste de mots BIP39.", diff --git a/packages/suite-data/files/translations/hu.json b/packages/suite-data/files/translations/hu.json index 4397cce8e98..e52fdd09966 100644 --- a/packages/suite-data/files/translations/hu.json +++ b/packages/suite-data/files/translations/hu.json @@ -193,7 +193,6 @@ "TR_404_GO_TO_DASHBOARD": "Tovább az Irányítópultra", "TR_404_TITLE": "404-es hiba: Az oldal nem található", "TR_7D_CHANGE": "7 napos változás", - "TR_ABORT": "Megszakítás", "TR_ACCESS_HIDDEN_WALLET": "Hozzáférés rejtett tárcához", "TR_ACCESS_STANDARD_WALLET": "Hozzáférés a standard tárcához", "TR_ACCOUNT_DETAILS_HEADER": "Fiók részletei", @@ -297,6 +296,8 @@ "TR_ALLOW_ANALYTICS_DESCRIPTION": "Minden adatot szigorúan anonim módon kezelünk; csak a Trezor ökoszisztéma fejlesztésére használjuk őket.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Automatikus Trezor Suite frissítések", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "A Trezor Suite automatikusan letölti a legújabb verziót a háttérben, és az alkalmazás újraindításakor telepíti azt. Ez biztosítja, hogy mindig naprakész legyél a legújabb funkciókkal és biztonsági javításokkal. A frissítések az engedélyed nélkül történnek.", + "TR_ALL_NETWORKS": "Minden hálózat ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Mind a(z) {networkCount} hálózat tokenjeinek megtekintése. A legnépszerűbb hálózatok alapján a jobb oldalon tudsz szűrni.", "TR_ALL_TRANSACTIONS": "Tranzakciók", "TR_AMOUNT_SENT": "Küldött összeg", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Nem elérhető a coinjoin számára - az összeg túl nagy.", @@ -333,7 +334,7 @@ "TR_BACKSPACE": "Visszatörlés", "TR_BACKUP": "Tárca biztonsági mentés", "TR_BACKUP_CHECKBOX_1_DESCRIPTION": "Győződj meg róla, hogy minden szót pontosan a megadott sorrendben jegyeztél fel. Gondoskodj róla, hogy a seed-ed ne legyen vizes, vagy elmosódott.", - "TR_BACKUP_CHECKBOX_1_TITLE": "Mielőtt pénzt küldesz a tárcára, ellenőrizd a biztonsági mentést az eszközbeállításokban.", + "TR_BACKUP_CHECKBOX_1_TITLE": "A tárca biztonsági mentése lehetővé teszi a pénzed visszaszerzését a Trezor elvesztése vagy sérülése esetén.", "TR_BACKUP_CHECKBOX_2_DESCRIPTION": "Ne mentsd a seededet a telefonodba vagy bármely egyéb meghackelhető eszközbe, ide értve a felhő alapú szolgáltatásokat is.", "TR_BACKUP_CHECKBOX_2_TITLE": "Semmi esetre se készíts fotót vagy digitális másolatot a biztonsági mentésedről.", "TR_BACKUP_CHECKBOX_3_DESCRIPTION": "Jól rejtsd el és használj megfelelő óvintézkedéseket, hogy rajtad kívül más ne láthassa a seededet.", @@ -345,8 +346,8 @@ "TR_BACKUP_RECOVERY_SEED": "Biztonsági mentés", "TR_BACKUP_RECOVERY_SEED_FAILED_DESC": "Sikertelen biztonsági mentés. Erősen ajánlott a biztonsági mentés készítése. A következő linken megismerheted hogyan hozható létre a tárca biztonsági mentése.", "TR_BACKUP_RECOVERY_SEED_FAILED_TITLE": "Tárca biztonsági mentés sikertelen", - "TR_BACKUP_SEED_IS_ULTIMATE": "Ha valaha is vissza kell állítanod a tárcádat, hogy hozzáférj a pénzedhez, akkor mindenképpen szükséged lesz a tárca biztonsági mentésére. Ne veszítsd vagy tüntesd el. Ha elveszett, elveszett. Senki, még a Trezor ügyfélszolgálat sem tud segíteni a visszaállításában. Légy felelősségteljes és gondoskodj róla, hogy a tárca biztonsági mentését biztonságosan tárold, és úgy kezeld, mintha az életed múlna rajta.", - "TR_BACKUP_SUBHEADING_1": "A tárca biztonsági mentése a Trezor által véletlenszerűen generált szavak listája. Fontos, hogy leírd és biztonságban tárold a biztonsági mentést, mert csak ennek segítségével állíthatod helyre a tárcádat és férhetsz hozzá a pénzedhez.", + "TR_BACKUP_SEED_IS_ULTIMATE": "Ha valaha is vissza kell állítanod a tárcádat, hogy hozzáférj a pénzedhez, akkor mindenképpen szükséged lesz a tárca biztonsági mentésére. Ne veszítsd vagy kótyavetyéld el. Ha elveszett, elveszett. Senki, még a Trezor ügyfélszolgálat sem tud segíteni a visszaállításában. Légy felelősségteljes és gondoskodj róla, hogy a tárca biztonsági mentését biztonságosan tárold, és úgy kezeld, mintha az életed múlna rajta.", + "TR_BACKUP_SUBHEADING_1": "A tárca biztonsági mentése a Trezorod által véletlenszerűen generált szavak listája. Fontos, hogy leírd és biztonságban tárold a biztonsági mentést, mert csak ennek segítségével állíthatod helyre a tárcádat és férhetsz hozzá a pénzedhez.", "TR_BACKUP_SUCCESSFUL": "Sikeres biztonsági mentés", "TR_BALANCE": "Egyenleg", "TR_BASIC_RECOVERY": "Normál helyreállítás", @@ -365,10 +366,9 @@ "TR_BTC_UNITS": "Bitcoin alapegység", "TR_BUG": "Hiba", "TR_BUMP_FEE": "Tranzakciós díj emelése", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "A tranzakcióid gyorsítása érdekében növeld meg a sorban lévő (nonce szerint) legrégebbi függőben lévő tranzakció díját. A tranzakciókat sorrendben kell megerősíteni. További információk", "TR_BUY": "Vásárlás", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Adásvételi tranzakciók", "TR_BUY_BUY": "Vásárlás", - "TR_BUY_BUY_AGAIN": "Vásárlás újra", "TR_BUY_CONFIRMED_ON_TREZOR": "Jóváhagyva a Trezoron", "TR_BUY_DETAIL_ERROR_BUTTON": "Vissza a fiókhoz", "TR_BUY_DETAIL_ERROR_SUPPORT": "Ugrás a szolgáltató ügyféltámogatási oldalára", @@ -395,10 +395,10 @@ "TR_BUY_MODAL_SECURITY_HEADER": "Első a biztonság a Trezorral", "TR_BUY_MODAL_TERMS_1": "Azért vagy itt, hogy kriptovalutát vásárolj. Ha más okból lettél erre az oldalra irányítva, kérlek lépj kapcsolatba a(z) {provider} ügyfélszolgálatával, mielőtt továbblépnél.", "TR_BUY_MODAL_TERMS_2": "Ezt a funkciót olyan pénzeszközök vásárlására használod, amelyek a közvetlen személyes felügyeleted alatt álló fiókba lesznek küldve.", - "TR_BUY_MODAL_TERMS_3": "Megértetted hogy a kriptovaluta tranzakciók visszafordíthatatlanok és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", + "TR_BUY_MODAL_TERMS_3": "Megértettem, hogy a kriptovaluta tranzakciók visszafordíthatatlanok és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", "TR_BUY_MODAL_TERMS_4": "Megértetted, hogy nem az Invity nyújtja a szolgáltatást. {provider} feltételei szabályozzák a szolgáltatást.", "TR_BUY_MODAL_TERMS_5": "Ezt a funkciót nem szerencsejátékra, sem csalásra, sem az Invity vagy a szolgáltató használati feltételeit, sem bármely más alkalmazható szabályozást sértve használod.", - "TR_BUY_MODAL_TERMS_6": "Megértetted, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi rendelkezések bizonyos területeken csak korlátozottan vonatkoznak. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagy téve.", + "TR_BUY_MODAL_TERMS_6": "Megértettem, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi szabályozások földrajzi területenként eltérőek. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagyok téve.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Invity által ellenőrzött partnerek", "TR_BUY_NETWORK": "{network} vásárlása", "TR_BUY_NOT_TRANSACTIONS": "Még nincsenek tranzakciók.", @@ -413,12 +413,10 @@ "TR_BUY_STATUS_PENDING": "Függőben", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Függőben", "TR_BUY_STATUS_SUCCESS": "Jóváhagyva", - "TR_BUY_TRANS_ID": "Tranzakció azonosító:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "{maximum} a maximum", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "A maximális mennyiség {maximum}{currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "A minimális mennyiség {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "A minimális mennyiség {minimum}{currency}", - "TR_BUY_VIEW_DETAILS": "Részletek megtekintése", "TR_BYTES": "bájtok", "TR_CAMERA_NOT_RECOGNIZED": "A kamera nem lett felismerve.", "TR_CAMERA_PERMISSION_DENIED": "A kamerához való hozzáférés meg lett tagadva.", @@ -437,7 +435,6 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor Összeg", "TR_CHAINED_TXS": "Kapcsolt tranzakciók", "TR_CHANGELOG": "Változások naplója", - "TR_CHANGELOG_ON_GITHUB": "Változások naplója a GitHub-on", "TR_CHANGE_ADDRESS_TOOLTIP": "Ez egy korábbi küldésnél létrehozott visszajáró-cím.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "A firmware típusa bármikor megváltoztatható a Beállításokban.", "TR_CHANGE_HOMESCREEN": "Kezdőképernyő módosítása", @@ -454,8 +451,6 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "A biztonsági mentés hitelesítése az eszköz szimulált helyreállításával.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Írd be a biztonsági mentés szavait az eszközödön látható sorrendben. A biztonság fokozása érdekében olyan szavakat is be kell majd írnod, amelyek a tárcád biztonsági mentésének nem részei.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Használd a kétgombos billentyűzetet a biztonsági mentés bevitelére. Így minden érzékeny információ biztonságban marad, még kétes vagy nem biztonságos számítógép vagy webböngésző elől is.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "A tárca biztonsági mentése az érintőképernyőn keresztül kerül bevitelre. Így elkerülhető, hogy egy nem biztonságos számítógép vagy webböngésző hozzáférhessen ezekhez az érzékeny adatokhoz.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "A tárca biztonsági mentése az érintőképernyőn keresztül kerül bevitelre. Így elkerülhető, hogy egy nem biztonságos számítógép vagy webböngésző hozzáférhessen ezekhez az érzékeny adatokhoz.", "TR_CHECK_SEED": "Biztonsági mentés ellenőrzése", "TR_CHECK_YOUR_DEVICE": "Nézd a Trezor képernyőjét", "TR_CHOOSE_RECOVERY_TYPE": "Helyreállítási mód kiválasztása", @@ -530,8 +525,12 @@ "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC soha nem szükséges", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Minden CEX és DEX ajánlat", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "Fix árfolyamú CEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "Változó árfolyamú CEX", "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", - "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Decentralizált váltó - kriptovaluták kereskedelme blokklánc tranzakciókon keresztül, központi közvetítő nélkül.", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Egy decentralizált tőzsde (DEX) lehetővé teszi a kriptovaluták kereskedelmét közvetlenül a blokkláncon, központi közvetítő nélkül.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "Fix árfolyamú CEX", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "Változó árfolyamú CEX", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Kiemelt ajánlatok", "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Fizetés:", "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Vételi módszer:", @@ -554,12 +553,6 @@ "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Nem található CEX szolgáltató", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Nem található DEX szolgáltató", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Nincs elérhető módszer", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Automatikus frissítés ekkor", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Vissza az Adásvétel-hez", - "TR_COINMARKET_NO_OFFERS_HEADER": "Nincsenek ajánlatok", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Sajnos szerver kapcsolódási probléma miatt jelenleg nincs elérhető ajánlat.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Sajnos jelenleg nincs elérhető ajánlat. Próbáld meg frissíteni az oldalt vagy változtasd meg a lekérdezést.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Oldal frissítése", "TR_COINMARKET_OFFERS_EMPTY": "Nincs a kéréshez elérhető ajánlat. Módosítsd az országot vagy a vételi összeget.", "TR_COINMARKET_OFFERS_REFRESH": "Az ajánlatok frissülnek:", "TR_COINMARKET_OFFERS_SELECT": "Kiválaszt", @@ -578,10 +571,24 @@ "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Készen állok a váltásra", "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "{fromCrypto} váltása {toCrypto}-ra/re {provider} szolgáltatóval", "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Jogi nyilatkozat", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Első a biztonság a Trezorral", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Kriptovalutákat akarok váltani DEX (decentralizált tőzsde) használatával {provider} szerződését használva.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Kriptovalutákat akarok váltani a saját fiókom részére. Megértettem, hogy a szolgáltató irányelvei megkövetelhetik a személyazonosság ellenőrzését.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Megértettem, hogy a kriptovaluta tranzakciók véglegesek, nem fordíthatók és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Megértettem, hogy nem az Invity nyújtja ezt a szolgáltatást. Ezt a(z) {provider} általános szerződési feltételei szabályozzák.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Ezt a funkciót nem szerencsejátékra, csalásra, vagy bármely más, az Invity vagy a szolgáltató használati feltételeit, vagy az irányadó törvényeket sértő módon használom.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Megértettem, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi szabályozások földrajzi területenként eltérőek. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagyok téve.", "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Invity által ellenőrzött partnerek", "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Készen állok a váltásra", "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "{fromCrypto} váltása {toCrypto}-ra/re {provider} szolgáltatóval", "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Jogi nyilatkozat", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Első a biztonság a Trezorral", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Azért vagyok itt, hogy kriptovalutát váltsak Ha más okból lettél erre az oldalra irányítva, kérlek lépj kapcsolatba a Trezor ügyfélszolgálatával mielőtt továbblépsz.", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Saját magam részére akarok kriptovalutákat váltani. Megértettem, hogy a szolgáltató irányelvei megkövetelhetik a személyazonosság ellenőrzését.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Megértettem, hogy a kriptovaluta tranzakciók visszafordíthatatlanok és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Megértettem, hogy nem az Invity nyújtja ezt a szolgáltatást. Ezt a(z) {provider} általános szerződési feltételei szabályozzák.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Ezt a funkciót nem szerencsejátékra, csalásra, vagy bármely más, az Invity vagy a szolgáltató használati feltételeit, vagy az irányadó törvényeket sértő módon használom.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Megértettem, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi szabályozások földrajzi területenként eltérőek. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagyok téve.", "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Invity által ellenőrzött partnerek", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} a(z) {networkName} hálózaton", "TR_COINMARKET_TRADE_FEE": "Adásvételi díj", @@ -659,7 +666,6 @@ "TR_COPY_ADDRESS_POLICY_ID": "Soha ne küldj pénzt egy policy azonosító címre.", "TR_COPY_AND_CLOSE": "Másol & Bezár", "TR_COPY_SIGNED_MESSAGE": "Aláírt üzenet másolása", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Másolás", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Változásnapló lekérése sikertelen", "TR_COULD_NOT_RETRIEVE_DATA": "Adatok lekérése sikertelen", "TR_COUNT_WALLETS": "{count} {count, plural, one {tárca} other {tárca}}", @@ -693,6 +699,7 @@ "TR_DASHBOARD_ASSET_FAILED": "A pénzügyi eszköz nem lett helyesen betöltve", "TR_DASHBOARD_DISCOVERY_ERROR": "Felderítési hiba", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "A fiókok nem lettek megfelelően betöltve {details}", + "TR_DATA": "Adat", "TR_DATABASE_UPGRADE_BLOCKED": "Az adatbázisfrissítést az alkalmazás egy másik futó példánya megakadályozta", "TR_DATA_ANALYTICS_CATEGORY_1": "Platform", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "Operációs rendszer, Trezor modell, verzió, stb.", @@ -748,6 +755,8 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Csatlakoztasd újra az eszközt anélkül, hogy hozzáérnél a képernyőjéhez.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Az eszköz helytelen állapotban észlelhető", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "A Trezorod leválasztódott a biztonsági mentés során. Erősen javasoljuk, hogy használd a gyári alaphelyzetbe állítást a Suite-ben az eszköz törléséhez, majd indítsd újra a tárca biztonsági mentésének folyamatát.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "A firmware hash-ellenőrzése sikertelen. Lehet, hogy a Trezorod hamisítvány.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "A firmware hash-ellenőrzést nem sikerült végrehajtani. Lehet, hogy a Trezorod hamisítvány.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Kikapcsolás", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Bekapcsolás", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "A firmware revíziójának ellenőrzése kulcsfontosságú biztonsági funkció. Erősen javasoljuk a bekapcsolva tartását.", @@ -828,7 +837,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Legújabb Bridge {version} letöltése", "TR_DO_NOT_DISCONNECT_DEVICE": "Ne válaszd le az eszközt", "TR_DO_NOT_SHOW_AGAIN": "Ne jelenjen meg többet", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Biztos, hogy kihagyod ezt a lépést?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Húzd és dobd ide a fájlt vagy kattints és válaszd ki a fájlok közül", "TR_DROPZONE_ERROR": "Importálási hiba: {error}", @@ -910,7 +918,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Csak a cseréhez szükséges pontos összeg jóváhagyása. Újabb tranzakciós díjat kell majd fizetned, ha újra hasonló cserét akarsz majd végrehajtani.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Korábbi jóváhagyás visszavonása", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Egy olyan tranzakció végrehajtása, amely visszavonja a(z) {provider} szerződésének korábbi jóváhagyását.", - "TR_EXCHANGE_BUY": "Cserébe", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Jóváhagyás a Trezoron, majd küldés", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Jóváhagyás és küldés", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Jóváhagyás létrehozása", @@ -932,8 +939,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "A tranzakció sikeres volt.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Jóváhagyva", "TR_EXCHANGE_DEX": "Decentralizált tőzsde ajánlata", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "A csere végrehajtásának becsült díja {symbol} {approvalFee} ({approvalFeeFiat}) a jóváhagyáshoz (amennyiben szükséges) és {symbol} {swapFee} ({swapFeeFiat}) a cseréért.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Nem maradt elegendő pénzeszköz a tranzakciós díjakra. Kérlek csökkentsd a mennyiséget erre az összegre: maximum {symbol}{max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "Érvénytelen {extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -943,7 +948,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Amit látsz, az pontosan annyi, mint amennyit kapni fogsz a váltás végén – a látható összeg nem fog változni az árfolyam kiválasztása és a tranzakció befejezése közt. Garantáltan megkapod a mutatott összeget, de ezek az árfolyamok általában kevésbé nagyvonalúak, azaz a pénzed nem fog olyan sok kripto-t venni számodra.", "TR_EXCHANGE_FLOAT": "Lebegő-árfolyamos ajánlat", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "A lebegő árfolyam azt jelenti, hogy az árfolyam kiválasztásától a tranzakció végrehajtásáig eltelt idő alatt esetlegesen bekövetkező piaci árváltozás miatt a végső jóváírt mennyiség is változhat. Ezek az árfolyamok általában magasabbak, vagyis végül akár több kriptovalutát is kaphatsz.", - "TR_EXCHANGE_PROVIDER": "Szolgáltató", "TR_EXCHANGE_RATE": "Árfolyam", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "A fogadó fiók a Suite-on kívül található.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Erre az alfanumerikus címre fognak a coinjaid érkezni.", @@ -960,7 +964,7 @@ "TR_EXCHANGE_STATUS_KYC": "KYC", "TR_EXCHANGE_STATUS_SUCCESS": "Jóváhagyva", "TR_EXCHANGE_SWAP_DATA": "A csere tranzakciós adata", - "TR_EXCHANGE_SWAP_SEND_TO": "{provider} szerződése", + "TR_EXCHANGE_SWAP_SEND_TO": "{provider} szerződésének címe", "TR_EXCHANGE_SWAP_SLIPPAGE": "Csúszás", "TR_EXCHANGE_SWAP_SLIPPAGE_AMOUNT": "Maximum csúszás mértéke", "TR_EXCHANGE_SWAP_SLIPPAGE_CUSTOM": "Egyedi", @@ -970,16 +974,9 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Kérlek számot adj meg.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Írd be a kívánt csúszást.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Csereajánlat összege", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Csúszás összesítve", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Csúszási tolerancia", - "TR_EXCHANGE_TRANS_ID": "Tranzakció azonosító:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "A Suite-ben nem szereplő {symbol} fiók használata", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Fogadó cím", - "TR_EXCHANGE_VIEW_DETAILS": "Részletek megtekintése", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Automatikus Trezor Suite frissítések", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "A Trezor Suite automatikusan letölti a legújabb verziót a háttérben, és az alkalmazás újraindításakor telepíti azt. Ez biztosítja, hogy mindig naprakész legyél a legújabb funkciókkal és biztonsági javításokkal. A frissítések az engedélyed nélkül történnek.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Okoslánc", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "A BNB okoslánc engedélyezése korábbi belső tranzakciók nélkül.", "TR_EXPERIMENTAL_FEATURES": "Kísérleti funkciók", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Kísérleti funkciók", "TR_EXPERIMENTAL_FEATURES_WARNING": "Csak tapasztalt felhasználóknak. Használat saját felelősségre. Ezek a funkciók tesztelés alatt állnak, instabilak lehetnek, és nem biztos, hogy hosszú távú támogatásban részesülnek.", @@ -1024,6 +1021,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Új firmware verzió érhető el. Frissítsd az eszközt most.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Az eszköz már a legfrissebb firmware-t használja, de szükség esetén újra is telepíthető.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "A firmware revízió ellenőrzése sikertelen volt. Lehet, hogy a Trezorod hamisítvány.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Nem sikerült elvégezni a firmware revíziójának ellenőrzését.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Befejezve", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Kisebb méretű firmware, amely csak Bitcoin műveleteket támogat.", "TR_FIRMWARE_SUBHEADING_NONE": "A Trezorokat firmware nélkül szállítjuk. Az eszközöd készen áll arra, hogy megkapja a legújabb, teljes körű firmware-t a biztonságos használathoz. Ha csak Bitcoint használsz, telepítsd a .", @@ -1271,7 +1269,7 @@ "TR_NAV_SIGN_AND_VERIFY": "Aláír & Hitelesít", "TR_NAV_SIGN_VERIFY": "Üzenet aláírása és hitelesítése", "TR_NAV_SOON_BADGE": "Hamarosan", - "TR_NAV_STAKING": "Letétbe helyezés", + "TR_NAV_STAKING": "Stake-elés", "TR_NAV_TOKENS": "Tokenek", "TR_NAV_TRADE": "Kereskedés", "TR_NAV_TRANSACTIONS": "Áttekintés", @@ -1305,6 +1303,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimism", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1317,7 +1316,6 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Új", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Új Trezor Bridge érhető el.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Új Trezor firmware áll rendelkezésre. Kérlek frissítsd az eszközöd.", "TR_NEXT_UP": "Következő", "TR_NONCE": "Nonce", "TR_NORMAL_ACCOUNTS": "Alapértelmezett fiókok", @@ -1343,10 +1341,6 @@ "TR_N_MIN": "{n} perc", "TR_N_TRANSACTIONS": "{value} {value, plural, one {tranzakció} other {tranzakció}}", "TR_OFF": "kikapcsolva", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "A megadott összeg ({amount}) magasabb, mint a megadható maximum összeg ({max}).", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "A megadott összeg ({amount}) magasabb, mint a megadható maximum összeg ({max}).", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "A megadott összeg ({amount}) magasabb, mint a megadható maximum összeg ({max}).", - "TR_OFFER_ERROR_MINIMUM_FIAT": "A megadott összeg ({amount}) magasabb, mint a megadható maximum összeg ({max}).", "TR_OFFICIAL_LANGUAGES": "Hivatalos", "TR_OK": "OK", "TR_ON": "bekapcsolva", @@ -1405,30 +1399,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Egyéb be-, és kimenetek", "TR_OUTGOING": "Kimenő", "TR_OUTPUTS": "Kimenetek", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "A minimális és maximális összeg, amennyiért ez a felhasználó {symbol}-t ad el.", - "TR_P2P_GET_STARTED_INTRO": "A tranzakciók kezdeményezése a szolgáltató ({providerName}) oldalán történik – kövesd gondosan az alábbi lépéseket.", - "TR_P2P_GET_STARTED_ITEM_1": "A \"Go to {providerName}\" lehetőséget választva partnerünk weboldalára irányítunk át.", - "TR_P2P_GET_STARTED_ITEM_3": "Amint a(z) {providerName} bekéri a küldési címet, ide visszatérve tudsz továbblépni.", - "TR_P2P_GET_STARTED_ITEM_4": "Mindjárt készen vagyunk! Fedd fel és másold ki a címed, illeszd be a \"Release address\" (kiküldési cím) mezőbe a(z) {providerName} oldalán, és véglegesítsd a tranzakciót.", - "TR_P2P_GO_TO_PROVIDER": "Tovább ide: {providerName}", - "TR_P2P_INFO": "A {peerToPeer} (P2P) technológiával nincs KYC-ellenőrzés sem a vevő, sem az eladó oldalán. Minden fél védve van a csalással szemben biztonságos {multisigEscrow} által.", - "TR_P2P_MODAL_CONFIRM": "Készen állok a vásárlásra", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "{cryptocurrency} peer-to-peer vásárlása {provider} szolgáltatónál.", - "TR_P2P_MODAL_LEGAL_HEADER": "Jogi nyilatkozat", - "TR_P2P_MODAL_SECURITY_HEADER": "Első a biztonság a Trezorral", - "TR_P2P_MODAL_TERMS_1": "Azért vagy itt, hogy egy általad választott másik személytől személyeazonosság ellenőrzése nélküli Peer-to-Peer (P2P) technológia használatával kriptovalutát vásárolj. Ha bármilyen más okból lettél erre az oldalra irányítva, kérjük lépj kapcsolatba az ügyfélszolgálattal, mielőtt továbblépsz.", - "TR_P2P_MODAL_TERMS_2": "Megértetted, hogy a kriptovaluta tranzakciók visszafordíthatatlanok és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", - "TR_P2P_MODAL_TERMS_4": "Megértetted, hogy nem az Invity nyújtja a szolgáltatást. {provider} feltételei szabályozzák a szolgáltatást.", - "TR_P2P_MODAL_TERMS_5": "Ezt a funkciót nem szerencsejátékra, sem csalásra, sem az Invity vagy a szolgáltató használati feltételeit, sem bármely más alkalmazható szabályozást sértve használod.", - "TR_P2P_MODAL_TERMS_6": "Megértetted, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi rendelkezések bizonyos területeken csak korlátozottan vonatkoznak. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagy téve.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Invity által ellenőrzött partnerek", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "A tranzakciót a {providerName} oldalon történő szerződéskötéstől számított, alábbi időkorláton belül kell lebonyolítani.", - "TR_P2P_PRICE": "1 {symbol} ára", - "TR_P2P_PRICE_TOOLTIP": "A felhasználó által kínált {symbol} árfolyam.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Szia, {providerName}-t használok!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "A kiválasztott összeg ({amount}) magasabb, mint az elfogadható maximum ({maximum}).", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "A kiválaszott összeg ({amount}) alacsonyabb, mint az elfogadható minimum összeg ({minimum}).", "TR_PAGINATION_NEWER": "Újabb", "TR_PAGINATION_OLDER": "Korábbi", "TR_PASSPHRASE_CASE_SENSITIVE": "Figyelem: A jelmondat esetében különbözőnek számítanak a kis-, és nagybetűk.", @@ -1497,6 +1467,13 @@ "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Elérhető frissítés ({newVersion})", "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Naprakész ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "A Suite letöltött egy új Trezor frissítést!", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Az alkalmazás frissült!", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Alkalmazásfrissítés elérhető", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Kattints az újraindításhoz és frissítéshez", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Kattints a frissítés indításához", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Trezor frissítés elérhető", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Újdonságok", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Olyan szavakat is be kellhet írnod, amelyek nem részei a tárcád biztonsági mentésének.", "TR_RANGE": "Időszak", "TR_READ_AND_UNDERSTOOD": "Elolvastam és megértettem a fentieket", @@ -1574,9 +1551,11 @@ "TR_SELECTED": "{amount} kiválasztva", "TR_SELECT_COIN_FOR_SETTINGS": "Válaszd ki az aktív coint a beállítások módosításához", "TR_SELECT_DEVICE": "Eszköz kiválasztása", + "TR_SELECT_NAME_OR_ADDRESS": "Keresés név, szimbólum, hálózat vagy szerződéses cím alapján", "TR_SELECT_NUMBER_OF_WORDS": "Válaszd ki, hogy hány szóból áll a seeded", "TR_SELECT_PASSPHRASE_SOURCE": "Válaszd ki a(z) \"{deviceLabel}\" eszközön, hogy hol viszed be a jelmondatot.", "TR_SELECT_RECOVERY_METHOD": "Helyreállítási mód kiválasztása", + "TR_SELECT_TOKEN": "Token választása", "TR_SELECT_TREZOR": "Trezor kiválasztása", "TR_SELECT_TREZOR_TO_CONTINUE": "A folytatáshoz válasszd ki a Trezorodat.", "TR_SELECT_TYPE": "Típus kiválasztása", @@ -1612,7 +1591,7 @@ "TR_SELL_MODAL_TERMS_3": "Megértettem hogy a kriptovaluta tranzakciók visszafordíthatatlanok és nem téríthetők vissza. Emiatt a csalárd vagy véletlen veszteségek sem visszaszerezhetők.", "TR_SELL_MODAL_TERMS_4": "Megértettem, hogy nem az Invity nyújtja a szolgáltatást. {provider} feltételei szabályozzák a szolgáltatást.", "TR_SELL_MODAL_TERMS_5": "Ezt a funkciót nem szerencsejátékra, sem csalásra, sem az Invity vagy a szolgáltató használati feltételeit vagy az alkalmazandó szabályozásokat sértve használom.", - "TR_SELL_MODAL_TERMS_6": "Megértettem, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök és különböző joghatósági területeken a törvényi szabályozások eltérhetnek. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagyok téve.", + "TR_SELL_MODAL_TERMS_6": "Megértetted, hogy a kriptovaluták elterjedőben lévő pénzügyi eszközök, amelyekre a törvényi szabályozások földrajzi területenként eltérőek. Ezáltal az átverés, a lopás és a piaci instabilitás kockázatának nagyobb mértékben ki vagyok téve.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Invity által ellenőrzött partnerek", "TR_SELL_REGISTER": "Regisztráció", "TR_SELL_SEND_FROM": "Küldés innen", @@ -1620,10 +1599,6 @@ "TR_SELL_STATUS_ERROR": "Elutasítva", "TR_SELL_STATUS_PENDING": "Függőben", "TR_SELL_STATUS_SUCCESS": "Jóváhagyva", - "TR_SELL_TRANS_ID": "Tranzakció azonosító:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "A maximális mennyiség {maximum}{currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "A minimális mennyiség {minimum}{currency}", - "TR_SELL_VIEW_DETAILS": "Részletek megtekintése", "TR_SENDFORM_LABELING_EXAMPLE_1": "Megtakarítások", "TR_SENDFORM_LABELING_EXAMPLE_2": "Lakbér", "TR_SENDING_SYMBOL": "{multiple, select, true {többféle token} false {{symbol}} other {{symbol}}} küldése", @@ -1690,7 +1665,9 @@ "TR_SIZE": "Méret", "TR_SKIP": "Kihagyás", "TR_SKIP_BACKUP": "Biztonsági mentés kihagyása", + "TR_SKIP_BACKUP_DESCRIPTION": "A tárca biztonsági mentése lehetővé teszi a pénzedhez való hozzáférés visszaszerzését a Trezorod elvesztése, ellopása vagy sérülése esetén. A biztonsági mentés nélkül végérvényesen elvesztheted a kriptódhoz való hozzáférést.", "TR_SKIP_PIN": "PIN kód kihagyása", + "TR_SKIP_PIN_DESCRIPTION": "Az eszköz PIN-kódja megakadályozza a jogosulatlan hozzáférést a Trezorhoz. Nélküle, az eszköz birtokában bárki hozzáfér a pénzedhez.", "TR_SKIP_ROUNDS": "Fordulók kihagyása", "TR_SKIP_ROUNDS_DESCRIPTION": "Fordulók kihagyásának engedélyezésével tovább nehezedik a bemenetek és kimenetek közti kapcsolat bizonyítása. Ez azt jelenti, hogy a pénz eredetét még inkább el tudod homályosítani.", "TR_SKIP_ROUNDS_HEADING": "Fordulók kihagyásának engedélyezése a Trezor számára", @@ -1712,13 +1689,16 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "A stake feloldási időszak befejezését követően igényelhető", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "a következő blokkban", "TR_STAKE_CLAIM_PENDING": "Folyamatban lévő igénylés", + "TR_STAKE_CLAIM_UNSTAKED": "Stake-eletlen {symbol} igénylése", "TR_STAKE_CONFIRM_AND_STAKE": "Megerősítés & stake-elés", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Részvételi időszak megerősítése", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Tudomásul veszem és hozzájárulok az Everstake-en való stake-eléshez.", "TR_STAKE_DAYS": "{days} nap", "TR_STAKE_DELEGATED": "Stake delegálás", "TR_STAKE_DEREGISTERED": "Stake cím regisztrációjának törlése", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Jutalmakat keresése hetente", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "A staking pool-ba belépés akár {days} napig is tarthat", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Belépés a staking pool-ba", "TR_STAKE_ETH": "Ethereum stake-elése", "TR_STAKE_ETH_CARD_TITLE": "{symbol} gyarapításának a legegyszerűbb módja.", "TR_STAKE_ETH_EARN_REPEAT": "Stake-elés. Jutalmak gyűjtése. Ismétlés.", @@ -1736,7 +1716,9 @@ "TR_STAKE_EVERSTAKE_MANAGES": "A stake-elt {symbol} coinodat az Everstake kezeli és védi az okosszerződéseivel, infrastruktúrájával és technológiájával.", "TR_STAKE_INSTANT": "Azonnali", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "\"Azonnal\" megkaptál {amount} {symbol}-t. {days, plural, =0 {} one {A mardék # napon belül lesz kifizetve.} other {A maradék # napon belül lesz kifizetve.}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} a számlán", "TR_STAKE_LEARN_MORE": "További információk", + "TR_STAKE_LEAVE_STAKING_POOL": "Staking pool elhagyása", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "A kiutalási díjak megfizetésére {amount} {symbol}-t kihagytunk.", "TR_STAKE_MAX": "Max", "TR_STAKE_MAX_FEE_DESC": "A maximális díj az a hálózati tranzakciós díj, amelyet még hajlandó vagy megfizetni a hálózaton azért, hogy a tranzakciód feldolgozásra kerüljön.", @@ -1754,13 +1736,15 @@ "TR_STAKE_RESTAKED_BADGE": "Újra stake-elve", "TR_STAKE_REWARDS": "Jutalmak", "TR_STAKE_SIGN_TRANSACTION": "Tranzakció aláírása", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Stake feloldási tranzakció aláírása", "TR_STAKE_STAKE": "Stake", "TR_STAKE_STAKED_AMOUNT": "Stake-elt összeg", "TR_STAKE_STAKED_AND_EARNING": "Stake-elve & jutalmak szerzése", "TR_STAKE_STAKED_ETH_AMOUNT_LOCKED": "A(z) {symbol} stake-elt összege zárolva van és nem lehet kereskedni, vagy elküldeni.", "TR_STAKE_STAKE_MORE": "Több stake-elése", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Stake-elés dióhéjban", - "TR_STAKE_STAKING_IS": "A stake-elés olyan, mint egy baráti gesztus, amikor ideiglenesen zárolod az Ethereum-jaid, hogy támogasd a blokklánc működését. Cserébe jutalomként {symbol} -t fogsz keresni!", + "TR_STAKE_STAKING_IS": "A stake-elés során ideiglenesen zárolod az Ethereum-jaid, hogy támogasd a blokklánc működését. Cserébe jutalomként {symbol} -t fogsz keresni!", + "TR_STAKE_STAKING_PROCESS": "Stake-elési folyamat", "TR_STAKE_START_STAKING": "Stake-elés megkezdése", "TR_STAKE_TIME_TO_CLAIM": "Idő az igénylésre", "TR_STAKE_TOTAL_PENDING": "Teljes stake függőben:", @@ -1770,17 +1754,20 @@ "TR_STAKE_UNSTAKE_TO_CLAIM": "Stake feloldása az igényléshez", "TR_STAKE_UNSTAKING": "Stake-elés feloldása", "TR_STAKE_UNSTAKING_PERIOD": "Stake feloldási időszak", + "TR_STAKE_UNSTAKING_PROCESS": "Stake feloldási folyamat", "TR_STAKE_UNSTAKING_TAKES": "A stake-elés feloldása általában nagyjából 3 napot vesz igénybe. Ezután kereskedhető és küldhető a pénzed.", "TR_STAKE_WHAT_IS_STAKING": "Mi az a stake-elés (\"sztékelés\")?", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "A stake-elt pénzedet az Everstake kezeli", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} azonnal stake-elve!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} stake-elése azonnal feloldva!", "TR_STAKING_DELEGATE": "Delegálás", "TR_STAKING_DEPOSIT": "Visszatéríthető letét", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "A letéti díj {feeAmount} ADA, amely a címed stake-eléshez való regisztrációjához szükséges. Amikor úgy döntesz, hogy a Cardano stake-et feloldod, a letéti díjat visszakapod.", "TR_STAKING_FEE": "Tranzakciós díj", "TR_STAKING_INSTANTLY_STAKED": "Azonnal stake-elve lett {amount} {symbol}. {days, plural, =0 {} one {A maradék {symbol} # napon belül stake-elve lesz.} other {A maradék {symbol} # napon belül stake-elve lesz.}}", - "TR_STAKING_IS_NOT_SUPPORTED": "A letétbe helyezés ezen a hálózaton nem támogatott.", + "TR_STAKING_IS_NOT_SUPPORTED": "A stake-elés ezen a hálózaton nem támogatott.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Nincs elég pénzeszköz a számlán.", - "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "A Trezor stake pooljában való stake-eléssel közvetlenül támogatod a Trezort és a Cardano ökoszisztémát a Trezor Suite-ból.", + "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "A Trezor stake pool-jában való stake-eléssel közvetlenül támogatod a Trezort és a Cardano ökoszisztémát a Trezor Suite-ból.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Egy harmadik fél stake pooljára delegálsz", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "A stake pool, amelyre delegálsz, túltelített. Kérjük delegáld újra a stake-edet hogy maximalizáld a stake-elésért járó jutalmakat.", "TR_STAKING_POOL_OVERSATURATED_TITLE": "A stake pool túltelített", @@ -1802,6 +1789,7 @@ "TR_START_COINJOIN": "Coinjoin indítása", "TR_START_RECOVERY": "Helyreállítás indítása", "TR_STEP": "{number}. lépés", + "TR_STEP_OF_TOTAL": "Token választása", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Még mindig nem látod a Trezorod?", "TR_STOP": "Megállítás", "TR_STOPPING": "Leállítás", @@ -1853,7 +1841,11 @@ "TR_TOKENS_EMPTY": "Nincsenek tokenek... még.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Nincsenek tokenek. Lehet, hogy el vannak rejtve.", "TR_TOKENS_SEARCH_TOOLTIP": "Keresés a token neve, szimbóluma vagy szerződésének címe alapján.", + "TR_TOKEN_NOT_FOUND": "Token nem található", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "A token nem található {networkName} hálózaton.", "TR_TOKEN_TRANSFERS": "{standard} Token átutalások", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Próbálj meg egy másik kersést.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Próbálj meg egy másik keresést, vagy válts másik hálózatra.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Nem felismert tokenek", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "A fel nem ismert tokenek potenciális kockázatot jelentenek. Légy óvatos.", "TR_TOO_LONG": "Az üzenet túl hosszú", @@ -1897,11 +1889,7 @@ "TR_TO_BTC": "BTC-ben", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Annak érdekében, hogy a címkék állandóak és elérhetőek legyenek különböző eszközökön, csatlakozz egy felhőalapú tárhelyszolgáltatóhoz.", "TR_TO_SATOSHIS": "sat-ban", - "TR_TRADE_BUYS": "vásárlás", - "TR_TRADE_ENTER_COIN": "Add meg a kriptó nevét vagy szimbólumát...", - "TR_TRADE_EXCHANGES": "átváltás", "TR_TRADE_REDIRECTING": "Átirányítás...", - "TR_TRADE_SELLS": "eladás", "TR_TRANSACTIONS_NOT_AVAILABLE": "Tranzakciós történek nem érhető el", "TR_TRANSACTIONS_SEARCH_TIP_1": "Tipp: Kereshetsz tranzakció azonosítókat, címeket, címkéket, összegeket és dátumokat.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Tipp: Kombinálhatod az ÉS (&) és a VAGY (|) operátorokat bonyolultabb keresésekhez. Például 2022-01-01 & < 2022-01-31 | > 2022-12-01 & < 2022-12-31 kulcsszó minden 2022 januári és 2022 decemberi tranzakciót eredményül hoz.", @@ -1942,13 +1930,17 @@ "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Szabályok automatikus telepítése", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Hiányzó udev szabályok", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Váratlan állapot: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Az eszközöd megfelelően van csatlakoztatva, de az internetböngésződ jelenleg még nem tud vele kommunikálni. Ehhez telepítened kell a Trezor Bridge programot.", "TR_TRY_AGAIN": "Próbáld újra", "TR_TXID": "TX ID", "TR_TXID_RBF": "Az eredeti, kicserélendő TX ID (tranzakció azonosító)", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Tranzakció megerősítve", "TR_TX_CONFIRMING": "Tranzakció megerősítése", + "TR_TX_DATA_FUNCTION": "Függvény", + "TR_TX_DATA_INPUT_DATA": "Bemenő adat", + "TR_TX_DATA_METHOD": "Módszer", + "TR_TX_DATA_METHOD_NAME": "Módszer neve", + "TR_TX_DATA_PARAMS": "Paraméterek", "TR_TX_DEPOSIT": "Betét", "TR_TX_FEE": "Tranzakciós díj", "TR_TX_TAB_AMOUNT": "Összeg", @@ -1988,6 +1980,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Firmware frissítése szükséges. A kezdőképernyő később a beállítások oldalon módosítható", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "A kezdőképernyő megváltoztatásához frissíteni kell a firmwaret", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Frissítés érhető el", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Automatikus frissítés engedélyezése", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Új", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Újraindítás és frissítés", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Szeretnéd a frissítést most telepíteni?", "TR_UPDATE_MODAL_NOT_NOW": "Később", @@ -1995,6 +1989,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "Frissítés indítása", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Frissítés letöltve", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Frissítés kilépésnél", + "TR_UPDATE_MODAL_WHATS_NEW": "Újdonságok", + "TR_UPDATE_MODAL_YOUR_VERSION": "A te verziód: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Frissítsd a firmware-t ennek a fióknak az eléréséhez. Lásd a fenti kék bannert.", "TR_UP_TO": "akár", "TR_UP_TO_DATE": "Naprakész", "TR_UP_TO_DAYS": "akár {days} nap", @@ -2048,6 +2045,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Hozzáférés rejtett tárcához", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Rejtett tárca", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Új tárca létrehozása vagy meglévő tárca visszaállítása tárca biztonsági mentésének használatával.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Folyamatosan a Trezor-élmény javításán dolgozunk, a következők változtak:", "TR_WEST": "Nyugat", "TR_WHAT_DATA_WE_COLLECT": "Milyen adatokat gyűjtünk?", "TR_WHAT_IS_PASSPHRASE": "Tudj meg többet a különbségről", diff --git a/packages/suite-data/files/translations/it.json b/packages/suite-data/files/translations/it.json index baa72e8e21b..ddbedb69a5f 100644 --- a/packages/suite-data/files/translations/it.json +++ b/packages/suite-data/files/translations/it.json @@ -1,5 +1,6 @@ { "AMOUNT": "Importo", + "AMOUNT_EXCEEDS_MAX": "L'importo supera il valore massimo consentito di {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "L'importo deve essere almeno {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "Il conto del destinatario richiede una riserva minima di {reserve} XRP per l'attivazione", "AMOUNT_IS_MORE_THAN_RESERVE": "L'importo è superiore alla riserva non spendibile richiesta ({reserve} XRP)", @@ -67,7 +68,7 @@ "LOCKTIME_ADD_TOOLTIP": "Il tempo di blocco imposta il tempo minimo necessario per il mining di una transazione in un blocco.", "LOCKTIME_BLOCKHEIGHT": "Altezza del blocco per tempo di blocco", "LOCKTIME_IS_NOT_INTEGER": "Il tempo di blocco non è un numero intero", - "LOCKTIME_IS_NOT_SET": "Tempo di blocco non impostato", + "LOCKTIME_IS_NOT_SET": "Il tempo di blocco non è impostato", "LOCKTIME_IS_TOO_BIG": "Il timestamp è troppo grande", "LOCKTIME_IS_TOO_LOW": "Il tempo di blocco è troppo breve", "LOCKTIME_SCHEDULE_SEND": "Tempo di blocco", @@ -112,7 +113,7 @@ "RECEIVE_ADDRESS_LIMIT_REACHED": "Hai raggiunto il limite massimo di 21 indirizzi nuovi e inutilizzati", "RECEIVE_ADDRESS_REVEAL": "Mostra indirizzo completo", "RECEIVE_ADDRESS_UNAVAILABLE": "Non disponibile", - "RECEIVE_DESC_BITCOIN": "Per ricevere fondi, devi ottenere un indirizzo di ricezione nuovo. Ti consigliamo di utilizzare sempre un indirizzo nuovo per evitare che qualcuno possa tenere traccia delle tue transazioni. Benché sia possibile riutilizzare un indirizzo, ti sconsigliamo di farlo a meno che non sia assolutamente necessario.", + "RECEIVE_DESC_BITCOIN": "Per ricevere fondi, devi ottenere un indirizzo di ricezione nuovo. Ti consigliamo di utilizzare sempre un nuovo indirizzo per evitare che qualcuno possa tenere traccia delle tue transazioni. Benché sia possibile riutilizzare un indirizzo, ti sconsigliamo di farlo a meno che non sia assolutamente necessario.", "RECEIVE_DESC_ETHEREUM": "Utilizza questo indirizzo anche per ricevere i token.", "RECEIVE_TABLE_ADDRESS": "Indirizzo", "RECEIVE_TABLE_NOT_USED": "Non utilizzato", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN modificato correttamente", "TOAST_QR_INCORRECT_ADDRESS": "Il codice QR contiene un indirizzo non valido per questo conto", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "Il codice QR è definito per il conto {coin}", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Schema protocollo sconosciuto: \"{scheme}\". Riprova o inserisci l'indirizzo manualmente.", "TOAST_RAW_TX_SENT": "Transazione inviata. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "Impostazioni modificate correttamente", "TOAST_SIGN_MESSAGE_ERROR": "Errore di firma del messaggio: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Vai alla Dashboard", "TR_404_TITLE": "Errore 404: Collegamento non trovato", "TR_7D_CHANGE": "Modifica 7d", - "TR_ABORT": "Interrompi", "TR_ACCESS_HIDDEN_WALLET": "Accedi al wallet nascosto", "TR_ACCESS_STANDARD_WALLET": "Accedi al wallet standard", "TR_ACCOUNT_DETAILS_HEADER": "Dettagli conto", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot è un nuovo tipo di indirizzo in grado di migliorare il livello di privacy e l'efficienza della rete. Alcuni servizi potrebbero non supportare ancora gli indirizzi Taproot.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "Il metodo al momento in uso e più comunemente accettato per generare e gestire indirizzi Cardano garantisce l'interoperabilità, la sicurezza e il supporto di tutti i tipi di token.", "TR_ACCOUNT_TYPE_COINJOIN": "Coinjoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Predefinito", "TR_ACCOUNT_TYPE_IMPORTED": "Importato", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "I conti Ledger sono compatibili con i percorsi di derivazione Ledger Live, per cui la migrazione da Ledger a Trezor risulta fluida.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "I conti Legacy sono compatibili con i percorsi di derivazione Ledger Legacy, per cui la migrazione da Ledger a Trezor risulta fluida.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "Il metodo al momento in uso e più comunemente accettato per generare e gestire indirizzi {value} garantisce l'interoperabilità, la sicurezza e il supporto di tutti i tipi di token.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "Il metodo al momento in uso e più comunemente accettato per generare e gestire indirizzi Solana garantisce l'interoperabilità, la sicurezza e il supporto di token SOL e SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Non supportato.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Questo tipo di conto non è supportato su questo modello di Trezor.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Aggiorna il firmware del dispositivo per abilitare questo tipo di conto.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP è una valuta digitale che consente pagamenti transfrontalieri rapidi ed economici senza dover ricorrere al mining tradizionale, grazie all'uso di un consensus ledger per la conferma rapida delle transazioni.", "TR_ACQUIRE_DEVICE": "Utilizza Trezor qui", "TR_ACQUIRE_DEVICE_TITLE": "Un'altra sessione è in esecuzione", "TR_ACTIVATED_COINS": "Coin attivati", "TR_ACTIVE": "attivo", + "TR_ADD": "Aggiungi", "TR_ADDRESSES": "Indirizzo", "TR_ADDRESSES_CHANGE": "Modifica indirizzi", "TR_ADDRESSES_FRESH": "Indirizzi nuovi", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Copia indirizzo", "TR_ADDRESS_MODAL_TITLE": "Indirizzo di ricezione {networkName}", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "Indirizzo di ricezione {networkCurrencyName} sulla rete {networkName}", - "TR_ADDRESS_PHISHING_WARNING": "Per evitare attacchi di phishing, devi verificare l'indirizzo su Trezor. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Per evitare attacchi di phishing, verifica l'indirizzo di ricezione su Trezor. {claim}", "TR_ADD_ACCOUNT": "Aggiungi conto", "TR_ADD_HIDDEN_WALLET": "Aggiungi wallet nascosto", "TR_ADD_NETWORK_ACCOUNT": "Aggiungi conto {network}", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Utilizzo dei dati", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Tutti i dati vengono mantenuti rigorosamente anonimi. Sono utilizzati solo per migliorare l'ecosistema di Trezor.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Aggiornamenti automatici di Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite scarica automaticamente l'ultima versione in background e la installa al riavvio dell'applicazione. In questo modo si garantisce che funzionalità e patch di sicurezza siano sempre aggiornati. Gli aggiornamenti vengono installati senza richiedere l'autorizzazione dell'utente.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Scaricare automaticamente la versione più recente di Trezor Suite in background e installarla al riavvio dell'app. In questo modo si garantisce che le funzionalità e i patch di sicurezza siano sempre aggiornati. Gli aggiornamenti vengono installati senza richiedere l'autorizzazione dell'utente.", + "TR_ALL_NETWORKS": "Tutte le reti ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Visualizza token da tutte le {networkCount} reti. Filtra in base alle reti più diffuse.", "TR_ALL_TRANSACTIONS": "Transazioni", "TR_AMOUNT_SENT": "Importo inviato", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Non idoneo per coinjoin - importo troppo elevato", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Anche se improbabile, potresti dover accedere al back-up del wallet in caso di problemi con l'aggiornamento del firmware.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Prima di passare a {bitcoinOnly}, devi aggiornare il firmware alla versione più recente.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Sono consapevole che sto compromettendo il mio anonimato", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Sono consapevole che sto compromettendo il mio anonimato.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Avvio di Trezor Bridge sulla porta 21324", "TR_BRIDGE_DEV_MODE_STOP": "Avvio di Trezor Bridge sulla porta predefinita", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Vuoi continuare? Il tuo dispositivo può essere utilizzato solo da un'app alla volta. Se al momento stai utilizzando un'altra app con il tuo dispositivo Trezor, termina prima quella sessione.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Il browser non è supportato. Per un'esperienza ottimale, scarica ed esegui l'app per desktop Trezor Suite in background oppure utilizza un browser basato su Chromium supportato e compatibile con WebUSB.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Un'altra app ha richiesto a Trezor Suite di connettersi al tuo dispositivo Trezor. Mantieni Trezor Suite in esecuzione in background e riprova l'azione nell'altra app.", + "TR_BRIDGE_TIP_AUTOSTART": "Suggerimento: Attiva la funzione di avvio automatico ed esegui sempre Bridge in background.", "TR_BTC_UNITS": "Unità Bitcoin", "TR_BUG": "Bug", "TR_BUMP_FEE": "Aumenta commissione", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "Per accelerare le transazioni, aumenta la commissione della transazione meno recente in coda (in base al nonce). Le transazioni devono essere confermate in ordine. Ulteriori informazioni", "TR_BUY": "Acquista", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Operazioni di trading", "TR_BUY_BUY": "Acquista", - "TR_BUY_BUY_AGAIN": "Acquista di nuovo", "TR_BUY_CONFIRMED_ON_TREZOR": "Confermato su Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Torna al conto", "TR_BUY_DETAIL_ERROR_SUPPORT": "Vai all'assistenza del provider", @@ -386,12 +397,12 @@ "TR_BUY_MODAL_FOR_YOUR_SAFETY": "Acquista {cryptocurrency} con {provider}", "TR_BUY_MODAL_LEGAL_HEADER": "Nota legale", "TR_BUY_MODAL_SECURITY_HEADER": "La sicurezza è al primo posto con Trezor", - "TR_BUY_MODAL_TERMS_1": "Sei qui per acquistare criptovalute. Se sei stato indirizzato a questo sito per qualsiasi altro motivo, contatta l'assistenza di {provider} prima di continuare.", - "TR_BUY_MODAL_TERMS_2": "Utilizzi questa funzionalità per acquistare fondi che verranno inviati a un conto sotto il tuo controllo diretto.", - "TR_BUY_MODAL_TERMS_3": "Sei consapevole che le transazioni in criptovaluta sono irreversibili e che potrebbero non essere rimborsate. Pertanto, è possibile che eventuali perdite accidentali o causate da azioni fraudolente non siano recuperabili.", - "TR_BUY_MODAL_TERMS_4": "Sei consapevole che Invity non fornisce questo servizio. Il servizio è disciplinato dai termini di {provider}.", - "TR_BUY_MODAL_TERMS_5": "Non utilizzi questa funzionalità per il gioco d'azzardo, per attività fraudolente o per violare in alcun modo i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", - "TR_BUY_MODAL_TERMS_6": "Sei consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare a seconda della giurisdizione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", + "TR_BUY_MODAL_TERMS_1": "Sono su questo sito per acquistare criptovalute. Se sono stato indirizzato a questo sito per qualsiasi altro motivo, contatterò l'assistenza di {provider} prima di continuare.", + "TR_BUY_MODAL_TERMS_2": "Utilizzerò questa funzione per acquistare criptovalute che saranno inviate sul mio conto.", + "TR_BUY_MODAL_TERMS_3": "Sono consapevole che le transazioni in criptovaluta sono definitive e non possono essere annullate o rimborsate. Le perdite causate da frodi o errori potrebbero non essere recuperate.", + "TR_BUY_MODAL_TERMS_4": "Sono consapevole che Invity non fornisce questo servizio. È soggetto a termini e condizioni di {provider}.", + "TR_BUY_MODAL_TERMS_5": "Non utilizzerò questa funzione per il gioco d'azzardo o per qualsiasi attività che violi i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", + "TR_BUY_MODAL_TERMS_6": "Sono consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare da regione a regione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Partner verificati da Invity", "TR_BUY_NETWORK": "Acquista {network}", "TR_BUY_NOT_TRANSACTIONS": "Nessuna transazione per ora.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "In sospeso", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "In sospeso", "TR_BUY_STATUS_SUCCESS": "Approvata", - "TR_BUY_TRANS_ID": "ID trans.:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Il valore massimo è {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Il valore massimo è {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Il valore minimo è {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Il valore minimo è {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Visualizza dettagli", "TR_BYTES": "byte", "TR_CAMERA_NOT_RECOGNIZED": "La fotocamera non è stata riconosciuta.", "TR_CAMERA_PERMISSION_DENIED": "L'autorizzazione per accedere alla fotocamera è stata negata.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Importo Trezor", "TR_CHAINED_TXS": "Transazioni concatenate", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog su GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Questo è un indirizzo modificato creato da un invio precedente.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Puoi modificare il tipo di firmware nelle impostazioni in qualsiasi momento.", "TR_CHANGE_HOMESCREEN": "Modifica homescreen", "TR_CHANGE_PIN": "Modifica PIN", "TR_CHANGE_WIPE_CODE": "Modifica codice di eliminazione", + "TR_CHECKED_BALANCES_ON": "Saldo controllato attivo", "TR_CHECKING_YOUR_DEVICE": "Verifica del dispositivo in corso", "TR_CHECKSUM_CONVERSION_INFO": "Convertito in checksum. Ulteriori informazioni", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Verificheremo l'integrità del tuo dispositivo Trezor, garantendone la sicurezza e confermando l'autenticità del chip.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Simula un recupero per verificare la frase Seed di recupero.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Immetti qui le parole della frase Seed di recupero nell'ordine visualizzato sul dispositivo. È possibile che ti venga chiesto di digitare alcune parole che non fanno parte della frase Seed di recupero come ulteriore misura di sicurezza.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Usa il tastierino a due pulsanti per immettere la frase Seed di recupero (back-up del wallet). In questo modo, puoi proteggere tutte le tue informazioni sensibili su un dispositivo non esposto ai rischi di sicurezza che caratterizzano i computer o i browser web.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "La frase Seed di recupero (back-up del wallet) viene immessa tramite touchscreen. In questo modo, si evita di esporre le informazioni sensibili ai rischi di sicurezza che derivano dall'uso di computer o browser web potenzialmente non sicuri.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Il back-up del wallet viene immesso tramite touchscreen. In questo modo, si evita di esporre le informazioni sensibili ai rischi di sicurezza che derivano dall'uso di computer o browser web potenzialmente non sicuri.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Il back-up del wallet viene immesso tramite touchscreen. In questo modo, si evita di esporre le informazioni sensibili ai rischi di sicurezza che derivano dall'uso di computer o browser web potenzialmente non sicuri.", "TR_CHECK_SEED": "Controlla back-up", "TR_CHECK_YOUR_DEVICE": "Controlla la schermata di Trezor", "TR_CHOOSE_RECOVERY_TYPE": "Scegli il tipo di recupero", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Protetto da Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Transazioni coinjoin", "TR_COINMARKET_BEST_RATE": "Miglior tasso", + "TR_COINMARKET_BUY_AND_SELL": "Acquista e vendi", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} acquisto} one {{totalBuys} acquisto} other {{totalBuys} acquisti} } • {totalSells, plural, =0 {{totalSells} vendita} one {{totalSells} vendita} other {{totalSells} vendite} }", + "TR_COINMARKET_CEX_TOOLTIP": "Exchange centralizzato", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Modifica l'importo o la valuta.", "TR_COINMARKET_COMPARE_OFFERS": "Confronta tutte le offerte", "TR_COINMARKET_COUNTRY": "Paese di residenza", + "TR_COINMARKET_DCA_DOWNLOAD": "Scarica l'app mobile Invity per iniziare a risparmiare in bitcoin", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Un piano di risparmio DCA per la custodia semplice e sicuro.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Sviluppato da SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Preleva per la custodia autonoma senza commissioni aggiuntive.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Prelievi gratuiti", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Un'interfaccia intuitiva, veloce e semplificata.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Semplicità d'uso", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Monitora la cronologia, gli importi e la frequenza dei tuoi investimenti.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Panoramica di DCA", + "TR_COINMARKET_DCA_HEADING": "Risparmia in bitcoin con l'app Invity", + "TR_COINMARKET_DEX_TOOLTIP": "Exchange decentralizzato", "TR_COINMARKET_ENTER_AMOUNT_IN": "Immetti importo in {currency}", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Tutte le opzioni KYC", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC mai richiesto", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Tutte le offerte CEX e DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX a tasso fisso", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX a tasso variabile", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Un Decentralized Exchange (DEX) consente di fare trading in cripto direttamente sulla blockchain senza bisogno di un'autorità o un intermediario centrali.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX a tasso fisso", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX a tasso variabile", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Offerte in primo piano", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Pagamento:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Metodo di ricezione:", "TR_COINMARKET_FEES_INCLUDED": "Commissioni incluse", "TR_COINMARKET_FEES_NOT_INCLUDED": "Commissioni non incluse", "TR_COINMARKET_FEES_ON_WEBSITE": "Alcune commissioni non sono incluse nel prezzo visualizzato. Potrai rivedere il prezzo finale sul sito web del fornitore.", @@ -518,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "KYC richiesto in casi eccezionali. KYC richiesto per i rimborsi. 👈", "TR_COINMARKET_KYC_POLICY": "Politica KYC", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC mai richiesto", - "TR_COINMARKET_KYC_YES_REFUND": "KYC richiesto in casi eccezionali. KYC non richiesto per i rimborsi. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "Il KYC è richiesto solo in casi eccezionali. Non è richiesto per i rimborsi. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Ultime transazioni", "TR_COINMARKET_NETWORK_FEE": "Commissione di rete", "TR_COINMARKET_NETWORK_TOKENS": "{networkName} token", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Nessun provider CEX trovato", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Nessun provider DEX trovato", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Nessun metodo disponibile", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Ricaricamento automatico tra", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Torna al trade", - "TR_COINMARKET_NO_OFFERS_HEADER": "Nessuna offerta", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Al momento non sono disponibili offerte a causa di un problema di connettività del server.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Al momento non sono disponibili offerte. Prova a ricaricare la pagina o a modificare la query.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Ricarica pagina", "TR_COINMARKET_OFFERS_EMPTY": "Nessuna offerta disponibile per la tua richiesta. Modifica il paese o l'importo di acquisto", "TR_COINMARKET_OFFERS_REFRESH": "Aggiornamento offerte tra", "TR_COINMARKET_OFFERS_SELECT": "Seleziona", "TR_COINMARKET_OFFER_LOOKING": "Ricerca della tua offerta migliore", - "TR_COINMARKET_OFFER_NO_FOUND": "Nessuna offerta disponibile per la tua richiesta. Modifica l'importo o la valuta.", + "TR_COINMARKET_OFFER_NO_FOUND": "Nessuna offerta disponibile per la tua richiesta.", "TR_COINMARKET_ON_NETWORK_CHAIN": "Sulla chain {networkName}", "TR_COINMARKET_OTHER_CURRENCIES": "Altre valute", "TR_COINMARKET_PAYMENT_METHOD": "Metodo di pagamento", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Metodo di ricezione", "TR_COINMARKET_SELL": "Vendi", "TR_COINMARKET_SHOW_OFFERS": "Confronta le offerte", + "TR_COINMARKET_SWAP": "Scambia", + "TR_COINMARKET_SWAP_AMOUNT": "Importo di scambio", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} cambio} one {{totalSwaps} cambio} other {{totalSwaps} cambi} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Desidero scambiare", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Scambia {fromCrypto} in {toCrypto} con {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Nota legale", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "La sicurezza è al primo posto con Trezor", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Desidero scambiare criptovalute tramite DEX (Decentralized Exchange) utilizzando il contratto di {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Desidero scambiare criptovalute sul mio conto. Sono consapevole che le politiche del provider potrebbero richiedere la verifica dell'identità.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Sono consapevole che le transazioni in criptovaluta sono definitive e non possono essere annullate o rimborsate. Le perdite causate da frodi o errori potrebbero non essere recuperate.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Sono consapevole che Invity non fornisce questo servizio. È soggetto a termini e condizioni di {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Non utilizzerò questa funzione per il gioco d'azzardo o per qualsiasi attività che violi i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Sono consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare da regione a regione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Partner verificati da Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Desidero scambiare", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Scambia {fromCrypto} in {toCrypto} con {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Nota legale", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "La sicurezza è al primo posto con Trezor", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Sono su questo sito per scambiare criptovalute. Se sono stato indirizzato a questo sito per qualsiasi altro motivo, contatterò l'assistenza di Trezor prima di continuare. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Desidero scambiare criptovalute sul mio conto. Sono consapevole che le politiche del provider potrebbero richiedere la verifica dell'identità.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Sono consapevole che le transazioni in criptovaluta sono definitive e non possono essere annullate o rimborsate. Le perdite causate da frodi o errori potrebbero non essere recuperate.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Sono consapevole che Invity non fornisce questo servizio. È soggetta ai Termini e condizioni di {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Non utilizzerò questa funzione per il gioco d'azzardo o per qualsiasi attività che violi i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Sono consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare da regione a regione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Partner verificati da Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} sulla rete {networkName}", "TR_COINMARKET_TRADE_FEE": "Commissione di trading", + "TR_COINMARKET_TRANS_ID": "Trans. ID:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Provider sconosciuto", + "TR_COINMARKET_VIEW_DETAILS": "Visualizza dettagli", "TR_COINMARKET_YOUR_BEST_OFFER": "La tua offerta migliore", "TR_COINMARKET_YOU_BUY": "Compra", "TR_COINMARKET_YOU_GET": "Ricevi", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Confermata", "TR_CONFIRMING_TX": "Conferma transazione in corso", "TR_CONFIRM_ACTION_ON_YOUR": "Segui le istruzioni visualizzate sullo schermo di Trezor", + "TR_CONFIRM_ADDRESS": "Conferma indirizzo", "TR_CONFIRM_BEFORE_COPY": "Conferma su Trezor prima di copiare", "TR_CONFIRM_CONDITIONS": "Conferma le condizioni prima di continuare.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Conferma il wallet nascosto vuoto sul dispositivo \"{deviceLabel}\".", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Gestisci", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Abilita l'apertura della finestra di dialogo di immissione della passphrase all'apertura di Trezor Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Utilizzi principalmente una passphrase?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verifica su Trezor per confermare l'indirizzo di ricezione. Si sconsiglia di continuare senza confermare.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verifica su Trezor per confermare l'indirizzo di ricezione. Si consiglia di non continuare senza confermare.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "Impossibile verificare l'indirizzo di ricezione", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Per inviare coin, collega Trezor.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Trezor non è connesso", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Collega Trezor per inviare", "TR_CONNECT_YOUR_DEVICE": "Connetti e sblocca Trezor", - "TR_CONTACT_SUPPORT": "Contatta l'assistenza", + "TR_CONTACT_SUPPORT": "Contatta l'Assistenza di Trezor", "TR_CONTACT_TREZOR_SUPPORT": "Contatta l'Assistenza di Trezor", "TR_CONTINUE": "Continua", "TR_CONTINUE_ANYWAY": "Continua comunque", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Non inviare mai fondi a un indirizzo ID policy.", "TR_COPY_AND_CLOSE": "Copia e chiudi", "TR_COPY_SIGNED_MESSAGE": "Copia messaggio firmato", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copia", + "TR_COPY_TO_CLIPBOARD": "Copia", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Impossibile recuperare il changelog", "TR_COULD_NOT_RETRIEVE_DATA": "Impossibile recuperare i dati", "TR_COUNT_WALLETS": "{count} {count, plural, one {wallet} other {wallet}}", @@ -630,7 +687,7 @@ "TR_CREATE_SHARES": "Crea condivisioni su Trezor", "TR_CREATE_SHARES_CARD_1": "Prendi carta e penna e stampa schede di back-up o usa Trezor Keep Metal", "TR_CREATE_SHARES_CARD_2": "Non fare foto o copie digitali del back-up del wallet", - "TR_CREATE_SHARES_CARD_3": "Assicurati che non ci siano persone intorno", + "TR_CREATE_SHARES_CARD_3": "Assicurati di non avere persone attorno", "TR_CREATE_SHARES_EXAMPLE": "Esempio: 5 condivisioni totali, 3 richieste per recuperare il wallet", "TR_CREATE_SHARES_EXPLANATION": "Seleziona il numero totale di condivisioni, quindi scegli il numero minimo richiesto per recuperare Trezor.", "TR_CREATE_WALLET": "Crea nuovo wallet", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Asset non caricati correttamente", "TR_DASHBOARD_DISCOVERY_ERROR": "Errore di individuazione", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "I conti non sono stati caricati correttamente {details}", + "TR_DATA": "Dati", "TR_DATABASE_UPGRADE_BLOCKED": "Aggiornamento del database bloccato da un'altra istanza dell'app", "TR_DATA_ANALYTICS_CATEGORY_1": "Piattaforma", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "OS, modello di Trezor, versione, ecc.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "In bootloader per errore?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Riconnetti il dispositivo senza toccare alcun pulsante.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Riconnetti il dispositivo senza toccare lo schermo.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Questo dispositivo è in uso altrove.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "Attualmente l'app {transportSessionOwner} potrebbe essere in uso su questo dispositivo. Se necessario, puoi assumere il controllo del dispositivo.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Attualmente un'altra app potrebbe essere in uso su questo dispositivo. Se necessario, puoi assumere il controllo del dispositivo.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Rilevato stato non corretto del dispositivo", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Trezor è stato disconnesso durante la procedura di back-up. Ti consigliamo di utilizzare l'opzione di ripristino delle impostazioni di fabbrica in Impostazioni dispositivo per eliminare il contenuto del dispositivo e riavviare la procedura di back-up.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Impossibile eseguire il controllo dell'hash del firmware. Trezor potrebbe essere contraffatto.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Non è stato possibile eseguire il controllo dell'hash del firmware. Trezor potrebbe essere contraffatto.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Disattiva", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Attiva", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Il controllo della revisione del firmware è una funzione di sicurezza fondamentale. Si consiglia vivamente di mantenerla attiva.", @@ -726,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "Il back-up del dispositivo \"{deviceLabel}\" non è stato eseguito", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "Il dispositivo \"{deviceLabel}\" non è connesso", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "Il dispositivo \"{deviceLabel}\" non è disponibile", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Verifica del dispositivo non riuscita", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Ricollega il dispositivo e prova a eseguire nuovamente la verifica. Se il problema persiste, contatta l'Assistenza di Trezor per capire cosa è successo al tuo dispositivo e i passi successivi da intraprendere.", "TR_DEVICE_NOT_CONNECTED": "Dispositivo non connesso", "TR_DEVICE_NOT_INITIALIZED": "Trezor non è configurato", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Ti guideremo nella procedura e potrai iniziare a utilizzarlo subito.", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Scarica la versione più recente di Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Non disconnettere il dispositivo", "TR_DO_NOT_SHOW_AGAIN": "Non mostrare più", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Saltare questo passaggio?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Trascina e rilascia il file qui o clicca per selezionare un file", "TR_DROPZONE_ERROR": "Importazione non riuscita: {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Partecipa", "TR_EARLY_ACCESS_ENABLED": "Programma di accesso in anteprima abilitato", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Partecipa al Programma", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Sono consapevole che il Programma mi consente di accedere a versioni non definitive del software, che potrebbero contenere errori suscettibili di influire sul normale funzionamento di Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Sono consapevole che il Programma mi consente di accedere a versioni non definitive del software, che potrebbero contenere errori suscettibili di influire sul normale funzionamento di Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Puoi disattivarlo in qualsiasi momento.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Prova le ultime funzionalità del prodotto prima del rilascio al pubblico.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Cerca prima nel campo qui sopra", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Puoi cercare gli aggiornamenti beta ora o al prossimo lancio.", "TR_EARLY_ACCESS_JOINED_TITLE": "Programma di accesso in anteprima abilitato", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Per eseguire il downgrade all'ultima versione stabile di Suite, clicca su \"Scarica stabile\" e reinstalla l'app.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Per eseguire il downgrade all'ultima versione stabile di Trezor Suite, fai clic su \"Scarica stabile\" e reinstalla l'app.", "TR_EARLY_ACCESS_LEFT_TITLE": "Hai abbandonato il Programma di accesso in anteprima. Le versioni beta non sono più disponibili.", "TR_EARLY_ACCESS_MENU": "Programma di accesso in anteprima", "TR_EARLY_ACCESS_REINSTALL": "Scarica stabile", @@ -842,8 +906,8 @@ "TR_ENTER_SEED_WORDS_ON_DEVICE": "Le parole vengono immesse sul dispositivo per motivi di sicurezza. Immetti le parole nell'ordine corretto.", "TR_ENTER_WIPECODE": "Immetti codice di eliminazione", "TR_ERROR": "Errore", - "TR_ERROR_CARDANO_DELEGATE": "Importo insufficiente", - "TR_ERROR_CARDANO_WITHDRAWAL": "Importo insufficiente", + "TR_ERROR_CARDANO_DELEGATE": "L'importo non è sufficiente", + "TR_ERROR_CARDANO_WITHDRAWAL": "L'importo non è sufficiente", "TR_ETH_ADDRESS_CANT_VERIFY_HISTORY": "Impossibile verificare la cronologia degli indirizzi. Verifica che l'indirizzo sia corretto.", "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "L'indirizzo non ha una cronologia delle transazioni e non è sottoposto a checksum. Verifica che l'indirizzo sia corretto.", "TR_EVM_EXPLANATION_DESCRIPTION": "Ha lo stesso stile di indirizzo di Ethereum ma utilizza coin e token unici che non possono essere utilizzati su altre reti.", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Approva solo l'importo esatto richiesto per questo scambio. Dovrai pagare una commissione aggiuntiva se desideri eseguire nuovamente uno swap simile.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Revoca approvazione precedente", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Esegui una transazione che rimuoverà la precedente approvazione del contratto con {provider}.", - "TR_EXCHANGE_BUY": "Per", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Conferma su Trezor e invia", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Conferma e invia", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Crea approvazione", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "La transazione è andata a buon fine.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Approvata", "TR_EXCHANGE_DEX": "Offerta su exchange decentralizzato", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Le commissioni per l'esecuzione di questo swap sono stimate a {approvalFee} ({approvalFeeFiat}) per l'approvazione (se richiesta) e a {swapFee} ({swapFeeFiat}) per lo swap.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Nessun fondo residuo per le commissioni di transazione. Riduci l'importo dello scambio a max {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} non è valido", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Con i tassi fissi, al termine dello scambio otterrai esattamente quello che vedi: l'importo resterà invariato da quando selezioni il tasso fino al completamento della transazione. Ti garantiamo l'importo indicato, ma questi tassi sono solitamente meno generosi, il che significa che potrai acquistare meno criptovalute con il tuo denaro.", "TR_EXCHANGE_FLOAT": "Offerta a tasso variabile", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Con i tassi variabili, l'importo finale che otterrai potrebbe variare leggermente a causa delle fluttuazioni del mercato da quando selezioni il tasso fino al completamento della transazione. Questi tassi sono solitamente più elevati, il che significa che alla fine potresti ottenere più criptovalute.", - "TR_EXCHANGE_PROVIDER": "Provider", "TR_EXCHANGE_RATE": "Prezzo", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Il conto di ricezione è esterno a Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Questo è l'indirizzo alfanumerico specifico a cui saranno inviati i coin.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Immetti un numero.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Immetti lo slippage desiderato.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Importo dell'offerta swap", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Riepilogo dello slippage", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolleranza allo slippage", - "TR_EXCHANGE_TRANS_ID": "ID trans.:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Utilizza un conto ({symbol}) non presente in Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Indirizzo di ricezione", - "TR_EXCHANGE_VIEW_DETAILS": "Visualizza dettagli", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Aggiornamenti automatici di Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite scarica automaticamente l'ultima versione in background e la installa al riavvio dell'applicazione. In questo modo si garantisce che le funzionalità e i patch di sicurezza siano sempre aggiornati. Gli aggiornamenti vengono installati senza richiedere l'autorizzazione dell'utente.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "Smart Chain BNB", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Attiva BNB Smart Chain senza transazioni interne storiche.", "TR_EXPERIMENTAL_FEATURES": "Sperimentale", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Funzionalità sperimentali", "TR_EXPERIMENTAL_FEATURES_WARNING": "Solo per utenti esperti. L'utilizzo del presente servizio avviene a proprio rischio e pericolo. Queste funzioni sono in fase di test. Di conseguenza potrebbero risultare instabili e non essere supportate a lungo termine.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migrazione delle password Dropbox", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Utility per recuperare le password memorizzate su Dropbox e protette da Trezor. Progettazione per i precedenti utenti dell'estensione Chrome di Trezor Password Manager.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Utilizza questa Utility per recuperare le password memorizzate su Dropbox e protette da Trezor. Progettato per gli utenti che utilizzavano l'estensione Trezor Password Manager per Chrome.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake è un sistema che consente l'accesso a siti web e applicazioni censurati.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Accedi a siti Web e app censurati utilizzando Tor Snowflake, un sistema progettato per evitare le restrizioni.", "TR_EXPORT_AS": "Esporta come {as}", "TR_EXPORT_FAIL": "Esportazione non riuscita.", "TR_EXPORT_TO_FILE": "Esporta su file", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Il nuovo firmware è ora disponibile. Aggiorna subito il tuo dispositivo o scegli di farlo in un secondo momento.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Il dispositivo è già aggiornato all'ultima versione del firmware. Se necessario, puoi reinstallare il firmware.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Impossibile eseguire il controllo della revisione del firmware. Trezor potrebbe essere contraffatto.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Non è stato possibile eseguire il controllo di revisione del firmware.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Completata", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Firmware leggero che supporta solo le operazioni Bitcoin.", "TR_FIRMWARE_SUBHEADING_NONE": "Trezor viene fornito senza firmware. Installa il firmware più recente per utilizzare il dispositivo in modo sicuro. Per gli utenti che utilizzano solo Bitcoin, si consiglia di installare il .", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Capito", "TR_GO_TO_ONBOARDING": "Avvia configurazione", "TR_GO_TO_SETTINGS": "Vai alle impostazioni", - "TR_GO_TO_SUITE": "Accedi a Suite", + "TR_GO_TO_SUITE": "Vai su Trezor Suite", "TR_GRAPH_LINEAR": "Lineare", "TR_GRAPH_LOGARITHMIC": "Logaritmico", "TR_GRAPH_MISSING_DATA": "XRP, Sol e gli importi di tutti i token sono inclusi nel saldo del wallet ma, al momento, non sono visualizzabili nella vista grafico.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Forum di Trezor", "TR_GUIDE_FORUM_LABEL": "Unisciti alla community di Trezor", "TR_GUIDE_SUGGESTION_LABEL": "Come andiamo?", - "TR_GUIDE_SUPPORT": "Contatta l'assistenza", + "TR_GUIDE_SUPPORT": "Contatta l'Assistenza di Trezor", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Assistenza e feedback", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Assistenza e feedback", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Aiutaci a migliorare", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "File system locale", "TR_LOG": "Log dell'applicazione", "TR_LOGIN_PROCEED": "Procedi", - "TR_LOG_DESCRIPTION": "Il log contiene tutte le informazioni tecniche necessarie su Trezor Suite. Può servire quando si contatta l'Assistenza di Trezor.", + "TR_LOG_DESCRIPTION": "Questo registro contiene informazioni tecniche essenziali su Trezor Suite e potrebbe essere necessario per contattare l'Assistenza di Trezor.", "TR_LOOKING_FOR_COINJOIN_ROUND": "In attesa di un round", "TR_LOW_ANONYMITY_WARNING": "Privacy molto bassa. Si raccomanda di impostare almeno il livello 1 su una scala da 1 a 5 poiché un valore inferiore a questa soglia non garantisce alcuna privacy.", "TR_LTC_ADDRESS_INFO": "Litecoin ha modificato il formato dell'indirizzo. Puoi trovare altre informazioni su come convertire il tuo indirizzo nel nostro blog. {TR_LEARN_MORE}", @@ -1202,6 +1256,7 @@ "TR_MULTI_SHARE_BACKUP_EXPLANATION_2": "Il back-up corrente ti consente comunque di recuperare i fondi. Conservalo in un luogo sicuro, separato dai back-up multi-condivisione.", "TR_MULTI_SHARE_BACKUP_GREAT": "Ottimo!", "TR_MULTI_SHARE_BACKUP_IN_PROGRESS": "Generazione di back-up multi-condivisione in corso", + "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION": "Prima di continuare, è necessario terminare la generazione delle sue azioni di Backup multi-comparto. Segua le istruzioni sullo schermo del suo Trezor.", "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING": "Eccoti! Riprendiamo da dove hai lasciato.", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP": "Hai perso il back-up del wallet?", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT": "Potrebbero non essere disponibili opzioni per recuperare il wallet. Contatta l'assistenza di Trezor.", @@ -1222,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portfolio", "TR_NAV_ANONYMIZE": "Rendi privati i coin", "TR_NAV_BUY": "Acquista", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Dettagli", "TR_NAV_RECEIVE": "Ricevi", "TR_NAV_SELL": "Vendi", @@ -1263,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Ottimismo", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1275,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nuovo", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "È disponibile un nuovo Trezor Bridge.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "È disponibile un nuovo firmware Trezor! Aggiorna il tuo dispositivo.", "TR_NEXT_UP": "Avanti", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (con \"{char}\" non consigliato)", + "TR_NON_ASCII_CHARS": "{label} (con caratteri non consigliati)", "TR_NORMAL_ACCOUNTS": "Conti predefiniti", "TR_NORTH": "Nord", "TR_NOTHING_TO_ANONYMIZE": "Nulla da rendere privato", @@ -1301,10 +1359,6 @@ "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transazione} other {transazioni}}", "TR_OFF": "off", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "L'importo di {amount} selezionato è superiore al massimo accettato di {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "L'importo di {amount} selezionato è superiore al massimo accettato di {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "L'importo di {amount} selezionato è inferiore al minimo accettato di {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "L'importo di {amount} selezionato è inferiore al minimo accettato di {min}.", "TR_OFFICIAL_LANGUAGES": "Ufficiale", "TR_OK": "OK", "TR_ON": "on", @@ -1332,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Raccolta dati anonimi", "TR_ONBOARDING_DEVICE_CHECK": "Controllo di sicurezza del dispositivo", "TR_ONBOARDING_DEVICE_CHECK_1": "Il mio ologramma era intatto e senza segni di manomissione.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Il mio dispositivo è stato acquistato nel Trezor Shop ufficiale o presso un rivenditore di fiducia.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Il mio dispositivo è stato acquistato nel Trezor Shop ufficiale o presso un rivenditore di fiducia.", "TR_ONBOARDING_DEVICE_CHECK_3": "La confezione del dispositivo era intatta e non manomessa.", "TR_ONBOARDING_DEVICE_CHECK_4": "Il firmware è già installato sul dispositivo Trezor connesso. Continuare con la configurazione solo se hai già utilizzato questo Trezor in precedenza.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Scarica l'app per desktop", @@ -1363,40 +1417,18 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Altri input e output", "TR_OUTGOING": "In uscita", "TR_OUTPUTS": "Output", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "L'importo minimo e massimo per il quale l'utente è disposto a vendere {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Devi avviare la transazione su {providerName}: assicurati di attenerti alle istruzioni riportate di seguito.", - "TR_P2P_GET_STARTED_ITEM_1": "Seleziona \"Vai a {providerName}\" per essere reindirizzato al sito web del nostro partner.", - "TR_P2P_GET_STARTED_ITEM_3": "Quando {providerName} ti chiede di fornire un indirizzo di ricezione, torna qui e continua.", - "TR_P2P_GET_STARTED_ITEM_4": "Ci siamo quasi! Visualizza e copia il tuo indirizzo, incollalo nel campo \"Indirizzo di rilascio\" su {providerName} e finalizza la transazione.", - "TR_P2P_GO_TO_PROVIDER": "Vai a {providerName}", - "TR_P2P_INFO": "La tecnologia {peerToPeer} (P2P) non prevede alcuna verifica KYC né per l'acquirente né per il venditore. Tutte le parti sono protette contro le frodi con il servizio {multisigEscrow} sicuro.", - "TR_P2P_MODAL_CONFIRM": "Desidero acquistare", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Acquisto peer-to-peer di {cryptocurrency} con {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Nota legale", - "TR_P2P_MODAL_SECURITY_HEADER": "La sicurezza è al primo posto con Trezor", - "TR_P2P_MODAL_TERMS_1": "Sei qui per acquistare criptovaluta da un'altra persona utilizzando la tecnologia peer-to-peer (P2P) senza verifica dell'ID. Se sei stato indirizzato a questo sito per qualsiasi altro motivo, contatta l'assistenza prima di continuare.", - "TR_P2P_MODAL_TERMS_2": "Sei consapevole che le transazioni in criptovaluta sono irreversibili e che potrebbero non essere rimborsate. Pertanto, è possibile che eventuali perdite accidentali o causate da azioni fraudolente non siano recuperabili.", - "TR_P2P_MODAL_TERMS_4": "Sei consapevole che Invity non fornisce questo servizio. Il servizio è disciplinato dai termini di {provider}.", - "TR_P2P_MODAL_TERMS_5": "Non utilizzi questa funzionalità per il gioco d'azzardo, per attività fraudolente o per violare in alcun modo i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", - "TR_P2P_MODAL_TERMS_6": "Sei consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare a seconda della giurisdizione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Partner verificati da Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "La transazione deve essere completata entro questo limite di tempo, a partire dalla creazione di un contratto sul sito {providerName}.", - "TR_P2P_PRICE": "Prezzo per 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "Prezzo per {symbol} offerto da questo utente.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Ciao, sto usando {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "L'importo di {amount} selezionato è superiore al massimo accettato di {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "L'importo di {amount} selezionato è inferiore al minimo accettato di {minimum}.", "TR_PAGINATION_NEWER": "Più recente", "TR_PAGINATION_OLDER": "Meno recente", "TR_PASSPHRASE_CASE_SENSITIVE": "Nota: la passphrase fa distinzione tra maiuscole e minuscole.", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "È importante conoscere prima il funzionamento della passphrase", "TR_PASSPHRASE_DESCRIPTION_ITEM2": "Una passphrase apre un wallet protetto da quella frase", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Nessuno può recuperarla, neanche l'assistenza di Trezor", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Nessuno può recuperarla, neanche l'Assistenza di Trezor", "TR_PASSPHRASE_HIDDEN_WALLET": "Wallet nascosto", "TR_PASSPHRASE_MISMATCH": "Mancata corrispondenza delle passphrase", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Le passphrase non corrispondono. Per motivi di sicurezza, ricomincia e inseriscile correttamente.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Ricomincia", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Si consiglia di utilizzare ABC, abc, 123, spazi o questi caratteri speciali", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "L'uso di caratteri speciali non presenti nell'elenco può mettere a rischio la compatibilità futura", "TR_PASSPHRASE_TOO_LONG": "La lunghezza della passphrase supera il limite consentito.", "TR_PASSPHRASE_WALLET": "Wallet nascosto n.{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Scopri come funziona una passphrase", @@ -1438,15 +1470,30 @@ "TR_PIN_SUBHEADING": "L'uso di un PIN efficace protegge Trezor da accessi fisici non autorizzati.", "TR_PLAY_IT_SAFE": "Non corriamo rischi", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Abilita la fotocamera per eseguire la scansione dei codici QR.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Connetti il dispositivo per continuare con la procedura di verifica.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Collega Trezor per continuare con la procedura di verifica.", "TR_PLEASE_ENABLE_PASSPHRASE": "Abilita la funzionalità passphrase per continuare con la procedura di verifica.", "TR_POLICY_ID_ADDRESS": "ID criteri:", "TR_PRIMARY_FIAT": "Valuta tradizionale", "TR_PRIVATE": "Privato", "TR_PRIVATE_DESCRIPTION": "Livello di privacy minimo: {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Continua con l'indirizzo non verificato", "TR_PROMO_BANNER_DASHBOARD": "Il wallet hardware più conveniente per gestire le cripto in modo sicuro", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Conferma su Trezor prima di eseguire la scansione", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Conferma innanzitutto l'indirizzo di ricezione sul dispositivo Trezor, poiché il suo display sicuro non può essere violato.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Abilitato", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Aggiornato di recente ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Riavvia per aggiornare", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Dispositivo Trezor", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Aggiornamento disponibile ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Aggiornato ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite ha scaricato un nuovo aggiornamento.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite è stata aggiornata.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Aggiornamento di Trezor Suite ora disponibile", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Riavvia e aggiorna", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Avvia aggiornamento", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Aggiornamento di Trezor ora disponibile", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Novità", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "È possibile che ti venga chiesto di digitare alcune parole che non fanno parte della frase Seed di recupero.", "TR_RANGE": "range", "TR_READ_AND_UNDERSTOOD": "Ho letto e compreso quanto sopra", @@ -1498,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Controlli di sicurezza", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "Prompt", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Puoi eseguire azioni potenzialmente pericolose, come la mancata corrispondenza delle chiavi o commissioni estreme, approvandole manualmente sul dispositivo Trezor.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Non modificare questa impostazione se non sai esattamente cosa stai facendo!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Modifica questa impostazione solo se sai esattamente cosa stai facendo!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Rigorosi", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Sicurezza completa di Trezor.", "TR_SCAN_QR_CODE": "Esegui scansione codice QR", @@ -1508,9 +1555,10 @@ "TR_SEARCH_TRANSACTIONS": "Cerca transazioni", "TR_SEARCH_UTXOS": "Cerca un indirizzo, un ID transazione o un'etichetta specifici", "TR_SECURITY_CHECKPOINT_GOT_SEED": "Disponi del back-up del wallet?", + "TR_SECURITY_CHECK_HOLOGRAM": "La confezione del dispositivo, inclusi gli ologrammi e i sigilli di sicurezza, è stata aggiornata nel tempo. Puoi verificare i dettagli della confezione qui. Assicurati che il tuo dispositivo sia stato acquistato sul Trezor Shop ufficiale o presso uno dei nostri rivenditori di fiducia. In caso contrario, c'è il rischio che il dispositivo possa essere contraffatto. Se sospetti che il dispositivo non sia originale, contatta l'Assistenza di Trezor.", "TR_SECURITY_FEATURES_COMPLETED_N": "Sicurezza ({n} di {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "I dispositivi configurati in modalità seedless non possono accedere a Trezor Suite. Ciò impedisce di perdere coin in modo irreversibile nel caso si utilizzi un dispositivo non configurato correttamente per lo scopo sbagliato.", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "La configurazione seedless non è supportata da Trezor Suite", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "I dispositivi con configurazione seedless non possono accedere a Trezor Suite ed evitare la perdita irreversibile di coin, che può verificarsi se un dispositivo non viene utilizzato correttamente.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "La configurazione seedless non è supportata su Trezor Suite", "TR_SEED_BACKUP_LENGTH": "La frase Seed di recupero può contenere 12, 18 o 24 parole.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "La frase Seed di recupero può contenere 12, 18, 20, 24 o 33 parole.", "TR_SEED_CHECK_FAIL_TITLE": "Verifica della frase Seed di recupero non riuscita", @@ -1520,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Immetti le parole della frase Seed di recupero nell'ordine visualizzato su Trezor.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Utilizza lo schermo touchscreen per immettere tutte le parole nell'ordine corretto fino al completamento della frase.", "TR_SEE_DETAILS": "Visualizza dettagli", + "TR_SEE_IF_ISSUE_PERSISTS": "Verifica se il problema persiste.", "TR_SELECTED": "{amount} selezionati", "TR_SELECT_COIN_FOR_SETTINGS": "Seleziona il coin attivo per modificare le impostazioni", "TR_SELECT_DEVICE": "Seleziona il dispositivo", + "TR_SELECT_NAME_OR_ADDRESS": "Ricerca per nome, simbolo, rete o indirizzo di contratto", "TR_SELECT_NUMBER_OF_WORDS": "Seleziona il numero di parole nella frase Seed di recupero", "TR_SELECT_PASSPHRASE_SOURCE": "Seleziona dove inserire la passphrase in \"{deviceLabel}\".", "TR_SELECT_RECOVERY_METHOD": "Seleziona il metodo di recupero", + "TR_SELECT_TOKEN": "Seleziona un token", "TR_SELECT_TREZOR": "Seleziona Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Seleziona Trezor per continuare.", "TR_SELECT_TYPE": "Seleziona il tipo", @@ -1556,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Vendi {cryptocurrency} con {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Nota legale", "TR_SELL_MODAL_SECURITY_HEADER": "La sicurezza è al primo posto con Trezor", - "TR_SELL_MODAL_TERMS_1": "Sei qui per vendere criptovalute. Se sei stato indirizzato a questo sito per qualsiasi altro motivo, contatta l'assistenza prima di continuare.", + "TR_SELL_MODAL_TERMS_1": "Sei qui per vendere criptovalute. Se stai visitando questo sito per qualsiasi altro motivo, contatta l'Assistenza di Trezor prima di continuare.", "TR_SELL_MODAL_TERMS_2": "Stai vendendo criptovalute per tuo conto. Sei consapevole che le politiche del provider potrebbero richiedere la verifica dell'identità.", "TR_SELL_MODAL_TERMS_3": "Sei consapevole che le transazioni in criptovaluta sono irreversibili e che potrebbero non essere rimborsate. Pertanto, è possibile che eventuali perdite accidentali o causate da azioni fraudolente non siano recuperabili.", "TR_SELL_MODAL_TERMS_4": "Sei consapevole che Invity non fornisce questo servizio. Il servizio è disciplinato dai termini di {provider}.", - "TR_SELL_MODAL_TERMS_5": "Non utilizzi questa funzionalità per il gioco d'azzardo, per attività fraudolente o per violare in alcun modo i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", + "TR_SELL_MODAL_TERMS_5": "Non utilizzerò questa funzione per il gioco d'azzardo o per qualsiasi attività che violi i Termini di servizio di Invity o del provider o qualsiasi normativa applicabile.", "TR_SELL_MODAL_TERMS_6": "Sei consapevole che le criptovalute sono uno strumento finanziario emergente e che le normative possono variare a seconda della giurisdizione. Ciò potrebbe comportare un rischio maggiore di frodi, furti o instabilità del mercato.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Partner verificati da Invity", "TR_SELL_REGISTER": "Registra", @@ -1569,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Rifiutata", "TR_SELL_STATUS_PENDING": "In sospeso", "TR_SELL_STATUS_SUCCESS": "Approvata", - "TR_SELL_TRANS_ID": "ID trans.:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Il valore massimo è {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Il valore minimo è {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Visualizza dettagli", "TR_SENDFORM_LABELING_EXAMPLE_1": "Risparmi", "TR_SENDFORM_LABELING_EXAMPLE_2": "Noleggio", "TR_SENDING_SYMBOL": "Invio di {multiple, select, true {più token} false {{symbol}} other {{symbol}}}", @@ -1627,6 +1674,8 @@ "TR_SHOW_LOG": "Mostra log", "TR_SHOW_MORE": "Mostra altro", "TR_SHOW_MORE_ADDRESSES": "Mostra altri ({count})", + "TR_SHOW_ON_TRAY": "Mostra icona nella barra delle applicazioni", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Controlla se Trezor Suite è in esecuzione in background.", "TR_SHOW_UNVERIFIED_ADDRESS": "Mostra indirizzo non verificato", "TR_SHOW_UNVERIFIED_XPUB": "Mostra chiave pubblica non verificata", "TR_SIDEBAR_ADD_COIN": "Aggiungi coin", @@ -1639,7 +1688,9 @@ "TR_SIZE": "Dimensioni", "TR_SKIP": "Ignora", "TR_SKIP_BACKUP": "Ignora back-up", + "TR_SKIP_BACKUP_DESCRIPTION": "Un back-up del wallet ti consente di recuperare i fondi in caso di smarrimento, furto o danneggiamento di Trezor. Senza un back-up, si potrebbe perdere permanente l'accesso alle crypto.", "TR_SKIP_PIN": "Ignora PIN", + "TR_SKIP_PIN_DESCRIPTION": "Un PIN del dispositivo impedisce l'accesso non autorizzato a Trezor. In sua assenza, chiunque abbia il tuo dispositivo potrà accedere ai tuoi fondi.", "TR_SKIP_ROUNDS": "Round saltato", "TR_SKIP_ROUNDS_DESCRIPTION": "La possibilità di saltare i round rende più difficile dimostrare una qualsiasi relazione tra i tuoi input. Ciò significa che puoi offuscare ulteriormente l'origine dei fondi.", "TR_SKIP_ROUNDS_HEADING": "Consenti a Trezor di saltare i round", @@ -1652,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Aggiunta al pool di staking", "TR_STAKE_ANY_AMOUNT_ETH": "Prevedi un importo minimo {amount} {symbol} in staking e inizia a guadagnare premi. Con il nostro attuale tasso APY del {apyPercent}%, anche i tuoi premi ne traggono vantaggio.", "TR_STAKE_APY": "Rendimento percentuale annuo", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Rendimento percentuale annuo", "TR_STAKE_AVAILABLE": "Disponibile", "TR_STAKE_CAN_CLAIM_WARNING": "Puoi già richiedere {amount} {symbol}. {br}Richiedi o attendi l'elaborazione del nuovo unstaking.", @@ -1661,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Puoi effettuare la richiesta al termine del periodo di unstaking.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "nel blocco successivo", "TR_STAKE_CLAIM_PENDING": "Richiesta in sospeso", + "TR_STAKE_CLAIM_UNSTAKED": "Richiedi {symbol} non in staking", "TR_STAKE_CONFIRM_AND_STAKE": "Conferma e metti in staking", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Conferma periodo di immissione", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Accetto e acconsento a eseguire lo staking con Everstake", "TR_STAKE_DAYS": "{days} giorni", "TR_STAKE_DELEGATED": "Delega di staking", "TR_STAKE_DEREGISTERED": "Deregistrazione di un indirizzo di staking", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Ricevi ricompense ogni settimana", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "L'accesso al pool di staking potrebbe richiedere fino a {days} giorni", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Entra nel pool di staking", "TR_STAKE_ETH": "Staking di Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "Il modo più semplice per guadagnare {symbol}.", + "TR_STAKE_ETH_CARD_TITLE": "Il modo più semplice per guadagnare {symbol}", "TR_STAKE_ETH_EARN_REPEAT": "Metti in staking. Guadagna ricompense. Rimetti in staking.", "TR_STAKE_ETH_EVERSTAKE": "Trezor e Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake è un leader globale e fornitore di tecnologia di staking", @@ -1680,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Guadagni anche sulle tue ricompense. Mantienile in staking e guarda crescere le tue ricompense in {symbol}.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Il tasso APY si applica anche sulle tue ricompense in {symbol}. Mantieni in staking i tuoi fondi o aggiungine altri per incrementare le tue ricompense.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Guarda crescere il tuo denaro", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Guadagna un APY* del {apyPercent}% mettendo in staking il tuo Ethereum con Trezor.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Guadagna un APY del {apyPercent}% mettendo in staking il tuo Ethereum con Trezor.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Il tuo {symbol} sarà bloccato durante questo periodo e non potrai annullare il blocco. Ulteriori informazioni", "TR_STAKE_EVERSTAKE_MANAGES": "Everstake gestisce e protegge il tuo {symbol} in staking tramite i suoi contratti intelligenti, la sua infrastruttura e la sua tecnologia.", "TR_STAKE_INSTANT": "Istantaneo", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Hai ricevuto {amount} {symbol} \"istantaneamente\". {days, plural, =0 {} one {Il residuo verrà pagato entro # giorno.} other { Il residuo verrà pagato entro # giorni}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} nel conto", "TR_STAKE_LEARN_MORE": "Ulteriori informazioni", + "TR_STAKE_LEAVE_STAKING_POOL": "Abbandona il pool di staking", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Non abbiamo incluso {amount} {symbol} per consentirti di pagare le commissioni di ritiro.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Abbiamo lasciato una piccola parte di {symbol}, così potrai pagare le spese di prelievo.", "TR_STAKE_MAX": "Max", "TR_STAKE_MAX_FEE_DESC": "La commissione massima è la commissione di transazione che sei disposto a pagare alla rete per assicurarti che la transazione venga elaborata.", "TR_STAKE_MAX_REWARD_DAYS": "Max. {count, plural, one {n. giorno} other {n. giorni}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "L'importo minimo per lo staking è di {amount} {symbol}", + "TR_STAKE_MONTHLY": "Mensile", "TR_STAKE_NEXT_PAYOUT": "Prossimo pagamento della ricompensa", "TR_STAKE_NOT_ENOUGH_FUNDS": "{symbol} insufficiente per pagare le commissioni di rete", "TR_STAKE_ONLY_REWARDS": "Solo ricompense", @@ -1702,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Registrazione di un indirizzo di staking", "TR_STAKE_RESTAKED_BADGE": "È stato effettuato nuovamente lo staking", "TR_STAKE_REWARDS": "Ricompense", + "TR_STAKE_SIGN_TRANSACTION": "Firma transazione", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Firma la transazione di unstaking", "TR_STAKE_STAKE": "Metti in staking", "TR_STAKE_STAKED_AMOUNT": "Importo in staking", "TR_STAKE_STAKED_AND_EARNING": "Ricompense guadagnate e in staking", @@ -1709,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Aumenta lo staking", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Lo staking in breve", "TR_STAKE_STAKING_IS": "La procedura di staking è una sorta di gesto amichevole con cui blocchi temporaneamente i tuoi asset Ethereum per supportare il funzionamento della blockchain. Come meritata ricompensa, in cambio guadagnerai più {symbol}!", + "TR_STAKE_STAKING_PROCESS": "Processo di staking", "TR_STAKE_START_STAKING": "Inizia a fare staking", "TR_STAKE_TIME_TO_CLAIM": "Tempo per la richiesta", "TR_STAKE_TOTAL_PENDING": "Totale staking in sospeso:", @@ -1717,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Non più in staking e richiedibili", "TR_STAKE_UNSTAKE_TO_CLAIM": "Esegui unstaking per richiedere", "TR_STAKE_UNSTAKING": "Unstaking", + "TR_STAKE_UNSTAKING_APPROXIMATE": "{symbol} disponibili subito approssimativamente", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "La liquidità della staking pool può consentire l'unstaking immediato di alcuni fondi. I fondi rimanenti seguiranno il periodo di unstaking.", "TR_STAKE_UNSTAKING_PERIOD": "Periodo di unstaking", + "TR_STAKE_UNSTAKING_PROCESS": "Processo di unstaking", "TR_STAKE_UNSTAKING_TAKES": "L'unstaking solitamente richiede circa 3 giorni. Una volta completata l'operazione, puoi scambiarli o inviarli.", + "TR_STAKE_WEEKLY": "Settimanale", "TR_STAKE_WHAT_IS_STAKING": "Che cos'è lo staking?", + "TR_STAKE_YEARLY": "Annuale", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "I tuoi fondi in staking sono gestiti da Everstake", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} messi subito in staking!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} rimossi subito dallo staking!", + "TR_STAKING_CONSOLIDATING_FUNDS": "Consolidamento di {symbol}", "TR_STAKING_DELEGATE": "Delegato", "TR_STAKING_DEPOSIT": "Deposito rimborsabile", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "La commissione di deposito è di {feeAmount} ADA ed è necessaria per registrare il tuo indirizzo e iniziare a fare staking. Se decidi di eseguire l'unstaking di Cardano, il deposito ti verrà restituito.", + "TR_STAKING_ESTIMATED_GAINS": "Guadagni stimati", "TR_STAKING_FEE": "Commissione", + "TR_STAKING_GETTING_READY": "I tuoi {symbol} sono pronti a generare reddito", "TR_STAKING_INSTANTLY_STAKED": "Hai messo in staking {amount} {symbol} istantaneamente. {days, plural, =0 {} one {Il residuo in {symbol} verrà messo in staking entro # giorno.} other { Il residuo in {symbol} verrà messo in staking entro # giorni}}", "TR_STAKING_IS_NOT_SUPPORTED": "Lo staking non è supportato su questa rete.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Non disponi di fondi sufficienti sul tuo conto.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Dopo aver confermato", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Eseguendo lo staking su un pool di staking di Trezor, sostieni direttamente l'ecosistema di Trezor e Cardano in Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Stai delegando a un pool di staking di terzi", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "Il pool di staking a cui stai delegando è sovrasaturo. Ridelega il tuo staking per massimizzare le ricompense per lo staking", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Il pool di staking è sovrasaturo", "TR_STAKING_REDELEGATE": "Ridelega", "TR_STAKING_REWARDS": "Ricompense disponibili", + "TR_STAKING_REWARDS_ARE_RESTAKED": "I premi vengono automaticamente resi disponibili per lo staking", "TR_STAKING_REWARDS_DESCRIPTION": "Ti ricordiamo che potrebbero essere necessari fino a 20 giorni prima di iniziare a ricevere le ricompense dopo la registrazione e la delega di staking iniziale. Al termine di questo periodo, riceverai la tua ricompensa ogni 5 giorni.", "TR_STAKING_REWARDS_TITLE": "Lo staking di Cardano è attivo", "TR_STAKING_STAKE_ADDRESS": "Il tuo indirizzo di staking", @@ -1742,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Non è stato possibile accedere al pool di staking di Trezor a cui delegare.", "TR_STAKING_TX_PENDING": "La transazione {txid} è stata inviata correttamente alla blockchain ed è in attesa di conferma.", "TR_STAKING_WITHDRAW": "Ritira", + "TR_STAKING_YOUR_EARNINGS": "I tuoi guadagni vengono automaticamente resi disponibili per lo staking, consentendoti di guadagnare un interesse composto.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "I tuoi {symbol} su cui è stato eseguito l'unstaking sono pronti", + "TR_STAKING_YOU_ARE_HERE": "Sei qui", "TR_STANDARD_WALLET_DESCRIPTION": "Nessuna passphrase", "TR_START": "Avvia", "TR_START_AGAIN": "Riavvia", @@ -1750,6 +1827,7 @@ "TR_START_COINJOIN": "Avvia coinjoin", "TR_START_RECOVERY": "Avvia recupero", "TR_STEP": "Passaggio {number}", + "TR_STEP_OF_TOTAL": "Fase {index} di {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Non vedi ancora il tuo dispositivo Trezor?", "TR_STOP": "Arresta", "TR_STOPPING": "Arresto in corso", @@ -1801,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Ancora nessun token.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Token non presenti. Potrebbero essere nascosti.", "TR_TOKENS_SEARCH_TOOLTIP": "Ricerca per token, simbolo o indirizzo di contratto.", + "TR_TOKEN_NOT_FOUND": "Token non trovato", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Token non trovato sulla rete {networkName}.", "TR_TOKEN_TRANSFERS": "Trasferimenti di token {standard}", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Prova una ricerca diversa.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Prova una ricerca diversa o passa a un'altra rete.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Token non riconosciuti", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "I token non riconosciuti comportano rischi potenziali. Fai attenzione.", "TR_TOO_LONG": "Il messaggio è troppo lungo", @@ -1813,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Aggiorna percorso", "TR_TOR_DESCRIPTION": "Indirizza tutto il traffico di Trezor Suite attraverso la rete Tor per ottenere maggiore privacy e sicurezza. Il caricamento e la connessione di Tor potrebbe richiedere del tempo.", "TR_TOR_DISABLE": "Disabilita Tor", + "TR_TOR_DISABLED": "Disabilitato", "TR_TOR_DISABLE_ONIONS_ONLY": "Back-end personalizzati non .onion mancanti", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Aggiungi gli indirizzi dei back-end personalizzati non .onion per evitare questo comportamento.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Ora puoi disabilitare Tor in tutta sicurezza.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "I back-end personalizzati non utilizzano più solo gli indirizzi .onion.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Disabilita Tor", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Disabilitando Tor, tutti i back-end .onion saranno reimpostati sui server Trezor predefiniti.", + "TR_TOR_DISABLING": "Disabilitazione in corso", "TR_TOR_ENABLE": "Abilita Tor", + "TR_TOR_ENABLED": "Abilitato", "TR_TOR_ENABLE_AND_CONFIRM": "Abilita Tor e conferma", "TR_TOR_ENABLE_TITLE": "Abilita Tor", + "TR_TOR_ENABLING": "Abilitazione in corso", + "TR_TOR_ERROR": "Errore", "TR_TOR_IS_SLOW_MESSAGE": "Tor si sta collegando alla rete.

Attendi.", "TR_TOR_KEEP_RUNNING": "Mantieni in esecuzione Tor", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Seleziona \"Mantieni in esecuzione Tor\" per continuare o \"Arresta Tor\" per uscire dal processo coinjoin.", + "TR_TOR_MISBEHAVING": "Comportarsi male", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Disabilita Tor e passa ai back-end predefiniti", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Abbandona", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Seleziona \"Abilita Tor\" per continuare o \"Abbandona\" per uscire dal processo.", @@ -1839,11 +1927,7 @@ "TR_TO_BTC": "A BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Per rendere le etichette compatibili e disponibili su diversi dispositivi, effettua la connessione a un provider di archiviazione nel cloud.", "TR_TO_SATOSHIS": "A sat", - "TR_TRADE_BUYS": "acquisti", - "TR_TRADE_ENTER_COIN": "Inserisci il nome o il simbolo della cripto...", - "TR_TRADE_EXCHANGES": "exchange", "TR_TRADE_REDIRECTING": "Reindirizzamento in corso...", - "TR_TRADE_SELLS": "vendite", "TR_TRANSACTIONS_NOT_AVAILABLE": "Cronologia transazioni non disponibile", "TR_TRANSACTIONS_SEARCH_TIP_1": "Suggerimento: puoi cercare le transazioni per ID, indirizzi, token, etichette, importi e date.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Suggerimento: per eseguire ricerche più complesse, combina gli operatori AND (&) e OR (|). Ad esempio, utilizzando > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 verranno visualizzate tutte le transazioni di gennaio o dicembre {lastYear}.", @@ -1858,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Cerca per ID transazione, etichetta o importo oppure utilizza gli operatori < > | & = !=.", "TR_TRANSACTION_DETAILS": "Dettagli", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Esecuzione della versione {version} di Trezor Bridge in corso", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutorial annullato", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutorial completato", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Impara a utilizzare il tuo dispositivo con l'aiuto di un breve tutorial", @@ -1865,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Chiudi le altre schede e finestre dove Trezor potrebbe essere in uso", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Dopo aver chiuso le altre schede e finestre, prova ad aggiornare questa pagina.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Dopo aver chiuso le altre schede e finestre del browser, prova a uscire e a riaprire Trezor Suite.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Procedura richiesta per abilitare la comunicazione", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Per risolvere il problema, procedi come segue.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Visita la pagina di stato di Trezor Bridge", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Assicurati che il processo di Trezor Bridge sia in esecuzione", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Solo i browser basati su Chromium attualmente consentono la comunicazione diretta con i dispositivi USB", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Solo i browser basati su Chromium attualmente consentono la comunicazione diretta con i dispositivi USB.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Utilizza un browser basato su Chromium", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "Il cavo deve essere completamente inserito. Se il dispositivo è collegato tramite USB-C, il cavo deve scattare in posizione.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Prova un altro cavo", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Con Trezor Bridge installato.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Prova a utilizzare un altro computer, se possibile", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Non si sa mai", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Prova a riavviare il computer", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Il riavvio del computer potrebbe risolvere il problema di comunicazione tra il browser e il dispositivo.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Riavvia il computer", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Esegui l'app per desktop Trezor Suite", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Utilizza l'app per desktop Trezor Suite", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Fai clic per passare da un'implementazione di Bridge alternativa a un'altra. Versione corrente: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Utilizza un'altra versione di Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Prova a installare le regole udev. Assicurati che siano salvate sul desktop prima dell'apertura.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Se il firmware del dispositivo è stato aggiornato l'ultima volta alla versione 2019 o precedente, segui le istruzioni riportate nella Knowledge Base", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Sembra che stai utilizzando un modello di Trezor precedente.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Connettilo direttamente al computer (senza un hub USB).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Prova un'altra porta USB", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Installa regole automaticamente", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Regole udev mancanti", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Stato imprevisto: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Il dispositivo è connesso correttamente, ma al momento non è in grado di comunicare con il browser. Devi installare Trezor Bridge.", "TR_TRY_AGAIN": "Riprova", "TR_TXID": "ID TX", "TR_TXID_RBF": "ID TX originale da sostituire", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transazione confermata", "TR_TX_CONFIRMING": "Conferma transazione in corso", + "TR_TX_DATA_FUNCTION": "Funzione", + "TR_TX_DATA_INPUT_DATA": "Dati di input", + "TR_TX_DATA_METHOD": "Dati di input", + "TR_TX_DATA_METHOD_NAME": "Nome del metodo", + "TR_TX_DATA_PARAMS": "Parametri", "TR_TX_DEPOSIT": "Deposito", "TR_TX_FEE": "Commissione", "TR_TX_TAB_AMOUNT": "Importo", @@ -1930,13 +2023,18 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Aggiornamento firmware necessario. Puoi modificare la homescreen nelle impostazioni in un secondo momento", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Aggiorna il firmware per modificare la homescreen", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Aggiornamento disponibile", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Abilita gli aggiornamenti automatici", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Nuovo", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Riavvia e aggiorna", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Vuoi installare l'aggiornamento ora?", "TR_UPDATE_MODAL_NOT_NOW": "Non ora", - "TR_UPDATE_MODAL_RESTART_NEEDED": "Questa operazione riavvierà Trezor Suite", + "TR_UPDATE_MODAL_RESTART_NEEDED": "Questa operazione riavvierà Trezor Suite.", "TR_UPDATE_MODAL_START_DOWNLOAD": "Avvia aggiornamento", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Aggiornamento scaricato", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Aggiorna quando esci", + "TR_UPDATE_MODAL_WHATS_NEW": "Novità", + "TR_UPDATE_MODAL_YOUR_VERSION": "La tua versione: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Aggiorna il firmware per accedere a questo conto. Vedi il banner blu sopra riportato.", "TR_UP_TO": "fino a", "TR_UP_TO_DATE": "Aggiornato", "TR_UP_TO_DAYS": "fino a {days} giorni", @@ -1990,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Accedi al wallet nascosto", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Wallet nascosto", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Crea un nuovo wallet o recuperane uno da un back-up utilizzando la frase Seed di recupero.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Ci impegniamo sempre a migliorare la tua esperienza con Trezor. Ecco le novità:", "TR_WEST": "Ovest", "TR_WHAT_DATA_WE_COLLECT": "Quali dati raccogliamo?", "TR_WHAT_IS_PASSPHRASE": "Scopri di più sulla differenza", @@ -2000,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Sono consapevole che devo avere un back-up della mia frase Seed di recupero per poter accedere sempre ai miei fondi", "TR_WIPE_DEVICE_TEXT": "Il ripristino del dispositivo comporta l'eliminazione di tutti i dati. Ripristina il tuo dispositivo solo se disponi di un back-up offline sicuro della tua frase Seed di recupero che ti consenta di recuperare i fondi.", "TR_WIPE_OR_UPDATE": "Ripristina il dispositivo o aggiorna il firmware", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Vai alle impostazioni del dispositivo", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Vai alle impostazioni del dispositivo.", "TR_WIPING_YOUR_DEVICE": "Il ripristino delle impostazioni di fabbrica cancella tutte le informazioni presenti nella memoria del dispositivo, inclusi il PIN e la frase Seed di recupero. Esegui il ripristino delle impostazioni di fabbrica solo se disponi di un back-up offline sicuro della tua frase Seed di recupero che ti consenta di recuperare i fondi.", "TR_WORDS": "{count} parole", "TR_WORD_DOES_NOT_EXIST": "La parola \"{word}\" non esiste nell'elenco di parole bip39.", diff --git a/packages/suite-data/files/translations/ja.json b/packages/suite-data/files/translations/ja.json index 96d90908478..1e2af30b021 100644 --- a/packages/suite-data/files/translations/ja.json +++ b/packages/suite-data/files/translations/ja.json @@ -1,5 +1,6 @@ { "AMOUNT": "金額", + "AMOUNT_EXCEEDS_MAX": "金額が最大許容値 {maxAmount} を超えています。", "AMOUNT_IS_BELOW_DUST": "金額はダストリミット ({dust}) 以上でなければいけません", "AMOUNT_IS_LESS_THAN_RESERVE": "受信者のアカウントを有効にするには最小準備金 {reserve} XRP以上の送金が必要です", "AMOUNT_IS_MORE_THAN_RESERVE": "金額が多すぎて支出不可の準備金 ({reserve} XRP) に抵触します", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PINが正常に変更されました", "TOAST_QR_INCORRECT_ADDRESS": "QRコードに無効なアドレスが含まれています", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "QRコードは {coin} アカウント用に定義されています", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "不明なプロトコルスキームです:\"{scheme}\"。再試行するか、アドレスを手動で入力してください。", "TOAST_RAW_TX_SENT": "取引を送信しました。TXID: {txid}", "TOAST_SETTINGS_APPLIED": "設定が正常に変更されました", "TOAST_SIGN_MESSAGE_ERROR": "メッセージに署名できませんでした: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "ダッシュボードに移動", "TR_404_TITLE": "エラー404: リンクが見つかりません", "TR_7D_CHANGE": "7日間の変動", - "TR_ABORT": "中止", "TR_ACCESS_HIDDEN_WALLET": "パスフレーズウォレットにアクセス", "TR_ACCESS_STANDARD_WALLET": "標準ウォレットにアクセス", "TR_ACCOUNT_DETAILS_HEADER": "アカウントの詳細", @@ -260,6 +261,7 @@ "TR_ACQUIRE_DEVICE_TITLE": "別のセッションが実行中です", "TR_ACTIVATED_COINS": "有効化されたコイン", "TR_ACTIVE": "有効", + "TR_ADD": "追加", "TR_ADDRESSES": "アドレス", "TR_ADDRESSES_CHANGE": "アドレスの変更", "TR_ADDRESSES_FRESH": "新しいアドレス", @@ -269,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "アドレスをコピー", "TR_ADDRESS_MODAL_TITLE": "{networkName} 受信アドレス", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkName} ネットワークの {networkCurrencyName} 受信アドレス", - "TR_ADDRESS_PHISHING_WARNING": "フィッシング攻撃を防ぐために、Trezorでアドレスを確認してください。 {claim}", + "TR_ADDRESS_PHISHING_WARNING": "フィッシング攻撃を防ぐために、Trezorで受信アドレスを確認してください。 {claim}", "TR_ADD_ACCOUNT": "アカウントを追加", "TR_ADD_HIDDEN_WALLET": "パスフレーズウォレット", "TR_ADD_NETWORK_ACCOUNT": "{network} のアカウントを追加", @@ -296,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "データ使用量", "TR_ALLOW_ANALYTICS_DESCRIPTION": "すべてのデータは厳密に匿名のままです。Trezorエコシステムを改善するためにのみ使されます。", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Trezor Suiteの自動アップデート", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suiteはバックグラウンドで最新バージョンを自動的にダウンロードし、アプリの再起動時にインストールします。 これにより、最新の機能とセキュリティパッチで常に最新の状態に保つことができます。アップデートは許可を必要とせずに行われます。", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "バックグラウンドで最新バージョンのTrezor Suiteを自動的にダウンロードし、アプリの再起動時にインストールします。 これにより、最新の機能とセキュリティパッチで常に最新の状態に保つことができます。アップデートは許可を必要とせずに行われます。", + "TR_ALL_NETWORKS": "全ネットワーク ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "全部で {networkCount} のネットワークからトークンを表示します。最も人気のあるネットワークでフィルタリングします。", "TR_ALL_TRANSACTIONS": "取引", "TR_AMOUNT_SENT": "送金額", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Coinjoin の利用は不適当です ー 金額が多すぎます", @@ -355,21 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "可能性は低いですが、ファームウェアのアップデートに問題が発生した場合、ウォレットのバックアップにアクセスすることが必要な場合があります。", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "{bitcoinOnly} に変更する前に、ファームウェアを新しいバージョンにアップグレードする必要があります。", - "TR_BREAKING_ANONYMITY_CHECKBOX": "匿名性を低下させていることを理解しています", + "TR_BREAKING_ANONYMITY_CHECKBOX": "匿名性が損なわれていることを理解しています。", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "ポート21324でTrezor Bridgeを開始しています", "TR_BRIDGE_DEV_MODE_STOP": "デフォルトポートでTrezor Bridgeを開始しています", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "本当ですか?デバイスは同時に1つのアプリによってのみ使用可能です。現在、Trezor デバイスを別のアプリで使用している場合は、まずそのセッションを終了してください。", "TR_BRIDGE_NEEDED_DESCRIPTION": "お使いのブラウザはサポートされていません。最高の体験を得るために、Trezor Suiteデスクトップアプリをダウンロードしてバックグラウンドで実行するか、WebUSBに対応したクロームベースのブラウザを使用してください。", "TR_BRIDGE_REQUESTED_DESCRIPTION": "別のアプリがTrezor SuiteにTrezorデバイスと接続するよう要求しました。Trezor Suiteをバックグラウンドで実行したまま、他のアプリで操作を再実行してください。", + "TR_BRIDGE_TIP_AUTOSTART": "ヒント: 自動起動機能を有効にすれば、Bridgeは常にバックグラウンドで実行されます。", "TR_BTC_UNITS": "Bitcoinの単位", "TR_BUG": "バグ", "TR_BUMP_FEE": "手数料を増額", "TR_BUMP_FEE_DISABLED_TOOLTIP": "取引を早めるには、キュー内の最も古い(nonceによる)保留中の取引の手数料を増やしてください。取引は順番に承認されねばなりません。詳細はこちら", "TR_BUY": " 購入", - "TR_BUY_ACCOUNT_TRANSACTIONS": "交換取引", "TR_BUY_BUY": " 購入", - "TR_BUY_BUY_AGAIN": "もう一度購入", "TR_BUY_CONFIRMED_ON_TREZOR": "Trezorで確認済", "TR_BUY_DETAIL_ERROR_BUTTON": "アカウントに戻る", "TR_BUY_DETAIL_ERROR_SUPPORT": "プロバイダサポートに移動", @@ -396,7 +399,7 @@ "TR_BUY_MODAL_SECURITY_HEADER": "Trezorでセキュリティを確保", "TR_BUY_MODAL_TERMS_1": "私は仮想通貨を購入するためにここにいます。他の理由でこのサイトに誘導された場合は、続行する前に {provider} のサポートに問い合わせます。", "TR_BUY_MODAL_TERMS_2": "私のアカウントに送金される仮想通貨を購入するために、この機能を使用しています。", - "TR_BUY_MODAL_TERMS_3": "仮想通貨取引は元に戻すことができず、返金できない場合があることを理解しています。 不正または偶発的な損失は回復できない場合があります。", + "TR_BUY_MODAL_TERMS_3": "仮想通貨の取引は最終的なものであり、取り消しや払い戻しができないことを理解しています。詐欺やミスによる損失は回復できない場合があります。", "TR_BUY_MODAL_TERMS_4": "Invity がこのサービスを提供していないことを理解しています。このサービスは {provider}の利用規約によって管理されています。", "TR_BUY_MODAL_TERMS_5": "ギャンブル、詐欺、あるいはInvity またはプロバイダーの利用規約、あるいは適用される規制に対する違反となるような目的のためにこの機能を使用していません。", "TR_BUY_MODAL_TERMS_6": "仮想通貨が新たな金融ツールであり、法域によって規制が異なる場合があることを理解しています。 これにより、詐欺、盗難、または市場の不安定性のリスクが高まる可能性があります。", @@ -414,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "保留中", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "保留中", "TR_BUY_STATUS_SUCCESS": "承認済", - "TR_BUY_TRANS_ID": "取引 ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "最大は {maximum} です", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "最大は {maximum} {currency} です", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "最小は {minimum} です", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "最小は {minimum} {currency} です", - "TR_BUY_VIEW_DETAILS": "詳細を表示", "TR_BYTES": "バイト", "TR_CAMERA_NOT_RECOGNIZED": "カメラが認識されませんでした。", "TR_CAMERA_PERMISSION_DENIED": "カメラへのアクセスが拒否されました。", @@ -438,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezorの金額", "TR_CHAINED_TXS": "連鎖取引", "TR_CHANGELOG": "変更履歴", - "TR_CHANGELOG_ON_GITHUB": "GitHubの変更履歴", "TR_CHANGE_ADDRESS_TOOLTIP": "これは以前の送金取引におけるお釣りのアドレスです。", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "ファームウェアの種類はいつでも設定で変更できます。", "TR_CHANGE_HOMESCREEN": "ホーム画面を変更", "TR_CHANGE_PIN": "PINを変更", "TR_CHANGE_WIPE_CODE": "ワイプコードを変更", + "TR_CHECKED_BALANCES_ON": "残高確認済み", "TR_CHECKING_YOUR_DEVICE": "デバイスをチェックしています", "TR_CHECKSUM_CONVERSION_INFO": "チェックサムに変換されました。詳細はこちら", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Trezorデバイスの完全性を検証し、その安全性を担保し、チップの信頼性を確認します。", @@ -455,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "あなたのウォレットのバックアップを確認するために復元のシミュレーションを実行します。", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "デバイスに表示される順序で、ウォレットのバックアップの単語をここに入力してください。追加のセキュリティ対策として、ウォレットのバックアップに含まれていない単語を入力するように求められる場合があります。", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "ウォレットのバックアップをデバイスの2つのボタンを使って入力します。これにより、あなたの機密情報を潜在的に安全ではないコンピューターやウェブブラウザに渡さないようにしています。", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "ウォレットのバックアップをデバイスのタッチスクリーンを使って入力します。これにより、あなたの機密情報を潜在的に安全ではないコンピューターやウェブブラウザに渡さないようにしています。", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "ウォレットのバックアップをデバイスのタッチスクリーンを使って入力します。これにより、あなたの機密情報を潜在的に安全ではないコンピューターやウェブブラウザに渡さないようにしています。", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "ウォレットのバックアップはタッチスクリーンを使用して入力されます。これにより、機密情報を安全でない可能性のあるコンピュータまたはWebブラウザに漏洩することを回避できます。", "TR_CHECK_SEED": "バックアップを確認", "TR_CHECK_YOUR_DEVICE": "Trezorの画面を確認", "TR_CHOOSE_RECOVERY_TYPE": "復元方法を選択", @@ -511,6 +511,7 @@ "TR_COINJOIN_TRANSACTION_BATCH": "Coinjoin の取引", "TR_COINMARKET_BEST_RATE": "ベストレート", "TR_COINMARKET_BUY_AND_SELL": "購入と売却", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {買い {totalBuys}} one {買い {totalBuys}} other {買い {totalBuys}} } • {totalSells, plural, =0 {売り {totalSells}} one {売り {totalSells}} other {売り {totalSells}} }", "TR_COINMARKET_CEX_TOOLTIP": "中央集権型取引所", "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "金額または通貨を変更します。", "TR_COINMARKET_COMPARE_OFFERS": "すべてのオファーを比較", @@ -527,6 +528,16 @@ "TR_COINMARKET_DCA_HEADING": "InvityアプリでBitcoinを貯めよう", "TR_COINMARKET_DEX_TOOLTIP": "分散型取引所", "TR_COINMARKET_ENTER_AMOUNT_IN": "{currency} の額を入力してください", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "すべてのKYCオプション", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC は全く必要ありません", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "CEXとDEXのすべてのオファー", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "固定レートのCEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "変動レートのCEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "分散型取引所(DEX)では、中央機関や仲介者を介さずに、ブロックチェーン上で仮想通貨を直接取引することができます。", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "固定レートのCEX", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "変動レートのCEX", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "注目のオファー", "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "支払い:", "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "受取り方法:", @@ -542,19 +553,13 @@ "TR_COINMARKET_KYC_NO_REFUND": "例外的なケースでKYCが要求されます。払い戻しにはKYCが必要です。👈", "TR_COINMARKET_KYC_POLICY": "KYCのポリシー", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC は全く必要ありません", - "TR_COINMARKET_KYC_YES_REFUND": "例外的なケースでKYCが要求されます。払い戻しにはKYCは不要です。👈", + "TR_COINMARKET_KYC_YES_REFUND": "KYCは例外的な場合にのみ要求されます。払い戻しには必要ありません。 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "最後の取引", "TR_COINMARKET_NETWORK_FEE": "ネットワーク手数料", "TR_COINMARKET_NETWORK_TOKENS": "{networkName} トークン", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "CEXプロバイダーが見つかりません", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "DEXプロバイダが見つかりません", "TR_COINMARKET_NO_METHODS_AVAILABLE": "利用可能なメソッドがありません", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "自動的にリロードされます: ", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "取引に戻る", - "TR_COINMARKET_NO_OFFERS_HEADER": "オファーがありません", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "申し訳ありませんが、接続の問題により現時点ではオファーはありません。", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "申し訳ありませんが、現時点ではオファーはありません。 ページをリロードするか、クエリを変更してみてください。", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "ページをリロード", "TR_COINMARKET_OFFERS_EMPTY": "ご希望に沿ったオファーはありません。国または購入金額を変更してください。", "TR_COINMARKET_OFFERS_REFRESH": "オファーが更新されます", "TR_COINMARKET_OFFERS_SELECT": "選択", @@ -570,6 +575,7 @@ "TR_COINMARKET_SHOW_OFFERS": "オファーを比較", "TR_COINMARKET_SWAP": "スワップ", "TR_COINMARKET_SWAP_AMOUNT": "スワップ額", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} スワップ} one {{totalSwaps} スワップ} other {{totalSwaps} スワップ} }", "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "スワップの準備完了", "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "{provider} で {fromCrypto} から {toCrypto} にスワップ", "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "法的通知", @@ -585,7 +591,7 @@ "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "{provider} で {fromCrypto} から {toCrypto} にスワップ", "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "法的通知", "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Trezorでセキュリティを確保してください", - "TR_COINMARKET_SWAP_MODAL_TERMS_1": "仮想通貨をスワップするためにここにいます。他の理由でこのサイトに誘導された場合は、続行する前にサポートに問い合わせます。", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "仮想通貨をスワップするためにここにいます。他の理由でこのサイトに誘導された場合は、続行する前にTrezor のサポートに問い合わせます。", "TR_COINMARKET_SWAP_MODAL_TERMS_2": "自分のアカウントでの仮想通貨のスワップを希望します。プロバイダのポリシーにより本人確認が必要な場合があることを認識しています。", "TR_COINMARKET_SWAP_MODAL_TERMS_3": "仮想通貨の取引は最終的なものであり、取り消しや払い戻しができないことを理解しています。詐欺やミスによる損失は回復できない場合があります。", "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Invity がこのサービスを提供していないことを理解しています。このサービスは {provider}の利用規約によって管理されています。", @@ -594,7 +600,9 @@ "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Invityによって認証されたパートナー", "TR_COINMARKET_TOKEN_NETWORK": "{networkName} ネットワークの {tokenName}", "TR_COINMARKET_TRADE_FEE": "取引手数料", + "TR_COINMARKET_TRANS_ID": "取引 ID: ", "TR_COINMARKET_UNKNOWN_PROVIDER": "不明なプロバイダー", + "TR_COINMARKET_VIEW_DETAILS": "詳細を表示", "TR_COINMARKET_YOUR_BEST_OFFER": "ベストオファー", "TR_COINMARKET_YOU_BUY": "購入する", "TR_COINMARKET_YOU_GET": "獲得する", @@ -619,6 +627,7 @@ "TR_CONFIRMED_TX": "承認済", "TR_CONFIRMING_TX": "取引を確認中", "TR_CONFIRM_ACTION_ON_YOUR": "Trezorの画面の指示に従ってください", + "TR_CONFIRM_ADDRESS": "アドレスを確認", "TR_CONFIRM_BEFORE_COPY": "コピーする前にTREZORで確認してください", "TR_CONFIRM_CONDITIONS": "続行する前に条件を確認してください。", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "”{deviceLabel}” デバイスで空のパスフレーズウォレットを確認します。", @@ -647,7 +656,7 @@ "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Trezorが接続されていません", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "送信するにはTrezorを接続してください", "TR_CONNECT_YOUR_DEVICE": "Trezorを接続してロックを解除する", - "TR_CONTACT_SUPPORT": "サポートに問い合わせる", + "TR_CONTACT_SUPPORT": "Trezorサポートに問い合わせ", "TR_CONTACT_TREZOR_SUPPORT": "Trezorサポートに連絡する", "TR_CONTINUE": "続ける", "TR_CONTINUE_ANYWAY": "とにかく続行する", @@ -667,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "ポリシーIDアドレスには資金を絶対に送らないでください。", "TR_COPY_AND_CLOSE": "コピーして閉じる", "TR_COPY_SIGNED_MESSAGE": "署名済のメッセージをコピー", - "TR_COPY_TO_CLIPBOARD_TX_ID": "コピー", + "TR_COPY_TO_CLIPBOARD": "コピー", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "更新履歴を取得できませんでした", "TR_COULD_NOT_RETRIEVE_DATA": "データを取得できませんでした", "TR_COUNT_WALLETS": "{count} ウォレット", @@ -701,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "資産が正しく読み込まれていません", "TR_DASHBOARD_DISCOVERY_ERROR": "ディスカバリーエラー", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "アカウントが正しく読み込まれませんでした {details}", + "TR_DATA": "データ", "TR_DATABASE_UPGRADE_BLOCKED": "他のアプリケーション・インスタンスによってデータベースのアップグレードがブロックされました", "TR_DATA_ANALYTICS_CATEGORY_1": "プラットフォーム", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "OS、Trezorモデル、バージョンなど", @@ -754,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "間違ってブートローダーモードになっているのですか?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "ボタンをタッチせずにデバイスを再接続してください。", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "画面に触れずにデバイスを再接続してください。", + "TR_DEVICE_CONNECTED_UNACQUIRED": "このデバイスは他の場所で使用されています。", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "アプリ {transportSessionOwner} が現在このデバイスを使用している可能性があります。必要に応じてデバイスを制御することができます。", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "別のアプリが現在このデバイスを使用している可能性があります。必要に応じてデバイスを制御することができます。", "TR_DEVICE_CONNECTED_WRONG_STATE": "不正な状態でデバイスが検出されました", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Trezorはバックアップ処理中に切断されました。 デバイスの設定で工場出荷時の状態にリセットするオプションを使用してデバイスを消去し、ウォレットのバックアッププロセスを再開することを強くお勧めします。", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "ファームウェアのハッシュチェックに失敗しました。あなたのTrezorは偽造品かもしれません。", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "ファームウェアのハッシュチェックが実行できませんでした。あなたのTrezorは偽造品かもしれません。", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "オフにする", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "オンにする", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "ファームウェアのリビジョンチェックは重要なセキュリティ機能です。オンにしておくことを強く推奨します。", @@ -774,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "デバイス \"{deviceLabel}\" はバックアップされていません", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "デバイス \"{deviceLabel}\" は接続されていません", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "デバイス \"{deviceLabel}\" は利用できません", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "デバイスの検証に失敗しました", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "デバイスを再接続して、もう一度検証してください。 問題が解決しない場合は、Trezor Support に連絡して、デバイスで何が起こっているのか、次に何をすべきかを確認してください。", "TR_DEVICE_NOT_CONNECTED": "デバイスが接続されていません", "TR_DEVICE_NOT_INITIALIZED": "Trezorがセットアップされていません", "TR_DEVICE_NOT_INITIALIZED_TEXT": "あなたがすぐに開始できるようにプロセスをガイドします。", @@ -836,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "最新の Bridge {version} をダウンロード", "TR_DO_NOT_DISCONNECT_DEVICE": "デバイスを取り外さないでください", "TR_DO_NOT_SHOW_AGAIN": "今後表示しない", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "このステップをスキップしますか?", "TR_DROPBOX": "ドロップボックス", "TR_DROPZONE": "ここにファイルをドラッグ&ドロップするか、クリックしてファイルを選択します", "TR_DROPZONE_ERROR": "インポートに失敗しました: {error}", @@ -857,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "参加", "TR_EARLY_ACCESS_ENABLED": "早期アクセスプログラムが有効になりました", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "参加する", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "これにより、Suiteの通常の動作に影響を与えるエラーが含まれている可能性があるプレリリースソフトウェアをテストすることができることを理解しています。", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "これにより、Trezor Suiteの通常の動作に影響を与えるエラーが含まれている可能性があるプレリリースソフトウェアをテストすることができることを理解しています。", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "いつでもオフにできます。", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "一般に公開する前に最新の製品機能をお試しください。", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "上記のフィールドを最初に確認してください", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "ベータ版のアップデートを今すぐ確認するか、次回の起動時に確認できます。", "TR_EARLY_ACCESS_JOINED_TITLE": "早期アクセスプログラムが有効になっています", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "最新の安定版Suiteにダウングレードするには、 \"安定版をダウンロード\" をクリックしてアプリを再インストールしてください。", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "最新の安定版Trezor Suiteにダウングレードするには、 \"安定版をダウンロード\" をクリックしてアプリを再インストールしてください。", "TR_EARLY_ACCESS_LEFT_TITLE": "あなたは早期アクセスプログラムを終了しました。以降ベータリリースは提供されません。", "TR_EARLY_ACCESS_MENU": "早期アクセスプログラム", "TR_EARLY_ACCESS_REINSTALL": "安定版をダウンロード", @@ -918,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "このスワップに必要な正確な金額のみを承認します。 同様のスワップを再度行う場合は、追加の手数料を支払う必要があります。", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "以前の承認を取り消す", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "{provider} との以前のコントラクトの承認を削除する取引を実行します。", - "TR_EXCHANGE_BUY": "対象", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Trezorで確認して送信する", "TR_EXCHANGE_CONFIRM_SEND_STEP": "確認して送信", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "承認を作成", @@ -940,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "取引が成功しました。", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "承認済", "TR_EXCHANGE_DEX": "分散型取引所(DEX)のオファー", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "このスワップを実行する手数料は、承認用(必要な場合)に {approvalFee} ({approvalFeeFiat}) 、スワップ用に {swapFee} ({swapFeeFiat}) と見積もられています。", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "取引手数料用の残高がありません。交換する数量を {symbol} {max} まで下げてください。", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} は無効です", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -951,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "固定レートの場合は取引の終了時にどのくらいの金額になるかを正確に表示します。一旦レートを選択すると、取引が完了するまで金額は変わりません。 表示される金額が保証されていますが、これらのレートは通常あまり寛大ではありません。つまり、最終的には得られるコインが少なめになります。", "TR_EXCHANGE_FLOAT": "変動レートのオファー", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "変動レートの場合、レートを選択した時点から取引が完了した時点までの市場の変動により、最終金額が若干変更される可能性があります。 一般的にこれらのレートは固定レートの場合より良いです。つまり、最終的にはより多くのコインを得ることができます。", - "TR_EXCHANGE_PROVIDER": "プロバイダー", "TR_EXCHANGE_RATE": "価格", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "受信アカウントは Suite 外です。", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "これはあなたのコインを受け取る特定の英数字のアドレスです。", @@ -978,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "数値を入力してください。", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "スリッページを入力してください。", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "スワップのオファー値", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "スリッページの概要", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "スリッページ許容値", - "TR_EXCHANGE_TRANS_ID": "取引 ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Suiteにないアカウント ({symbol}) を使用する", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "受信アドレス", - "TR_EXCHANGE_VIEW_DETAILS": "詳細を表示", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Trezor Suiteの自動アップデート", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suiteはバックグラウンドで最新バージョンを自動的にダウンロードし、アプリの再起動時にインストールします。 これにより、最新の機能とセキュリティパッチで常に最新の状態に保つことができます。アップデートは許可を必要とせずに行われます。", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNBスマートチェーン", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "BNBスマートチェーンのネットワークを有効にしまが、内部取引の履歴はありません。", "TR_EXPERIMENTAL_FEATURES": "実験的な機能", "TR_EXPERIMENTAL_FEATURES_ALLOW": "実験的な機能", "TR_EXPERIMENTAL_FEATURES_WARNING": "上級者向けです。自己責任で使用してください。これらの機能はテスト中であり、不安定な場合があり、長期的なサポートがない可能性があります。", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Dropboxのパスワードを移行する", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Dropboxに保存され、Trezorによって保護されたパスワードを取得するためのユーティリティで、Trezor Password ManagerのChrome拡張を使っているユーザー向けに設計されています。", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "このユーティリティーを使用して、Dropboxに保存され、Trezorによって保護されたパスワードを取得します。Trezor Password ManagerのChrome拡張を使っていた以前のユーザー向けに設計されています。", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflakeは、検閲されたウェブサイトやアプリにアクセスできるようにするシステムです。", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "制限を回避するために設計されたシステムである Tor Snowflake を使用して、検閲された Web サイトやアプリにアクセスします。", "TR_EXPORT_AS": "{as} としてエクスポート", "TR_EXPORT_FAIL": "エクスポートに失敗しました", "TR_EXPORT_TO_FILE": "ファイルにエクスポート", @@ -1032,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "新しいファームウェアが利用可能になりました。今すぐデバイスをアップデートしてください。", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "お使いのデバイスは既に最新のファームウェアに更新されています。必要に応じてファームウェアを再インストールすることができます。", "TR_FIRMWARE_REVISION_CHECK_FAILED": "ファームウェアのリビジョンチェックに失敗しました。このTrezorは偽造品かもしれません。", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "ファームウェアのリビジョンチェックを実行できませんでした。", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "完了しました", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Bitcoinの操作のみをサポートする軽量ファームウェア。", "TR_FIRMWARE_SUBHEADING_NONE": "お客様のTrezorは、ファームウェアを搭載していない状態で出荷されています。デバイスを安全に使用するには、最新のファームウェアをインストールしてください。Bitcoinのみを使用する場合は、 をインストールすることをお勧めします。", @@ -1069,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "了解", "TR_GO_TO_ONBOARDING": "セットアップを開始", "TR_GO_TO_SETTINGS": "設定に移動", - "TR_GO_TO_SUITE": "Suite にアクセス", + "TR_GO_TO_SUITE": "Trezor Suite に移動", "TR_GRAPH_LINEAR": "リニア", "TR_GRAPH_LOGARITHMIC": "対数", "TR_GRAPH_MISSING_DATA": "XRP、SOLなど全てのトークンはポートフォリオの残高に含まれていますが、グラフ表示は現在サポートされていません。", @@ -1088,7 +1094,7 @@ "TR_GUIDE_FORUM": "Trezor フォーラム", "TR_GUIDE_FORUM_LABEL": "Trezorコミュニティと繋がる", "TR_GUIDE_SUGGESTION_LABEL": "このアプリはいかがですか?", - "TR_GUIDE_SUPPORT": "サポートに問い合わせる", + "TR_GUIDE_SUPPORT": "Trezorサポートに問い合わせ", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "サポートとフィードバック", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "サポートとフィードバック", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "改善にご協力ください", @@ -1220,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "ローカルファイル システム", "TR_LOG": "アプリケーションのログ", "TR_LOGIN_PROCEED": "続行する", - "TR_LOG_DESCRIPTION": "ログには、Trezor Suite に関するすべての必要な技術情報が含まれています。Trezorサポートにコンタクトする際に必要となる場合があります。", + "TR_LOG_DESCRIPTION": "このログには、Trezor Suite に関する重要な技術情報が含まれており、Trezor サポートに連絡する際に必要となる場合があります。", "TR_LOOKING_FOR_COINJOIN_ROUND": "ラウンドを待っています", "TR_LOW_ANONYMITY_WARNING": "匿名性が低すぎます。 1 in 5 未満は匿名とはいえないので、それ以上を推奨します。", "TR_LTC_ADDRESS_INFO": "Litecoinはアドレスのフォーマットを変更しました。私たちのブログであなたのアドレスを変換する方法についての詳細をご覧ください。 {TR_LEARN_MORE}", @@ -1313,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimism", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1325,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "新規", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "新しいTrezor Bridgeが利用可能です。", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "新しいTrezorファームウェアが利用可能です!今すぐアップデートしてください。", "TR_NEXT_UP": "次へ", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (非推奨の文字 \"{char}\" を含む)", + "TR_NON_ASCII_CHARS": "{label} (非推奨の文字を含む)", "TR_NORMAL_ACCOUNTS": "デフォルトのアカウント", "TR_NORTH": "北", "TR_NOTHING_TO_ANONYMIZE": "匿名化するものがありません", @@ -1345,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "標準ウォレット", "TR_NO_SEARCH_RESULTS": "検索条件に該当する結果はありません", "TR_NO_SPENDABLE_UTXOS": "あなたのアカウントには、使用可能なUTXOがありません。", - "TR_NO_TRANSPORT": "ブラウザがデバイスと通信できません", + "TR_NO_TRANSPORT": "お使いのブラウザはデバイスと通信できません", "TR_NO_TRANSPORT_DESKTOP": "アプリがデバイスと通信できません", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} アカウント: {fiatValue}", "TR_N_MIN": "{n} 分", "TR_N_TRANSACTIONS": "{value} 取引", "TR_OFF": "OFF", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "選択した金額 {amount} は許容される最大金額 {max} を超えています。", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "選択した金額 {amount} は許容される最大金額 {max} を超えています。", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "選択した金額 {amount} は許容される最小金額 {min} を下回っています。", - "TR_OFFER_ERROR_MINIMUM_FIAT": "選択した金額 {amount} は許容される最小金額 {min} を下回っています。", "TR_OFFICIAL_LANGUAGES": "オフィシャル", "TR_OK": "OK", "TR_ON": "ON", @@ -1413,30 +1417,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "その他の入力と出力", "TR_OUTGOING": "送信", "TR_OUTPUTS": "アウトプット", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "このユーザーが販売を希望する {symbol} の最低額と最高額。", - "TR_P2P_GET_STARTED_INTRO": "{providerName} で取引を開始する必要があります。 注意深く以下の手順に従ってください。", - "TR_P2P_GET_STARTED_ITEM_1": "\" {providerName} に移動\" を選択すると、弊社パートナーのサイトに移動します。", - "TR_P2P_GET_STARTED_ITEM_3": "{providerName} がリリースアドレスを要求したら、こちらに戻って続行してください。", - "TR_P2P_GET_STARTED_ITEM_4": "もう少しで完了です! アドレスを表示、コピーして、 {providerName} の \"リリースアドレス\" 欄に貼り付けて、取引を確定してください。", - "TR_P2P_GO_TO_PROVIDER": "{providerName} に移動", - "TR_P2P_INFO": "{peerToPeer} (P2P)技術のおかげで、買い手側も売り手側もKYCの確認は必要ありません。すべての関係者は、安全な {multisigEscrow} によって詐欺から保護されています。", - "TR_P2P_MODAL_CONFIRM": "購入の準備完了", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "{provider} を使って、ピア・ツー・ピアで {cryptocurrency} を購入する", - "TR_P2P_MODAL_LEGAL_HEADER": "法的通知", - "TR_P2P_MODAL_SECURITY_HEADER": "Trezorでセキュリティを確保", - "TR_P2P_MODAL_TERMS_1": "あなたはピアツーピア(P2P)技術を使用して、選択した別の人からID認証なしで仮想通貨を購入するためにここにいます。 その他の理由でこのサイトに誘導された場合は、続行する前にサポートに問い合わせください。", - "TR_P2P_MODAL_TERMS_2": "仮想通貨取引は元に戻すことができず、返金できない場合があることを理解してください。 ですから、不正または偶発的な損失は回復できない場合があります。", - "TR_P2P_MODAL_TERMS_4": "Invityがこのサービスを提供していないことを理解してください。 {provider} の規約がサービスに適用されます。", - "TR_P2P_MODAL_TERMS_5": "ギャンブル、詐欺、あるいはInvity またはプロバイダーの利用規約、あるいは適用される規制に対する違反となるような目的のためにこの機能を使用していません。", - "TR_P2P_MODAL_TERMS_6": "仮想通貨が新たな金融ツールであり、法域によって規制が異なる場合があることを理解してください。 これにより、詐欺、盗難、または市場の不安定性のリスクが高まる可能性があります。", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Invityによって認証されたパートナー", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "{providerName} サイトで契約を作成してから起算して、この時間制限内に取引を完了する必要があります。", - "TR_P2P_PRICE": "1 {symbol} の価格", - "TR_P2P_PRICE_TOOLTIP": "このユーザが提供する {symbol} の価格。", - "TR_P2P_TITLE_NOT_AVAILABLE": "やあ、私は {providerName} を使っています!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "選択した金額 {amount} は許容される最大金額 {max} を超えています。", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "選択した金額 {amount} は許容される最小金額 {minimum} を下回っています。", "TR_PAGINATION_NEWER": "新しい", "TR_PAGINATION_OLDER": "古い", "TR_PASSPHRASE_CASE_SENSITIVE": "注意: パスフレーズは大文字と小文字を区別します。", @@ -1447,6 +1427,8 @@ "TR_PASSPHRASE_MISMATCH": "パスフレーズが一致しません", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "パスフレーズが一致しませんでした。安全のため最初からやり直して、正しく入力してください。", "TR_PASSPHRASE_MISMATCH_START_OVER": "最初からやり直す", + "TR_PASSPHRASE_NON_ASCII_CHARS": "ABCabc123スペース、または以下の特殊文字を使用することをお勧めします。", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "リストされていない特殊文字を使用すると、将来の互換性に問題を生じます", "TR_PASSPHRASE_TOO_LONG": "パスフレーズの長さが制限を超えています。", "TR_PASSPHRASE_WALLET": "パスフレーズウォレット #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "パスフレーズの仕組みを学ぶ", @@ -1488,12 +1470,13 @@ "TR_PIN_SUBHEADING": "強力なPINを使用することで、権限のない物理的なアクセスからTrezorを保護します。", "TR_PLAY_IT_SAFE": "安全に実行しましょう", "TR_PLEASE_ALLOW_YOUR_CAMERA": "QRコードをスキャンできるようにカメラを有効にしてください。", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "検証プロセスを続行するには、デバイスを接続してください。", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "検証プロセスを続行するには、Trezor を接続してください。", "TR_PLEASE_ENABLE_PASSPHRASE": "検証プロセスを続行するにはパスフレーズ機能を有効にしてください。", "TR_POLICY_ID_ADDRESS": "ポリシーID:", "TR_PRIMARY_FIAT": "法定通貨", "TR_PRIVATE": "プライベート", "TR_PRIVATE_DESCRIPTION": "{targetAnonymity} 以上の匿名性", + "TR_PROCEED_UNVERIFIED_ADDRESS": "アドレス未確認のまま進む", "TR_PROMO_BANNER_DASHBOARD": "最も便利な 仮想通貨を安全に管理するハードウェアウォレット", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "スキャンする前にTrezorで確認してください", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Trezor のディスプレイはハッキングできないので信頼できます。最初にTrezorのディスプレイで受信アドレスを確認してください。", @@ -1504,6 +1487,13 @@ "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "更新可能です ({newVersion})", "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "最新版です ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suiteは新しいアップデートをダウンロードしました。", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suiteがアップデートされました。", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Trezor Suiteのアップデートが利用可能です", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "再起動してアップデート", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "アップデートを開始", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Trezorのアップデートが利用可能です", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "新着情報", "TR_RANDOM_SEED_WORDS_DISCLAIMER": " ウォレットのバックアップの一部ではない単語を入力するように求められることがあります。", "TR_RANGE": "範囲", "TR_READ_AND_UNDERSTOOD": "私は上記を読んで理解しました", @@ -1555,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "安全性チェック", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "通知", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "鍵の不一致や極端な手数料の許可などの安全でない可能性のあるアクションはTrezorで手動で承認してください。", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "あなたが何をしているかを知っていない限り、これを変更しないでください!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "自分が何をしているか分かっている場合のみ、これを変更してください!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "厳格", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Trezorの完全なセキュリティ。", "TR_SCAN_QR_CODE": "QRコードを読み取る", @@ -1567,7 +1557,7 @@ "TR_SECURITY_CHECKPOINT_GOT_SEED": "ウォレットのバックアップはありますか?", "TR_SECURITY_CHECK_HOLOGRAM": "ホログラムやセキュリティーシールを含むデバイスのパッケージは時とともに変化していますのでご注意ください。パッケージの詳細はこちらでご確認いただけます。また、デバイスをTrezorショップ、あるいは信頼できる代理店から購入したことを確認してください。さもなければ、あなたが手にしているデバイスは偽造品である可能性があります。デバイスが純正品ではない疑いがある場合は、Trezorサポートにお問い合わせください。", "TR_SECURITY_FEATURES_COMPLETED_N": "セキュリティ: ({n} / {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "シードレスモードに設定されたデバイスはTrezor Suiteにアクセスできません。 これは、誤った目的のために不適切にセットアップされたデバイスが使用された場合に備えて、不可逆的なコインの損失を避けるためです。", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "デバイスが不適切に使用された場合に起こり得る不可逆的なコインの損失を避けるため、シードレスモードに設定されたデバイスはTrezor Suiteにアクセスできません。", "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "シードレスセットアップはTrezor Suiteではサポートされていません", "TR_SEED_BACKUP_LENGTH": "あなたのウォレットのバックアップには12、18、または24個の単語が含まれています。", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "あなたのウォレットのバックアップには12、18、20、24、または33の単語が含まれています。", @@ -1578,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Trezorに表示される順序で、ウォレットのバックアップの単語を入力してください。 ", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "タッチスクリーンディスプレイを使用して、すべての単語を正しい順序で最後まで入力します。", "TR_SEE_DETAILS": "詳細を表示", + "TR_SEE_IF_ISSUE_PERSISTS": "問題が解決しない場合は確認してください。", "TR_SELECTED": "{amount} を選択済", "TR_SELECT_COIN_FOR_SETTINGS": "設定を変更するコインを選択", "TR_SELECT_DEVICE": "デバイスを選択", + "TR_SELECT_NAME_OR_ADDRESS": "名前、シンボル、ネットワークまたはコントラクトアドレスで検索", "TR_SELECT_NUMBER_OF_WORDS": "ウォレットのバックアップの単語数を選択してください", "TR_SELECT_PASSPHRASE_SOURCE": "\"{deviceLabel}\" のパスフレーズを入力する場所を選択します。", "TR_SELECT_RECOVERY_METHOD": "復元方法を選択", + "TR_SELECT_TOKEN": "トークンを選択", "TR_SELECT_TREZOR": "Trezorを選択", "TR_SELECT_TREZOR_TO_CONTINUE": "続けるにはTrezorを選択してください。", "TR_SELECT_TYPE": "タイプを選択", @@ -1614,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "{provider} で {cryptocurrency} を売却する", "TR_SELL_MODAL_LEGAL_HEADER": "法的通知", "TR_SELL_MODAL_SECURITY_HEADER": "Trezorでセキュリティを確保", - "TR_SELL_MODAL_TERMS_1": "あなたは仮想通貨を売却するためにここにいます。他の理由でこのサイトに誘導された場合は、続行する前にサポートにお問い合わせください。", + "TR_SELL_MODAL_TERMS_1": "あなたは仮想通貨を売却するためにここにいます。他の理由でこのサイトに誘導された場合は、続行する前にTrezorのサポートに問い合わせてください。", "TR_SELL_MODAL_TERMS_2": "あなたは自分のアカウントのために仮想通貨を売却しています。プロバイダのポリシーにより本人確認が必要な場合があることを認めます。", "TR_SELL_MODAL_TERMS_3": "仮想通貨取引は元に戻すことができず、返金できない場合があることを理解してください。 ですから、不正または偶発的な損失は回復できない場合があります。", "TR_SELL_MODAL_TERMS_4": "Invityがこのサービスを提供していないことを理解してください。 {provider} の規約がサービスに適用されます。", - "TR_SELL_MODAL_TERMS_5": "ギャンブル、詐欺、あるいはInvity またはプロバイダーの利用規約、あるいは適用される規制に対する違反となるような目的のためにこの機能を使用しないでください。", + "TR_SELL_MODAL_TERMS_5": "ギャンブル、詐欺、あるいはInvity またはプロバイダーの利用規約、あるいは適用される規制に対する違反となるような目的のためにこの機能を使用していません。", "TR_SELL_MODAL_TERMS_6": "仮想通貨が新たな金融ツールであり、法域によって規制が異なる場合があることを理解してください。 これにより、詐欺、盗難、または市場の不安定性のリスクが高まる可能性があります。", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Invityによって認証されたパートナー", "TR_SELL_REGISTER": "登録", @@ -1627,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "拒否されました", "TR_SELL_STATUS_PENDING": "保留中", "TR_SELL_STATUS_SUCCESS": "承認済", - "TR_SELL_TRANS_ID": "取引 ID:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "最大は {maximum} {currency} です", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "最小は {minimum} {currency} です", - "TR_SELL_VIEW_DETAILS": "詳細を表示", "TR_SENDFORM_LABELING_EXAMPLE_1": "貯蓄", "TR_SENDFORM_LABELING_EXAMPLE_2": "賃料", "TR_SENDING_SYMBOL": "{multiple, select, true {複数のトークン} false {{symbol}} other {{symbol}}}を送信中", @@ -1685,6 +1674,8 @@ "TR_SHOW_LOG": "ログを表示", "TR_SHOW_MORE": "もっと表示", "TR_SHOW_MORE_ADDRESSES": "さらに表示 ({count})", + "TR_SHOW_ON_TRAY": "トレイにアイコンを表示", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Trezor Suiteがバックグラウンドで実行されているかどうかを監視します。", "TR_SHOW_UNVERIFIED_ADDRESS": "未検証のアドレスを表示", "TR_SHOW_UNVERIFIED_XPUB": "未検証の公開鍵を表示", "TR_SIDEBAR_ADD_COIN": "コインを追加", @@ -1697,7 +1688,9 @@ "TR_SIZE": "サイズ", "TR_SKIP": "スキップ", "TR_SKIP_BACKUP": "バックアップをスキップ", + "TR_SKIP_BACKUP_DESCRIPTION": "ウォレットのバックアップがあれば、Trezorを紛失、盗難、破損した場合に資金を回復することができます。バックアップがなければ、仮想通貨へのアクセスを永久に失う可能性があります。", "TR_SKIP_PIN": "PINをスキップ", + "TR_SKIP_PIN_DESCRIPTION": "デバイスのPIN は、Trezorへの不正アクセスを防止します。これがなければ、デバイスを持っている人なら誰でもあなたの資金にアクセスすることができてしまいます。", "TR_SKIP_ROUNDS": "ラウンドをスキップ", "TR_SKIP_ROUNDS_DESCRIPTION": "ラウンドをスキップできるようにすることで、入力間の関係を証明することがより困難になります。 つまり、資金の起源をさらに難読化することができます。", "TR_SKIP_ROUNDS_HEADING": "Trezorがラウンドをスキップすることを許可する", @@ -1710,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "ステーキングプールに追加中", "TR_STAKE_ANY_AMOUNT_ETH": "少なくとも {amount} {symbol} をステークして、報酬を獲得しましょう。現在のAPYレートは {apyPercent}%で、報酬が得られます!", "TR_STAKE_APY": "年間利回り", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*年間利回り", "TR_STAKE_AVAILABLE": "利用可能です", "TR_STAKE_CAN_CLAIM_WARNING": "すでに {amount} {symbol}を請求することができます。 {br}請求するか、新しいステーク解除が処理されるまでお待ちください。", @@ -1719,13 +1713,16 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "ステーク解除期間に到達した時点で請求できます。", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "次のブロックで", "TR_STAKE_CLAIM_PENDING": "請求保留中", + "TR_STAKE_CLAIM_UNSTAKED": "ステークを解除した {symbol} を請求する", "TR_STAKE_CONFIRM_AND_STAKE": "確認してステークする", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "エントリー期間の確認", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "私は、Everstakeを利用してステーキングすることに同意します", "TR_STAKE_DAYS": "{count, plural, other {# 日}}", "TR_STAKE_DELEGATED": "ステークの委任", "TR_STAKE_DEREGISTERED": "ステークアドレスの登録解除", + "TR_STAKE_EARN_REWARDS_WEEKLY": "報酬を毎週獲得する", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "ステーキングプールに入るには、最大 {count, plural, other {# 日}}かかります。", + "TR_STAKE_ENTER_THE_STAKING_POOL": "ステーキングプールに入る", "TR_STAKE_ETH": "Ethereumをステークする", "TR_STAKE_ETH_CARD_TITLE": "{symbol} を稼ぐ最も簡単な方法", "TR_STAKE_ETH_EARN_REPEAT": "ステークする。報酬を得る。繰り返す。", @@ -1743,12 +1740,16 @@ "TR_STAKE_EVERSTAKE_MANAGES": "Everstakeは、スマートコントラクト、インフラストラクチャー、テクノロジーを駆使して、お客様のステークされた {symbol} を管理・保護します。", "TR_STAKE_INSTANT": "インスタント", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "あなたは {amount} {symbol} を \"インスタント \"で受け取りました。 {days, plural, =0 {} other { 残りは#日以内に支払われます}}.", + "TR_STAKE_IN_ACCOUNT": "アカウント中の {symbol} ", "TR_STAKE_LEARN_MORE": "もっと詳しく", + "TR_STAKE_LEAVE_STAKING_POOL": "ステーキングプールから出る", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "{amount} {symbol} を出金手数料のために残してあります。", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "引き出し手数料の支払いを可能にするために、少額の {symbol} を残してあります。", "TR_STAKE_MAX": "最大", "TR_STAKE_MAX_FEE_DESC": "最大手数料は、取引が処理されることを確実にするためにネットワーク上で支払う取引手数料です。", "TR_STAKE_MAX_REWARD_DAYS": "最大 {count, plural, other {# 日}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "ステークの最小金額は {amount} {symbol} です", + "TR_STAKE_MONTHLY": "毎月", "TR_STAKE_NEXT_PAYOUT": "次の報酬支払い", "TR_STAKE_NOT_ENOUGH_FUNDS": "ネットワーク手数料を支払うのに {symbol} が足りません", "TR_STAKE_ONLY_REWARDS": "報酬のみ", @@ -1760,6 +1761,8 @@ "TR_STAKE_REGISTERED": "ステークアドレスの登録", "TR_STAKE_RESTAKED_BADGE": "再ステーク中", "TR_STAKE_REWARDS": "報酬", + "TR_STAKE_SIGN_TRANSACTION": "取引に署名する", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "ステーク解除の取引に署名する", "TR_STAKE_STAKE": "ステーク", "TR_STAKE_STAKED_AMOUNT": "ステーク額", "TR_STAKE_STAKED_AND_EARNING": "ステーク額と獲得した報酬", @@ -1767,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "さらにステークする", "TR_STAKE_STAKING_IN_A_NUTSHELL": "ステーキングの概要", "TR_STAKE_STAKING_IS": "ステーキングでは、ブロックチェーンの運営をサポートするために一時的にEthereumの資産をロックします。その見返りとして、報酬のEthereumを得ることができます!", + "TR_STAKE_STAKING_PROCESS": "ステークの手順", "TR_STAKE_START_STAKING": "ステーキングを開始", "TR_STAKE_TIME_TO_CLAIM": "請求の時期", "TR_STAKE_TOTAL_PENDING": "保留中のステーク合計:", @@ -1775,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "ステークが解除され、請求の準備ができています", "TR_STAKE_UNSTAKE_TO_CLAIM": "請求のためステークを解除", "TR_STAKE_UNSTAKING": "ステークを解除中", + "TR_STAKE_UNSTAKING_APPROXIMATE": "ある程度の {symbol} は即座に可能です", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "ステーキングプールの流動性は、ある程度の資金の即時のステーク解除を可能にします。残りの資金はステーク解除期間に従います。", "TR_STAKE_UNSTAKING_PERIOD": "ステーク解除期間", + "TR_STAKE_UNSTAKING_PROCESS": "ステーク解除の手順", "TR_STAKE_UNSTAKING_TAKES": "現在、ステーキング解除には {count, plural, other {# 日}}かかります。完了後、資金で取引をしたり、送金できるようになります。", + "TR_STAKE_WEEKLY": "毎週", "TR_STAKE_WHAT_IS_STAKING": "ステーキングとは?", + "TR_STAKE_YEARLY": "毎年", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "ステークされた資金はEverstakeによって管理されます", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} が簡単にステークされました!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} が簡単にステーク解除されました!", + "TR_STAKING_CONSOLIDATING_FUNDS": "あなたの {symbol} を統合しています", "TR_STAKING_DELEGATE": "委任", "TR_STAKING_DEPOSIT": "払戻し可能な保証金", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "入金手数料は {feeAmount} ADAで、ステーキングを開始するにはアドレスの登録が必要です。Cardanoのステーキングを解除すると保証金は返金されます。", + "TR_STAKING_ESTIMATED_GAINS": "推定利益", "TR_STAKING_FEE": "手数料", + "TR_STAKING_GETTING_READY": "{symbol} は作業の準備中です", "TR_STAKING_INSTANTLY_STAKED": "あなたは {amount} {symbol} をインスタントでステークしました。 {days, plural, =0 {} other { 残りの {symbol} は # 日以内にステークされます。}}", "TR_STAKING_IS_NOT_SUPPORTED": "このネットワークでは、ステーキングはサポートされていません。", "TR_STAKING_NOT_ENOUGH_FUNDS": "アカウントに充分な資金がありません。", + "TR_STAKING_ONCE_YOU_CONFIRM": "確認", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Trezorステークプールにステークすることによって、TrezorとCardanoエコシステムをTrezor Suiteから直接サポートすることができます。", "TR_STAKING_ON_3RD_PARTY_TITLE": "あなたはサードパーティのステークプールに委任しています", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "委任しているステークプールが飽和状態です。 ステーク報酬を最大化するために、ステークを再委任してください", "TR_STAKING_POOL_OVERSATURATED_TITLE": "ステークプールが飽和状態です", "TR_STAKING_REDELEGATE": "再委任", "TR_STAKING_REWARDS": "得られる報酬", + "TR_STAKING_REWARDS_ARE_RESTAKED": "報酬は自動的に再ステークされます", "TR_STAKING_REWARDS_DESCRIPTION": "最初のステークの登録と委任の後、報酬を受取り始めるまでに最大20日かかることに注意してください。 この期間が完了すると、5日ごとに報酬を受取れます。", "TR_STAKING_REWARDS_TITLE": "Cardanoのステーキングは有効です", "TR_STAKING_STAKE_ADDRESS": "あなたのステークアドレス", @@ -1800,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Trezorステークプールにデリゲートすることができませんでした。", "TR_STAKING_TX_PENDING": "あなたの取引 {txid} は正常にブロックチェーンに送信され、承認を待っています。", "TR_STAKING_WITHDRAW": "出金", + "TR_STAKING_YOUR_EARNINGS": "収益は自動的に再ステークされ、 複利を得ることができます。", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "ステーク解除された {symbol} の準備ができました", + "TR_STAKING_YOU_ARE_HERE": "あなたはここにいます", "TR_STANDARD_WALLET_DESCRIPTION": "パスフレーズがありません", "TR_START": "開始", "TR_START_AGAIN": "再度開始", @@ -1808,6 +1827,7 @@ "TR_START_COINJOIN": "Coinjoinを開始", "TR_START_RECOVERY": "復元を開始", "TR_STEP": "ステップ {number}", + "TR_STEP_OF_TOTAL": "ステップ {index} / {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "まだTrezorが表示されていませんか?", "TR_STOP": "停止", "TR_STOPPING": "停止中", @@ -1859,7 +1879,11 @@ "TR_TOKENS_EMPTY": "まだトークンがありません...", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "トークンはありません。トークンは隠されている可能性があります。", "TR_TOKENS_SEARCH_TOOLTIP": "トークン、シンボルまたはコントラクトアドレスで検索します。", + "TR_TOKEN_NOT_FOUND": "トークンが見つかりません", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "{networkName} ネットワーク上にトークンが見つかりません。", "TR_TOKEN_TRANSFERS": "{standard} トークン転送", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "別の検索を試してください。", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "別の検索を試すか、別のネットワークに切り替えてください。", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "認識できないトークン", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "認識できないトークンは潜在的なリスクをもたらす可能性があります。注意してください。", "TR_TOO_LONG": "メッセージが長すぎます", @@ -1903,11 +1927,7 @@ "TR_TO_BTC": "BTCに", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "ラベルを永続化し、異なるデバイスで利用できるようにするには、クラウドストレージプロバイダに接続します。", "TR_TO_SATOSHIS": "satsに", - "TR_TRADE_BUYS": "購入", - "TR_TRADE_ENTER_COIN": "仮想通貨名またはシンボルを入力してください...", - "TR_TRADE_EXCHANGES": "取引所", "TR_TRADE_REDIRECTING": "リダイレクト中...", - "TR_TRADE_SELLS": "売却", "TR_TRANSACTIONS_NOT_AVAILABLE": "取引履歴が利用できません", "TR_TRANSACTIONS_SEARCH_TIP_1": "ヒント: 取引ID、アドレス、トークン、ラベル、金額、日付を検索できます。", "TR_TRANSACTIONS_SEARCH_TIP_10": "ヒント: AND (&) と OR (|) 演算子を組み合わせることで、より複雑な検索を行うことができます。 例えば、 > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 は{lastYear}年1月または{lastYear}年12月のすべての取引を表示します。", @@ -1922,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "取引ID、ラベル、または金額で検索するか、< > | & = != などの演算子を使用します。", "TR_TRANSACTION_DETAILS": "詳細", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "実行中のTrezorブリッジのバージョン {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "チュートリアルがキャンセルされました", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "チュートリアルが完了しました", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "短いチュートリアルでデバイスの使い方を学びましょう", @@ -1929,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Trezorを使用している可能性のある他のタブやウィンドウを閉じる", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "他のタブやウィンドウを閉じたら、このページをリフレッシュしてみてください。", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "他のブラウザのタブとウィンドウを閉じた後、Trezor Suiteを終了して再度開いてみてください。", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "通信を可能にするために必要なステップ", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "この問題を解決するには、以下の手順を試しください。", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Trezor Bridgeのステータスページ を確認してください", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Trezor Bridgeのプロセスが起動していることを確認してください", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "現在、クロームベースのブラウザだけがUSBデバイスとの直接通信を許可しています", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "現在、クロームベースのブラウザだけがUSBデバイスとの直接通信を許可しています。", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "クロームベースのブラウザを使用してください", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "ケーブルは完全に挿入する必要があります。 USB-Cで接続するデバイスの場合、ケーブルは適切に \"クリック\" する必要があります。", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "別のケーブルをお試しください", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Trezor Bridgeがインストールされている必要があります。", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "可能であれば、別のコンピュータを使用してみてください", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "念のために", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "コンピューターを再起動してみてください", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "コンピュータを再起動すると、ブラウザとデバイス間の通信の問題が解決する場合があります。", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "コンピューターを再起動してください", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Trezor Suite デスクトップアプリを実行", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Trezor Suiteデスクトップアプリを使用する", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "クリックして他のブリッジの実装に切り替えます。現在のバージョン: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "別のバージョンのTrezor Bridgeを使用する", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "udev rulesをインストールしてみてください。開く前にそれらをデスクトップに保存してください。", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "最後にデバイスのファームウェアを更新したのが2019年以前の場合は、ナレッジベースの指示に従ってください。", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Trezorの旧モデルをお使いのようです。", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "USBハブではなく、コンピュータに直接接続してください。", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "別のUSBポートを試してください", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "自動的にルールをインストール", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "udevルールがありません", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "予期しない状態: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "お使いのデバイスは正しく接続されていますが、ブラウザは現在それと通信できません。Trezor Bridgeをインストールする必要があります。", "TR_TRY_AGAIN": "もう一度試してください", "TR_TXID": "TX ID", "TR_TXID_RBF": "置換される前のTx ID", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "取引が確認されました", "TR_TX_CONFIRMING": "取引を確認中", + "TR_TX_DATA_FUNCTION": "機能", + "TR_TX_DATA_INPUT_DATA": "入力データ", + "TR_TX_DATA_METHOD": "入力データ", + "TR_TX_DATA_METHOD_NAME": "メソッド名", + "TR_TX_DATA_PARAMS": "パラメータ", "TR_TX_DEPOSIT": "入金", "TR_TX_FEE": "手数料", "TR_TX_TAB_AMOUNT": "金額", @@ -1994,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "ファームウェアの更新が必要です。後ほど設定のページでホーム画面を変更することができます。", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "ホーム画面を変更するにはファームウェアを更新してください", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "アップデートがあります", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "自動更新を有効にする", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "新規", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "アップデートして再起動", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "今すぐアップデートをインストールしますか?", "TR_UPDATE_MODAL_NOT_NOW": "今はしない", @@ -2001,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "ダウンロード", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "アップデートをダウンロードしました", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "終了時に更新する", + "TR_UPDATE_MODAL_WHATS_NEW": "新着情報", + "TR_UPDATE_MODAL_YOUR_VERSION": "現在のバージョン: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "このアカウントにアクセスするにはファームウェアをアップグレードしてください。上の青いバナーを参照してください。", "TR_UP_TO": "最大", "TR_UP_TO_DATE": "最新です", "TR_UP_TO_DAYS": "最大 {count, plural, other {# 日}}", @@ -2024,7 +2058,7 @@ "TR_VERIFY_TREZOR_OWNERSHIP_CARD_2": "ウォレットのバックアップの写真を撮ったり、デジタルコピーをしないでください", "TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION": "現在のウォレットのバックアップをTrezorに入力することで、このウォレットを所有していることを確認してください。", "TR_VERSION": "バージョン: {version}", - "TR_VERSION_HAS_BEEN_RELEASED": "バージョン {version} がリリースされました!", + "TR_VERSION_HAS_BEEN_RELEASED": "v{version} がリリースされました!", "TR_VIEW": "表示", "TR_VIEW_ACCOUNT": "アカウントを表示", "TR_VIEW_ALL": "すべて表示", @@ -2054,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "パスフレーズウォレットにアクセスする", "TR_WALLET_SELECTION_HIDDEN_WALLET": "隠しウォレット", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "新しいウォレットを作成するか、ウォレットのバックアップを使って復元します。", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "私たちはTrezorの体験を改善するために常に努力しています。新着情報です:", "TR_WEST": "西", "TR_WHAT_DATA_WE_COLLECT": "どのようなデータが収集されるのですか?", "TR_WHAT_IS_PASSPHRASE": "違いについて詳しく知る", @@ -2064,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "資金へのアクセスを回復するためには、ウォレットのバックアップが必要だと理解しています", "TR_WIPE_DEVICE_TEXT": "デバイスをリセットすると、そのデータはすべて削除されます。デバイスのリセットは、資金へのアクセスを回復するためのウォレットのバックアップがある場合にのみ行ってください。", "TR_WIPE_OR_UPDATE": "デバイスをリセットまたはファームウェアをアップデート", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "デバイスの設定に移動", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "デバイスの設定に移動します。", "TR_WIPING_YOUR_DEVICE": "ファクトリーリセットはデバイスメモリを消去し、ウォレットのバックアップとPINを含むすべての情報を削除します。 ファクトリーリセットは、資金へのアクセスの回復に必要なウォレットのバックアップがある場合にのみ行ってください。", "TR_WORDS": "{count} 単語", "TR_WORD_DOES_NOT_EXIST": "単語 \"{word}\" は BIP-39 の単語リストに存在しません。", diff --git a/packages/suite-data/files/translations/ko.json b/packages/suite-data/files/translations/ko.json index f29092526d5..73be3fb70be 100644 --- a/packages/suite-data/files/translations/ko.json +++ b/packages/suite-data/files/translations/ko.json @@ -198,9 +198,7 @@ "TR_BUG": "버그", "TR_BUMP_FEE": "범프 수수료", "TR_BUY": "구매", - "TR_BUY_ACCOUNT_TRANSACTIONS": "거래 트랜잭션", "TR_BUY_BUY": "구매하기", - "TR_BUY_BUY_AGAIN": "다시 구매하기", "TR_BUY_CONFIRMED_ON_TREZOR": "Trezor에서 확인됨", "TR_BUY_DETAIL_ERROR_BUTTON": "계정으로 돌아가기", "TR_BUY_DETAIL_ERROR_SUPPORT": "제공업체 지원팀으로 이동", diff --git a/packages/suite-data/files/translations/lol.json b/packages/suite-data/files/translations/lol.json index 7e11884f86a..0cc12a714f4 100644 --- a/packages/suite-data/files/translations/lol.json +++ b/packages/suite-data/files/translations/lol.json @@ -1,5 +1,6 @@ { "AMOUNT": "crwdns22287:0crwdne22287:0", + "AMOUNT_EXCEEDS_MAX": "crwdns36740:0{maxAmount}crwdne36740:0", "AMOUNT_IS_BELOW_DUST": "crwdns22291:0{dust}crwdne22291:0", "AMOUNT_IS_LESS_THAN_RESERVE": "crwdns22293:0{reserve}crwdne22293:0", "AMOUNT_IS_MORE_THAN_RESERVE": "crwdns22295:0{reserve}crwdne22295:0", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "crwdns22653:0crwdne22653:0", "TOAST_QR_INCORRECT_ADDRESS": "crwdns28940:0crwdne28940:0", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "crwdns28942:0{coin}crwdne28942:0", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "crwdns36742:0{scheme}crwdne36742:0", "TOAST_RAW_TX_SENT": "crwdns22655:0{txid}crwdne22655:0", "TOAST_SETTINGS_APPLIED": "crwdns22657:0crwdne22657:0", "TOAST_SIGN_MESSAGE_ERROR": "crwdns22659:0{error}crwdne22659:0", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "crwdns22687:0crwdne22687:0", "TR_404_TITLE": "crwdns22689:0crwdne22689:0", "TR_7D_CHANGE": "crwdns33426:0crwdne33426:0", - "TR_ABORT": "crwdns28944:0crwdne28944:0", "TR_ACCESS_HIDDEN_WALLET": "crwdns22691:0crwdne22691:0", "TR_ACCESS_STANDARD_WALLET": "crwdns22693:0crwdne22693:0", "TR_ACCOUNT_DETAILS_HEADER": "crwdns22695:0crwdne22695:0", @@ -260,6 +261,7 @@ "TR_ACQUIRE_DEVICE_TITLE": "crwdns22759:0crwdne22759:0", "TR_ACTIVATED_COINS": "crwdns35222:0crwdne35222:0", "TR_ACTIVE": "crwdns22765:0crwdne22765:0", + "TR_ADD": "crwdns36744:0crwdne36744:0", "TR_ADDRESSES": "crwdns35140:0crwdne35140:0", "TR_ADDRESSES_CHANGE": "crwdns35298:0crwdne35298:0", "TR_ADDRESSES_FRESH": "crwdns28383:0crwdne28383:0", @@ -297,6 +299,8 @@ "TR_ALLOW_ANALYTICS_DESCRIPTION": "crwdns22829:0crwdne22829:0", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "crwdns36442:0crwdne36442:0", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "crwdns36444:0crwdne36444:0", + "TR_ALL_NETWORKS": "crwdns36664:0{networkCount}crwdne36664:0", + "TR_ALL_NETWORKS_TOOLTIP": "crwdns36666:0{networkCount}crwdne36666:0", "TR_ALL_TRANSACTIONS": "crwdns22833:0crwdne22833:0", "TR_AMOUNT_SENT": "crwdns33169:0crwdne33169:0", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "crwdns32719:0crwdne32719:0", @@ -362,16 +366,14 @@ "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "crwdns35856:0crwdne35856:0", "TR_BRIDGE_NEEDED_DESCRIPTION": "crwdns35858:0crwdne35858:0", "TR_BRIDGE_REQUESTED_DESCRIPTION": "crwdns35860:0crwdne35860:0", + "TR_BRIDGE_TIP_AUTOSTART": "crwdns36746:0crwdne36746:0", "TR_BTC_UNITS": "crwdns29084:0crwdne29084:0", "TR_BUG": "crwdns22931:0crwdne22931:0", "TR_BUMP_FEE": "crwdns22933:0crwdne22933:0", "TR_BUMP_FEE_DISABLED_TOOLTIP": "crwdns36554:0crwdne36554:0", "TR_BUY": "crwdns22937:0crwdne22937:0", - "TR_BUY_ACCOUNT_TRANSACTIONS": "crwdns22939:0crwdne22939:0", "TR_BUY_BUY": "crwdns22943:0crwdne22943:0", - "TR_BUY_BUY_AGAIN": "crwdns22945:0crwdne22945:0", "TR_BUY_CONFIRMED_ON_TREZOR": "crwdns22949:0crwdne22949:0", - "TR_BUY_CONFIRM_ON_TREZOR": "crwdns36652:0crwdne36652:0", "TR_BUY_DETAIL_ERROR_BUTTON": "crwdns22953:0crwdne22953:0", "TR_BUY_DETAIL_ERROR_SUPPORT": "crwdns22955:0crwdne22955:0", "TR_BUY_DETAIL_ERROR_TEXT": "crwdns22957:0crwdne22957:0", @@ -415,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "crwdns23039:0crwdne23039:0", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "crwdns23041:0crwdne23041:0", "TR_BUY_STATUS_SUCCESS": "crwdns23043:0crwdne23043:0", - "TR_BUY_TRANS_ID": "crwdns23055:0crwdne23055:0", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "crwdns23061:0{maximum}crwdne23061:0", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "crwdns23063:0{maximum}crwdnd23063:0{currency}crwdne23063:0", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "crwdns23065:0{minimum}crwdne23065:0", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "crwdns23067:0{minimum}crwdnd23067:0{currency}crwdne23067:0", - "TR_BUY_VIEW_DETAILS": "crwdns23071:0crwdne23071:0", "TR_BYTES": "crwdns23073:0crwdne23073:0", "TR_CAMERA_NOT_RECOGNIZED": "crwdns23077:0crwdne23077:0", "TR_CAMERA_PERMISSION_DENIED": "crwdns23079:0crwdne23079:0", @@ -439,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "crwdns28930:0crwdne28930:0", "TR_CHAINED_TXS": "crwdns23083:0crwdne23083:0", "TR_CHANGELOG": "crwdns23087:0crwdne23087:0", - "TR_CHANGELOG_ON_GITHUB": "crwdns23089:0crwdne23089:0", "TR_CHANGE_ADDRESS_TOOLTIP": "crwdns32344:0crwdne32344:0", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "crwdns29092:0crwdne29092:0", "TR_CHANGE_HOMESCREEN": "crwdns23091:0crwdne23091:0", "TR_CHANGE_PIN": "crwdns23093:0crwdne23093:0", "TR_CHANGE_WIPE_CODE": "crwdns35210:0crwdne35210:0", + "TR_CHECKED_BALANCES_ON": "crwdns36748:0crwdne36748:0", "TR_CHECKING_YOUR_DEVICE": "crwdns33328:0crwdne33328:0", "TR_CHECKSUM_CONVERSION_INFO": "crwdns36158:0crwdne36158:0", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "crwdns33330:0crwdne33330:0", @@ -456,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "crwdns23105:0crwdne23105:0", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "crwdns33253:0crwdne33253:0", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "crwdns33255:0crwdne33255:0", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "crwdns33257:0crwdne33257:0", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "crwdns35494:0crwdne35494:0", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "crwdns36750:0crwdne36750:0", "TR_CHECK_SEED": "crwdns23111:0crwdne23111:0", "TR_CHECK_YOUR_DEVICE": "crwdns23115:0crwdne23115:0", "TR_CHOOSE_RECOVERY_TYPE": "crwdns23121:0crwdne23121:0", @@ -512,6 +511,7 @@ "TR_COINJOIN_TRANSACTION_BATCH": "crwdns32634:0crwdne32634:0", "TR_COINMARKET_BEST_RATE": "crwdns36028:0crwdne36028:0", "TR_COINMARKET_BUY_AND_SELL": "crwdns36556:0crwdne36556:0", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "crwdns36752:0totalBuys={totalBuys}crwdnd36752:0totalBuys={totalBuys}crwdnd36752:0totalBuys={totalBuys}crwdnd36752:0totalBuys={totalBuys}crwdnd36752:0totalSells={totalSells}crwdnd36752:0totalSells={totalSells}crwdnd36752:0totalSells={totalSells}crwdnd36752:0totalSells={totalSells}crwdne36752:0", "TR_COINMARKET_CEX_TOOLTIP": "crwdns36446:0crwdne36446:0", "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "crwdns36502:0crwdne36502:0", "TR_COINMARKET_COMPARE_OFFERS": "crwdns36032:0crwdne36032:0", @@ -560,12 +560,6 @@ "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "crwdns36078:0crwdne36078:0", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "crwdns36080:0crwdne36080:0", "TR_COINMARKET_NO_METHODS_AVAILABLE": "crwdns36160:0crwdne36160:0", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "crwdns28459:0crwdne28459:0", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "crwdns28461:0crwdne28461:0", - "TR_COINMARKET_NO_OFFERS_HEADER": "crwdns28463:0crwdne28463:0", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "crwdns28465:0crwdne28465:0", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "crwdns28467:0crwdne28467:0", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "crwdns28469:0crwdne28469:0", "TR_COINMARKET_OFFERS_EMPTY": "crwdns36082:0crwdne36082:0", "TR_COINMARKET_OFFERS_REFRESH": "crwdns36084:0crwdne36084:0", "TR_COINMARKET_OFFERS_SELECT": "crwdns36086:0crwdne36086:0", @@ -581,6 +575,7 @@ "TR_COINMARKET_SHOW_OFFERS": "crwdns36100:0crwdne36100:0", "TR_COINMARKET_SWAP": "crwdns36558:0crwdne36558:0", "TR_COINMARKET_SWAP_AMOUNT": "crwdns36560:0crwdne36560:0", + "TR_COINMARKET_SWAP_COUNTER": "crwdns36754:0totalSwaps={totalSwaps}crwdnd36754:0totalSwaps={totalSwaps}crwdnd36754:0totalSwaps={totalSwaps}crwdnd36754:0totalSwaps={totalSwaps}crwdne36754:0", "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "crwdns36562:0crwdne36562:0", "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "crwdns36564:0{fromCrypto}crwdnd36564:0{toCrypto}crwdnd36564:0{provider}crwdne36564:0", "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "crwdns36566:0crwdne36566:0", @@ -605,7 +600,9 @@ "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "crwdns36604:0crwdne36604:0", "TR_COINMARKET_TOKEN_NETWORK": "crwdns36102:0{tokenName}crwdnd36102:0{networkName}crwdne36102:0", "TR_COINMARKET_TRADE_FEE": "crwdns36104:0crwdne36104:0", + "TR_COINMARKET_TRANS_ID": "crwdns36756:0crwdne36756:0", "TR_COINMARKET_UNKNOWN_PROVIDER": "crwdns36162:0crwdne36162:0", + "TR_COINMARKET_VIEW_DETAILS": "crwdns36758:0crwdne36758:0", "TR_COINMARKET_YOUR_BEST_OFFER": "crwdns36106:0crwdne36106:0", "TR_COINMARKET_YOU_BUY": "crwdns36108:0crwdne36108:0", "TR_COINMARKET_YOU_GET": "crwdns36110:0crwdne36110:0", @@ -679,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "crwdns35832:0crwdne35832:0", "TR_COPY_AND_CLOSE": "crwdns28820:0crwdne28820:0", "TR_COPY_SIGNED_MESSAGE": "crwdns28822:0crwdne28822:0", - "TR_COPY_TO_CLIPBOARD_TX_ID": "crwdns23213:0crwdne23213:0", + "TR_COPY_TO_CLIPBOARD": "crwdns36810:0crwdne36810:0", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "crwdns23215:0crwdne23215:0", "TR_COULD_NOT_RETRIEVE_DATA": "crwdns23217:0crwdne23217:0", "TR_COUNT_WALLETS": "crwdns23221:0count={count}crwdnd23221:0count={count}crwdne23221:0", @@ -713,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "crwdns23261:0crwdne23261:0", "TR_DASHBOARD_DISCOVERY_ERROR": "crwdns23263:0crwdne23263:0", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "crwdns23267:0{details}crwdne23267:0", + "TR_DATA": "crwdns36684:0crwdne36684:0", "TR_DATABASE_UPGRADE_BLOCKED": "crwdns23271:0crwdne23271:0", "TR_DATA_ANALYTICS_CATEGORY_1": "crwdns23273:0crwdne23273:0", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "crwdns23275:0crwdne23275:0", @@ -766,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "crwdns28617:0crwdne28617:0", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "crwdns22389:0crwdne22389:0", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "crwdns28621:0crwdne28621:0", + "TR_DEVICE_CONNECTED_UNACQUIRED": "crwdns36760:0crwdne36760:0", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "crwdns36762:0{transportSessionOwner}crwdne36762:0", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "crwdns36764:0crwdne36764:0", "TR_DEVICE_CONNECTED_WRONG_STATE": "crwdns23305:0crwdne23305:0", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "crwdns23309:0crwdne23309:0", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "crwdns36688:0crwdne36688:0", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "crwdns36690:0crwdne36690:0", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "crwdns36122:0crwdne36122:0", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "crwdns36124:0crwdne36124:0", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "crwdns36126:0crwdne36126:0", @@ -786,6 +789,8 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "crwdns23329:0{deviceLabel}crwdne23329:0", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "crwdns23331:0{deviceLabel}crwdne23331:0", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "crwdns23333:0{deviceLabel}crwdne23333:0", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "crwdns36766:0crwdne36766:0", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "crwdns36768:0crwdne36768:0", "TR_DEVICE_NOT_CONNECTED": "crwdns23337:0crwdne23337:0", "TR_DEVICE_NOT_INITIALIZED": "crwdns23339:0crwdne23339:0", "TR_DEVICE_NOT_INITIALIZED_TEXT": "crwdns23341:0crwdne23341:0", @@ -848,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "crwdns23433:0{version}crwdne23433:0", "TR_DO_NOT_DISCONNECT_DEVICE": "crwdns32638:0crwdne32638:0", "TR_DO_NOT_SHOW_AGAIN": "crwdns35936:0crwdne35936:0", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "crwdns23437:0crwdne23437:0", "TR_DROPBOX": "crwdns23439:0crwdne23439:0", "TR_DROPZONE": "crwdns23441:0crwdne23441:0", "TR_DROPZONE_ERROR": "crwdns23443:0{error}crwdne23443:0", @@ -930,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "crwdns28537:0crwdne28537:0", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "crwdns28539:0crwdne28539:0", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "crwdns28541:0{provider}crwdne28541:0", - "TR_EXCHANGE_BUY": "crwdns23493:0crwdne23493:0", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "crwdns23501:0crwdne23501:0", "TR_EXCHANGE_CONFIRM_SEND_STEP": "crwdns23503:0crwdne23503:0", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "crwdns28543:0crwdne28543:0", @@ -952,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "crwdns23531:0crwdne23531:0", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "crwdns23533:0crwdne23533:0", "TR_EXCHANGE_DEX": "crwdns28547:0crwdne28547:0", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "crwdns28759:0{approvalFee}crwdnd28759:0{approvalFeeFiat}crwdnd28759:0{swapFee}crwdnd28759:0{swapFeeFiat}crwdne28759:0", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "crwdns28557:0{symbol}crwdnd28557:0{max}crwdne28557:0", "TR_EXCHANGE_EXTRA_FIELD": "crwdns23535:0{extraFieldName}crwdne23535:0", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "crwdns23537:0{extraFieldName}crwdne23537:0", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "crwdns23539:0{extraFieldDescription}crwdne23539:0", @@ -963,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "crwdns23551:0crwdne23551:0", "TR_EXCHANGE_FLOAT": "crwdns23553:0crwdne23553:0", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "crwdns23557:0crwdne23557:0", - "TR_EXCHANGE_PROVIDER": "crwdns23583:0crwdne23583:0", "TR_EXCHANGE_RATE": "crwdns23585:0crwdne23585:0", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "crwdns23591:0crwdne23591:0", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "crwdns23593:0crwdne23593:0", @@ -990,16 +990,9 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "crwdns28579:0crwdne28579:0", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "crwdns28581:0crwdne28581:0", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "crwdns28583:0crwdne28583:0", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "crwdns28585:0crwdne28585:0", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "crwdns28587:0crwdne28587:0", - "TR_EXCHANGE_TRANS_ID": "crwdns23637:0crwdne23637:0", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "crwdns23639:0{symbol}crwdne23639:0", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "crwdns23647:0crwdne23647:0", - "TR_EXCHANGE_VIEW_DETAILS": "crwdns23649:0crwdne23649:0", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "crwdns36478:0crwdne36478:0", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "crwdns36480:0crwdne36480:0", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "crwdns36148:0crwdne36148:0", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "crwdns36150:0crwdne36150:0", "TR_EXPERIMENTAL_FEATURES": "crwdns28675:0crwdne28675:0", "TR_EXPERIMENTAL_FEATURES_ALLOW": "crwdns35842:0crwdne35842:0", "TR_EXPERIMENTAL_FEATURES_WARNING": "crwdns35846:0crwdne35846:0", @@ -1326,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "crwdns24119:0crwdne24119:0", "TR_NETWORK_NAMECOIN": "crwdns24121:0crwdne24121:0", "TR_NETWORK_NEM": "crwdns24123:0crwdne24123:0", + "TR_NETWORK_OP": "crwdns36698:0crwdne36698:0", "TR_NETWORK_POLYGON": "crwdns35268:0crwdne35268:0", "TR_NETWORK_SOLANA_DEVNET": "crwdns35174:0crwdne35174:0", "TR_NETWORK_SOLANA_MAINNET": "crwdns35176:0crwdne35176:0", @@ -1338,9 +1332,10 @@ "TR_NETWORK_ZCASH": "crwdns24137:0crwdne24137:0", "TR_NEW_FEE": "crwdns24143:0crwdne24143:0", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "crwdns24145:0crwdne24145:0", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "crwdns24147:0crwdne24147:0", "TR_NEXT_UP": "crwdns35734:0crwdne35734:0", "TR_NONCE": "crwdns24149:0crwdne24149:0", + "TR_NON_ASCII_CHAR": "crwdns36812:0{label}crwdnd36812:0{char}crwdne36812:0", + "TR_NON_ASCII_CHARS": "crwdns36814:0{label}crwdne36814:0", "TR_NORMAL_ACCOUNTS": "crwdns32400:0crwdne32400:0", "TR_NORTH": "crwdns24151:0crwdne24151:0", "TR_NOTHING_TO_ANONYMIZE": "crwdns32769:0crwdne32769:0", @@ -1364,10 +1359,6 @@ "TR_N_MIN": "crwdns35736:0{n}crwdne35736:0", "TR_N_TRANSACTIONS": "crwdns24173:0value={value}crwdnd24173:0value={value}crwdne24173:0", "TR_OFF": "crwdns24175:0crwdne24175:0", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "crwdns24177:0{amount}crwdnd24177:0{max}crwdne24177:0", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "crwdns24179:0{amount}crwdnd24179:0{max}crwdne24179:0", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "crwdns24181:0{amount}crwdnd24181:0{min}crwdne24181:0", - "TR_OFFER_ERROR_MINIMUM_FIAT": "crwdns24183:0{amount}crwdnd24183:0{min}crwdne24183:0", "TR_OFFICIAL_LANGUAGES": "crwdns33305:0crwdne33305:0", "TR_OK": "crwdns32783:0crwdne32783:0", "TR_ON": "crwdns24189:0crwdne24189:0", @@ -1426,30 +1417,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "crwdns32644:0crwdne32644:0", "TR_OUTGOING": "crwdns24273:0crwdne24273:0", "TR_OUTPUTS": "crwdns24275:0crwdne24275:0", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "crwdns32412:0{symbol}crwdne32412:0", - "TR_P2P_GET_STARTED_INTRO": "crwdns32426:0{providerName}crwdne32426:0", - "TR_P2P_GET_STARTED_ITEM_1": "crwdns32428:0{providerName}crwdne32428:0", - "TR_P2P_GET_STARTED_ITEM_3": "crwdns32432:0{providerName}crwdne32432:0", - "TR_P2P_GET_STARTED_ITEM_4": "crwdns32434:0{providerName}crwdne32434:0", - "TR_P2P_GO_TO_PROVIDER": "crwdns32440:0{providerName}crwdne32440:0", - "TR_P2P_INFO": "crwdns32444:0{peerToPeer}crwdnd32444:0{multisigEscrow}crwdne32444:0", - "TR_P2P_MODAL_CONFIRM": "crwdns33059:0crwdne33059:0", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "crwdns33061:0{cryptocurrency}crwdnd33061:0{provider}crwdne33061:0", - "TR_P2P_MODAL_LEGAL_HEADER": "crwdns33063:0crwdne33063:0", - "TR_P2P_MODAL_SECURITY_HEADER": "crwdns33065:0crwdne33065:0", - "TR_P2P_MODAL_TERMS_1": "crwdns33067:0crwdne33067:0", - "TR_P2P_MODAL_TERMS_2": "crwdns33069:0crwdne33069:0", - "TR_P2P_MODAL_TERMS_4": "crwdns33071:0{provider}crwdne33071:0", - "TR_P2P_MODAL_TERMS_5": "crwdns33073:0crwdne33073:0", - "TR_P2P_MODAL_TERMS_6": "crwdns33075:0crwdne33075:0", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "crwdns33077:0crwdne33077:0", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "crwdns32474:0{providerName}crwdne32474:0", - "TR_P2P_PRICE": "crwdns32476:0{symbol}crwdne32476:0", - "TR_P2P_PRICE_TOOLTIP": "crwdns32478:0{symbol}crwdne32478:0", - "TR_P2P_TITLE_NOT_AVAILABLE": "crwdns32500:0{providerName}crwdne32500:0", - "TR_P2P_USER_REPUTATION": "crwdns32506:0{rating}crwdnd32506:0{numberOfTrades}crwdne32506:0", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "crwdns32512:0{amount}crwdnd32512:0{maximum}crwdne32512:0", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "crwdns32514:0{amount}crwdnd32514:0{minimum}crwdne32514:0", "TR_PAGINATION_NEWER": "crwdns24279:0crwdne24279:0", "TR_PAGINATION_OLDER": "crwdns24281:0crwdne24281:0", "TR_PASSPHRASE_CASE_SENSITIVE": "crwdns28423:0crwdne28423:0", @@ -1460,6 +1427,8 @@ "TR_PASSPHRASE_MISMATCH": "crwdns35810:0crwdne35810:0", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "crwdns35812:0crwdne35812:0", "TR_PASSPHRASE_MISMATCH_START_OVER": "crwdns35814:0crwdne35814:0", + "TR_PASSPHRASE_NON_ASCII_CHARS": "crwdns36816:0crwdne36816:0", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "crwdns36818:0crwdne36818:0", "TR_PASSPHRASE_TOO_LONG": "crwdns24297:0crwdne24297:0", "TR_PASSPHRASE_WALLET": "crwdns24299:0#{id}crwdne24299:0", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "crwdns35598:0crwdne35598:0", @@ -1509,7 +1478,6 @@ "TR_PRIVATE_DESCRIPTION": "crwdns32709:0{targetAnonymity}crwdne32709:0", "TR_PROCEED_UNVERIFIED_ADDRESS": "crwdns36632:0crwdne36632:0", "TR_PROMO_BANNER_DASHBOARD": "crwdns35802:0crwdne35802:0", - "TR_QR_CODE": "crwdns36654:0crwdne36654:0", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "crwdns35154:0crwdne35154:0", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "crwdns35216:0crwdne35216:0", "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "crwdns36526:0crwdne36526:0", @@ -1519,6 +1487,13 @@ "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "crwdns36534:0crwdne36534:0", "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "crwdns36536:0{newVersion}crwdne36536:0", "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "crwdns36538:0{currentVersion}crwdne36538:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "crwdns36700:0crwdne36700:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "crwdns36702:0crwdne36702:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "crwdns36704:0crwdne36704:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "crwdns36706:0crwdne36706:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "crwdns36708:0crwdne36708:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "crwdns36710:0crwdne36710:0", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "crwdns36712:0crwdne36712:0", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "crwdns24395:0crwdne24395:0", "TR_RANGE": "crwdns24397:0crwdne24397:0", "TR_READ_AND_UNDERSTOOD": "crwdns33446:0crwdne33446:0", @@ -1593,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "crwdns24265:0crwdne24265:0", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "crwdns32913:0crwdne32913:0", "TR_SEE_DETAILS": "crwdns24539:0crwdne24539:0", + "TR_SEE_IF_ISSUE_PERSISTS": "crwdns36770:0crwdne36770:0", "TR_SELECTED": "crwdns32340:0{amount}crwdne32340:0", "TR_SELECT_COIN_FOR_SETTINGS": "crwdns28781:0crwdne28781:0", "TR_SELECT_DEVICE": "crwdns24545:0crwdne24545:0", + "TR_SELECT_NAME_OR_ADDRESS": "crwdns36668:0crwdne36668:0", "TR_SELECT_NUMBER_OF_WORDS": "crwdns24551:0crwdne24551:0", "TR_SELECT_PASSPHRASE_SOURCE": "crwdns24553:0{deviceLabel}crwdne24553:0", "TR_SELECT_RECOVERY_METHOD": "crwdns24555:0crwdne24555:0", + "TR_SELECT_TOKEN": "crwdns36670:0crwdne36670:0", "TR_SELECT_TREZOR": "crwdns35822:0crwdne35822:0", "TR_SELECT_TREZOR_TO_CONTINUE": "crwdns35824:0crwdne35824:0", "TR_SELECT_TYPE": "crwdns35272:0crwdne35272:0", @@ -1642,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "crwdns24639:0crwdne24639:0", "TR_SELL_STATUS_PENDING": "crwdns24641:0crwdne24641:0", "TR_SELL_STATUS_SUCCESS": "crwdns24643:0crwdne24643:0", - "TR_SELL_TRANS_ID": "crwdns24655:0crwdne24655:0", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "crwdns24661:0{maximum}crwdnd24661:0{currency}crwdne24661:0", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "crwdns24665:0{minimum}crwdnd24665:0{currency}crwdne24665:0", - "TR_SELL_VIEW_DETAILS": "crwdns24667:0crwdne24667:0", "TR_SENDFORM_LABELING_EXAMPLE_1": "crwdns32915:0crwdne32915:0", "TR_SENDFORM_LABELING_EXAMPLE_2": "crwdns32917:0crwdne32917:0", "TR_SENDING_SYMBOL": "crwdns28844:0multiple={multiple}crwdnd28844:0symbol={symbol}crwdnd28844:0symbol={symbol}crwdne28844:0", @@ -1700,6 +1674,8 @@ "TR_SHOW_LOG": "crwdns24717:0crwdne24717:0", "TR_SHOW_MORE": "crwdns24719:0crwdne24719:0", "TR_SHOW_MORE_ADDRESSES": "crwdns24721:0{count}crwdne24721:0", + "TR_SHOW_ON_TRAY": "crwdns36820:0crwdne36820:0", + "TR_SHOW_ON_TRAY_DESCRIPTION": "crwdns36822:0crwdne36822:0", "TR_SHOW_UNVERIFIED_ADDRESS": "crwdns24727:0crwdne24727:0", "TR_SHOW_UNVERIFIED_XPUB": "crwdns33225:0crwdne33225:0", "TR_SIDEBAR_ADD_COIN": "crwdns35280:0crwdne35280:0", @@ -1712,7 +1688,9 @@ "TR_SIZE": "crwdns24735:0crwdne24735:0", "TR_SKIP": "crwdns24737:0crwdne24737:0", "TR_SKIP_BACKUP": "crwdns24741:0crwdne24741:0", + "TR_SKIP_BACKUP_DESCRIPTION": "crwdns36716:0crwdne36716:0", "TR_SKIP_PIN": "crwdns24749:0crwdne24749:0", + "TR_SKIP_PIN_DESCRIPTION": "crwdns36718:0crwdne36718:0", "TR_SKIP_ROUNDS": "crwdns32536:0crwdne32536:0", "TR_SKIP_ROUNDS_DESCRIPTION": "crwdns32538:0crwdne32538:0", "TR_SKIP_ROUNDS_HEADING": "crwdns32540:0crwdne32540:0", @@ -1725,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "crwdns35308:0crwdne35308:0", "TR_STAKE_ANY_AMOUNT_ETH": "crwdns35310:0{amount}crwdnd35310:0{symbol}crwdnd35310:0{apyPercent}crwdne35310:0", "TR_STAKE_APY": "crwdns35312:0crwdne35312:0", + "TR_STAKE_APY_ABBR": "crwdns36772:0crwdne36772:0", "TR_STAKE_APY_DESC": "crwdns35314:0crwdne35314:0", "TR_STAKE_AVAILABLE": "crwdns35316:0crwdne35316:0", "TR_STAKE_CAN_CLAIM_WARNING": "crwdns35318:0{amount}crwdnd35318:0{symbol}crwdnd35318:0{br}crwdne35318:0", @@ -1765,10 +1744,12 @@ "TR_STAKE_LEARN_MORE": "crwdns35370:0crwdne35370:0", "TR_STAKE_LEAVE_STAKING_POOL": "crwdns36642:0crwdne36642:0", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "crwdns35372:0{amount}crwdnd35372:0{symbol}crwdne35372:0", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "crwdns36774:0{symbol}crwdne36774:0", "TR_STAKE_MAX": "crwdns35374:0crwdne35374:0", "TR_STAKE_MAX_FEE_DESC": "crwdns35376:0crwdne35376:0", "TR_STAKE_MAX_REWARD_DAYS": "crwdns35512:0count={count}crwdne35512:0", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "crwdns35378:0{amount}crwdnd35378:0{symbol}crwdne35378:0", + "TR_STAKE_MONTHLY": "crwdns36776:0crwdne36776:0", "TR_STAKE_NEXT_PAYOUT": "crwdns35380:0crwdne35380:0", "TR_STAKE_NOT_ENOUGH_FUNDS": "crwdns35382:0{symbol}crwdne35382:0", "TR_STAKE_ONLY_REWARDS": "crwdns35384:0crwdne35384:0", @@ -1798,26 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "crwdns35416:0crwdne35416:0", "TR_STAKE_UNSTAKE_TO_CLAIM": "crwdns35418:0crwdne35418:0", "TR_STAKE_UNSTAKING": "crwdns35420:0crwdne35420:0", + "TR_STAKE_UNSTAKING_APPROXIMATE": "crwdns36778:0{symbol}crwdne36778:0", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "crwdns36780:0crwdne36780:0", "TR_STAKE_UNSTAKING_PERIOD": "crwdns35422:0crwdne35422:0", "TR_STAKE_UNSTAKING_PROCESS": "crwdns36650:0crwdne36650:0", "TR_STAKE_UNSTAKING_TAKES": "crwdns35424:0count={count}crwdne35424:0", + "TR_STAKE_WEEKLY": "crwdns36782:0crwdne36782:0", "TR_STAKE_WHAT_IS_STAKING": "crwdns35428:0crwdne35428:0", + "TR_STAKE_YEARLY": "crwdns36784:0crwdne36784:0", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "crwdns35468:0crwdne35468:0", "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "crwdns36658:0{amount}crwdnd36658:0{symbol}crwdne36658:0", "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "crwdns36660:0{amount}crwdnd36660:0{symbol}crwdne36660:0", + "TR_STAKING_CONSOLIDATING_FUNDS": "crwdns36786:0{symbol}crwdne36786:0", "TR_STAKING_DELEGATE": "crwdns28858:0crwdne28858:0", "TR_STAKING_DEPOSIT": "crwdns28860:0crwdne28860:0", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "crwdns28862:0{feeAmount}crwdne28862:0", + "TR_STAKING_ESTIMATED_GAINS": "crwdns36788:0crwdne36788:0", "TR_STAKING_FEE": "crwdns28864:0crwdne28864:0", + "TR_STAKING_GETTING_READY": "crwdns36790:0{symbol}crwdne36790:0", "TR_STAKING_INSTANTLY_STAKED": "crwdns36012:0amount={amount}crwdnd36012:0symbol={symbol}crwdnd36012:0days={days}crwdnd36012:0symbol={symbol}crwdnd36012:0symbol={symbol}crwdne36012:0", "TR_STAKING_IS_NOT_SUPPORTED": "crwdns28902:0crwdne28902:0", "TR_STAKING_NOT_ENOUGH_FUNDS": "crwdns28866:0crwdne28866:0", + "TR_STAKING_ONCE_YOU_CONFIRM": "crwdns36792:0crwdne36792:0", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "crwdns28868:0crwdne28868:0", "TR_STAKING_ON_3RD_PARTY_TITLE": "crwdns28870:0crwdne28870:0", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "crwdns28872:0crwdne28872:0", "TR_STAKING_POOL_OVERSATURATED_TITLE": "crwdns28874:0crwdne28874:0", "TR_STAKING_REDELEGATE": "crwdns28876:0crwdne28876:0", "TR_STAKING_REWARDS": "crwdns28878:0crwdne28878:0", + "TR_STAKING_REWARDS_ARE_RESTAKED": "crwdns36794:0crwdne36794:0", "TR_STAKING_REWARDS_DESCRIPTION": "crwdns28880:0crwdne28880:0", "TR_STAKING_REWARDS_TITLE": "crwdns28882:0crwdne28882:0", "TR_STAKING_STAKE_ADDRESS": "crwdns28884:0crwdne28884:0", @@ -1826,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "crwdns28890:0crwdne28890:0", "TR_STAKING_TX_PENDING": "crwdns28892:0{txid}crwdne28892:0", "TR_STAKING_WITHDRAW": "crwdns28894:0crwdne28894:0", + "TR_STAKING_YOUR_EARNINGS": "crwdns36796:0crwdne36796:0", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "crwdns36798:0{symbol}crwdne36798:0", + "TR_STAKING_YOU_ARE_HERE": "crwdns36800:0crwdne36800:0", "TR_STANDARD_WALLET_DESCRIPTION": "crwdns24785:0crwdne24785:0", "TR_START": "crwdns24787:0crwdne24787:0", "TR_START_AGAIN": "crwdns24789:0crwdne24789:0", @@ -1834,6 +1827,7 @@ "TR_START_COINJOIN": "crwdns32953:0crwdne32953:0", "TR_START_RECOVERY": "crwdns24795:0crwdne24795:0", "TR_STEP": "crwdns32955:0{number}crwdne32955:0", + "TR_STEP_OF_TOTAL": "crwdns36672:0{index}crwdnd36672:0{total}crwdne36672:0", "TR_STILL_DONT_SEE_YOUR_TREZOR": "crwdns24801:0crwdne24801:0", "TR_STOP": "crwdns33249:0crwdne33249:0", "TR_STOPPING": "crwdns33267:0crwdne33267:0", @@ -1885,7 +1879,11 @@ "TR_TOKENS_EMPTY": "crwdns24935:0crwdne24935:0", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "crwdns35772:0crwdne35772:0", "TR_TOKENS_SEARCH_TOOLTIP": "crwdns35826:0crwdne35826:0", + "TR_TOKEN_NOT_FOUND": "crwdns36674:0crwdne36674:0", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "crwdns36676:0{networkName}crwdne36676:0", "TR_TOKEN_TRANSFERS": "crwdns33183:0{standard}crwdne33183:0", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "crwdns36678:0crwdne36678:0", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "crwdns36680:0crwdne36680:0", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "crwdns35284:0crwdne35284:0", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "crwdns35286:0crwdne35286:0", "TR_TOO_LONG": "crwdns28739:0crwdne28739:0", @@ -1929,11 +1927,7 @@ "TR_TO_BTC": "crwdns29088:0crwdne29088:0", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "crwdns24975:0crwdne24975:0", "TR_TO_SATOSHIS": "crwdns29090:0crwdne29090:0", - "TR_TRADE_BUYS": "crwdns24979:0crwdne24979:0", - "TR_TRADE_ENTER_COIN": "crwdns36488:0crwdne36488:0", - "TR_TRADE_EXCHANGES": "crwdns24981:0crwdne24981:0", "TR_TRADE_REDIRECTING": "crwdns24985:0crwdne24985:0", - "TR_TRADE_SELLS": "crwdns24989:0crwdne24989:0", "TR_TRANSACTIONS_NOT_AVAILABLE": "crwdns24995:0crwdne24995:0", "TR_TRANSACTIONS_SEARCH_TIP_1": "crwdns24997:0crwdne24997:0", "TR_TRANSACTIONS_SEARCH_TIP_10": "crwdns24999:0{lastYear}crwdnd24999:0{lastYear}crwdnd24999:0{lastYear}crwdnd24999:0{lastYear}crwdnd24999:0{lastYear}crwdne24999:0", @@ -1948,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "crwdns25017:0crwdne25017:0", "TR_TRANSACTION_DETAILS": "crwdns25019:0crwdne25019:0", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "crwdns35874:0{version}crwdne35874:0", + "TR_TREZOR_CONNECT": "crwdns36824:0crwdne36824:0", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "crwdns33269:0crwdne33269:0", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "crwdns33271:0crwdne33271:0", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "crwdns33273:0crwdne33273:0", @@ -1968,19 +1963,27 @@ "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "crwdns25057:0crwdne25057:0", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "crwdns35882:0crwdne35882:0", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "crwdns35884:0crwdne35884:0", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "crwdns36802:0{currentVersion}crwdne36802:0", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "crwdns36804:0crwdne36804:0", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "crwdns25059:0crwdne25059:0", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "crwdns36806:0crwdne36806:0", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "crwdns36808:0crwdne36808:0", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "crwdns25061:0crwdne25061:0", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "crwdns25063:0crwdne25063:0", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "crwdns28375:0crwdne28375:0", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "crwdns25065:0crwdne25065:0", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "crwdns25067:0{error}crwdne25067:0", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "crwdns25069:0crwdne25069:0", "TR_TRY_AGAIN": "crwdns25071:0crwdne25071:0", "TR_TXID": "crwdns25079:0crwdne25079:0", "TR_TXID_RBF": "crwdns35218:0crwdne35218:0", "TR_TX_CONFIRMATIONS": "crwdns25081:0{confirmationsCount}crwdne25081:0", "TR_TX_CONFIRMED": "crwdns35430:0crwdne35430:0", "TR_TX_CONFIRMING": "crwdns35446:0crwdne35446:0", + "TR_TX_DATA_FUNCTION": "crwdns36720:0crwdne36720:0", + "TR_TX_DATA_INPUT_DATA": "crwdns36722:0crwdne36722:0", + "TR_TX_DATA_METHOD": "crwdns36724:0crwdne36724:0", + "TR_TX_DATA_METHOD_NAME": "crwdns36726:0crwdne36726:0", + "TR_TX_DATA_PARAMS": "crwdns36728:0crwdne36728:0", "TR_TX_DEPOSIT": "crwdns29068:0crwdne29068:0", "TR_TX_FEE": "crwdns25085:0crwdne25085:0", "TR_TX_TAB_AMOUNT": "crwdns25089:0crwdne25089:0", @@ -2020,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "crwdns33191:0crwdne33191:0", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "crwdns33167:0crwdne33167:0", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "crwdns25133:0crwdne25133:0", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "crwdns36730:0crwdne36730:0", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "crwdns36732:0crwdne36732:0", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "crwdns25137:0crwdne25137:0", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "crwdns25141:0crwdne25141:0", "TR_UPDATE_MODAL_NOT_NOW": "crwdns28445:0crwdne28445:0", @@ -2027,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "crwdns25147:0crwdne25147:0", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "crwdns25149:0crwdne25149:0", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "crwdns32316:0crwdne32316:0", + "TR_UPDATE_MODAL_WHATS_NEW": "crwdns36734:0crwdne36734:0", + "TR_UPDATE_MODAL_YOUR_VERSION": "crwdns36736:0{version}crwdne36736:0", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "crwdns36662:0crwdne36662:0", "TR_UP_TO": "crwdns35432:0crwdne35432:0", "TR_UP_TO_DATE": "crwdns25153:0crwdne25153:0", "TR_UP_TO_DAYS": "crwdns35434:0count={count}crwdne35434:0", @@ -2080,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "crwdns25205:0crwdne25205:0", "TR_WALLET_SELECTION_HIDDEN_WALLET": "crwdns25209:0crwdne25209:0", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "crwdns25221:0crwdne25221:0", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "crwdns36738:0crwdne36738:0", "TR_WEST": "crwdns25223:0crwdne25223:0", "TR_WHAT_DATA_WE_COLLECT": "crwdns25229:0crwdne25229:0", "TR_WHAT_IS_PASSPHRASE": "crwdns25233:0crwdne25233:0", diff --git a/packages/suite-data/files/translations/nl.json b/packages/suite-data/files/translations/nl.json index ef2c077d137..db48509c4e6 100644 --- a/packages/suite-data/files/translations/nl.json +++ b/packages/suite-data/files/translations/nl.json @@ -188,7 +188,6 @@ "TR_404_GO_TO_DASHBOARD": "Ga nu naar Dashboard", "TR_404_TITLE": "Fout 404", "TR_7D_CHANGE": "7d verandering", - "TR_ABORT": "Breek af", "TR_ACCESS_HIDDEN_WALLET": "Toegang tot verborgen wallet", "TR_ACCESS_STANDARD_WALLET": "Toegang tot standaard wallet", "TR_ACCOUNT_DETAILS_HEADER": "Accountgegevens", @@ -316,9 +315,7 @@ "TR_BUG": "Fout", "TR_BUMP_FEE": "Transactiekosten verhogen", "TR_BUY": "Kopen", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Rekening transacties", "TR_BUY_BUY": "kopen", - "TR_BUY_BUY_AGAIN": "Opnieuw kopen", "TR_BUY_CONFIRMED_ON_TREZOR": "Bevestigd op trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Terug naar account", "TR_BUY_DETAIL_ERROR_SUPPORT": "Open partner's ondersteuningssite", @@ -357,12 +354,10 @@ "TR_BUY_STATUS_PENDING": "Lopende", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Lopende", "TR_BUY_STATUS_SUCCESS": "Goedgekeurd", - "TR_BUY_TRANS_ID": "Transport-ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximum is {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum is {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum is {minimum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum is {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Details bekijken", "TR_BYTES": "bytes", "TR_CAMERA_NOT_RECOGNIZED": "De camera is niet herkend.", "TR_CAMERA_PERMISSION_DENIED": "Toegang tot de camera is geweigerd.", @@ -380,7 +375,6 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor-bedrag", "TR_CHAINED_TXS": "Ketting transacties", "TR_CHANGELOG": "Log van wijzigingen", - "TR_CHANGELOG_ON_GITHUB": "Overzicht aanpassingen op GitHub", "TR_CHANGE_HOMESCREEN": "Overzicht-scherm wijzigen", "TR_CHANGE_PIN": "Wijzigen", "TR_CHANGE_WIPE_CODE": "Wijzig wiscode", @@ -407,8 +401,6 @@ "TR_COINJOIN_PHASE_0_MESSAGE": "Inputs verzamelen", "TR_COINJOIN_PHASE_2_MESSAGE": "Uitvoer registreren", "TR_COINJOIN_STEP_1_TITLE": "Voeg bitcoin toe", - "TR_COINMARKET_NO_OFFERS_HEADER": "Geen opties", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Vernieuw pagina", "TR_COINS": "Munten", "TR_COIN_SETTINGS": "Munt instellingen", "TR_COLOR_SCHEME": "Kleurenschema", @@ -445,7 +437,6 @@ "TR_CONTRACT_TRANSACTION": "Contracttransactie", "TR_CONVERT_TO_LOWERCASE": "Zet om naar kleine letters", "TR_COPY_AND_CLOSE": "Kopieer & sluit", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Kopiëren", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Kon de changelog niet ophalen", "TR_COULD_NOT_RETRIEVE_DATA": "Kon gegevens niet ophalen", "TR_COUNT_WALLETS": "{count} {count, plural, one {portemonnee} other {portemonnees}}", @@ -552,7 +543,6 @@ "TR_DOWNLOADING": "Downloaden", "TR_DOWNLOAD_LATEST_BRIDGE": "Download laatste bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Koppel je apparaat niet los", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Wilt u deze stap echt overslaan?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Sleep het bestand hierheen of klik om bestanden te selecteren", "TR_DROPZONE_ERROR": "Importeren mislukt {error}", @@ -587,7 +577,6 @@ "TR_EXCHANGE_APPROVAL_TO_SWAP_BUTTON": "Ga door met omwisselen", "TR_EXCHANGE_APPROVAL_VALUE": "Goedkeuringswaarde", "TR_EXCHANGE_APPROVAL_VALUE_INFINITE": "Oneindige waarde", - "TR_EXCHANGE_BUY": "Voor", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Bevestigen bij Trezor & Verzenden", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Bevestigen & Verzenden", "TR_EXCHANGE_CREATE_SUITE_ACCOUNT": "Maak een nieuw {symbol} account aan", @@ -615,7 +604,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Vast tarief biedt info", "TR_EXCHANGE_FLOAT": "Aanbieding zweeftarief", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Zweefsnelheid biedt info", - "TR_EXCHANGE_PROVIDER": "dienstverlener", "TR_EXCHANGE_RATE": "Wisselkoers", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Account ontvangen valt buiten het pak.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Dit is het specifieke alfanumerieke adres dat je munten zal ontvangen.", @@ -633,10 +621,8 @@ "TR_EXCHANGE_STATUS_SUCCESS": "Geslaagd", "TR_EXCHANGE_SWAP_SLIPPAGE_CUSTOM": "Aangepast", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Voer een nummer in.", - "TR_EXCHANGE_TRANS_ID": "Transport-ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Een {symbol} -account zonder suite gebruiken", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Adres ontvangen", - "TR_EXCHANGE_VIEW_DETAILS": "Details bekijken", "TR_EXPERIMENTAL_FEATURES": "Experimenteel", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Experimentele functies", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor snowflake", @@ -823,10 +809,6 @@ "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {account} other {accounts}} • {fiatValue}", "TR_N_TRANSACTIONS": "{value} {value, plural, one {Transactie} other {Transacties}}", "TR_OFF": "uit", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Het gekozen bedrag van {currency} {amount} is hoger dan het maximaal geaccepteerde bedrag van {currency} {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Het gekozen bedrag van {currency} {amount} is hoger dan het maximaal geaccepteerde bedrag van {currency} {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Het gekozen bedrag van {currency} {amount} is lager dan het minimaal geaccepteerde bedrag van {currency} {max}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Het gekozen bedrag van {currency} {amount} is lager dan het minimaal geaccepteerde bedrag van {currency} {max}.", "TR_ON": "aan", "TR_ONBOARDING_ADVANCED": "Geavanceerd", "TR_ONBOARDING_ALLOW_ANALYTICS": "Sta anoniem gegevens verzamelen toe", @@ -877,7 +859,6 @@ "TR_SELL_GO_TO_TRANSACTION": "Ga verder", "TR_SELL_STATUS_ERROR": "Geweigerd", "TR_SELL_STATUS_SUCCESS": "Goedgekeurd", - "TR_SELL_TRANS_ID": "Trans. ID:", "TR_SETTINGS": "Instellingen", "TR_SETTINGS_SAME_AS_SYSTEM": "Systeem", "TR_SETUP_WIPE_CODE": "Stel wiscode in", @@ -898,7 +879,6 @@ "TR_TOR_DISABLE": "Schakel Tor uit", "TR_TO_BTC": "Naar BTC", "TR_TO_SATOSHIS": "Naar sat", - "TR_TRADE_EXCHANGES": "exchanges", "TR_TRANSACTION_DETAILS": "Details", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Probeer een andere kabel", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Met Trezor Bridge geïnstalleerd.", diff --git a/packages/suite-data/files/translations/pl.json b/packages/suite-data/files/translations/pl.json index 5cbe4dadc8d..1dca8a43339 100644 --- a/packages/suite-data/files/translations/pl.json +++ b/packages/suite-data/files/translations/pl.json @@ -153,7 +153,6 @@ "TR_404_DESCRIPTION": "Kurza twarz! Przykro nam. Wygląda na to, że adres URL jest nieprawidłowy lub uszkodzony.", "TR_404_GO_TO_DASHBOARD": "Przejdź do pulpitu", "TR_404_TITLE": "Błąd 404: Nie znaleziono odnośnika", - "TR_ABORT": "Anuluj", "TR_ACCOUNT_DETAILS_HEADER": "Szczegóły konta", "TR_ACCOUNT_DETAILS_TYPE_HEADER": "Typ konta", "TR_ACCOUNT_DETAILS_XPUB": "Ostrożnie obchodź się ze swoim kluczem publicznym (xPUB). Gdy zostanie ujawniony, podmioty zewnętrzne uzyskają dostęp do całej Twojej historii transakcji.", @@ -200,8 +199,5 @@ "TR_NAV_ANONYMIZE": "Anonimizuj monety", "TR_NORMAL_ACCOUNTS": "Domyślne konta", "TR_NOT_PRIVATE": "Nieprywatne", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Minimalna i maksymalna kwota, za jaką użytkownik może sprzedać {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Należy zainicjować transakcję na {providerName} – zastosuj się do poniższych kroków.", - "TR_P2P_GET_STARTED_ITEM_1": "Wybierz “Przejdź do {providerName}”, aby przejść na stronę internetową naszego partnera.", "TR_UPDATE_MODAL_NOT_NOW": "Nie teraz" } diff --git a/packages/suite-data/files/translations/pt.json b/packages/suite-data/files/translations/pt.json index 40ff6688a06..49d61ee82ad 100644 --- a/packages/suite-data/files/translations/pt.json +++ b/packages/suite-data/files/translations/pt.json @@ -1,12 +1,13 @@ { "AMOUNT": "Valor", + "AMOUNT_EXCEEDS_MAX": "O valor excede o valor máximo permitido de {maxAmount}.", "AMOUNT_IS_BELOW_DUST": "O valor deve ser pelo menos {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "A conta receptora requer reserva mínima {reserve} XRP para ativar", "AMOUNT_IS_MORE_THAN_RESERVE": "O valor está acima da reserva inutilizável exigida ({reserve} XRP)", "AMOUNT_IS_NOT_ENOUGH": "Não há fundos suficientes", - "AMOUNT_IS_NOT_INTEGER": "A quantidade não é um número inteiro.", + "AMOUNT_IS_NOT_INTEGER": "O valor não é um número inteiro", "AMOUNT_IS_NOT_IN_RANGE_DECIMALS": "Máximo de {decimals} casas decimais permitido", - "AMOUNT_IS_NOT_SET": "Quantia não está definida", + "AMOUNT_IS_NOT_SET": "O valor não está definido", "AMOUNT_IS_TOO_LOW": "A quantidade é muito baixa.", "AMOUNT_NOT_ENOUGH_CURRENCY_FEE": "Não é suficiente {symbol} para cobrir a taxa de transação", "AMOUNT_SEND_MAX": "Enviar máx.", @@ -31,8 +32,8 @@ "DATA_NOT_VALID_HEX": "Hexadecimal inválido", "DESTINATION_TAG": "Tag de destino", "DESTINATION_TAG_IS_NOT_NUMBER": "A etiqueta de destino não é um número", - "DESTINATION_TAG_IS_NOT_VALID": "Etiqueta de destino inválida", - "DESTINATION_TAG_NOT_SET": "Etiqueta de destino não definida", + "DESTINATION_TAG_IS_NOT_VALID": "A etiqueta de destino é inválida", + "DESTINATION_TAG_NOT_SET": "A etiqueta de destino não está definida", "DESTINATION_TAG_TOOLTIP": "A etiqueta de destino é um código de identificação único que identifica o recebedor de uma transação.", "DISCONNECT_DEVICE_DESCRIPTION": "Seu dispositivo foi limpo e não tem mais chaves privadas.", "DOWNLOAD_TRANSACTION": "Baixar como .txt", @@ -66,8 +67,8 @@ "LOCKTIME_ADD": "Adicionar Locktime", "LOCKTIME_ADD_TOOLTIP": "O Locktime define quando uma transação pode ser minerada em um bloco.", "LOCKTIME_BLOCKHEIGHT": "Altura do bloco do Locktime", - "LOCKTIME_IS_NOT_INTEGER": "O Locktime não é um número inteiro", - "LOCKTIME_IS_NOT_SET": "Locktime não definido", + "LOCKTIME_IS_NOT_INTEGER": "O tempo de bloqueio não é um número inteiro", + "LOCKTIME_IS_NOT_SET": "O tempo de bloqueio não está definido", "LOCKTIME_IS_TOO_BIG": "O registro temporal é muito grande", "LOCKTIME_IS_TOO_LOW": "O Locktime é muito baixo", "LOCKTIME_SCHEDULE_SEND": "Tempo de bloqueio", @@ -124,7 +125,7 @@ "RECIPIENT_ADDRESS": "Endereço", "RECIPIENT_CANNOT_SEND_TO_MYSELF": "Não posso enviar para mim", "RECIPIENT_IS_NOT_SET": "O endereço não está definido", - "RECIPIENT_IS_NOT_VALID": "Endereço inválido", + "RECIPIENT_IS_NOT_VALID": "O endereço é inválido", "RECIPIENT_REQUIRES_UPDATE": "Taproot não é suportado pela sua versão de firmware. Por favor, atualize o firmware do seu dispositivo.", "RECIPIENT_SCAN": "Pesquisar", "REFRESH": "Atualizar", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN alterado com sucesso", "TOAST_QR_INCORRECT_ADDRESS": "O QR code contém um endereço inválido para esta conta", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "O QR code está definido para a conta {coin}", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Esquema de protocolo desconhecido: \"{scheme}\". Tente novamente ou insira o endereço manualmente.", "TOAST_RAW_TX_SENT": "Transação enviada. TXID: {txid}", "TOAST_SETTINGS_APPLIED": "Configurações alteradas com sucesso", "TOAST_SIGN_MESSAGE_ERROR": "Erro de assinatura da mensagem: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Ir para o Painel", "TR_404_TITLE": "Erro 404: Não encontrado", "TR_7D_CHANGE": "Alteração 7d", - "TR_ABORT": "Abortar", "TR_ACCESS_HIDDEN_WALLET": "Acessar carteira oculta", "TR_ACCESS_STANDARD_WALLET": "Acessar a carteira padrão", "TR_ACCOUNT_DETAILS_HEADER": "Detalhes da conta", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot é um novo tipo de endereço que pode melhorar a privacidade e a eficiência da rede. Alguns serviços ainda podem não suportar endereços Taproot.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "O método atual e mais amplamente aceito de gerar e gerenciar endereços da Cardano garante interoperabilidade, segurança e suporte para todos os tipos de tokens.", "TR_ACCOUNT_TYPE_COINJOIN": "CoinJoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Padrão", "TR_ACCOUNT_TYPE_IMPORTED": "Importado", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "As contas contábeis são compatíveis com os caminhos de derivação do Ledger Live, permitindo a migração tranquila da Ledger para a Trezor.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "As contas Legacy são compatíveis com os caminhos de derivação do Ledger Legacy, permitindo a migração tranquila das contas contábeis para a Trezor.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "O método atual e mais amplamente aceito de gerar e gerenciar endereços da {value} garante interoperabilidade, segurança e suporte para todos os tipos de tokens.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "O método atual e mais amplamente aceito de gerar e gerenciar endereços da Solana garante interoperabilidade, segurança e suporte tokens SOL e SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Não suportado.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Este tipo de conta não é suportado neste modelo Trezor.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Atualize o firmware do dispositivo para habilitar este tipo de conta.", + "TR_ACCOUNT_TYPE_XRP_DESC": "O XRP é uma moeda digital que permite pagamentos internacionais, rápidos e de baixo custo sem depender de mineração tradicional, usando um livro razão de consenso para confirmações rápidas de transação.", "TR_ACQUIRE_DEVICE": "Use seu Trezor aqui", "TR_ACQUIRE_DEVICE_TITLE": "Há outra sessão em andamento", "TR_ACTIVATED_COINS": "Moedas ativadas", "TR_ACTIVE": "ativo", + "TR_ADD": "Adicionar", "TR_ADDRESSES": "Endereço", "TR_ADDRESSES_CHANGE": "Alterar endereços", "TR_ADDRESSES_FRESH": "Endereços novos", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Copiar endereço", "TR_ADDRESS_MODAL_TITLE": "Endereço de recebimento {networkName}", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "Endereço de recebimento {networkCurrencyName} na rede {networkName}", - "TR_ADDRESS_PHISHING_WARNING": "Para evitar ataques de phishing, você deve verificar o endereço na sua Trezor. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Para evitar ataques de phishing, verifique o endereço de recebimento em seu Trezor. {claim}", "TR_ADD_ACCOUNT": "Adicionar conta", "TR_ADD_HIDDEN_WALLET": "Adicionar carteira oculta", "TR_ADD_NETWORK_ACCOUNT": "Adicionar conta {network}", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Utilização de dados", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Todos os dados são mantidos estritamente anônimos e são usados apenas para melhorar o ecossistema Trezor.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Atualizações automáticas do Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "O Trezor Suite baixa automaticamente a nova versão em segundo plano e a instala ao reiniciar o aplicativo. Isso garante que você esteja sempre atualizado com os recursos e patches de segurança mais recentes. As atualizações são feitas sem exigir sua permissão.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Baixe automaticamente a versão mais recente do Trezor Suite em segundo plano e instale-a ao reiniciar o aplicativo. Isso garante que você esteja sempre atualizado com os recursos e patches de segurança mais recentes. As atualizações são feitas sem exigir sua permissão.", + "TR_ALL_NETWORKS": "Todas as redes ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Visualizar tokens de todas as {networkCount} redes. Filtrar pelas redes mais populares.", "TR_ALL_TRANSACTIONS": "Transações", "TR_AMOUNT_SENT": "Quantia enviada", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Não adequado para CoinJoin - quantidade alta demais", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Embora improvável, pode ser necessário acessar o backup da sua carteira caso uma atualização de firmware dê problema.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Antes de alternar para {bitcoinOnly}, precisamos atualizar seu firmware para uma versão mais recente.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Entendo que estou prejudicando meu anonimato", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Entendo que estou comprometendo meu anonimato.", "TR_BRIDGE": "Trezor Bridge ", "TR_BRIDGE_DEV_MODE_START": "Iniciando Trezor Bridge na porta 21324", "TR_BRIDGE_DEV_MODE_STOP": "Iniciando Trezor Bridge na porta padrão", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Tem certeza? Seu dispositivo só pode ser usado por um aplicativo por vez. Se estiver utilizando outro aplicativo com o seu dispositivo Trezor, termine a outra sessão primeiro.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Seu navegador não é compatível. Para obter a melhor experiência, baixe e execute o aplicativo de desktop Trezor Suite em segundo plano ou use um navegador compatível com Chromium que aceite WebUSB.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Outro aplicativo solicitou que a Trezor Suite se conecte ao seu dispositivo Trezor. Mantenha o Trezor Suite em segundo plano e tente novamente em outro aplicativo.", + "TR_BRIDGE_TIP_AUTOSTART": "Dica: ativar o recurso de inicialização automática e ter o Bridge sempre rodando em segundo plano.", "TR_BTC_UNITS": "Unidades Bitcoin", "TR_BUG": "Bug", "TR_BUMP_FEE": "Aumentar taxa", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "Para acelerar suas transações, aumente a taxa na transação pendente mais antiga (por Nonce) na fila. As transações devem ser confirmadas na ordem. Saiba mais", "TR_BUY": "Comprar", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Transações de negociação", "TR_BUY_BUY": "Comprar", - "TR_BUY_BUY_AGAIN": "Comprar novamente", "TR_BUY_CONFIRMED_ON_TREZOR": "Confirmado no Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Voltar à conta", "TR_BUY_DETAIL_ERROR_SUPPORT": "Ir para o suporte do fornecedor", @@ -379,19 +390,19 @@ "TR_BUY_DETAIL_WAITING_FOR_USER_TEXT": "{providerName} precisa de alguns detalhes finais para concluir esta transação. Visite o site deles para prosseguir.", "TR_BUY_DETAIL_WAITING_FOR_USER_TITLE": "Complete sua transação", "TR_BUY_FOOTER_TEXT_1": "O Invity é uma ferramenta de comparação que liga você às melhores exchanges fornecedoras. Eles só usam a localização para mostrar as ofertas mais relevantes.", - "TR_BUY_FOOTER_TEXT_2": "O Invity não vê suas informações de pagamento ou de KYC; você só as compartilha com a exchange fornecedora se optar por concluir a transação.", + "TR_BUY_FOOTER_TEXT_2": "A Invity não vê suas informações de pagamento ou de KYC; você só as compartilha com a exchange fornecedora se optar por concluir a transação.", "TR_BUY_GO_TO_PAYMENT": "Finalizar transação", "TR_BUY_LEARN_MORE": "Saiba mais", "TR_BUY_MODAL_CONFIRM": "Estou pronto(a) para comprar", "TR_BUY_MODAL_FOR_YOUR_SAFETY": "Comprar {cryptocurrency} com {provider}", "TR_BUY_MODAL_LEGAL_HEADER": "Aviso legal", "TR_BUY_MODAL_SECURITY_HEADER": "A Trezor prioriza sua segurança", - "TR_BUY_MODAL_TERMS_1": "Você está aqui para comprar criptomoedas. Se você chegou a este site por qualquer outro motivo, contate o suporte {provider} antes de prosseguir.", - "TR_BUY_MODAL_TERMS_2": "Você está usando este recurso para comprar fundos que irão para uma conta sob seu controle pessoal direto.", - "TR_BUY_MODAL_TERMS_3": "Você entende que transações de criptomoeda são irreversíveis e não permitem reembolso. Assim, perdas por fraudes ou acidentes podem ser irrecuperáveis.", - "TR_BUY_MODAL_TERMS_4": "Você entende que a Invity não oferece este serviço. O serviço é regido pelos termos de {provider}.", - "TR_BUY_MODAL_TERMS_5": "Você não está usando esse recurso para realizar aposta, fraude ou outra violação dos Termos de Serviço da Invity ou do fornecedor ou de quaisquer regulamentos aplicáveis.", - "TR_BUY_MODAL_TERMS_6": "Você entende que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem variar entre jurisdições. Isso pode acarretar em um maior risco de fraude, roubo ou instabilidade de mercado para você.", + "TR_BUY_MODAL_TERMS_1": "Estou aqui para comprar criptomoedas. Se eu for direcionado a este site por qualquer outro motivo, entrarei em contato com o suporte da {provider} antes de prosseguir.", + "TR_BUY_MODAL_TERMS_2": "Estou usando esse recurso para comprar criptomoedas que serão enviadas para minha própria conta.", + "TR_BUY_MODAL_TERMS_3": "Compreendo que as transações de criptomoedas são finais e não podem ser revertidas ou reembolsadas. Perdas devido a fraudes ou erros podem não ser recuperáveis.", + "TR_BUY_MODAL_TERMS_4": "Compreendo que a Invity não oferece este serviço. Ela é regida pelos Termos e Condições do {provider}.", + "TR_BUY_MODAL_TERMS_5": "Não estou usando esse recurso para realizar aposta, fraude ou qualquer outra atividade que viole os Termos de Serviço da Invity ou do fornecedor ou de quaisquer leis aplicáveis.", + "TR_BUY_MODAL_TERMS_6": "Compreendo que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem ser diferentes a depender da região. Isso pode aumentar o risco de fraude, roubo ou instabilidade de mercado.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Parceiros verificados pela Invity", "TR_BUY_NETWORK": "Comprar {network}", "TR_BUY_NOT_TRANSACTIONS": "Nenhuma transação ainda.", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "Pendente", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Pendente", "TR_BUY_STATUS_SUCCESS": "Aprovado", - "TR_BUY_TRANS_ID": "ID. da trans:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "O máximo é {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "O máximo é {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "O mínimo é {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "O mínimo é {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Ver detalhes", "TR_BYTES": "bytes", "TR_CAMERA_NOT_RECOGNIZED": "A câmera não foi reconhecida.", "TR_CAMERA_PERMISSION_DENIED": "A permissão para acessar a câmera foi negada.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Valor na Trezor", "TR_CHAINED_TXS": "Transações em chain", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog no GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Este é um endereço de alteração criado a partir de um envio anterior.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Você pode alterar seu tipo de firmware em Configurações a qualquer momento.", "TR_CHANGE_HOMESCREEN": "Alterar tela inicial", "TR_CHANGE_PIN": "Alterar PIN", "TR_CHANGE_WIPE_CODE": "Alterar código de limpeza", + "TR_CHECKED_BALANCES_ON": "Saldos verificados em", "TR_CHECKING_YOUR_DEVICE": "Verificando seu dispositivo", "TR_CHECKSUM_CONVERSION_INFO": "Convertido para checksum. Saiba mais", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Verificaremos a integridade do seu dispositivo Trezor, garantindo sua segurança e confirmando a autenticidade do chip.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Realize uma recuperação simulada para verificar sua semente de recuperação.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Digite as palavras da sua semente de recuperação aqui na ordem exibida em seu dispositivo. Você pode precisar digitar algumas palavras que não fazem parte de sua semente de recuperação como uma medida de segurança adicional.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Use o teclado com dois botões para digitar sua semente de recuperação (backup de carteira). Ao fazer isso, você estará mantendo todas as suas informações confidenciais em segurança, longe de qualquer computador ou navegador da web com suspeito ou inseguro.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Sua semente de recuperação (backup de carteira) é inserida usando o touchscreen. Isso evita expor suas informações confidenciais a um computador ou navegador da web potencialmente inseguro.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Sua seed de recuperação (backup da carteira) foi inserida usando os botões da sua Trezor. Isso evita expor qualquer uma das suas informações confidenciais a um computador potencialmente inseguro ou navegador da web.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Seu backup de carteira é inserido usando o touchscreen. Isso evita expor suas informações confidenciais a um computador ou navegador da web potencialmente inseguro.", "TR_CHECK_SEED": "Verificar cópia de segurança", "TR_CHECK_YOUR_DEVICE": "Verifique a tela da sua Trezor", "TR_CHOOSE_RECOVERY_TYPE": "Escolha o tipo de recuperação", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Protegido pela sua Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Transação CoinJoin", "TR_COINMARKET_BEST_RATE": "Melhor taxa", + "TR_COINMARKET_BUY_AND_SELL": "Compra e venda", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} compra} one {{totalBuys} compra} other {{totalBuys} compras} } • {totalSells, plural, =0 {{totalSells} venda} one {{totalSells} venda} other {{totalSells} vendas} }", + "TR_COINMARKET_CEX_TOOLTIP": "Corretoras centralizadas", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Alterar valor ou moeda.", "TR_COINMARKET_COMPARE_OFFERS": "Compare todas as ofertas", "TR_COINMARKET_COUNTRY": "País de residência", + "TR_COINMARKET_DCA_DOWNLOAD": "Baixe o aplicativo móvel Invity para começar a economizar em Bitcoin", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Um plano de economia DCA seguro e simples.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Desenvolvido pela SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Retirar para a autocustódia sem taxas extras.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Saques gratuitos", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Uma interface rápida, simplificada e de fácil utilização.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Fácil de usar", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Monitore seu histórico de investimentos, quantidade e frequência.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Visão geral do DCA", + "TR_COINMARKET_DCA_HEADING": "Economize em Bitcoin com o aplicativo Invity", + "TR_COINMARKET_DEX_TOOLTIP": "Exchanges decentralizadas", "TR_COINMARKET_ENTER_AMOUNT_IN": "Insira o valor em {currency}", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Todas as opções de KYC", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC nunca é necessário", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Todas as ofertas CEX e DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "CEX de taxa fixa", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "CEX de taxa flutuante", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "O câmbio descentralizado (DEX) permite que você negocie a criptomoeda diretamente no blockchain sem a necessidade de uma autoridade central ou intermediário.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "CEX de taxa fixa", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "CEX de taxa flutuante", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Ofertas em destaque", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Pagamento:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Método de recepção:", "TR_COINMARKET_FEES_INCLUDED": "Taxas incluídas", "TR_COINMARKET_FEES_NOT_INCLUDED": "Taxas não incluídas", "TR_COINMARKET_FEES_ON_WEBSITE": "Certas taxas não estão incluídas no preço exibido. Você revisará o preço final no site do provedor.", @@ -518,24 +553,18 @@ "TR_COINMARKET_KYC_NO_REFUND": "KYC solicitado em casos excepcionais. KYC necessário para reembolsos. 👈", "TR_COINMARKET_KYC_POLICY": "Política de KYC", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC nunca é necessário", - "TR_COINMARKET_KYC_YES_REFUND": "KYC solicitado em casos excepcionais. KYC não necessário para reembolsos. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "O KYC só é solicitado em casos excepcionais. Não é necessário para reembolsos. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Últimas transações", "TR_COINMARKET_NETWORK_FEE": "Taxa de rede", "TR_COINMARKET_NETWORK_TOKENS": "Tokens {networkName}", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "Nenhum provedor CEX encontrado", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "Nenhum provedor DEX encontrado", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Nenhum método disponível", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Carregamento automático em", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Voltar à negociação", - "TR_COINMARKET_NO_OFFERS_HEADER": "Sem ofertas", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Lamento, não temos nenhuma oferta no momento devido a um problema de conectividade do servidor.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Lamento, no momento não temos nenhuma oferta. Tente recarregar a página ou alterar sua consulta.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Recarregar página", "TR_COINMARKET_OFFERS_EMPTY": "Nenhuma oferta para seu pedido. Altere o país ou compre o valor", "TR_COINMARKET_OFFERS_REFRESH": "Ofertas atualizam em", "TR_COINMARKET_OFFERS_SELECT": "Selecione", "TR_COINMARKET_OFFER_LOOKING": "Buscando sua melhor oferta", - "TR_COINMARKET_OFFER_NO_FOUND": "Nenhuma oferta disponível para seu pedido. Alterar valor ou moeda.", + "TR_COINMARKET_OFFER_NO_FOUND": "Nenhuma oferta disponível para seu pedido.", "TR_COINMARKET_ON_NETWORK_CHAIN": "Na chain {networkName}", "TR_COINMARKET_OTHER_CURRENCIES": "Outras moedas", "TR_COINMARKET_PAYMENT_METHOD": "Método de pagamento", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Método de recebimento", "TR_COINMARKET_SELL": "Vender", "TR_COINMARKET_SHOW_OFFERS": "Comparar ofertas", + "TR_COINMARKET_SWAP": "Swap", + "TR_COINMARKET_SWAP_AMOUNT": "Valor do swap", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} troca} one {{totalSwaps} troca} other {{totalSwaps} trocas} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Estou pronto(a) para fazer swap", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "Fazer swap de {fromCrypto} para {toCrypto} com {provider}", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Aviso legal", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "A Trezor prioriza sua segurança", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Eu gostaria de fazer swap de criptomoedas com DEX (câmbio descentralizado) usando o contrato do {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Quero trocar criptomoedas para a minha própria conta. Compreendo que as políticas do fornecedor podem exigir verificação de identidade.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Compreendo que as transações de criptomoedas são finais e não podem ser revertidas ou reembolsadas. Perdas devido a fraudes ou erros podem não ser recuperáveis.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Compreendo que a Invity não oferece este serviço. Ela é regida pelos Termos e Condições do {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Não estou usando esse recurso para realizar aposta, fraude ou qualquer outra atividade que viole os Termos de Serviço da Invity ou do fornecedor ou de quaisquer leis aplicáveis.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Compreendo que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem ser diferentes a depender da região. Isso pode aumentar o risco de fraude, roubo ou instabilidade de mercado.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Parceiros verificados pela Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Estou pronto(a) para fazer swap", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Fazer swap de {fromCrypto} para {toCrypto} com {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Aviso legal", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "A Trezor prioriza sua segurança", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Estou aqui para trocar criptomoedas. Se eu for direcionado a este site por qualquer outro motivo, entrarei em contato com o suporte da Trezor antes de prosseguir. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Quero trocar criptomoedas para a minha própria conta. Compreendo que as políticas do fornecedor podem exigir verificação de identidade.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Compreendo que as transações de criptomoedas são finais e não podem ser revertidas ou reembolsadas. Perdas devido a fraudes ou erros podem não ser recuperáveis.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Compreendo que a Invity não oferece este serviço. Ela é regida pelos Termos e Condições do {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Não estou usando esse recurso para realizar aposta, fraude ou qualquer outra atividade que viole os Termos de Serviço da Invity ou do fornecedor ou de quaisquer leis aplicáveis.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Compreendo que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem ser diferentes a depender da região. Isso pode aumentar o risco de fraude, roubo ou instabilidade de mercado.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Parceiros verificados pela Invity", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} em {networkName} rede", "TR_COINMARKET_TRADE_FEE": "Taxa de comércio", + "TR_COINMARKET_TRANS_ID": "ID. da trans:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Fornecedor desconhecido", + "TR_COINMARKET_VIEW_DETAILS": "Ver detalhes", "TR_COINMARKET_YOUR_BEST_OFFER": "Sua melhor oferta", "TR_COINMARKET_YOU_BUY": "Você compra", "TR_COINMARKET_YOU_GET": "Você recebe", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Confirmado", "TR_CONFIRMING_TX": "Confirmando a transação", "TR_CONFIRM_ACTION_ON_YOUR": "Siga as instruções na tela da sua Trezor", + "TR_CONFIRM_ADDRESS": "Confirmar endereço", "TR_CONFIRM_BEFORE_COPY": "Confirme no Trezor antes de copiar", "TR_CONFIRM_CONDITIONS": "Confirme as condições antes de prosseguir.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Confirmar carteira oculta vazia no dispositivo \"{deviceLabel}\".", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Gerenciar", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Ativar a exibição da caixa de diálogo de entrada de senha quando você abrir o Suite.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Você usa primeiro uma frase secreta?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verifique no Trezor para confirmar o endereço de recebimento. Não é recomendável continuar sem confirmar.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Verifique no Trezor para confirmar o endereço de recebimento. Não é recomendável continuar sem confirmação.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "O endereço de recebimento não pode ser verificado", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Para enviar moedas, conecte sua Trezor.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Sua Trezor não está conectada", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Conecte a Trezor para enviar", "TR_CONNECT_YOUR_DEVICE": "Conecte e desbloqueie sua Trezor", - "TR_CONTACT_SUPPORT": "Contatar o suporte", + "TR_CONTACT_SUPPORT": "Contatar o suporte da Trezor", "TR_CONTACT_TREZOR_SUPPORT": "Contatar o suporte da Trezor", "TR_CONTINUE": "Continuar", "TR_CONTINUE_ANYWAY": "Continuar mesmo assim", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Nunca envie fundos para um endereço de ID de política.", "TR_COPY_AND_CLOSE": "Copiar e fechar", "TR_COPY_SIGNED_MESSAGE": "Copiar mensagem assinada", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copiar", + "TR_COPY_TO_CLIPBOARD": "Copiar", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Não consegui recuperar o changelog", "TR_COULD_NOT_RETRIEVE_DATA": "Não consegui recuperar dados", "TR_COUNT_WALLETS": "{count} {count, plural, one {carteira} other {carteiras}}", @@ -630,7 +687,7 @@ "TR_CREATE_SHARES": "Crie cotas na Trezor", "TR_CREATE_SHARES_CARD_1": "Pegue a caneta e papel. Ou imprima Cartões Trezorou use uma KriptoSteel", "TR_CREATE_SHARES_CARD_2": "Não tire fotos ou cópias digitais do backup", - "TR_CREATE_SHARES_CARD_3": "Certifique-se de que você está sozinho, sem curiosos espectadores", + "TR_CREATE_SHARES_CARD_3": "Certifique-se de que você esteja sozinho, sem curiosos espectadores", "TR_CREATE_SHARES_EXAMPLE": "ex: 5 compartilhamentos no total, pelo menos 3 compartilhamentos para recuperação", "TR_CREATE_SHARES_EXPLANATION": "Agora, você selecionará a quantidade de ações e o mínimo de ações necessárias para recuperar seu Trezor.", "TR_CREATE_WALLET": "Criar nova carteira", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Ativo não carregado corretamente", "TR_DASHBOARD_DISCOVERY_ERROR": "Erro de descoberta", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "As contas não foram carregadas corretamente {details}", + "TR_DATA": "Dados", "TR_DATABASE_UPGRADE_BLOCKED": "Atualização da base de dados bloqueada por outra instância do aplicativo", "TR_DATA_ANALYTICS_CATEGORY_1": "Plataforma", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "SO, modelo Trezor, versão, etc.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "No bootloader por engano?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Reconecte o dispositivo sem tocar em nenhum botão.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Reconecte o dispositivo sem tocar na tela.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Esse dispositivo está sendo usado em outro local.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "O aplicativo {transportSessionOwner} pode estar usando este dispositivo no momento. Você pode assumir o controle do dispositivo, se necessário.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Outro aplicativo pode estar usando este dispositivo no momento. Você pode assumir o controle do dispositivo, se necessário.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Dispositivo detectado em estado incorreto", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Sua Trezor foi desconectada no processo de cópia de segurança. Recomendamos fortemente o uso da redefinição de fábrica nas configurações do dispositivo para limpá-lo e reiniciar o processo de cópia de segurança da carteira.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Falha na verificação da hash de firmware. Seu Trezor pode ser falsificado.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Não foi possível realizar a verificação da hash do firmware. Seu Trezor pode ser falsificado.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Desligar", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Ligar", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "A verificação da revisão do firmware é um recurso de segurança crucial. Recomendamos fortemente mantê-la ativada.", @@ -726,8 +789,10 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "O dispositivo “{deviceLabel}” está sem cópia de segurança", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "O dispositivo “{deviceLabel}” está desconectado", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "O dispositivo “{deviceLabel}” está indisponível", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Falha na verificação do dispositivo", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Reconecte o dispositivo e tente verificar novamente. Se o problema persistir, entre em contato com o Suporte da Trezor para descobrir o que está acontecendo com seu dispositivo e o que fazer a seguir.", "TR_DEVICE_NOT_CONNECTED": "Dispositivo desconectado", - "TR_DEVICE_NOT_INITIALIZED": "A Trezor não está configurada", + "TR_DEVICE_NOT_INITIALIZED": "O Trezor não está configurado", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Vamos ajudar você no processo para começarmos agora mesmo.", "TR_DEVICE_SECURITY": "Segurança", "TR_DEVICE_SETTINGS_AFTER_DELAY": "Após o atraso", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "Fazer download do Bridge {version} mais recente", "TR_DO_NOT_DISCONNECT_DEVICE": "Não desconecte seu dispositivo", "TR_DO_NOT_SHOW_AGAIN": "Não mostrar novamente", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Ignorar esta etapa?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Arraste e solte o arquivo aqui ou clique para selecionar entre arquivos", "TR_DROPZONE_ERROR": "Falha na importação: {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Junte-se", "TR_EARLY_ACCESS_ENABLED": "Programa de Acesso Antecipado ativado", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Participar do Programa", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Entendo que isto me permite testar software de pré-lançamento, que pode conter erros que afetam o funcionamento normal do Suite.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Entendo que isto me permite testar software de pré-lançamento, que pode conter erros que afetam o funcionamento normal do Trezor Suite.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "Você pode desativá-lo a qualquer momento.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Teste os mais recentes recursos do produto antes do lançamento ao público em geral.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Verifique primeiro o campo acima", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Você pode verificar por atualizações beta agora ou no próximo lançamento.", "TR_EARLY_ACCESS_JOINED_TITLE": "Programa de Acesso Antecipado ativado", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Para retornar à versão estável mais recente do Suite, clique em \"Download de versão estável\" e reinstale o aplicativo.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Para retornar à versão estável mais recente do Trezor Suite, clique em \"Baixar versão estável\" e reinstale o aplicativo.", "TR_EARLY_ACCESS_LEFT_TITLE": "Você deixou o Programa de Acesso Antecipado. Lançamentos Beta não são mais oferecidos.", "TR_EARLY_ACCESS_MENU": "Programa de Acesso Antecipado (Beta)", "TR_EARLY_ACCESS_REINSTALL": "Download de versão estável", @@ -842,8 +906,8 @@ "TR_ENTER_SEED_WORDS_ON_DEVICE": "As palavras são sempre digitadas no dispositivo por razões de segurança. Digite todas as palavras na ordem correta.", "TR_ENTER_WIPECODE": "Insira o código de limpeza", "TR_ERROR": "Erro", - "TR_ERROR_CARDANO_DELEGATE": "Valor insuficiente", - "TR_ERROR_CARDANO_WITHDRAWAL": "Valor insuficiente", + "TR_ERROR_CARDANO_DELEGATE": "O valor não é suficiente", + "TR_ERROR_CARDANO_WITHDRAWAL": "O valor não é suficiente", "TR_ETH_ADDRESS_CANT_VERIFY_HISTORY": "Não foi possível verificar o histórico de endereços. Verifique se o endereço está correto.", "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "Endereço não tem histórico de transações e não passou por checksum. Verifique se o endereço está correto.", "TR_EVM_EXPLANATION_DESCRIPTION": "Ele compartilha o mesmo estilo de endereço que o Ethereum, mas tem moedas e tokens únicos próprios incompatíveis com outras redes.", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Aprova apenas o valor exato necessário para este swap. Você precisará pagar uma taxa adicional se quiser fazer um swap similar novamente.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Revogar aprovação prévia", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Realiza uma transação que removerá a aprovação prévia do contrato com {provider}.", - "TR_EXCHANGE_BUY": "Para", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirmar na Trezor e enviar", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirmar e enviar", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Criar aprovação", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Sua transação foi bem sucedida.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Aprovado", "TR_EXCHANGE_DEX": "Oferta de exchange descentralizada", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "As taxas para este swap são estimadas em {approvalFee} ({approvalFeeFiat}) para aprovação (se necessário) e {swapFee} ({swapFeeFiat}) para o swap.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Não restam fundos para as taxas de transação. Reduza o valor da troca para no máximo {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} é inválido", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "As taxas corrigidas mostram exatamente com quanto você vai ficar ao final da troca — o valor não mudará entre a seleção da taxa e a conclusão da transação. O valor exibido é garantido, mas essas taxas costumam ser menos generosas, fazendo com que seu dinheiro não compre tanta criptomoeda.", "TR_EXCHANGE_FLOAT": "Oferta de taxa flutuante", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "As taxas flutuantes significam que o valor final que você receberá pode mudar um pouco devido a flutuações no mercado entre a seleção da taxa e a conclusão da transação. Estas taxas são normalmente mais altas, fazendo com que você possa acabar com mais criptomoedas no final.", - "TR_EXCHANGE_PROVIDER": "Fornecedor", "TR_EXCHANGE_RATE": "Preço", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "A conta de recebimento está fora do Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Este é o endereço alfanumérico específico que receberá suas moedas.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Insira um número.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Introduza o slippage desejado.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Valor da oferta de swap", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Resumo do slippage", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolerância de slippage", - "TR_EXCHANGE_TRANS_ID": "ID. da trans:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Use uma conta ({symbol}) que não esteja no Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Endereço de recebimento", - "TR_EXCHANGE_VIEW_DETAILS": "Ver detalhes", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Atualizações automáticas do Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "O Trezor Suite baixa automaticamente a nova versão em segundo plano e a instala ao reiniciar o aplicativo. Isso garante que você esteja sempre atualizado com os recursos e patches de segurança mais recentes. As atualizações são feitas sem exigir sua permissão.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Habilite a BNB Smart Chain sem transações internas históricas.", "TR_EXPERIMENTAL_FEATURES": "Experimental", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Recursos experimentais", "TR_EXPERIMENTAL_FEATURES_WARNING": "Apenas para usuários experientes. Use por sua conta e risco. Estes recursos estão em teste, talvez estejam instáveis e podem não ter suporte a longo prazo.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Migre senhas do Dropbox", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Um utilitário para recuperar senhas armazenadas no Dropbox e protegidas pela Trezor. Projetado para usuários anteriores de extensão do Chrome do Trezor Password Manager.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Use este utilitário para recuperar senhas armazenadas no Dropbox e protegidas pela Trezor. Projetado para usuários antigos da extensão Trezor Password Manager Chrome.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake é um sistema que permite acesso a sites e aplicativos censurados.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Acesse sites e aplicativos censurados usando o Tor Snowflake, um sistema projetado para ignorar restrições.", "TR_EXPORT_AS": "Exportar como {as}", "TR_EXPORT_FAIL": "A exportação falhou.", "TR_EXPORT_TO_FILE": "Exportar para arquivo", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "O novo firmware já está disponível. Atualize seu dispositivo agora ou opte por fazê-lo mais tarde.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Seu dispositivo já está usando o firmware mais recente. Você pode reinstalar o firmware se necessário.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Falha na verificação da revisão do firmware. Sua Trezor pode ser uma falsificação.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Não foi possível executar a verificação de revisão do firmware.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Concluído", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Firmware leve que suporta apenas operações Bitcoin.", "TR_FIRMWARE_SUBHEADING_NONE": "Sua Trezor foi enviada sem firmware. Instale o firmware mais recente e use seu dispositivo com segurança. Se você usa apenas o bitcoin, recomendamos instalar o .", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Entendido", "TR_GO_TO_ONBOARDING": "Iniciar configuração", "TR_GO_TO_SETTINGS": "Ir para configurações", - "TR_GO_TO_SUITE": "Acessar o Suite", + "TR_GO_TO_SUITE": "Vá para o Trezor Suite", "TR_GRAPH_LINEAR": "Linear", "TR_GRAPH_LOGARITHMIC": "Logarítmico", "TR_GRAPH_MISSING_DATA": "Transações de XRP, SOL e todas as quantias de token estão no saldo do portfólio, mas atualmente são incompatíveis com a exibição de gráfico.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Fórum Trezor", "TR_GUIDE_FORUM_LABEL": "Conecte-se com a comunidade Trezor", "TR_GUIDE_SUGGESTION_LABEL": "Como estamos indo?", - "TR_GUIDE_SUPPORT": "Contatar o suporte", + "TR_GUIDE_SUPPORT": "Contatar o suporte da Trezor", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Suporte e Comentários", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Suporte e Comentários", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "Ajude-nos a melhorar", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Sistema de arquivos locais", "TR_LOG": "Registro do aplicativo", "TR_LOGIN_PROCEED": "Proceder", - "TR_LOG_DESCRIPTION": "O registro contém todas as informações técnicas do Trezor Suite. Ele pode ser pedido pelo Suporte da Trezor para ajudar na resolução de problemas.", + "TR_LOG_DESCRIPTION": "Este log contém informações técnicas essenciais sobre o Trezor Suite e pode ser necessário ao entrar em contato com o suporte da Trezor.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Aguardando uma rodada", "TR_LOW_ANONYMITY_WARNING": "Privacidade muito baixa. Recomendamos usar pelo menos 1 em 5, já que qualquer coisa abaixo deste limite não é privada.", "TR_LTC_ADDRESS_INFO": "A Litecoin mudou o formato dos endereços. Encontre mais informações sobre como converter seu endereço em nosso blog. {TR_LEARN_MORE}", @@ -1223,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portfólio", "TR_NAV_ANONYMIZE": "Tornar as moedas privadas", "TR_NAV_BUY": "Comprar", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Detalhes", "TR_NAV_RECEIVE": "Receber", "TR_NAV_SELL": "Vender", @@ -1264,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Otimismo", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1276,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Novo", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "O novo Trezor Bridge está disponível.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "O novo firmware Trezor está disponível! Atualize seu dispositivo.", "TR_NEXT_UP": "Próximo", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (com \"{char}” não recomendado)", + "TR_NON_ASCII_CHARS": "{label} (com caracteres não recomendados)", "TR_NORMAL_ACCOUNTS": "Contas padrão", "TR_NORTH": "Norte", "TR_NOTHING_TO_ANONYMIZE": "Nada para tornar privado", @@ -1296,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Carteira padrão", "TR_NO_SEARCH_RESULTS": "Nenhum resultado para seu critério de pesquisa", "TR_NO_SPENDABLE_UTXOS": "Não há UTXOs para ser gastos em sua conta.", - "TR_NO_TRANSPORT": "Navegador não consegue se comunicar com o dispositivo", + "TR_NO_TRANSPORT": "Seu navegador não consegue se comunicar com o seu dispositivo", "TR_NO_TRANSPORT_DESKTOP": "Aplicativo não consegue se comunicar com o dispositivo", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {conta} other {contas}} • {fiatValue}", "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transação} other {transações}}", "TR_OFF": "desativado", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "A quantidade escolhida de {amount} é maior que o máximo aceito de {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "A quantidade escolhida de {amount} é maior que o máximo aceito de {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "A quantidade escolhida de {amount} é menor que o mínimo aceito de {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "A quantidade escolhida de {amount} é menor que o mínimo aceito de {min}.", "TR_OFFICIAL_LANGUAGES": "Oficial", "TR_OK": "OK", "TR_ON": "ativado", @@ -1333,7 +1386,7 @@ "TR_ONBOARDING_DATA_COLLECTION_HEADING": "Coleta anônima de dados", "TR_ONBOARDING_DEVICE_CHECK": "Verificação de segurança do dispositivo", "TR_ONBOARDING_DEVICE_CHECK_1": "Meu holograma estava intacto e inalterado.", - "TR_ONBOARDING_DEVICE_CHECK_2": "Comprei meu dispositivo na loja oficial Trezor ou em um revendedor confiável.", + "TR_ONBOARDING_DEVICE_CHECK_2": "Comprei meu dispositivo na loja oficial Trezor ou com um revendedor confiável.", "TR_ONBOARDING_DEVICE_CHECK_3": "A embalagem não foi adulterada.", "TR_ONBOARDING_DEVICE_CHECK_4": "O firmware já está instalado no Trezor conectado. Só continue com a configuração se já tiver utilizado este Trezor antes.", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "Download do app para desktop", @@ -1364,40 +1417,18 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Outras entradas e saídas", "TR_OUTGOING": "Saída", "TR_OUTPUTS": "Saídas", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "O valor mínimo e máximo pelo qual o usuário está disposto a vender {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Você precisa iniciar a transação em {providerName} – certifique-se de seguir os passos abaixo com atenção.", - "TR_P2P_GET_STARTED_ITEM_1": "Selecione \"Ir para {providerName}\" para ser redirecionado ao site do nosso parceiro.", - "TR_P2P_GET_STARTED_ITEM_3": "Quando {providerName} pedir um endereço de liberação, volte aqui e continue.", - "TR_P2P_GET_STARTED_ITEM_4": "Quase lá! Revele e copie seu endereço, coloque-o no campo \"Liberar endereço\" em {providerName} e finalize a transação.", - "TR_P2P_GO_TO_PROVIDER": "Ir para {providerName}", - "TR_P2P_INFO": "Com a tecnologia {peerToPeer} (P2P), não há verificação KYC envolvida, nem do comprador, nem do vendedor. Todas as partes estão protegidas contra fraudes através da segurança de {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "Estou pronto(a) para comprar", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Comprar {cryptocurrency} peer-to-peer com {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Aviso legal", - "TR_P2P_MODAL_SECURITY_HEADER": "A Trezor prioriza sua segurança", - "TR_P2P_MODAL_TERMS_1": "Você está aqui para comprar criptomoedas de uma pessoa à sua escolha usando a tecnologia peer-to-peer (P2P) sem verificação de identidade. Se você chegou a este site por qualquer outro motivo, contate o suporte antes de prosseguir.", - "TR_P2P_MODAL_TERMS_2": "Você entende que transações de criptomoeda são irreversíveis e não permitem reembolso. Assim, perdas por fraudes ou acidentes podem ser irrecuperáveis.", - "TR_P2P_MODAL_TERMS_4": "Você entende que a Invity não oferece este serviço. O serviço é regido pelos termos de {provider}.", - "TR_P2P_MODAL_TERMS_5": "Você não está usando esse recurso para realizar aposta, fraude ou outra violação dos Termos de Serviço da Invity ou do fornecedor ou de quaisquer regulamentos aplicáveis.", - "TR_P2P_MODAL_TERMS_6": "Você entende que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem variar entre jurisdições. Isso pode acarretar em um maior risco de fraude, roubo ou instabilidade de mercado para você.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Parceiros verificados pela Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "A transação precisa ser concluída dentro deste prazo, contando a partir da criação de um contrato no site do {providerName}.", - "TR_P2P_PRICE": "Preço para 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "{symbol} preço oferecido por este usuário.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Olá, estou usando {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "A quantidade escolhida de {amount} é maior que o máximo aceito de {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "A quantidade escolhida de {amount} é menor que o mínimo aceito de {minimum}.", "TR_PAGINATION_NEWER": "Mais recentes", "TR_PAGINATION_OLDER": "Mais antigas", "TR_PASSPHRASE_CASE_SENSITIVE": "Observação: A senha é sensível a maiúsculas e minúsculas.", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "Aprenda primeiro como a frase secreta funciona", "TR_PASSPHRASE_DESCRIPTION_ITEM2": "Uma frase secreta abre uma carteira protegida por essa frase", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Ninguém pode recuperá-la, nem mesmo o suporte Trezor", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Ninguém pode recuperá-lo, nem mesmo o suporte da Trezor", "TR_PASSPHRASE_HIDDEN_WALLET": "Carteira oculta", "TR_PASSPHRASE_MISMATCH": "Frase secreta incorreta", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "As frases secretas não se correspondem. Para segurança, comece novamente e insira as frases corretamente.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Recomeçar", + "TR_PASSPHRASE_NON_ASCII_CHARS": "Recomendamos o uso de ABC, abc, 123, espaços ou esses caracteres especiais", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "O uso de caracteres especiais não listados arrisca a compatibilidade futura.", "TR_PASSPHRASE_TOO_LONG": "O comprimento da senha ultrapassa o limite.", "TR_PASSPHRASE_WALLET": "Carteira oculta #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Saiba como funciona uma frase secreta", @@ -1439,15 +1470,30 @@ "TR_PIN_SUBHEADING": "Usar um PIN forte protege sua Trezor do acesso físico não autorizado.", "TR_PLAY_IT_SAFE": "Segurança em primeiro lugar", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Habilite sua câmera para digitalizar QR codes.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Conecte seu dispositivo para continuar com o processo de verificação.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Conecte seu Trezor para continuar com o processo de verificação.", "TR_PLEASE_ENABLE_PASSPHRASE": "Habilite o recurso de senha para continuar com o processo de verificação.", "TR_POLICY_ID_ADDRESS": "ID da política:", "TR_PRIMARY_FIAT": "Moeda Fiduciária", "TR_PRIVATE": "Privado", "TR_PRIVATE_DESCRIPTION": "Privacidade pelo menos {targetAnonymity}", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Continue com o endereço não verificado", "TR_PROMO_BANNER_DASHBOARD": "A mais conveniente carteira de hardware para gerenciar com segurança suas criptomoedas", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Confirme no Trezor antes de copiar", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Primeiro, confirme o endereço de recebimento na tela protegida contra invasões do seu dispositivo Trezor.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Ativado", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Recém-atualizado ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Reinicie para atualizar", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Dispositivo Trezor", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Atualização disponível ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Até a data ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "O Trezor Suite fez o download de uma nova atualização.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "O Trezor Suite foi atualizado.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Atualização do Trezor Suite já disponível.", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Reiniciar e atualizar", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Iniciar atualização", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Atualização do Trezor já está disponível.", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "O que há de novo?", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Você pode precisar digitar algumas palavras que não fazem parte da sua semente de recuperação.", "TR_RANGE": "faixa", "TR_READ_AND_UNDERSTOOD": "Eu li e entendi o que foi dito acima", @@ -1499,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Verificações de segurança", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "Prompt", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Permite ações potencialmente inseguras, tais como chaves que não correspondem ou taxas extremas, aprovando-as manualmente na sua Trezor.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Não mude isto, a menos que saiba o que está fazendo!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Só mude isso se você souber o que está fazendo!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Estrito", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Segurança total de Trezor.", "TR_SCAN_QR_CODE": "Digitalizar QR code", @@ -1509,9 +1555,9 @@ "TR_SEARCH_TRANSACTIONS": "Pesquisar transações", "TR_SEARCH_UTXOS": "Procure por um endereço específico, ID da transação ou rótulo", "TR_SECURITY_CHECKPOINT_GOT_SEED": "Você tem sua cópia de segurança?", - "TR_SECURITY_CHECK_HOLOGRAM": "Por favor, note que os pacotes do dispositivo, incluindo hologramas e selos de segurança, foram atualizados com o tempo. Você pode verificar os detalhes do embalagem aqui. Certifique-se de que seu dispositivo foi comprado da Loja oficial do Trezor ou de um de nossos vendedoresconfiáveis. Caso contrário, corre-se o risco de o seu dispositivo ser falso. Se você suspeitar que seu dispositivo não é verdadeiro, por favor entre em contato com o suporte do Trezor.", + "TR_SECURITY_CHECK_HOLOGRAM": "Observe que a embalagem do dispositivo, incluindo hologramas e selos de segurança, foi atualizada ao longo do tempo. Você pode verificar os detalhes da embalagem aqui. Certifique-se de que o seu dispositivo foi adquirido na loja oficial da Trezor ou em um dos nossos
vendedores de confiança. Caso contrário, existe o risco de o seu dispositivo ser falsificado. Se suspeitar que o seu dispositivo não é genuíno, entre em contato com o suporte Trezor.", "TR_SECURITY_FEATURES_COMPLETED_N": "Segurança ({n} de {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Dispositivos configurados no modo sem frase-semente não podem acessar a Trezor Suite. Isto é para evitar a perda irreversível de moedas, no caso de uma configuração inadequada ser usada para um propósito errado.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Os dispositivos configurados no modo sem frase-semente não podem acessar o Trezor Suite para evitar perdas irreversíveis de moedas, o que pode ocorrer se um dispositivo for usado incorretamente.", "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "A configuração sem frase-semente não é suportada pelo Trezor Suite", "TR_SEED_BACKUP_LENGTH": "Sua frase-semente de recuperação pode conter 12, 18, ou 24 palavras.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Sua frase-semente de recuperação pode conter 12, 18, 20, 24, ou 33 palavras.", @@ -1522,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Digite as palavras da sua frase-semente de recuperação na ordem apresentada no seu dispositivo.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Usando a tela touchscreen, digite todas as palavras na ordem correta até a conclusão.", "TR_SEE_DETAILS": "Ver detalhes", + "TR_SEE_IF_ISSUE_PERSISTS": "Veja se o problema persiste.", "TR_SELECTED": "{amount} selecionado", "TR_SELECT_COIN_FOR_SETTINGS": "Selecione a moeda ativa para alterar as configurações", "TR_SELECT_DEVICE": "Selecione o dispositivo", + "TR_SELECT_NAME_OR_ADDRESS": "Pesquise por nome, símbolo, rede, ou endereço de contrato.", "TR_SELECT_NUMBER_OF_WORDS": "Selecione o número de palavras na sua frase-semente de recuperação", "TR_SELECT_PASSPHRASE_SOURCE": "Selecione onde digitar a senha em \"{deviceLabel}\".", "TR_SELECT_RECOVERY_METHOD": "Selecione o método de recuperação", + "TR_SELECT_TOKEN": "Selecione um token.", "TR_SELECT_TREZOR": "Selecione Trezor", "TR_SELECT_TREZOR_TO_CONTINUE": "Selecione Trezor para continuar.", "TR_SELECT_TYPE": "Selecione o tipo", @@ -1558,11 +1607,11 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "Vender {cryptocurrency} com {provider}", "TR_SELL_MODAL_LEGAL_HEADER": "Aviso legal", "TR_SELL_MODAL_SECURITY_HEADER": "A Trezor prioriza sua segurança", - "TR_SELL_MODAL_TERMS_1": "Você está aqui para vender criptomoedas. Se você chegou a este site por qualquer outro motivo, contate o suporte antes de prosseguir.", + "TR_SELL_MODAL_TERMS_1": "Você está aqui para vender criptomoedas. Se você chegou a este site por qualquer outro motivo, contate o suporte da Trezor antes de prosseguir.", "TR_SELL_MODAL_TERMS_2": "Você está vendendo criptomoedas por conta própria. Você reconhece que as políticas do fornecedor podem exigir verificação de identidade.", "TR_SELL_MODAL_TERMS_3": "Você entende que transações de criptomoeda são irreversíveis e não permitem reembolso. Assim, perdas por fraudes ou acidentes podem ser irrecuperáveis.", "TR_SELL_MODAL_TERMS_4": "Você entende que a Invity não oferece este serviço. O serviço é regido pelos termos de {provider}.", - "TR_SELL_MODAL_TERMS_5": "Você não está usando esse recurso para realizar aposta, fraude ou outra violação dos Termos de Serviço da Invity ou do fornecedor ou de quaisquer regulamentos aplicáveis.", + "TR_SELL_MODAL_TERMS_5": "Não estou usando esse recurso para realizar aposta, fraude ou qualquer outra atividade que viole os Termos de Serviço da Invity ou do fornecedor ou de quaisquer leis aplicáveis.", "TR_SELL_MODAL_TERMS_6": "Você entende que criptomoedas são uma ferramenta financeira emergente e que as regulamentações podem variar entre jurisdições. Isso pode acarretar em um maior risco de fraude, roubo ou instabilidade de mercado para você.", "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "Parceiros verificados pela Invity", "TR_SELL_REGISTER": "Registrar", @@ -1571,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Recusado", "TR_SELL_STATUS_PENDING": "Pendente", "TR_SELL_STATUS_SUCCESS": "Aprovado", - "TR_SELL_TRANS_ID": "ID. da trans:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "O máximo é {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "O mínimo é {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Ver detalhes", "TR_SENDFORM_LABELING_EXAMPLE_1": "Poupança", "TR_SENDFORM_LABELING_EXAMPLE_2": "Aluguel", "TR_SENDING_SYMBOL": "Enviando {multiple, select, true {vários tokens} false {{symbol}} other {{symbol}}}", @@ -1629,6 +1674,8 @@ "TR_SHOW_LOG": "Mostrar registro", "TR_SHOW_MORE": "Mostrar mais", "TR_SHOW_MORE_ADDRESSES": "Mostrar mais ({count})", + "TR_SHOW_ON_TRAY": "Mostrar ícone na bandeja.", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Monitore se o Trezor Suite está sendo executado em segundo plano.", "TR_SHOW_UNVERIFIED_ADDRESS": "Mostrar endereço não verificado", "TR_SHOW_UNVERIFIED_XPUB": "Mostrar chave pública não verificada", "TR_SIDEBAR_ADD_COIN": "Adicionar uma moeda", @@ -1641,7 +1688,9 @@ "TR_SIZE": "Tamanho", "TR_SKIP": "Pular", "TR_SKIP_BACKUP": "Pular cópia de segurança", + "TR_SKIP_BACKUP_DESCRIPTION": "Um backup da carteira permite que você recupere seus fundos se o seu Trezor for perdido, roubado ou danificado. Sem um backup, você pode perder o acesso à sua criptografia permanentemente.", "TR_SKIP_PIN": "Pular PIN", + "TR_SKIP_PIN_DESCRIPTION": "O PIN do dispositivo impede o acesso não autorizado ao seu Trezor. Sem ele, qualquer pessoa com seu dispositivo pode acessar seus fundos.", "TR_SKIP_ROUNDS": "Pulando rodada", "TR_SKIP_ROUNDS_DESCRIPTION": "Ao permitir que rodadas sejam puladas, você dificulta provar qualquer relação entre suas entradas. Isto possibilita ofuscar ainda mais a origem dos fundos.", "TR_SKIP_ROUNDS_HEADING": "Permitir que a Trezor pule rodadas", @@ -1654,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Adicionando ao pool de stake", "TR_STAKE_ANY_AMOUNT_ETH": "Aposte um valor mínimo de {amount} {symbol} e comece a ganhar recompensas. Com a nossa taxa APY atual de {apyPercent}%, suas recompensas ganham também!", "TR_STAKE_APY": "Rendimento percentual anual", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Rendimento percentual anual", "TR_STAKE_AVAILABLE": "Disponível", "TR_STAKE_CAN_CLAIM_WARNING": "Você já pode resgatar {amount} {symbol}. {br}Resgate ou aguarde até a nova remoção de stake ser processada.", @@ -1663,15 +1713,18 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Você pode resgatar após o período de remoção de stake estar concluído.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "no próximo bloco", "TR_STAKE_CLAIM_PENDING": "Resgate pendente", + "TR_STAKE_CLAIM_UNSTAKED": "Reivindicação removida do stake {symbol}", "TR_STAKE_CONFIRM_AND_STAKE": "Confirmar e fazer stake", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Confirmar periodo de entrada", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Eu reconheço e consinto em fazer stake com Everstake", "TR_STAKE_DAYS": "{days} dias", "TR_STAKE_DELEGATED": "Delegação de stake", "TR_STAKE_DEREGISTERED": "Remoção de registro de um endereço de stake", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Ganhe recompensas semanalmente", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "Entrar no pool de stake pode levar até {days} dias", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Entre no pool do stake", "TR_STAKE_ETH": "Fazer stake de Ethereum", - "TR_STAKE_ETH_CARD_TITLE": "A maneira mais fácil de ganhar {symbol}.", + "TR_STAKE_ETH_CARD_TITLE": "A maneira mais fácil de ganhar {symbol}", "TR_STAKE_ETH_EARN_REPEAT": "Faça stake. Ganhe recompensas. Repita.", "TR_STAKE_ETH_EVERSTAKE": "Trezor e Everstake", "TR_STAKE_ETH_EVERSTAKE_DESC": "A Everstake é líder global e fornecedora de tecnologia de staking", @@ -1682,17 +1735,21 @@ "TR_STAKE_ETH_REWARDS_EARN": "Suas recompensas também ganham recompensas. Mantenha-as em stake e veja suas recompensas de {symbol} voarem.", "TR_STAKE_ETH_REWARDS_EARN_APY": "Suas recompensas de {symbol} também ganham a taxa APY. Mantenha seus fundos em stake ou adicione mais para aumentar suas recompensas.", "TR_STAKE_ETH_SEE_MONEY_DANCE": "Veja seu dinheiro trabalhar", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Ganhe {apyPercent}% de APY* colocando seu Ethereum em Tezor.", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "Ganhe {apyPercent}% de APY ao fazer stake do seu Ethereum com a Trezor.", "TR_STAKE_ETH_WILL_BE_BLOCKED": "Seu {symbol} será bloqueado durante esse periodo e você não poderá cancelar isso. Saiba mais", "TR_STAKE_EVERSTAKE_MANAGES": "A Everstake gerencia e protege seus {symbol} em stake com smart contracts, infraestrutura e tecnologia.", "TR_STAKE_INSTANT": "Instantâneo", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "Você recebeu {amount} {symbol} “instantaneamente”. {days, plural, =0 {} one {O restante será pago dentro de # dia.} other { O restante será pago dentro de # dias}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} na conta", "TR_STAKE_LEARN_MORE": "Saiba mais", + "TR_STAKE_LEAVE_STAKING_POOL": "Sair do pool do stake", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Deixamos {amount} {symbol} de fora para que você possa pagar taxas de saque.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Deixamos uma pequena quantia de {symbol} para que você possa pagar as taxas de retirada.", "TR_STAKE_MAX": "Máx.", "TR_STAKE_MAX_FEE_DESC": "A taxa máxima é a taxa de transação que você se dispõe a pagar à rede para garantir o processamento de sua transação.", "TR_STAKE_MAX_REWARD_DAYS": "Máximo de {days} dias", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "O valor mínimo para colocar em stake é {amount} {symbol}", + "TR_STAKE_MONTHLY": "Mensal", "TR_STAKE_NEXT_PAYOUT": "Pagamento da próxima recompensa", "TR_STAKE_NOT_ENOUGH_FUNDS": "Não há {symbol} suficiente para pagar as taxas da rede", "TR_STAKE_ONLY_REWARDS": "Somente recompensas", @@ -1704,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Registro de um endereço de stake", "TR_STAKE_RESTAKED_BADGE": "Apostado novamente", "TR_STAKE_REWARDS": "Recompensas", + "TR_STAKE_SIGN_TRANSACTION": "Assinar transação", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Assinar a transação de remoção do stake", "TR_STAKE_STAKE": "Stake", "TR_STAKE_STAKED_AMOUNT": "Valor em stake", "TR_STAKE_STAKED_AND_EARNING": "Em stake e ganhando recompensas", @@ -1711,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Fazer mais stake", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Stake em poucas palavras", "TR_STAKE_STAKING_IS": "O stake é como um gesto amigável, no qual você bloqueia temporariamente seus ativos Ethereum para apoiar à operação da blockchain. Como recompensa, você acaba ganhando mais {symbol} em troca!", + "TR_STAKE_STAKING_PROCESS": "Processo para inclusão no stake", "TR_STAKE_START_STAKING": "Comece a fazer stake", "TR_STAKE_TIME_TO_CLAIM": "É hora de resgatar", "TR_STAKE_TOTAL_PENDING": "Total em stake pendente:", @@ -1719,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Removido do stake e pronto para resgate", "TR_STAKE_UNSTAKE_TO_CLAIM": "Remover do stake para resgatar", "TR_STAKE_UNSTAKING": "Removendo do stake", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Aproximadamente {symbol} disponível instantaneamente", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "A liquidez do pool do stake pode permitir o desbloqueio imediato de alguns fundos. Os fundos restantes seguirão o período de remoção de stake.", "TR_STAKE_UNSTAKING_PERIOD": "Período de remoção de stake", + "TR_STAKE_UNSTAKING_PROCESS": "Processo para sair do stake", "TR_STAKE_UNSTAKING_TAKES": "A remoção do stake costuma levar cerca de 3 dias. Depois da conclusão, você pode trocar ou enviar os fundos.", + "TR_STAKE_WEEKLY": "Semanal", "TR_STAKE_WHAT_IS_STAKING": "O que é stake?", + "TR_STAKE_YEARLY": "Anual", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Seus fundos em stake são mantidos pela Everstake", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} incluído no stake instantaneamente!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} removido do stake instantaneamente!", + "TR_STAKING_CONSOLIDATING_FUNDS": "Consolidando o seu {symbol} para você", "TR_STAKING_DELEGATE": "Delegar", "TR_STAKING_DEPOSIT": "Depósito reembolsável", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "A taxa de depósito é de {feeAmount} ADA e é necessário registrar seu endereço para começar a fazer stake. Se você optar por remover seu Cardano do stake, receberá o depósito de volta.", + "TR_STAKING_ESTIMATED_GAINS": "Ganhos estimados", "TR_STAKING_FEE": "Taxa", - "TR_STAKING_INSTANTLY_STAKED": "Você instantaneamente apostou {amount} {symbol}. {days, plural, =0 {} one {O restante {symbol} é apostado dentro de # dias.} other { O restante {symbol} é apostado no prazo de # dias}}", + "TR_STAKING_GETTING_READY": "O seu {symbol} se preparando para o trabalho", + "TR_STAKING_INSTANTLY_STAKED": "Você instantaneamente apostou {amount} {symbol}. {days, plural, =0 {} one {O {symbol} restante será incluído em stake dentro de # dias.} other { O {symbol} restante será incluído em stake dentro de # dias}}", "TR_STAKING_IS_NOT_SUPPORTED": "O stake não é suportado nesta rede.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Você não tem fundos suficientes na sua conta.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Depois de confirmar,", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Ao realizar stake no pool de stake da Trezor, você apoia diretamente a Trezor e o ecossistema Cardano no Trezor Suite.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Você está delegando em um pool de stake de terceiros", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "O stake em que você está delegando está super saturado. Delegue novamente seu stake para maximizar suas recompensas de stake", "TR_STAKING_POOL_OVERSATURATED_TITLE": "O pool de stake está super saturado", "TR_STAKING_REDELEGATE": "Delegar novamente", "TR_STAKING_REWARDS": "Recompensas disponíveis", + "TR_STAKING_REWARDS_ARE_RESTAKED": "as recompensas são automaticamente apostadas novamente", "TR_STAKING_REWARDS_DESCRIPTION": "Observe que pode levar até 20 dias para começar a receber suas recompensas após o registro inicial do stake e delegação. Após esse período, você receberá sua recompensa a cada 5 dias.", "TR_STAKING_REWARDS_TITLE": "Stake de Cardano está ativo", "TR_STAKING_STAKE_ADDRESS": "O endereço do seu stake", @@ -1744,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Não foi possível acessar o pool de stake do Trezor para delegar.", "TR_STAKING_TX_PENDING": "Sua transação {txid} foi enviada com sucesso para a blockchain e está aguardando confirmação.", "TR_STAKING_WITHDRAW": "Retirada", + "TR_STAKING_YOUR_EARNINGS": "Seus ganhos são automaticamente apostados novamente, permitindo que você ganhe juros compostos.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Seu {symbol} removido do stake está pronto", + "TR_STAKING_YOU_ARE_HERE": "Você está aqui", "TR_STANDARD_WALLET_DESCRIPTION": "Sem senha", "TR_START": "Começar", "TR_START_AGAIN": "Começar de novo", @@ -1752,6 +1827,7 @@ "TR_START_COINJOIN": "Iniciar CoinJoin", "TR_START_RECOVERY": "Iniciar recuperação", "TR_STEP": "Etapa {number}", + "TR_STEP_OF_TOTAL": "Etapa {index} de {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Ainda não visualiza sua Trezor?", "TR_STOP": "Parar", "TR_STOPPING": "Parando", @@ -1803,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Sem tokens... ainda.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Nenhum tokens. Eles podem estar ocultos.", "TR_TOKENS_SEARCH_TOOLTIP": "Pesquise por token, símbolo ou endereço de contrato.", + "TR_TOKEN_NOT_FOUND": "Token não encontrado", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "Token não encontrado na rede {networkName}.", "TR_TOKEN_TRANSFERS": "Transferências de token {standard}", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Experimente uma pesquisa diferente.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Tente uma pesquisa diferente ou mude para outra rede.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Tokens não reconhecidos", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Tokens não reconhecidos representam riscos potenciais. Tenha cuidado.", "TR_TOO_LONG": "A mensagem é muito longa", @@ -1815,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Atualizar caminho", "TR_TOR_DESCRIPTION": "Roteie todo o tráfego do Suite pela rede Tor e aumente sua privacidade e segurança. Pode levar algum tempo até que o Tor carregue e estabeleça uma conexão.", "TR_TOR_DISABLE": "Desabilitar Tor", + "TR_TOR_DISABLED": "Desativado", "TR_TOR_DISABLE_ONIONS_ONLY": "Backends personalizados não onion ausentes", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Adicione endereços de backend personalizados não onion para evitar este comportamento.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Você pode desativar o Tor em segurança agora.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Backends personalizados não estão mais usando apenas endereços onion.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Desabilitar Tor", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Desativar o Tor agora irá redefinir todos os Onion de backends para os servidores padrão da Trezor.", + "TR_TOR_DISABLING": "Desativando", "TR_TOR_ENABLE": "Habilitar Tor", + "TR_TOR_ENABLED": "Ativado", "TR_TOR_ENABLE_AND_CONFIRM": "Habilitar Tor e confirmar", "TR_TOR_ENABLE_TITLE": "Habilitar Tor", + "TR_TOR_ENABLING": "Ativando", + "TR_TOR_ERROR": "Erro", "TR_TOR_IS_SLOW_MESSAGE": "O Tor está se conectando à rede.

Aguarde um momento.", "TR_TOR_KEEP_RUNNING": "Continuar executando o Tor", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Selecione \"Continuar executando o Tor\" para continuar ou \"Parar Tor\" para sair do processo de CoinJoin.", + "TR_TOR_MISBEHAVING": "Comportamento inadequado", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Desativar o Tor e alternar para os backends predefinidos", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Sair", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Selecione \"Ativar Tor\" para continuar ou \"Sair do processo\" para deixar o processo.", @@ -1841,11 +1927,7 @@ "TR_TO_BTC": "Para BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Para tornar seus rótulos persistentes e disponíveis em diferentes dispositivos, conecte-se ao provedor de armazenamento em nuvem.", "TR_TO_SATOSHIS": "Para sat", - "TR_TRADE_BUYS": "compras", - "TR_TRADE_ENTER_COIN": "Insira o nome ou símbolo da criptomoeda...", - "TR_TRADE_EXCHANGES": "trocas", "TR_TRADE_REDIRECTING": "Redirecionando...", - "TR_TRADE_SELLS": "vendas", "TR_TRANSACTIONS_NOT_AVAILABLE": "Histórico de transações não disponível", "TR_TRANSACTIONS_SEARCH_TIP_1": "Dica: Você pode buscar IDs de transação, endereços, tokens, rótulos, valores e datas.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Dica: Você pode combinar operadores E (&) e OU (|) para pesquisas mais complexas. Por exemplo, > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 exibirá todas as transações em janeiro ou dezembro de {lastYear}.", @@ -1860,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "Pesquise por ID de transação, rótulo ou quantidade ou use operadores como < > | & = !=.", "TR_TRANSACTION_DETAILS": "Detalhes", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Versão em execução de Trezor Bridge {version}", + "TR_TREZOR_CONNECT": "O Trezor está conectado", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Tutorial cancelado", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Tutorial concluído", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Aprenda a usar seu dispositivo com a ajuda de um breve tutorial", @@ -1867,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Feche outras abas e janelas que possam estar usando sua Trezor", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Após fechar outras abas e janelas, tente atualizar esta página.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Depois de fechar outras abas e janelas do navegador, tente sair e reabrir o aplicativo Trezor Suite.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Etapas necessárias para ativar comunicação", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Tente estas etapas para solucionar esse problema.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "Visite a página de status Trezor Bridge", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Certifique-se de que o processo da Trezor Bridge está sendo executado", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Hoje, só navegadores baseados em Chromium permitem comunicação direta com dispositivos USB", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Hoje, só navegadores baseados em Chromium permitem comunicação direta com dispositivos USB.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Use um navegador baseado em Chromium", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "O cabo deve estar totalmente inserido. No caso de um dispositivo conectado a USB-C, o cabo deve ser encaixado.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Experimente um cabo diferente", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Com a Trezor Bridge instalada.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Tente usar um computador diferente, se você puder", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Apenas para garantir", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Tente reiniciar o seu computador", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Reiniciar o computador pode corrigir o problema de comunicação entre o navegador e o dispositivo.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Reinicie o seu computador", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Execute aplicativo de desktop Trezor Suite", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Use o aplicativo de desktop Trezor Suite", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Clique para alternar uma implementação de ponte alternativa. Versão atual: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Use outra versão da Trezor Bridge", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "Tente instalar regras de udev. Certifique-se de salvá-las primeiro na área de trabalho antes de abrir.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Se tiver atualizado o firmware do seu dispositivo pela última vez em 2019 ou antes, siga as instruções da Base de conhecimento", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Parece que você pode estar usando um modelo Trezor mais antigo.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Conecte-o diretamente ao seu computador (sem um hub USB).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Tente uma porta USB diferente", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Instalar regras automaticamente", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Regras de udev ausentes", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Um estado inesperado: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Seu dispositivo está conectado corretamente, mas seu navegador não pode se comunicar com ele no momento. Você precisará instalar a Trezor Bridge.", "TR_TRY_AGAIN": "Tente novamente", "TR_TXID": "ID do TX", "TR_TXID_RBF": "ID original do TX a ser substituído", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "Transação confirmada", "TR_TX_CONFIRMING": "Confirmando a transação", + "TR_TX_DATA_FUNCTION": "Função", + "TR_TX_DATA_INPUT_DATA": "Dados de entrada", + "TR_TX_DATA_METHOD": "Dados de entrada", + "TR_TX_DATA_METHOD_NAME": "Nome do método", + "TR_TX_DATA_PARAMS": "Parâmetros", "TR_TX_DEPOSIT": "Depósito", "TR_TX_FEE": "Taxa", "TR_TX_TAB_AMOUNT": "Quantia", @@ -1932,13 +2023,18 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Atualização de firmware necessária. Você pode alterar sua tela inicial na página de configurações mais tarde", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Atualize seu firmware para alterar sua tela inicial", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Atualização disponível", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Ativar atualizações automáticas", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Novo", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Reiniciar e atualizar", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Gostaria de instalar a atualização agora?", "TR_UPDATE_MODAL_NOT_NOW": "Agora não", - "TR_UPDATE_MODAL_RESTART_NEEDED": "Isto reinicializará o Trezor Suite", + "TR_UPDATE_MODAL_RESTART_NEEDED": "Isto reinicializará o Trezor Suite.", "TR_UPDATE_MODAL_START_DOWNLOAD": "Iniciar atualização", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Atualização baixada", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Atualizar ao sair", + "TR_UPDATE_MODAL_WHATS_NEW": "O que há de novo?", + "TR_UPDATE_MODAL_YOUR_VERSION": "Sua versão: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Atualize seu firmware para acessar esta conta. Veja o banner azul acima.", "TR_UP_TO": "até", "TR_UP_TO_DATE": "Até a data", "TR_UP_TO_DAYS": "até {days} dias", @@ -1992,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Acessar carteira oculta", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Carteira oculta", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Crie uma nova carteira ou restaure-a usando sua semente de recuperação.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Estamos sempre nos esforçando para melhorar sua experiência com a Trezor. Aqui estão as novidades:", "TR_WEST": "Oeste", "TR_WHAT_DATA_WE_COLLECT": "Que dados recolhemos?", "TR_WHAT_IS_PASSPHRASE": "Saiba mais sobre a diferença", @@ -2002,7 +2099,7 @@ "TR_WIPE_DEVICE_CHECKBOX_2_TITLE": "Entendo que devo ter uma cópia de segurança da minha semente de recuperação para manter acesso aos meus fundos", "TR_WIPE_DEVICE_TEXT": "A redefinição do dispositivo remove todos os seus dados. Redefina seu dispositivo apenas se você tiver uma cópia off-line da sua semente de recuperação, que lhe permitirá reaver seus fundos. ", "TR_WIPE_OR_UPDATE": "Redefinir dispositivo ou atualizar firmware", - "TR_WIPE_OR_UPDATE_DESCRIPTION": "Ir para Configurações do dispositivo", + "TR_WIPE_OR_UPDATE_DESCRIPTION": "Ir para Configurações do dispositivo.", "TR_WIPING_YOUR_DEVICE": "A redefinição de fábrica limpa a memória do dispositivo, apagando todas as informações, incluindo a semente de recuperação e o PIN. Somente execute uma redefinição de fábrica se você tiver uma cópia off-line segura da sua semente de recuperação, que lhe permitirá reaver seus fundos. ", "TR_WORDS": "{count} palavras", "TR_WORD_DOES_NOT_EXIST": "A palavra \"{word}\" não existe na lista de palavras bip39.", diff --git a/packages/suite-data/files/translations/ro.json b/packages/suite-data/files/translations/ro.json index e6f69fd0449..c3cf445db10 100644 --- a/packages/suite-data/files/translations/ro.json +++ b/packages/suite-data/files/translations/ro.json @@ -162,7 +162,6 @@ "TR_404_DESCRIPTION": "Ei bine… ceva este stricat. Vă rugăm să mergeți la tabloul de bord.", "TR_404_GO_TO_DASHBOARD": "Mergeți la tabloul de bord acum!", "TR_404_TITLE": "Eroare 404", - "TR_ABORT": "Anuleaza", "TR_ACCESS_HIDDEN_WALLET": "Accesați portofelul ascuns", "TR_ACCESS_STANDARD_WALLET": "Accesați portofelul standard", "TR_ACCOUNT_DETAILS_HEADER": "Detaliile contului", @@ -282,9 +281,7 @@ "TR_BUG": "Bug", "TR_BUMP_FEE": "Comision de trimitere", "TR_BUY": "Cumpărați", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Tranzacții cont", "TR_BUY_BUY": "cumpărați", - "TR_BUY_BUY_AGAIN": "Cumpărați încă o dată", "TR_BUY_CONFIRMED_ON_TREZOR": "Confirmat pe trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Înapoi la cont", "TR_BUY_DETAIL_ERROR_SUPPORT": "Deschideți site-ul de asistență al partenerului", @@ -318,12 +315,10 @@ "TR_BUY_STATUS_PENDING": "În aşteptare", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "În aşteptare", "TR_BUY_STATUS_SUCCESS": "Aprobat", - "TR_BUY_TRANS_ID": "ID Trans.:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximul este {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximul este {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimul este {minimum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimul este {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Vizualizare detalii", "TR_BYTES": "octeți", "TR_CAMERA_NOT_RECOGNIZED": "Camera nu a fost recunoscută.", "TR_CAMERA_PERMISSION_DENIED": "Permisiunea de a accesa camera a fost refuzată.", @@ -336,7 +331,6 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Valoare Trezor", "TR_CHAINED_TXS": "Tranzacții înlănțuite", "TR_CHANGELOG": "Jurnal de modificări", - "TR_CHANGELOG_ON_GITHUB": "Jurnal de modificări pe GitHub", "TR_CHANGE_HOMESCREEN": "Schimbați ecranul de pornire", "TR_CHANGE_PIN": "Schimbare PIN", "TR_CHECK_FINGERPRINT": "Verificați amprenta digitală", @@ -353,12 +347,6 @@ "TR_COINJOIN_PHASE_4_MESSAGE": "Se încheie runda", "TR_COINJOIN_ROUND_COUNTDOWN_OVERTIME": "un moment", "TR_COINJOIN_TRANSACTION_BATCH": "Tranzacții CoinJoin", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Încărcare automată în", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Înapoi la Comerț", - "TR_COINMARKET_NO_OFFERS_HEADER": "Nicio ofertă", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Ne pare rău, nu avem nicio ofertă în acest moment din cauza unei probleme de conectivitate a serverului.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Ne pare rău, nu avem nicio ofertă în acest moment. Încercați să reîncărcați pagina sau să vă schimbați interogarea.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Reîncărcați pagina", "TR_COINS": "Monede", "TR_COIN_SETTINGS": "Setări monedă", "TR_COLOR_SCHEME": "Paletă de culori", @@ -384,7 +372,6 @@ "TR_CONTINUE_TO_PIN": "Continuați către PIN", "TR_COPY_AND_CLOSE": "Copiază și închide", "TR_COPY_SIGNED_MESSAGE": "Copiază mesajul semnat", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Copiaţi", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Nu s-a putut prelua jurnalul modificărilor", "TR_COULD_NOT_RETRIEVE_DATA": "Nu s-au putut prelua datele", "TR_COUNT_WALLETS": "{count} {count, plural, one {portofel} other {portofele}}", @@ -476,7 +463,6 @@ "TR_DOWNLOAD": "Descărcați", "TR_DOWNLOADING": "Se descarcă", "TR_DOWNLOAD_LATEST_BRIDGE": "Descărcați cea mai recentă versiune Bridge {version}", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Chiar doriți să săriți peste acest pas?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Trageți și plasați fișierul aici sau faceți clic pentru a selecta din fișiere", "TR_DROPZONE_ERROR": "Importarea a eșuat {error}", @@ -531,7 +517,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Aprobați doar suma exactă necesară pentru acest schimb. Va trebui să plătiți o taxă suplimentară dacă doriți să faceți din nou un schimb similar.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Revocă aprobarea anterioară", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Efectuați o tranzacție care va elimina aprobarea anterioară a contractului cu {provider}.", - "TR_EXCHANGE_BUY": "Pentru", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirmați pe Trezor și trimiteți", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirmați și trimiteți", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Creați aprobarea", @@ -553,8 +538,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Tranzacția s-a încheiat cu succes.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Schimbul a avut succes", "TR_EXCHANGE_DEX": "Oferta de schimb descentralizată", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Comisioanele pentru efectuarea acestui schimb sunt estimate la {approvalFee} {approvalFeeFiat} pentru aprobare (dacă este necesar) și {swapFee} {swapFeeFiat} pentru schimb.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Nu au rămas fonduri pentru taxele de tranzacție. Vă rugăm să reduceți suma de schimb la maxim {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} nu este valid", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldName} este un alt mod de a identifica tranzacția pe care o veți primi; ajută bursa să se asigure că depozitul corect ajunge în contul corect. Unele valute folosesc, de asemenea, termenii \"memo\", \"message\" sau \"payment ID\". {extraFieldDescription}", @@ -564,7 +547,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Informații privind ofertele cu rată fixă TODO", "TR_EXCHANGE_FLOAT": "Oferta cu rată flotantă", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Informații privind ofertele cu rată flotantă TODO", - "TR_EXCHANGE_PROVIDER": "furnizor", "TR_EXCHANGE_RATE": "Rata de schimb", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Contul de primire este în afara Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Aceasta este adresa alfanumerică specifică care va primi monedele.", @@ -585,10 +567,8 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_CUSTOM": "Particularizată", "TR_EXCHANGE_SWAP_SLIPPAGE_MINIMUM": "Suma minimă primită", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Vă rugăm să introduceți un număr.", - "TR_EXCHANGE_TRANS_ID": "ID Trans.:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Folosiți un cont non-suite de {symbol}", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Adresa de primire", - "TR_EXCHANGE_VIEW_DETAILS": "Vizualizare detalii", "TR_EXPORT_AS": "Exportați ca {as}", "TR_EXPORT_FAIL": "Exportare eșuată.", "TR_EXPORT_TO_FILE": "Exportați în fișier", @@ -777,7 +757,6 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nou", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Noul Trezor Bridge este disponibil.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Firmware Trezor nou este disponibil! Vă rugăm să vă actualizați dispozitivul.", "TR_NONCE": "Nonce", "TR_NORTH": "Nord", "TR_NOTIFICATIONS": "Notificări", @@ -789,10 +768,6 @@ "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {cont} few {conturi} other {de conturi}} • {fiatValue}", "TR_N_TRANSACTIONS": "{value} {value, plural, one {tranzacţie} few {tranzacţii} other {de tranzacţii}}", "TR_OFF": "dezactivat", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Suma aleasă de {amount} {currency} este mai mare decât maximul acceptat de {max} {currency}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Suma aleasă de {amount} {currency} este mai mare decât maximul acceptat de {max} {currency}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Suma aleasă de {amount} {currency} este mai mică decât minimul acceptat de {min} {currency}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Suma aleasă de {amount} {currency} este mai mică decât minimul acceptat de {min} {currency}.", "TR_OK": "OK", "TR_ON": "activat", "TR_ONBOARDING_ADVANCED": "Avansat", @@ -1010,7 +985,6 @@ "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Instalează automat regulile", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Lipsesc regulile udev", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Stare neașteptată: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Dispozitivul este conectat corect, dar browserul de internet nu poate comunica în acest moment. Va trebui să instalați Trezor Bridge.", "TR_TRY_AGAIN": "Încercați din nou", "TR_TXID": "TX ID", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", diff --git a/packages/suite-data/files/translations/ru.json b/packages/suite-data/files/translations/ru.json index 67f19d921e8..db80d779be1 100644 --- a/packages/suite-data/files/translations/ru.json +++ b/packages/suite-data/files/translations/ru.json @@ -128,6 +128,7 @@ "RECIPIENT_REQUIRES_UPDATE": "Taproot не поддерживается вашей версией прошивки. Пожалуйста, обновите прошивку вашего устройства.", "RECIPIENT_SCAN": "Сканировать", "REFRESH": "Обновить", + "REMAINING_BALANCE_LESS_THAN_RENT": "После отправки этой суммы на Вашем счету останется {remainingSolBalance} SOL. Непустой счет должен поддерживать баланс более {rent} SOL.", "REVIEW_AND_SEND_TRANSACTION": "Проверить и отправить", "SEND_RAW": "Отправить необработанную", "SEND_RAW_TRANSACTION_TOOLTIP": "Вы можете сами внести все исходные данные для вашей транзакции.", @@ -162,6 +163,7 @@ "TOAST_COPY_TO_CLIPBOARD": "Скопировано в буфер обмена", "TOAST_DEVICE_WIPED": "Устройство успешно стерто", "TOAST_DISCOVERY_ERROR": "Ошибка обнаружения учетной записи: {error}", + "TOAST_ESTIMATED_FEE_ERROR": "Оценка комиссии из сети не удалась. Использование резервного значения.", "TOAST_GENERIC_ERROR": "Ошибка: {error}", "TOAST_PIN_CHANGED": "PIN-код был изменен\n", "TOAST_QR_INCORRECT_ADDRESS": "QR-код содержит неверный адрес для этой учетной записи", @@ -171,11 +173,14 @@ "TOAST_SIGN_MESSAGE_ERROR": "Ошибка подписи сообщения: {error}", "TOAST_SIGN_MESSAGE_SUCCESS": "Сообщение было подписано", "TOAST_SIGN_TX_ERROR": "Ошибка подписи транзакции: {error}", + "TOAST_SUCCESSFUL_CLAIM": "{symbol} получено успешно", "TOAST_TX_BUTTON": "Подробнее", "TOAST_TX_CLAIMED": "", "TOAST_TX_CONFIRMED": "Транзакция {amount} на {account} успешно подтверждена", "TOAST_TX_RECEIVED": "{amount} получено на {account}", "TOAST_TX_SENT": "{amount} отправлено с {account}", + "TOAST_TX_STAKED": "{amount} состаковано с {account}", + "TOAST_TX_UNSTAKED": "{amount} отстаковано", "TOAST_VERIFY_ADDRESS_ERROR": "Ошибка проверки адреса: {error}", "TOAST_VERIFY_MESSAGE_ERROR": "Ошибка проверки сообщения: {error}", "TOAST_VERIFY_MESSAGE_SUCCESS": "Проверка сообщения успешна", @@ -188,7 +193,6 @@ "TR_404_GO_TO_DASHBOARD": "Перейдите на Инфопанель", "TR_404_TITLE": "Ошибка 404: Ссылка не найдена", "TR_7D_CHANGE": "Смена 7d", - "TR_ABORT": "Прервать", "TR_ACCESS_HIDDEN_WALLET": "Войти в скрытый кошелек", "TR_ACCESS_STANDARD_WALLET": "Войти в стандартный кошелек", "TR_ACCOUNT_DETAILS_HEADER": "Account details", @@ -227,8 +231,19 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot - это новый тип учетной записи, который может повысить конфиденциальность и эффективность сети. Некоторые службы могут пока не поддерживать адреса формата Taproot.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "Текущий и наиболее широко распространенный метод генерации и управления адресами Cardano обеспечивает совместимость, безопасность и поддержку всех типов токенов.", + "TR_ACCOUNT_TYPE_COINJOIN": "Coinjoin", + "TR_ACCOUNT_TYPE_DEFAULT": "По-умолчанию", + "TR_ACCOUNT_TYPE_IMPORTED": "Импортирован", + "TR_ACCOUNT_TYPE_LEDGER": "Ledger", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Счета Ledger совместимы с путями деривации Ledger Live, что обеспечивает плавный переход от Ledger к Trezor.", + "TR_ACCOUNT_TYPE_LEGACY": "Устаревший", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Устаревшие счета совместимы с путями деривации Ledger Legacy, что обеспечивает плавный переход от Ledger к Trezor.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "Текущий и наиболее широко распространенный метод генерации и управления адресами {value} обеспечивает совместимость, безопасность и поддержку всех типов токенов.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "Текущий и наиболее широко распространенный метод генерации и управления адресами Solana обеспечивает совместимость, безопасность и поддержку токенов SOL и SPL.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Не поддерживается.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Этот тип учетной записи не поддерживается на этой модели Trezor.", + "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", "TR_ACCOUNT_TYPE_SHELLEY": "Shelley", "TR_ACCOUNT_TYPE_SHELLEY_DESC": "Адреса эпохи Shelley представили новый тип кошелька, который может поддерживать делегирование stake и получение вознаграждений.", "TR_ACCOUNT_TYPE_SLIP25_DESC": "Что такое счет coinjoin?", @@ -237,18 +252,22 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_DESC": "Bip44Change аккаунт", "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_NAME": "Bip44Change", "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", + "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Пожалуйста, обновите прошивку устройства, чтобы включить этот тип счета.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP - это цифровая валюта, которая позволяет осуществлять быстрые и недорогие трансграничные платежи, не прибегая к традиционному майнингу, используя консенсусную бухгалтерскую книгу для быстрого подтверждения транзакций.", "TR_ACQUIRE_DEVICE": "Использовать Trezor здесь", "TR_ACQUIRE_DEVICE_TITLE": "Запущена другая сессия ", "TR_ACTIVATED_COINS": "Активируйте криптовалюты", "TR_ACTIVE": "активированных", "TR_ADDRESSES": "Адрес", + "TR_ADDRESSES_CHANGE": "Изменить адреса", "TR_ADDRESSES_FRESH": "Свежие адреса", "TR_ADDRESSES_USED": "Используемые адреса", "TR_ADDRESS_DISPLAY": "Отображение адреса", "TR_ADDRESS_DISPLAY_DESCRIPTION": "Отображать адрес с пробелами (bc1w etes ... v54d 8d) или без (bc1wetes...v54d8d).", "TR_ADDRESS_MODAL_CLIPBOARD": "Копировать адрес", "TR_ADDRESS_MODAL_TITLE": "{networkName} адрес для получения", + "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkCurrencyName} получает адрес в сети {networkName} ", "TR_ADDRESS_PHISHING_WARNING": "Чтобы предотвратить фишинговые атаки, Вам следует проверить адрес, указанный на Вашем Trezor. {claim}", "TR_ADD_ACCOUNT": "Добавить счёт", "TR_ADD_HIDDEN_WALLET": "Добавить скрытый кошелёк", @@ -275,6 +294,10 @@ "TR_ALL": "Все", "TR_ALLOW_ANALYTICS": "Данные об использовании", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Все данные остаются строго анонимными; они используются только для улучшения экосистемы Trezor.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Автоматические обновления Trezor Suite", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite автоматически загружает последнюю версию в фоновом режиме и устанавливает ее при перезапуске приложения. Это гарантирует, что Вы всегда будете в курсе последних функций и исправлений безопасности. Обновления происходят без Вашего разрешения.", + "TR_ALL_NETWORKS": "Все сети ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Просмотрите токены из всех {networkCount} сетей. Фильтруйте по наиболее популярным сетям справа.", "TR_ALL_TRANSACTIONS": "Транзакции", "TR_AMOUNT_SENT": "Отправленная сумма", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Не подходит для CoinJoin - слишком высокая сумма", @@ -296,6 +319,8 @@ "TR_AUTH_CONFIRM_FAILED_RETRY": "Повторить", "TR_AUTH_CONFIRM_FAILED_TITLE": "Неправильная кодовая фраза", "TR_AUTOSTOP_COINJOIN_ENABLED": "Coinjoin будет остановлен после этого раунда", + "TR_AUTO_START": "Запускать Trezor Suite автоматически", + "TR_AUTO_START_DESCRIPTION": "Запускать Trezor Suite в фоновом режиме, когда Вы входите в компьютер.", "TR_AUTO_STOP_TOOLTIP": "Coinjoin находится в стадии подписания. Нажмите, чтобы остановить его после этого раунда.", "TR_AVAILABLE_NOW_FOR": "Доступно сейчас для", "TR_AVOID_USING_DEVICE": "Не используйте это устройство и не переводите на него средства.", @@ -332,15 +357,17 @@ "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "Прежде чем Вы сможете перейти на прошивку только для Bitcoin, нам необходимо обновить Вашу прошивку до более новой версии.", "TR_BREAKING_ANONYMITY_CHECKBOX": "Я понимаю, что нарушаю свою анонимность", + "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Запуск Trezor Bridge на порту 21324", "TR_BRIDGE_DEV_MODE_STOP": "Запуск Trezor Bridge на порту по умолчанию", + "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Вы уверены? Ваше устройство может одновременно использоваться только одним приложением. Если в данный момент Вы используете другое приложение на устройстве Trezor, сначала завершите эту сессию.", + "TR_BRIDGE_NEEDED_DESCRIPTION": "Ваш браузер не поддерживается. Для получения наилучших впечатлений загрузите и запустите в фоновом режиме настольное приложение Trezor Suite или используйте поддерживаемый браузер на базе Chromium, совместимый с WebUSB.", + "TR_BRIDGE_REQUESTED_DESCRIPTION": "Другое приложение запросило Trezor Suite для соединения с Вашим устройством Trezor. Продолжайте работу Trezor Suite в фоновом режиме и повторите действие в другом приложении.", "TR_BTC_UNITS": "Единицы Bitcoin", "TR_BUG": "Что-то не работает?", "TR_BUMP_FEE": "Повысить комиссию", "TR_BUY": "Купить", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Сделки", "TR_BUY_BUY": "Купите", - "TR_BUY_BUY_AGAIN": "Купить снова", "TR_BUY_CONFIRMED_ON_TREZOR": "Подтверждено на Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Назад к счету", "TR_BUY_DETAIL_ERROR_SUPPORT": "Перейти к поддержке провайдера", @@ -385,12 +412,10 @@ "TR_BUY_STATUS_PENDING": "В ожидании", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "В ожидании", "TR_BUY_STATUS_SUCCESS": "Одобрено", - "TR_BUY_TRANS_ID": "Транс. ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Максимум - {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Максимум составляет {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Минимум - {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Минимум составляет {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Посмотреть детали", "TR_BYTES": "байтов", "TR_CAMERA_NOT_RECOGNIZED": "Камера не была обнаружена.", "TR_CAMERA_PERMISSION_DENIED": "Разрешение на доступ к камере было отклонено.", @@ -409,13 +434,13 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Сумма Трезор", "TR_CHAINED_TXS": "Цепные транзакции", "TR_CHANGELOG": "Список изменений", - "TR_CHANGELOG_ON_GITHUB": "Список изменений на GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Этот адрес был создан как неизрасходованный остаток (\"change\") предыдущей транзакции.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Тип прошивки в любое время можно изменить в Настройках.", "TR_CHANGE_HOMESCREEN": "Поменять заставку", "TR_CHANGE_PIN": "Изменить PIN", "TR_CHANGE_WIPE_CODE": "Изменение кода очистки", "TR_CHECKING_YOUR_DEVICE": "Проверка устройства", + "TR_CHECKSUM_CONVERSION_INFO": "Преобразовано в контрольную сумму. Подробнее", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Мы проверим целостность Вашего устройства Trezor, обеспечим его сохранность и подтвердим подлинность чипа.", "TR_CHECK_DEVICE_ORIGIN_TITLE": "Проверить устройство", "TR_CHECK_FINGERPRINT": "Проверьте fingerprint ", @@ -425,8 +450,6 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Выполните пробное восстановление для проверки резервной копии.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Введите слова из Вашей резервной копии в том порядке, в котором они отображаются на Вашем устройстве. В качестве дополнительной меры безопасности Вам может быть предложено ввести несколько слов, которые не являются частью резервной копии.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Ваша фраза восстановления (резервная копия кошелька) вводится с помощью кнопок Trezor Model R. Это позволяет избежать раскрытия любой конфиденциальной информации на потенциально небезопасном компьютере или веб-браузере.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Ваша фраза восстановления (резервная копия кошелька) вводится с помощью кнопок Trezor Model R. Это позволяет избежать раскрытия любой конфиденциальной информации на потенциально небезопасном компьютере или веб-браузере.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Ваша фраза восстановления (резервная копия кошелька) вводится с помощью кнопок Trezor Model R. Это позволяет избежать раскрытия любой конфиденциальной информации на потенциально небезопасном компьютере или веб-браузере.", "TR_CHECK_SEED": "Проверить резервную копию", "TR_CHECK_YOUR_DEVICE": "Посмотрите на экран своего Trezor", "TR_CHOOSE_RECOVERY_TYPE": "Выберите тип восстановления", @@ -479,12 +502,76 @@ "TR_COINJOIN_TILE_3_DESCRIPTION": "Ваш биткоин всегда находится под Вашим контролем", "TR_COINJOIN_TILE_3_TITLE": "Защищено вашим Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Транзакции coinjoin", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Автоматическое обновление через", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Назад к Торговле", - "TR_COINMARKET_NO_OFFERS_HEADER": "Нет предложений", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Извините, в данный момент у нас нет предложений из-за проблем с подключением к серверу.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Извините, в настоящее время у нас нет предложений. Попробуйте перезагрузить страницу или изменить запрос.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Обновить предложения", + "TR_COINMARKET_BEST_RATE": "Лучший курс", + "TR_COINMARKET_BUY_AND_SELL": "Купить и продать", + "TR_COINMARKET_CEX_TOOLTIP": "Централизованный обмен", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Изменить сумму или валюту.", + "TR_COINMARKET_COMPARE_OFFERS": "Сравнить все предложения", + "TR_COINMARKET_COUNTRY": "Страна резиденции", + "TR_COINMARKET_DCA_DOWNLOAD": "Скачайте мобильное приложение Invity, чтобы начать экономить в биткойнах", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Безопасный и простой кастодиальный сберегательный план DCA.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "Разработано SatoshiLabs", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Вывод на собственный кастодий без дополнительных комиссий.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Бесплатное снятие", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Быстрый, оптимизированный, удобный интерфейс.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Простота использования", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Отслеживайте историю Ваших инвестиций, их количество и частоту.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "Обзор DCA", + "TR_COINMARKET_DCA_HEADING": "Экономьте в биткойнах с помощью приложения Invity", + "TR_COINMARKET_DEX_TOOLTIP": "Децентрализованный обмен", + "TR_COINMARKET_ENTER_AMOUNT_IN": "Введите сумму в {currency}", + "TR_COINMARKET_NETWORK_TOKENS": "", + "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "", + "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "", + "TR_COINMARKET_NO_METHODS_AVAILABLE": "", + "TR_COINMARKET_OFFERS_EMPTY": "", + "TR_COINMARKET_OFFERS_REFRESH": "", + "TR_COINMARKET_OFFERS_SELECT": "", + "TR_COINMARKET_OFFER_LOOKING": "", + "TR_COINMARKET_OFFER_NO_FOUND": "", + "TR_COINMARKET_ON_NETWORK_CHAIN": "", + "TR_COINMARKET_OTHER_CURRENCIES": "", + "TR_COINMARKET_PAYMENT_METHOD": "", + "TR_COINMARKET_POPULAR_CURRENCIES": "", + "TR_COINMARKET_RATE": "", + "TR_COINMARKET_RECEIVE_METHOD": "", + "TR_COINMARKET_SELL": "", + "TR_COINMARKET_SHOW_OFFERS": "", + "TR_COINMARKET_SWAP": "", + "TR_COINMARKET_SWAP_AMOUNT": "", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Правовая информация", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Безопасность прежде всего с Вашим Trezor", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "Я хочу обменивать криптовалюты на DEX (децентрализованная биржа), используя контракт {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Я хочу обменивать криптовалюты на свой собственный счет. Я понимаю, что политика провайдера может потребовать проверки личности.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Я понимаю, что транзакции с криптовалютой являются окончательными и не могут быть отменены или возвращены. Потери из-за мошенничества или ошибок могут быть невозможны.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Я понимаю, что Invity не предоставляет эту услугу. Она регулируется правилами и условиями сайта {provider}.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Я не использую эту функцию для азартных игр, мошенничества или любой деятельности, нарушающей Условия предоставления услуг Invity или провайдера, а также любые применимые законы.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Я понимаю, что криптовалюты - это развивающийся финансовый инструмент и что нормативные акты могут отличаться в зависимости от региона. Это может увеличить риск мошенничества, кражи или нестабильности рынка.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Проверенные партнеры Invity", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Я готов обменивать", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "Поменять {fromCrypto} на {toCrypto} с помощью {provider}", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Правовая информация", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Безопасность прежде всего с Вашим Trezor", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Я здесь для того, чтобы обменивать криптовалюты. Если меня направили на этот сайт по какой-либо другой причине, я свяжусь со службой поддержки Trezor, прежде чем продолжить.", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Я хочу обменивать криптовалюты на свой собственный счет. Я понимаю, что политика провайдера может потребовать проверки личности.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Я понимаю, что транзакции с криптовалютой являются окончательными и не могут быть отменены или возвращены. Потери из-за мошенничества или ошибок могут быть невозможны.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Я понимаю, что Invity не предоставляет эту услугу. Она регулируется правилами и условиями сайта {provider}.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Я не использую эту функцию для азартных игр, мошенничества или любой деятельности, нарушающей Условия предоставления услуг Invity или провайдера, а также любые применимые законы.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Я понимаю, что криптовалюты - это развивающийся финансовый инструмент и что нормативные акты могут отличаться в зависимости от региона. Это может увеличить риск мошенничества, кражи или нестабильности рынка.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Проверенные партнеры Invity", + "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} в сети {networkName}", + "TR_COINMARKET_TRADE_FEE": "Коммиссии торгов", + "TR_COINMARKET_UNKNOWN_PROVIDER": "Неизвестный провайдер", + "TR_COINMARKET_YOUR_BEST_OFFER": "Ваше лучшее предложение", + "TR_COINMARKET_YOU_BUY": "Вы покупаете", + "TR_COINMARKET_YOU_GET": "Вы получаете", + "TR_COINMARKET_YOU_PAY": "Вы заплатите", + "TR_COINMARKET_YOU_RECEIVE": "Вы получите", + "TR_COINMARKET_YOU_SELL": "Вы продаете", + "TR_COINMARKET_YOU_WILL_GET": "Вы получите", + "TR_COINMARKET_YOU_WILL_PAY": "Вы заплатите", "TR_COINS": "Монеты", "TR_COIN_CONTROL": "Coin control", "TR_COIN_CONTROL_TOOLTIP": "Coin control позволяет вручную выбирать UTXO, которые будут использоваться в качестве входов для транзакции.", @@ -501,6 +588,7 @@ "TR_CONFIRMED_TX": "Подтверждено", "TR_CONFIRMING_TX": "Подтверждение транзакции", "TR_CONFIRM_ACTION_ON_YOUR": "Следуйте инструкциям на экране Вашего Trezor", + "TR_CONFIRM_ADDRESS": "Подтвердить адрес", "TR_CONFIRM_BEFORE_COPY": "Подтвердить Trezor перед копированием", "TR_CONFIRM_CONDITIONS": "Подтвердите условия, прежде чем продолжить.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "Подтвердите пустой Скрытый кошелек на \"{deviceLabel}\" устройстве.", @@ -512,13 +600,22 @@ "TR_CONFIRM_ON_TREZOR": "Подтвердите на Trezor", "TR_CONFIRM_PASSPHRASE": "Подтверждение кодовой фразы", "TR_CONFIRM_PASSPHRASE_SOURCE": "Подтверждение пустой кодовой фразы скрытого кошелька на устройстве \"{deviceLabel}\".", + "TR_CONFIRM_PASSPHRASE_WITHOUT_ADVICE_DESCRIPTION": "Введите свою кодовую фразу для авторизации этого действия.", "TR_CONGRATS": "Поздравляем!", "TR_CONNECT": "Подключиться", "TR_CONNECTED": "Подключён", "TR_CONNECTED_DIFFERENT_DEVICE": "Подключено другое устройство?", "TR_CONNECTED_TO_PROVIDER": "Подключено к {provider} как {user}", "TR_CONNECTED_TO_PROVIDER_LOCALLY": "Сохраняем метки локально ", + "TR_CONNECTION_LOST": "Соединение потеряно", + "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Управление", + "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Включите появления диалога ввода парольной фразы при открытии Trezor Suite.", + "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Используете ли Вы в основном парольную фразу?", "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Проверьте на Trezor, чтобы подтвердить адрес получения. Продолжать без подтверждения не рекомендуется.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "Адрес получения не может быть проверен", + "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Чтобы отправить монеты, подключите Ваш Trezor.", + "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Ваш Trezor не подключен", + "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Подключите Trezor для отправки", "TR_CONNECT_YOUR_DEVICE": "Подключите ваш Trezor", "TR_CONTACT_SUPPORT": "Обратиться в поддержку", "TR_CONTACT_TREZOR_SUPPORT": "Обратитесь в службу поддержки Trezor", @@ -528,14 +625,18 @@ "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION": "Сбрасывая устройство, помните: даже поддержка Trezor не поможет вам восстановить ваши средства без фразы восстановления. В случае если у вас есть несколько фраз восстановления, убедитесь, что держите наготове фразу именно для сбрасываемого в данный момент устройства Trezor.", "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION_2": "在继续之前, 中检查您的备份。这是检查和验证恢复种子的简单方法。", "TR_CONTINUE_ON_TREZOR": "Продолжите на Trezor", + "TR_CONTINUE_TO_BACKUP": "Продолжите для создания резервной копии", "TR_CONTINUE_TO_PIN": "Настроить и PIN-код", "TR_CONTRACT": "Контракт", + "TR_CONTRACT_ADDRESS": "Адрес договора:", "TR_CONTRACT_TRANSACTION": "Контрактная транзакция", "TR_CONVERT_TO_CHECKSUM_ADDRESS": "Преобразовать в адрес контрольной суммы", "TR_CONVERT_TO_LOWERCASE": "Преобразовать в строчные буквы", + "TR_COPY_ADDRESS_CONTRACT": "Никогда не отправляйте средства на контрактный адрес.", + "TR_COPY_ADDRESS_FINGERPRINT": "Никогда не отправляйте средства на адрес отпечатка.", + "TR_COPY_ADDRESS_POLICY_ID": "Никогда не пересылайте средства на адрес идентификатора политики.", "TR_COPY_AND_CLOSE": "Скопировать и закрыть", "TR_COPY_SIGNED_MESSAGE": "Копировать подписанное сообщение", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Копировать", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Не удалось загрузить список изменений", "TR_COULD_NOT_RETRIEVE_DATA": "Не удалось загрузить данные", "TR_COUNT_WALLETS": "{count} {count, plural, one {кошелёк} few {кошелька} many {кошельков} other {кошельков}}", @@ -667,7 +768,6 @@ "TR_DOWNLOADING": "Загрузка", "TR_DOWNLOAD_LATEST_BRIDGE": "Скачать последнюю версию Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Не отключайте свое устройство", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Вы уверены, что хотите пропустить этот шаг?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Перетащите сюда файл или нажмите для выбора из файлов", "TR_DROPZONE_ERROR": "Импорт не удался: {error}", @@ -745,7 +845,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Одобрите только точную сумму, необходимую для данного обмена. Вам нужно будет заплатить дополнительную комиссию, если Вы захотите совершить подобный обмен еще раз.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Отменить предыдущее разрешение", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Выполнит операцию, которая удалит предыдущее одобрение контракта с {provider}.", - "TR_EXCHANGE_BUY": "Для", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Подтвердить на Trezor и отправить", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Проверка и отправка", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Создать разрешение", @@ -767,8 +866,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Ваша транзакция прошла успешно.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Одобрено", "TR_EXCHANGE_DEX": "Предложение децентрализованной биржи", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Комиссия за выполнение этого обмена оценивается в {approvalFee} ({approvalFeeFiat}) на утверждение (если требуется) и {swapFee} ({swapFeeFiat}) на замену.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Не осталось средств для оплаты комиссии за транзакцию. Пожалуйста, уменьшите сумму обмена максимального до {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} является недействительным", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -778,7 +875,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Фиксированные курсы показывают Вам точную сумму, которую Вы получите в конце обмена - сумма не изменится с момента выбора курса до завершения транзакции. Вам гарантирована указанная сумма, но эти ставки обычно менее щедрые, а значит, за свои деньги не купишь столько криптовалют.", "TR_EXCHANGE_FLOAT": "Предложение с плавающей ставкой", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Плавающие ставки означают, что конечная сумма, которую Вы получите, может немного измениться из-за колебаний на рынке в период между моментом выбора ставки и моментом завершения транзакции. Эти ставки обычно выше, что означает, что в конечном итоге Вы можете получить больше криптовалюты.", - "TR_EXCHANGE_PROVIDER": "Провайдер", "TR_EXCHANGE_RATE": "Цена", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Счет получения находится за пределами Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Это конкретный буквенно-цифровой адрес, на который поступит ваша криптовалюта.", @@ -805,13 +901,14 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Пожалуйста, введите номер.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Введите желаемое отклонение от курса", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Сумма предложения обмена", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Итоги отклонения от курса", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Допустимость отклонения от курса", - "TR_EXCHANGE_TRANS_ID": "ID транзакции:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Используйте учетную запись ({symbol}), которой нет в Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Адрес получения", - "TR_EXCHANGE_VIEW_DETAILS": "Посмотреть детали", "TR_EXPERIMENTAL_FEATURES": "Экспериментальные функции", + "TR_EXPERIMENTAL_PASSWORD_MANAGER": "", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "", "TR_EXPORT_AS": "Экспортировать как {as}", "TR_EXPORT_FAIL": "Экспорт не удался.", "TR_EXPORT_TO_FILE": "Экспорт в файл", @@ -837,12 +934,15 @@ "TR_FIAT_RATES_NOT_AVAILABLE": "Нет данных", "TR_FIAT_RATES_NOT_AVAILABLE_TOOLTIP": "В настоящее время фиатные тарифы не доступны.", "TR_FINAL_HEADING": "Настройка завершена!", + "TR_FINGERPRINT_ADDRESS": "", "TR_FIRMWARE": "Прошивка", "TR_FIRMWARE_CHECK_AUTHENTICITY_SUCCESS": "Прошивка аутентичная", "TR_FIRMWARE_HASH_MISMATCH": "На Вашем Trezor установлена неофициальная прошивка. Пожалуйста, немедленно свяжитесь с help@trezor.io.", "TR_FIRMWARE_INSTALLED_TEXT": "На этом устройстве установлена прошивка {version} ({type}).", "TR_FIRMWARE_IS_POTENTIALLY_RISKY": "Обновление прошивки - потенциально рискованная операция. Если что-то пойдет не так (повреждение кабеля и т.д.), устройство может оказаться в стертом состоянии, что приведет к временной потере доступа ко всем вашим монетам, пока вы не восстановите хранилище из резервной копии.", "TR_FIRMWARE_IS_UP_TO_DATE": "Прошивка готова", + "TR_FIRMWARE_LANGUAGE_CHANGED": "", + "TR_FIRMWARE_LANGUAGE_FETCH_ERROR": "", "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Доступна новая версия прошивки. Вы можете либо обновить устройство сейчас, либо продолжить и обновить его позже.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Ваше устройство уже обновлено до последней версии прошивки. При необходимости Вы можете переустановить прошивку.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Завершена", @@ -1097,7 +1197,6 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Новый", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Доступен новый Trezor Bridge", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Доступна новая прошивка Trezor! Пожалуйста, обновите свое устройство.", "TR_NONCE": "Нонс", "TR_NORMAL_ACCOUNTS": "Счета по умолчанию", "TR_NORTH": "Север", @@ -1118,10 +1217,6 @@ "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {счёт} few {счёта} many {счетов} other {счетов}} • {fiatValue}", "TR_N_TRANSACTIONS": "{value} {value, plural, one {транзакция} 0 {транзакций} few {транзакции} many {транзакций} other {транзакции4}}", "TR_OFF": "выкл", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Выбранная сумма {amount} больше, чем принятый максимум {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Выбранная сумма {amount} больше, чем допустимый максимум {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Выбранная сумма {amount} меньше, чем принятый минимум {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Выбранная сумма {amount} меньше, чем принятый минимум {min}.", "TR_OFFICIAL_LANGUAGES": "Официальные", "TR_OK": "OK", "TR_ON": "вкл", @@ -1154,30 +1249,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Другие входы и выходы", "TR_OUTGOING": "Исходящие", "TR_OUTPUTS": "Выходы", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Минимальная и максимальная сумма, за которую этот пользователь готов продать {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Вам необходимо инициировать транзакцию на сайте {providerName} - внимательно выполните следующие шаги.", - "TR_P2P_GET_STARTED_ITEM_1": "Выберите «Перейти на сайт {providerName}» для перенаправления на сайт наших партнеров.", - "TR_P2P_GET_STARTED_ITEM_3": "Как только {providerName} попросит у вас «release address», вернитесь сюда и продолжите.", - "TR_P2P_GET_STARTED_ITEM_4": "Почти готово! Откройте и скопируйте свой адрес, вставьте его в поле «release address» на сайте {providerName} и завершите транзакцию.", - "TR_P2P_GO_TO_PROVIDER": "Перейти на сайт {providerName}", - "TR_P2P_INFO": "При использовании технологии {peerToPeer} (P2P) нет необходимости в проверке KYC ни со стороны покупателя, ни со стороны продавца. Все стороны защищены от мошенничества надежным {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "Я готов купить", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Одноранговая покупка {cryptocurrency} с {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Юридическое уведомление", - "TR_P2P_MODAL_SECURITY_HEADER": "Безопасность прежде всего с Вашим Trezor", - "TR_P2P_MODAL_TERMS_1": "Вы находитесь здесь, чтобы купить криптовалюту у другого выбранного Вами человека, используя технологию Peer-to-Peer (P2P) без проверки ID. Если Вы попали на этот сайт по какой-либо другой причине, пожалуйста, свяжитесь со службой поддержки, прежде чем продолжить.", - "TR_P2P_MODAL_TERMS_2": "Вы понимаете, что операции с криптовалютой необратимы и не подлежат возврату. Таким образом, мошеннические или случайные потери могут быть невозместимы.", - "TR_P2P_MODAL_TERMS_4": "Вы понимаете, что Invity не предоставляет эту услугу. Условия компании {provider} регулируют данную услугу.", - "TR_P2P_MODAL_TERMS_5": "Вы не используете эту функцию для азартных игр, мошенничества или любого другого нарушения Условий предоставления услуг Invity или провайдера, а также любых применимых нормативных актов.", - "TR_P2P_MODAL_TERMS_6": "Вы понимаете, что криптовалюты являются развивающимся финансовым инструментом и что в разных юрисдикциях могут действовать разные нормативные акты. Это может подвергнуть Вас повышенному риску мошенничества, кражи или нестабильности рынка.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Проверенные партнеры компании Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "Сделка должна быть завершена в течение этого срока, считая с момента создания контракта на сайте {providerName} .", - "TR_P2P_PRICE": "Цена за 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "{symbol} цена, предложенная этим пользователем.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Привет, я использую {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Выбранная сумма {amount} выше, чем допустимый максимум {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Выбранное количество {amount} меньше допустимого минимума {minimum}.", "TR_PAGINATION_NEWER": "Новее", "TR_PAGINATION_OLDER": "Старше", "TR_PASSPHRASE_CASE_SENSITIVE": "Примечание: Кодовая фраза чувствительна к регистру.", @@ -1332,10 +1403,6 @@ "TR_SELL_STATUS_ERROR": "Отклонено", "TR_SELL_STATUS_PENDING": "В ожидании", "TR_SELL_STATUS_SUCCESS": "Одобрено", - "TR_SELL_TRANS_ID": "ID транзакции:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Максимум составляет {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Минимум составляет {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Посмотреть детали", "TR_SENDFORM_LABELING_EXAMPLE_1": "Сбережения", "TR_SENDFORM_LABELING_EXAMPLE_2": "Аренда", "TR_SENDING_SYMBOL": "Отправка {multiple, select, true {нескольких токенов} false {{symbol}} other {{symbol}}}", @@ -1514,10 +1581,7 @@ "TR_TO_BTC": "В BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Чтобы Ваши метки синхронизировались и были доступны на разных устройствах, подключитесь к сервису облачного хранилища.", "TR_TO_SATOSHIS": "В sat", - "TR_TRADE_BUYS": "покупки", - "TR_TRADE_EXCHANGES": "обмены", "TR_TRADE_REDIRECTING": "Перенаправление ...", - "TR_TRADE_SELLS": "продажи", "TR_TRANSACTIONS_NOT_AVAILABLE": "История транзакций недоступна", "TR_TRANSACTIONS_SEARCH_TIP_1": "Совет: Вы можете искать среди ID транзакций, адресов, меток, сумм и дат.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Совет: Для более сложного поиска можно комбинировать операторы AND (&) и OR (|). Например, >> 2020-01-01 & < 2020-01-31 | > 2020-12-01 & < 2020-12-31 покажет все транзакции за январь 2020 года или декабрь 2020 года.", @@ -1552,7 +1616,6 @@ "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Установить правила автоматически", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Отсутствуют правила udev", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Неожиданное состояние: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Ваше устройство подключено правильно, но в данный момент Ваш интернет-браузер не может установить с ним связь. Вам необходимо установить Trezor Bridge.", "TR_TRY_AGAIN": "Ввести другую кодовую фразу", "TR_TXID": "TX ID", "TR_TX_CONFIRMATIONS": "{confirmationsCount}х", diff --git a/packages/suite-data/files/translations/sk.json b/packages/suite-data/files/translations/sk.json index a6d4b2e4e52..36c83cfcf21 100644 --- a/packages/suite-data/files/translations/sk.json +++ b/packages/suite-data/files/translations/sk.json @@ -191,7 +191,6 @@ "TR_404_GO_TO_DASHBOARD": "Prejsť na ovládací panel", "TR_404_TITLE": "Chyba 404: Odkaz nebol nájdený", "TR_7D_CHANGE": "7d change", - "TR_ABORT": "Abort", "TR_ACCESS_HIDDEN_WALLET": "Prístup k skrytej peňaženke", "TR_ACCESS_STANDARD_WALLET": "Prístup k štandardnej peňaženke", "TR_ACCOUNT_DETAILS_HEADER": "Podrobnosti o účte", @@ -343,9 +342,7 @@ "TR_BUG": "Bug", "TR_BUMP_FEE": "Bump fee", "TR_BUY": "Kúpiť", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Obchodné transakcie", "TR_BUY_BUY": "Kúpiť", - "TR_BUY_BUY_AGAIN": "Kúpiť znova", "TR_BUY_CONFIRMED_ON_TREZOR": "Potvrdené na Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Späť do účtu", "TR_BUY_DETAIL_ERROR_SUPPORT": "Prejsť na podporu poskytovateľa", @@ -390,12 +387,10 @@ "TR_BUY_STATUS_PENDING": "Pending", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Pending", "TR_BUY_STATUS_SUCCESS": "Approved", - "TR_BUY_TRANS_ID": "Trans. ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maximum is {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum is {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum is {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum is {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "View details", "TR_BYTES": "bytes", "TR_CAMERA_NOT_RECOGNIZED": "The camera was not recognized.", "TR_CAMERA_PERMISSION_DENIED": "Permission to access the camera was denied.", @@ -414,7 +409,6 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor amount", "TR_CHAINED_TXS": "Chained transactions", "TR_CHANGELOG": "Changelog", - "TR_CHANGELOG_ON_GITHUB": "Changelog on GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "This is a change address created from a previous send.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "You can change your firmware type in Settings anytime.", "TR_CHANGE_HOMESCREEN": "Change homescreen", @@ -430,8 +424,6 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Perform a simulated recovery to verify your wallet backup.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Enter the words from your wallet backup here in the order displayed on your device. You may be asked to type some words that aren't part of your wallet backup as an additional security measure.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Use the two-button pad to enter your wallet backup. By doing this, you're keeping all your sensitive info safe and sound, away from any shady or insecure computer or web browser.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Your wallet backup is entered using the touchscreen. This avoids exposing any of your sensitive information to a potentially insecure computer or web browser.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Your wallet backup is entered using the touchscreen. This avoids exposing any of your sensitive information to a potentially insecure computer or web browser.", "TR_CHECK_SEED": "Check backup", "TR_CHECK_YOUR_DEVICE": "Check your Trezor's screen", "TR_CHOOSE_RECOVERY_TYPE": "Choose recovery type", @@ -484,12 +476,6 @@ "TR_COINJOIN_TILE_3_DESCRIPTION": "Your bitcoin is always under your control", "TR_COINJOIN_TILE_3_TITLE": "Protected by your Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Coinjoin transactions", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Auto-reloading in", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Back to Trade", - "TR_COINMARKET_NO_OFFERS_HEADER": "Žiadne ponuky", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Je nám ľúto, ale momentálne nemáme žiadne ponuky z dôvodu problémov s pripojením k serveru.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Je nám ľúto, ale momentálne nemáme žiadne ponuky. Skúste znovu načítať stránku alebo zmeňte svoj požiadavok.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Obnoviť stránku", "TR_COINS": "Crypto", "TR_COIN_CONTROL": "Coin control", "TR_COIN_CONTROL_TOOLTIP": "Coin control enables manual selection of UTXOs to be used as inputs for a transaction.", @@ -553,7 +539,6 @@ "TR_COPY_ADDRESS_POLICY_ID": "Never send funds to a policy ID address.", "TR_COPY_AND_CLOSE": "Kopírovať a zavrieť", "TR_COPY_SIGNED_MESSAGE": "Skopírovať podpísanú správu", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Skopírovať", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Nepodarilo sa načítať zoznam zmien", "TR_COULD_NOT_RETRIEVE_DATA": "Nepodarilo sa načítať údaje", "TR_COUNT_WALLETS": "{count} {count, plural, one {wallet} other {wallets}}", @@ -709,7 +694,6 @@ "TR_DOWNLOADING": "Downloading", "TR_DOWNLOAD_LATEST_BRIDGE": "Download latest Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Don't disconnect your device", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Do you really want to skip this step?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Drag and drop file here or click to select from files", "TR_DROPZONE_ERROR": "Import failed: {error}", @@ -789,7 +773,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Approve only the exact amount required for this swap. You will need to pay an additional fee if you want to make a similar swap again.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Zrušenie predchádzajúceho schválenia", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Perform a transaction that will remove previous approval of contract with {provider}.", - "TR_EXCHANGE_BUY": "For", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Confirm on Trezor & send", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Confirm & Send", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Vytvoriť schválenie", @@ -811,8 +794,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Vaša transakcia bola úspešná.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Approved", "TR_EXCHANGE_DEX": "Decentralized exchange offer", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "The fees to perform this swap are estimated at {approvalFee} ({approvalFeeFiat}) for approval (if required) and {swapFee} ({swapFeeFiat}) for the swap.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "No funds remaining for the transaction fees. Please lower the exchange amount to max {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} je neplatný", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -822,7 +803,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Fixed rates show you exactly how much you'll end up with at the end of the exchange—the amount won't change between when you select the rate and when your transaction is complete. You're guaranteed the amount shown, but these rates are usually less generous, meaning your money won't buy as much crypto.", "TR_EXCHANGE_FLOAT": "Ponuka s pohyblivou sadzbou", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Floating rates mean that the final amount you'll get may change slightly due to fluctuations in the market between when you select the rate and when your transaction is complete. These rates are usually higher, meaning you could end up with more crypto in the end.", - "TR_EXCHANGE_PROVIDER": "Poskytovateľ", "TR_EXCHANGE_RATE": "Cena", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Receive account is outside of Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "This is the specific alphanumeric address that will receive your coins.", @@ -849,12 +829,9 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Zadajte číslo.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Zadajte požadovaný sklz.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Swap offer amount", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Prehľad sklzu", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Tolerancia sklzu", - "TR_EXCHANGE_TRANS_ID": "Trans. ID:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Use an account ({symbol}) that isn't in Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Adresa príjemcu", - "TR_EXCHANGE_VIEW_DETAILS": "Zobraziť podrobnosti", "TR_EXPERIMENTAL_FEATURES": "Experimentálne funkcie", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Allow experimental features", "TR_EXPERIMENTAL_FEATURES_WARNING": "There is no guarantee of long-term support of these features.", @@ -1181,7 +1158,6 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Nový", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "New Trezor Bridge is available.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Nový firmvér Trezor je k dispozícii! Aktualizujte svoje zariadenie.", "TR_NEXT_UP": "Next", "TR_NONCE": "Nonce", "TR_NORMAL_ACCOUNTS": "Default accounts", @@ -1205,10 +1181,6 @@ "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {transaction} other {transactions}}", "TR_OFF": "off", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Zvolená suma {currency} {amount} je vyššia ako akceptované maximum {currency} {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Zvolená suma {currency} {amount} je vyššia ako akceptované maximum {currency} {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Zvolená suma {currency} {amount} je nižšia ako akceptované minimum {currency} {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Zvolená suma {currency} {amount} je nižšia ako akceptované minimum {currency} {min}.", "TR_OFFICIAL_LANGUAGES": "Official", "TR_OK": "OK", "TR_ON": "on", @@ -1265,30 +1237,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Other inputs and outputs", "TR_OUTGOING": "Outgoing", "TR_OUTPUTS": "Outputs", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "The minimum and maximum amount for which this user is willing to sell {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "You need to initiate the transaction on {providerName} – make sure to follow the steps below carefully.", - "TR_P2P_GET_STARTED_ITEM_1": "Select “Go to {providerName}” to be redirected to our partner’s website.", - "TR_P2P_GET_STARTED_ITEM_3": "Once {providerName} asks you for a release address, return here and continue.", - "TR_P2P_GET_STARTED_ITEM_4": "Almost there! Reveal and copy your address, paste it into the “Release address” field back on {providerName}, and finalize the transaction.", - "TR_P2P_GO_TO_PROVIDER": "Go to {providerName}", - "TR_P2P_INFO": "With {peerToPeer} (P2P) technology, there is no KYC verification involved neither on the side of the buyer nor the seller. All parties are protected against fraud by secure {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "I’m ready to buy", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "Peer-to-Peer Buy {cryptocurrency} with {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Legal notice", - "TR_P2P_MODAL_SECURITY_HEADER": "Security first with your Trezor", - "TR_P2P_MODAL_TERMS_1": "You’re here to buy cryptocurrency from another person you choose using Peer-to-Peer (P2P) technology without ID verification. If you were directed to this site for any other reason, please contact support before proceeding.", - "TR_P2P_MODAL_TERMS_2": "You understand that cryptocurrency transactions are irreversible and may not be refunded. Thus, fraudulent or accidental losses may be unrecoverable.", - "TR_P2P_MODAL_TERMS_4": "You understand that Invity does not provide this service. {provider}’s terms govern the service.", - "TR_P2P_MODAL_TERMS_5": "You're not using this feature for gambling, fraud or any other violation of either Invity’s or the provider's Terms of service, or of any applicable regulations.", - "TR_P2P_MODAL_TERMS_6": "You understand that cryptocurrencies are an emerging financial tool and that regulations may vary in different jurisdictions. This may put you at a higher risk of fraud, theft, or market instability.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Verified partners by Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "The transaction needs to be completed within this time limit, counting from creating a contract on the {providerName} site.", - "TR_P2P_PRICE": "Price for 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "{symbol} price offered by this user.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Hey there, I’m using {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "The chosen amount of {amount} is higher than the accepted maximum of {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "The chosen amount of {amount} is lower than the accepted minimum of {minimum}.", "TR_PAGINATION_NEWER": "Newer", "TR_PAGINATION_OLDER": "Older", "TR_PASSPHRASE_CASE_SENSITIVE": "Note: Passphrase is case-sensitive.", @@ -1470,10 +1418,6 @@ "TR_SELL_STATUS_ERROR": "Rejected", "TR_SELL_STATUS_PENDING": "Pending", "TR_SELL_STATUS_SUCCESS": "Approved", - "TR_SELL_TRANS_ID": "Trans. ID:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Maximum is {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum is {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "View details", "TR_SENDFORM_LABELING_EXAMPLE_1": "Savings", "TR_SENDFORM_LABELING_EXAMPLE_2": "Rent", "TR_SENDING_SYMBOL": "Sending {multiple, select, true {multiple tokens} false {{symbol}} other {{symbol}}}", @@ -1732,10 +1676,7 @@ "TR_TO_BTC": "To BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "To make your labels consistent and available on different devices, connect to a cloud storage provider.", "TR_TO_SATOSHIS": "To sats", - "TR_TRADE_BUYS": "buys", - "TR_TRADE_EXCHANGES": "exchanges", "TR_TRADE_REDIRECTING": "Presmerovanie ...", - "TR_TRADE_SELLS": "predáva", "TR_TRANSACTIONS_NOT_AVAILABLE": "História transakcií nie je k dispozícii", "TR_TRANSACTIONS_SEARCH_TIP_1": "Tip: Môžete vyhľadávať ID transakcií, adresy, štítky, sumy a dátumy.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Tip: Combine AND (&) and OR (|) operators for more complex searches. For example > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 will show all transactions in January or December {lastYear}.", @@ -1770,7 +1711,6 @@ "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Automatická inštalácia pravidiel", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Chýbajúce pravidlá udev", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Neočakávaný stav: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Vaše zariadenie je správne pripojené, ale váš internetový prehliadač s ním momentálne nemôže komunikovať. Budete musieť nainštalovať most Trezor Bridge.", "TR_TRY_AGAIN": "Skúste to znova", "TR_TXID": "ID TX", "TR_TXID_RBF": "Original TX ID to be replaced", diff --git a/packages/suite-data/files/translations/tr.json b/packages/suite-data/files/translations/tr.json index 4b2487e3a4a..8bb0683c922 100644 --- a/packages/suite-data/files/translations/tr.json +++ b/packages/suite-data/files/translations/tr.json @@ -1,10 +1,11 @@ { "AMOUNT": "Tutar", + "AMOUNT_EXCEEDS_MAX": "Tutar, izin verilen maksimum değer olan {maxAmount}'i aşıyor.", "AMOUNT_IS_BELOW_DUST": "Tutar en az {dust} olmalıdır", "AMOUNT_IS_LESS_THAN_RESERVE": "Alıcı hesabının etkinleştirilmesi için minimum {reserve} XRP rezervi gerekir", "AMOUNT_IS_MORE_THAN_RESERVE": "Tutar, zorunlu harcanamaz rezervin ({reserve} XRP) üzerindedir", "AMOUNT_IS_NOT_ENOUGH": "Yeterli fon yok", - "AMOUNT_IS_NOT_INTEGER": "Tutar bir tam sayı değil", + "AMOUNT_IS_NOT_INTEGER": "Tutar bir tamsayı değil", "AMOUNT_IS_NOT_IN_RANGE_DECIMALS": "Maksimum {decimals} ondalık sayıya izin verilir", "AMOUNT_IS_NOT_SET": "Tutar ayarlanmamış", "AMOUNT_IS_TOO_LOW": "Tutar çok düşük", @@ -66,8 +67,8 @@ "LOCKTIME_ADD": "Kilit süresi ekle", "LOCKTIME_ADD_TOOLTIP": "Kilit süresi, bir işlemin bir bloğa çıkarılabileceği en erken zamanı belirler.", "LOCKTIME_BLOCKHEIGHT": "Kilit süresi blok yüksekliği", - "LOCKTIME_IS_NOT_INTEGER": "Kilit süresi bir tam sayı değil", - "LOCKTIME_IS_NOT_SET": "Kilit süresi ayarlanmamış", + "LOCKTIME_IS_NOT_INTEGER": "Kilitli kalma süresi bir tamsayı değil", + "LOCKTIME_IS_NOT_SET": "Kilitli kalma süresi ayarlanmamış", "LOCKTIME_IS_TOO_BIG": "Zaman damgası çok büyük", "LOCKTIME_IS_TOO_LOW": "Kilit süresi çok düşük", "LOCKTIME_SCHEDULE_SEND": "Kilit süresi", @@ -129,7 +130,7 @@ "RECIPIENT_SCAN": "Tarama", "REFRESH": "Yinele", "REMAINING_BALANCE_LESS_THAN_RENT": "Bu tutarı gönderdikten sonra hesabınızda {remainingSolBalance} SOL kalacaktır. Boş olmayan bir hesapta {rent} SOL tutarından yüksek bir bakiye tutulmalıdır.", - "REVIEW_AND_SEND_TRANSACTION": "İncele ve Gönder", + "REVIEW_AND_SEND_TRANSACTION": "İncele ve gönder", "SEND_RAW": "Ham gönder", "SEND_RAW_TRANSACTION_TOOLTIP": "İşleminiz için tüm ham verileri kendiniz sağlayabilirsiniz.", "SEND_TRANSACTION": "Gönder", @@ -168,6 +169,7 @@ "TOAST_PIN_CHANGED": "PIN kodu başarıyla değiştirildi", "TOAST_QR_INCORRECT_ADDRESS": "QR kodu bu hesap için geçersiz adres içeriyor", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "{coin} hesabı için QR kodu tanımlandı", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "Bilinmeyen protokol şeması: \"{scheme}\". Tekrar deneyin veya adresi manuel olarak girin.", "TOAST_RAW_TX_SENT": "İşlem gönderildi. İşlem kimliği: {txid}", "TOAST_SETTINGS_APPLIED": "Ayarlar başarıyla değiştirildi", "TOAST_SIGN_MESSAGE_ERROR": "Mesaj imzalama hatası: {error}", @@ -193,7 +195,6 @@ "TR_404_GO_TO_DASHBOARD": "Panoya git", "TR_404_TITLE": "Hata 404: Bağlantı bulunamadı", "TR_7D_CHANGE": "7d değişikliği", - "TR_ABORT": "Durdur", "TR_ACCESS_HIDDEN_WALLET": "Gizli cüzdana eriş", "TR_ACCESS_STANDARD_WALLET": "Standart cüzdana eriş", "TR_ACCOUNT_DETAILS_HEADER": "Hesap ayrıntıları", @@ -232,10 +233,16 @@ "TR_ACCOUNT_TYPE_BIP86_DESC": "Taproot, gizliliği ve ağ verimliliğini artırabilen yeni bir adres türüdür. Bazı hizmetlerin henüz Taproot adreslerini desteklemeyebileceğini unutmayın.", "TR_ACCOUNT_TYPE_BIP86_NAME": "Taproot", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", + "TR_ACCOUNT_TYPE_CARDANO_DESC": "Cardano adreslerini oluşturmanın ve yönetmenin mevcut ve en yaygın kabul gören yöntemi, birlikte çalışabilirlik, güvenlik ve tüm token türleri için destek sağlar.", "TR_ACCOUNT_TYPE_COINJOIN": "Coinjoin", + "TR_ACCOUNT_TYPE_DEFAULT": "Varsayılan", "TR_ACCOUNT_TYPE_IMPORTED": "İçe aktarıldı", "TR_ACCOUNT_TYPE_LEDGER": "Defter", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Defter (Ledger) hesapları, Ledger'dan Trezor'a sorunsuz geçişe olanak tanıyan Ledger Live türetme yollarıyla uyumludur.", "TR_ACCOUNT_TYPE_LEGACY": "Legacy", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "Eski (Legacy) hesaplar, Ledger'dan Trezor'a sorunsuz geçişe olanak tanıyan Ledger Legacy türetme yollarıyla uyumludur.", + "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "{value} adreslerini oluşturmanın ve yönetmenin mevcut ve en yaygın kabul gören yöntemi, birlikte çalışabilirlik, güvenlik ve tüm token türleri için destek sağlar.", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "Solana adreslerini oluşturmanın ve yönetmenin mevcut ve en yaygın kabul gören yöntemi, birlikte çalışabilirlik, güvenlik ve SOL ve SPL token'ları için destek sağlar.", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "Desteklenmiyor.", "TR_ACCOUNT_TYPE_NO_SUPPORT": "Bu hesap türü bu Trezor modelinde desteklenmiyor.", "TR_ACCOUNT_TYPE_SEGWIT": "Legacy SegWit", @@ -249,10 +256,12 @@ "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "Taproot", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "Bu Hesap Türünü etkinleştirmek için lütfen cihaz bellenimini güncelleyin.", + "TR_ACCOUNT_TYPE_XRP_DESC": "XRP, hızlı işlem onayları için bir mutabakat defteri kullanarak geleneksel madenciliğe ihtiyaç duymadan hızlı, düşük maliyetli sınır ötesi ödemeler sağlayan bir dijital para birimidir.", "TR_ACQUIRE_DEVICE": "Trezor'u burada kullanın", "TR_ACQUIRE_DEVICE_TITLE": "Başka bir oturum çalışıyor", "TR_ACTIVATED_COINS": "Coin'ler etkinleştirildi", "TR_ACTIVE": "etkin", + "TR_ADD": "Ekle", "TR_ADDRESSES": "Adres", "TR_ADDRESSES_CHANGE": "Adresleri değiştir", "TR_ADDRESSES_FRESH": "Yeni adresler", @@ -262,7 +271,7 @@ "TR_ADDRESS_MODAL_CLIPBOARD": "Adresi kopyala", "TR_ADDRESS_MODAL_TITLE": "{networkName} alma adresi", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkName} ağı üzerinden {networkCurrencyName} alma adresi", - "TR_ADDRESS_PHISHING_WARNING": "Kimlik avı saldırılarını önlemek için Trezor'unuzdaki adresi doğrulamalısınız. {claim}", + "TR_ADDRESS_PHISHING_WARNING": "Kimlik avı saldırılarını önlemek için Trezor'unuzdaki alma adresini doğrulayın. {claim}", "TR_ADD_ACCOUNT": "Hesap ekle", "TR_ADD_HIDDEN_WALLET": "Gizli cüzdan ekle", "TR_ADD_NETWORK_ACCOUNT": "{network} hesabı ekle", @@ -289,7 +298,9 @@ "TR_ALLOW_ANALYTICS": "Veri kullanımı", "TR_ALLOW_ANALYTICS_DESCRIPTION": "Tüm veriler kesinlikle anonim tutulur. Yalnızca Trezor Ecosystem'i geliştirmek için kullanılır.", "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "Otomatik Trezor Suite güncellemeleri", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite, en son sürümü arka planda otomatik olarak indirir ve uygulama yeniden başlatılırken yükler. Bu sayede en son özellikler ve güvenlik yamalarıyla her zaman güncel kalırsınız. Güncellemeler izniniz istenmeden gerçekleşir.", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite'in en son sürümünü arka planda otomatik olarak indirin ve uygulamayı yeniden başlatırken yükleyin. Bu sayede en son özellikler ve güvenlik yamalarıyla her zaman güncel kalırsınız. Güncellemeler izniniz istenmeden gerçekleşir.", + "TR_ALL_NETWORKS": "Tüm ağlar ({networkCount})", + "TR_ALL_NETWORKS_TOOLTIP": "Tüm {networkCount} ağlarındaki token'ları görüntüleyin. En popüler ağlara göre filtreleyin.", "TR_ALL_TRANSACTIONS": "İşlemler", "TR_AMOUNT_SENT": "Tutar gönderildi", "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "Coinjoin için uygun değil - tutar çok yüksek", @@ -348,20 +359,20 @@ "TR_BEFORE_ANY_FURTHER_ACTIONS": "Pek olası olmasa da, bir bellenim güncelleme sorunu olması durumunda cüzdan yedeklemenize erişmeniz gerekebilir.", "TR_BIP_SIG_FORMAT": "Trezor", "TR_BITCOIN_ONLY_UNAVAILABLE": "{bitcoinOnly}'ye geçmeden önce belleniminizi en son sürüme yükseltmeniz gerekir.", - "TR_BREAKING_ANONYMITY_CHECKBOX": "Anonimliğime zarar vereceğimi anlıyorum", + "TR_BREAKING_ANONYMITY_CHECKBOX": "Anonimliğime zarar vereceğimi anlıyorum.", "TR_BRIDGE": "Trezor Bridge", "TR_BRIDGE_DEV_MODE_START": "Trezor Bridge, 21324 numaralı bağlantı noktasında başlatılıyor", "TR_BRIDGE_DEV_MODE_STOP": "Trezor Bridge, varsayılan bağlantı noktasında başlatılıyor", "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "Emin misiniz? Cihazınız aynı anda yalnızca bir uygulama tarafından kullanılabilir. Trezor cihazınızla şu anda başka bir uygulama kullanıyorsanız öncelikle o oturumu sonlandırın.", "TR_BRIDGE_NEEDED_DESCRIPTION": "Tarayıcınız desteklenmiyor. En iyi deneyim için Trezor Suite masaüstü uygulamasını arka planda indirip çalıştırın veya WebUSB ile uyumlu, desteklenen bir Chromium tabanlı tarayıcı kullanın.", "TR_BRIDGE_REQUESTED_DESCRIPTION": "Başka bir uygulama, Trezor Suite'ten Trezor cihazınıza bağlanmayı talep etti. Trezor Suite'i arka planda açık tutun ve diğer uygulamada işlemi tekrar deneyin.", + "TR_BRIDGE_TIP_AUTOSTART": "İpucu: Otomatik başlatma özelliğini etkinleştirin ve Bridge'in her zaman arka planda çalışmasını sağlayın.", "TR_BTC_UNITS": "Bitcoin birimleri", "TR_BUG": "Hata", "TR_BUMP_FEE": "Ücreti artır", + "TR_BUMP_FEE_DISABLED_TOOLTIP": "İşlemlerinizi hızlandırmak için kuyrukta bekleyen en eski (nonce'a göre) işlemin ücretini artırın. İşlemler sırayla onaylanmalıdır. Daha fazla bilgi edinin", "TR_BUY": "Satın al", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Alım-satım işlemleri", "TR_BUY_BUY": "Satın al", - "TR_BUY_BUY_AGAIN": "Tekrar satın al", "TR_BUY_CONFIRMED_ON_TREZOR": "Trezor'da onaylandı", "TR_BUY_DETAIL_ERROR_BUTTON": "Hesaba geri dön", "TR_BUY_DETAIL_ERROR_SUPPORT": "Sağlayıcı destek bölümüne git", @@ -389,7 +400,7 @@ "TR_BUY_MODAL_TERMS_1": "Kripto para satın almak için buradasınız. Bu siteye başka bir nedenle yönlendirildiyseniz devam etmeden önce lütfen {provider} destek ekibiyle iletişime geçin.", "TR_BUY_MODAL_TERMS_2": "Bu özelliği, doğrudan kişisel kontrolünüz altındaki bir hesaba gönderilecek fonları satın almak için kullanıyorsunuz.", "TR_BUY_MODAL_TERMS_3": "Kripto para işlemlerinin geri dönüşü olmadığını ve iade edilemeyeceğini anlıyorsunuz. Bu nedenle, hileli veya kazara oluşan kayıplar telafi edilemez olabilir.", - "TR_BUY_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorsunuz. Hizmet, {provider} şartlarına göre yönetilir.", + "TR_BUY_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorum. {provider} Hüküm ve Koşullarına tabidir.", "TR_BUY_MODAL_TERMS_5": "Bu özelliği kumar, dolandırıcılık veya Invity'nin veya sağlayıcının hizmet şartlarını ya da geçerli düzenlemeleri ihlal etmek için kullanmayacaksınız.", "TR_BUY_MODAL_TERMS_6": "Kripto paraların gelişmekte olan bir finansal araç olduğunu ve düzenlemelerin farklı yargı bölgelerinde değişiklik gösterebileceğini anlıyorsunuz. Bu durum sizi daha yüksek dolandırıcılık, hırsızlık veya piyasa istikrarsızlığı riskiyle karşı karşıya bırakabilir.", "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "Invity tarafından doğrulanmış iş ortakları", @@ -406,12 +417,10 @@ "TR_BUY_STATUS_PENDING": "Beklemede", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "Beklemede", "TR_BUY_STATUS_SUCCESS": "Onaylandı", - "TR_BUY_TRANS_ID": "İşlem Kimliği:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Maksimum {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Maksimum {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Minimum {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "Ayrıntıları görüntüle", "TR_BYTES": "bayt", "TR_CAMERA_NOT_RECOGNIZED": "Kamera tanınmadı.", "TR_CAMERA_PERMISSION_DENIED": "Kameraya erişim izni reddedildi.", @@ -430,12 +439,12 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor tutarı", "TR_CHAINED_TXS": "Zincir işlemler", "TR_CHANGELOG": "Değişiklik Günlüğü", - "TR_CHANGELOG_ON_GITHUB": "GitHub'da Değişiklik Günlüğü", "TR_CHANGE_ADDRESS_TOOLTIP": "Bu, önceki bir gönderimden oluşturulan bir değişiklik adresidir.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Bellenim türünüzü istediğiniz zaman Ayarlar'dan değiştirebilirsiniz.", "TR_CHANGE_HOMESCREEN": "Ana ekranı değiştir", "TR_CHANGE_PIN": "PIN kodunu değiştir", "TR_CHANGE_WIPE_CODE": "Silme kodunu değiştir", + "TR_CHECKED_BALANCES_ON": "Kontrol edilen bakiyeler", "TR_CHECKING_YOUR_DEVICE": "Cihazınız kontrol ediliyor", "TR_CHECKSUM_CONVERSION_INFO": "Sağlama toplamına dönüştürüldü. Daha fazla bilgi edinin", "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "Trezor cihazınızın bütünlüğünü doğrulayarak güvenliğini sağlar ve çipin orijinalliğini onaylarız.", @@ -447,8 +456,7 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Kurtarma parolanızı doğrulamak için simüle edilmiş bir kurtarma gerçekleştirin.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Kurtarma parolanızdaki kelimeleri cihazınızda görüntülenen sırayla buraya girin. Ek bir güvenlik önlemi olarak kurtarma parolanızın parçası olmayan bazı kelimeleri yazmanız istenebilir.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Kurtarma parolanızı (cüzdan yedeklemesi) girmek için iki düğmeli pedi kullanın. Bunu yaparak, tüm hassas bilgilerinizi güvende ve sağlam bir şekilde tutar, herhangi bir şüpheli veya güvenilir olmayan bilgisayar veya web tarayıcısından uzak tutarsınız.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Kurtarma parolanız (cüzdan yedeklemesi) dokunmatik ekran kullanılarak girilir. Bu, hassas bilgilerinizin potansiyel olarak güvenilir olmayan bir bilgisayara veya web tarayıcısına maruz kalmasını önler.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Cüzdan yedeklemeniz dokunmatik ekran kullanılarak girilir. Bu, hassas bilgilerinizin potansiyel olarak güvenilir olmayan bir bilgisayara veya web tarayıcısına maruz kalmasını önler.", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "Cüzdan yedeklemeniz dokunmatik ekran kullanılarak girilir. Bu, hassas bilgilerinizin potansiyel olarak güvenilir olmayan bir bilgisayara veya web tarayıcısına maruz kalmasını önler.", "TR_CHECK_SEED": "Yedeklemeyi kontrol et", "TR_CHECK_YOUR_DEVICE": "Trezor ekranınızı kontrol edin", "TR_CHOOSE_RECOVERY_TYPE": "Kurtarma türünü seçin", @@ -502,10 +510,37 @@ "TR_COINJOIN_TILE_3_TITLE": "Trezor'unuz tarafından korunuyor", "TR_COINJOIN_TRANSACTION_BATCH": "Coinjoin işlemleri", "TR_COINMARKET_BEST_RATE": "En iyi oran", + "TR_COINMARKET_BUY_AND_SELL": "Al-Sat", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} alış} one {{totalBuys} alış} other {{totalBuys} alış} } • {totalSells, plural, =0 {{totalSells} satış} one {{totalSells} satış} other {{totalSells} satış} }", + "TR_COINMARKET_CEX_TOOLTIP": "Merkezi takas", + "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "Tutarı veya para birimini değiştirin.", "TR_COINMARKET_COMPARE_OFFERS": "Tüm teklifleri karşılaştır", "TR_COINMARKET_COUNTRY": "İkamet edilen ülke", + "TR_COINMARKET_DCA_DOWNLOAD": "Bitcoin ile tasarruf etmeye başlamak için Invity mobil uygulamasını indirin", + "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "Güvenli ve basit bir gözetim DCA tasarruf planıdır.", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "SatoshiLabs tarafından geliştirilmiştir", + "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "Ekstra ücret ödemeden kendi kendine gözetim için para çekin.", + "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "Ücretsiz para çekme", + "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "Hızlı, basit, kullanıcı dostu bir arayüz.", + "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "Kullanımı kolay", + "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "Yatırım geçmişinizi, tutarını ve sıklığını izleyin.", + "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "DCA'ya Genel Bakış", + "TR_COINMARKET_DCA_HEADING": "Invity uygulaması ile Bitcoin ile tasarruf edin", + "TR_COINMARKET_DEX_TOOLTIP": "Merkeziyetsiz takas", "TR_COINMARKET_ENTER_AMOUNT_IN": "Tutarı {currency} cinsinden gir", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "Tüm KYC seçenekleri", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "KYC hiçbir zaman gerekli değildir", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "Tüm CEX ve DEX teklifleri", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "DEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "Sabit oranlı CEX", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "Değişken oranlı CEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "DEX", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "Merkeziyetsiz bir borsa (DEX), merkezi bir otoriteye veya aracıya ihtiyaç duymadan doğrudan blok zinciri üzerinde kripto alım satımı yapmanızı sağlar.", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "Sabit oranlı CEX", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "Değişken oranlı CEX", "TR_COINMARKET_FEATURED_OFFERS_HEADING": "Öne çıkan teklifler", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "Ödeme:", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "Alma yöntemi", "TR_COINMARKET_FEES_INCLUDED": "Ücretler dahil", "TR_COINMARKET_FEES_NOT_INCLUDED": "Ücretler dahil değil", "TR_COINMARKET_FEES_ON_WEBSITE": "Gösterilen fiyata bazı ücretler dahil değildir. Son fiyatı sağlayıcının web sitesinde gözden geçirebileceksiniz.", @@ -518,19 +553,13 @@ "TR_COINMARKET_KYC_NO_REFUND": "İstisna durumlarında KYC talep edilir. Para iadeleri için KYC gereklidir. 👈", "TR_COINMARKET_KYC_POLICY": "KYC politikası", "TR_COINMARKET_KYC_POLICY_NEVER_REQUIRED": "KYC hiçbir zaman gerekli değildir", - "TR_COINMARKET_KYC_YES_REFUND": "İstisna durumlarında KYC talep edilir. Para iadeleri için KYC gerekli değildir. 🤝", + "TR_COINMARKET_KYC_YES_REFUND": "Yalnızca istisnai durumlarda KYC talep edilir. Para iadeleri için KYC gerekli değildir. 🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "Son işlemler", "TR_COINMARKET_NETWORK_FEE": "Ağ ücreti", "TR_COINMARKET_NETWORK_TOKENS": "{networkName} token'ları", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "CEX sağlayıcısı bulunamadı", "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "DEX sağlayıcısı bulunamadı", "TR_COINMARKET_NO_METHODS_AVAILABLE": "Yöntem mevcut değil", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Otomatik yeniden yükleme:", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Alım-satım işlemine geri dön", - "TR_COINMARKET_NO_OFFERS_HEADER": "Teklif yok", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "Üzgünüz, sunucu bağlantı sorunu nedeniyle şu anda herhangi bir teklifimiz bulunmamaktadır.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "Üzgünüz, şu anda herhangi bir teklifimiz bulunmamaktadır. Sayfayı yeniden yüklemeyi veya sorunuzu değiştirmeyi deneyin.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Sayfayı yeniden yükle", "TR_COINMARKET_OFFERS_EMPTY": "Talebiniz için teklif yok. Lütfen ülkeyi veya satın alınan tutarı değiştirin", "TR_COINMARKET_OFFERS_REFRESH": "Şunlarda teklifleri yenileme:", "TR_COINMARKET_OFFERS_SELECT": "Seç", @@ -544,9 +573,36 @@ "TR_COINMARKET_RECEIVE_METHOD": "Alma yöntemi", "TR_COINMARKET_SELL": "Sat", "TR_COINMARKET_SHOW_OFFERS": "Teklifleri karşılaştır", + "TR_COINMARKET_SWAP": "Takas", + "TR_COINMARKET_SWAP_AMOUNT": "Takas tutarı", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} takas} one {{totalSwaps} takas} other {{totalSwaps} takas} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "Takas yapmaya hazırım", + "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "{provider} ile {fromCrypto} para biriminden {toCrypto} para birimine takas yap", + "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "Yasal uyarı", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Trezor'unuzla önce güvenlik", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "{provider}'nın sözleşmesini kullanarak DEX (merkeziyetsiz borsa) kullanarak kripto para takası yapmak istiyorum.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "Kendi hesabım için kripto para takası yapmak istiyorum. Sağlayıcının politikalarının kimlik doğrulaması gerektirebileceğini anlıyorum.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "Kripto para işlemlerinin nihai olduğunu ve geri alınamayacağını veya iade edilemeyeceğini anlıyorum. Dolandırıcılık veya hatalardan kaynaklanan kayıplar telafi edilemeyebilir.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorum. {provider} Hüküm ve Koşullarına tabidir.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "Bu özelliği kumar, dolandırıcılık veya Invity'nin veya sağlayıcının Hizmet Şartlarını veya geçerli yasaları ihlal eden herhangi bir faaliyet için kullanmayacağım.", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "Kripto paraların gelişmekte olan bir finansal araç olduğunu ve düzenlemelerin bölgeye göre farklılık gösterebileceğini anlıyorum. Bu durum dolandırıcılık, hırsızlık veya piyasa istikrarsızlığı riskini artırabilir.", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "Invity tarafından doğrulanmış iş ortakları", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "Takas yapmaya hazırım", + "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "{provider} ile {fromCrypto} para biriminden {toCrypto} para birimine takas yap", + "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "Yasal uyarı", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Trezor'unuzla önce güvenlik", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "Kripto para takası yapmak için buradayım. Bu siteye başka bir nedenle yönlendirildiysem devam etmeden önce Trezor Support ile iletişime geçeceğim. ", + "TR_COINMARKET_SWAP_MODAL_TERMS_2": "Kendi hesabım için kripto para takası yapmak istiyorum. Sağlayıcının politikalarının kimlik doğrulaması gerektirebileceğini anlıyorum.", + "TR_COINMARKET_SWAP_MODAL_TERMS_3": "Kripto para işlemlerinin nihai olduğunu ve geri alınamayacağını veya iade edilemeyeceğini anlıyorum. Dolandırıcılık veya hatalardan kaynaklanan kayıplar telafi edilemeyebilir.", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorum. {provider} Hüküm ve Koşullarına tabidir.", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "Bu özelliği kumar, dolandırıcılık veya Invity'nin veya sağlayıcının Hizmet Şartlarını veya geçerli yasaları ihlal eden herhangi bir faaliyet için kullanmayacağım.", + "TR_COINMARKET_SWAP_MODAL_TERMS_6": "Kripto paraların gelişmekte olan bir finansal araç olduğunu ve düzenlemelerin bölgeye göre farklılık gösterebileceğini anlıyorum. Bu durum dolandırıcılık, hırsızlık veya piyasa istikrarsızlığı riskini artırabilir.", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "Invity tarafından doğrulanmış iş ortakları", "TR_COINMARKET_TOKEN_NETWORK": "{networkName} ağında {tokenName} token'ı", "TR_COINMARKET_TRADE_FEE": "Alım satım ücreti", + "TR_COINMARKET_TRANS_ID": "İşlem Kimliği:", "TR_COINMARKET_UNKNOWN_PROVIDER": "Bilinmeyen sağlayıcı", + "TR_COINMARKET_VIEW_DETAILS": "Ayrıntıları görüntüle", "TR_COINMARKET_YOUR_BEST_OFFER": "En iyi teklifiniz", "TR_COINMARKET_YOU_BUY": "Satın alacağınız tutar:", "TR_COINMARKET_YOU_GET": "Alacağınız tutar:", @@ -571,6 +627,7 @@ "TR_CONFIRMED_TX": "Onaylandı", "TR_CONFIRMING_TX": "İşlem onaylanıyor", "TR_CONFIRM_ACTION_ON_YOUR": "Trezor ekranınızdaki talimatları izleyin", + "TR_CONFIRM_ADDRESS": "Adresi onayla", "TR_CONFIRM_BEFORE_COPY": "Kopyalamadan önce Trezor'da onaylayın", "TR_CONFIRM_CONDITIONS": "Devam etmeden önce koşulları onaylayın.", "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "\"{deviceLabel}\" cihazında boş Gizli cüzdanı onaylayın.", @@ -593,13 +650,13 @@ "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "Yönet", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "Trezor Suite'i açtığınızda parola girişi iletişim kutusunun açılmasını etkinleştirin.", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "Öncelikle bir parola kullanıyor musunuz?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Alma adresini onaylamak için Trezor'da doğrulayın. Onaylamadan devam etmeniz önerilmez.", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "Alma adresini onaylamak için Trezor'unuzda doğrulayın. Onaylamadan devam etmeniz tavsiye edilmez.", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "Alma adresi doğrulanamıyor", "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "Coin göndermek için Trezor'unuzu bağlayın.", "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "Trezor'unuz bağlı değil", "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "Göndermek için Trezor'u Bağlayın", "TR_CONNECT_YOUR_DEVICE": "Trezor'unuzu bağlayın ve kilidini açın", - "TR_CONTACT_SUPPORT": "Destek ekibiyle iletişime geçin", + "TR_CONTACT_SUPPORT": "Trezor Support ile iletişime geçin", "TR_CONTACT_TREZOR_SUPPORT": "Trezor Support ile iletişime geçin", "TR_CONTINUE": "Devam", "TR_CONTINUE_ANYWAY": "Yine de devam et", @@ -619,7 +676,7 @@ "TR_COPY_ADDRESS_POLICY_ID": "Asla bir politika kimliği adresine fon göndermeyin.", "TR_COPY_AND_CLOSE": "Kopyala ve Kapat", "TR_COPY_SIGNED_MESSAGE": "İmzalanan mesajı kopyala", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Kopyala", + "TR_COPY_TO_CLIPBOARD": "Kopyala", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Değişiklik günlüğü alınamadı", "TR_COULD_NOT_RETRIEVE_DATA": "Veriler alınamadı", "TR_COUNT_WALLETS": "{count} {count, plural, one {cüzdan} other {cüzdanlar}}", @@ -653,6 +710,7 @@ "TR_DASHBOARD_ASSET_FAILED": "Varlık doğru yüklenmedi", "TR_DASHBOARD_DISCOVERY_ERROR": "Bulma hatası", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "Hesaplar düzgün yüklenmedi {details}", + "TR_DATA": "Veri", "TR_DATABASE_UPGRADE_BLOCKED": "Veri tabanı yükseltmesi başka bir uygulama örneği tarafından engellendi", "TR_DATA_ANALYTICS_CATEGORY_1": "Platform", "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "İşletim sistemi, Trezor modeli, sürümü vb.", @@ -706,8 +764,13 @@ "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "Yanlışlıkla önyükleyici modunda mı?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "Herhangi bir düğmeye dokunmadan cihazı yeniden bağlayın.", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "Ekrana dokunmadan cihazı yeniden bağlayın.", + "TR_DEVICE_CONNECTED_UNACQUIRED": "Bu cihaz başka bir yerde kullanılıyor.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "Bu cihazı şu anda {transportSessionOwner} uygulaması kullanıyor olabilir. Gerekirse cihazın kontrolünü ele alabilirsiniz.", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "Bu cihazı şu anda başka bir uygulama kullanıyor olabilir. Gerekirse cihazın kontrolünü ele alabilirsiniz.", "TR_DEVICE_CONNECTED_WRONG_STATE": "Cihaz yanlış durumda algılandı", "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "Yedekleme işlemi sırasında Trezor'unuzun bağlantısı kesildi. Cihazınızı silmek ve cüzdan yedekleme işlemini tekrar başlatmak için Cihaz ayarlarındaki fabrika ayarlarına sıfırlama seçeneğini kullanmanızı önemle tavsiye ederiz.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "Ürün yazılımı karma kontrolü başarısız oldu. Trezor'unuz sahte olabilir.", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "Ürün yazılımı karma kontrolü gerçekleştirilemedi. Trezor'unuz sahte olabilir.", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "Kapat", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "Aç", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "Ürün yazılımı revizyonu kontrolü, önemli bir güvenlik özelliğidir. Bunu açık tutmanızı önemle tavsiye ederiz.", @@ -726,8 +789,10 @@ "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "\"{deviceLabel}\" cihazı yedeklenmedi", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "\"{deviceLabel}\" cihazı bağlı değil", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "\"{deviceLabel}\" cihazı kullanılamıyor", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "Cihaz doğrulaması başarısız", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "Cihazınızı yeniden bağlayın ve tekrar doğrulamayı deneyin. Sorun devam ederse cihazınızda neler olduğunu ve bundan sonra ne yapmanız gerektiğini öğrenmek için Trezor Support ile iletişime geçin.", "TR_DEVICE_NOT_CONNECTED": "Cihaz bağlı değil", - "TR_DEVICE_NOT_INITIALIZED": "Trezor kurulmadı", + "TR_DEVICE_NOT_INITIALIZED": "Trezor ayarlanmamış", "TR_DEVICE_NOT_INITIALIZED_TEXT": "Süreç boyunca size rehberlik edeceğiz ve hemen başlamanızı sağlayacağız.", "TR_DEVICE_SECURITY": "Güvenlik", "TR_DEVICE_SETTINGS_AFTER_DELAY": "Bekleme süresi", @@ -788,7 +853,6 @@ "TR_DOWNLOAD_LATEST_BRIDGE": "En son Bridge sürümünü ({version}) indir", "TR_DO_NOT_DISCONNECT_DEVICE": "Cihazınızın bağlantısını kesmeyin", "TR_DO_NOT_SHOW_AGAIN": "Bir daha gösterme", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Bu adım atlansın mı?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Dosyayı buraya sürükleyip bırakın veya dosyalardan seçmek için tıklayın", "TR_DROPZONE_ERROR": "İçe aktarma başarısız: {error}", @@ -809,13 +873,13 @@ "TR_EARLY_ACCESS_ENABLE": "Katıl", "TR_EARLY_ACCESS_ENABLED": "Erken Erişim Programı etkinleştirildi", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "Programa katıl", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Bunun, Suite'in normal çalışmasını etkileyebilecek hatalar içerebilen ön sürüm yazılımı test etmemi sağladığını anlıyorum.", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "Bunun, Trezor Suite'in normal çalışmasını etkileyebilecek hatalar içerebilen ön sürüm yazılımı test etmemi sağladığını anlıyorum.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "İstediğiniz zaman kapatabilirsiniz.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "Genel kullanıma sunulmadan önce en son ürün özelliklerini deneyin.", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "Önce yukarıdaki alanı kontrol edin", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "Beta güncellemelerini şimdi veya bir sonraki sürümde kontrol edebilirsiniz.", "TR_EARLY_ACCESS_JOINED_TITLE": "Erken Erişim Programı etkinleştirildi", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Suite'in en son kararlı sürümüne geçmek için lütfen \"Kararlı sürümü indir\" seçeneğine tıklayın ve uygulamayı yeniden yükleyin.", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "Trezor Suite'in en son kararlı sürümüne geçmek için \"Kararlı sürümü indir\" seçeneğine tıklayın ve uygulamayı yeniden yükleyin.", "TR_EARLY_ACCESS_LEFT_TITLE": "Erken Erişim Programından ayrıldınız. Beta sürümler artık sunulmamaktadır.", "TR_EARLY_ACCESS_MENU": "Erken Erişim Programı", "TR_EARLY_ACCESS_REINSTALL": "Kararlı sürümü indir", @@ -870,7 +934,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Yalnızca bu takas için gereken tam tutarı onaylayın. Benzer bir takası tekrar yapmak isterseniz ek bir ücret ödemeniz gerekir.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Önceki onayı iptal et", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "{provider} ile yapılan sözleşmenin önceki onayını kaldıracak bir işlem gerçekleştirin.", - "TR_EXCHANGE_BUY": "Şunun için:", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Trezor'da onayla ve gönder", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Onayla ve Gönder", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Onay oluştur", @@ -892,8 +955,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "İşleminiz başarılı oldu.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Onaylandı", "TR_EXCHANGE_DEX": "Merkeziyetsiz borsa arzı", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Bu takası gerçekleştirme ücretleri onay için {approvalFee} ({approvalFeeFiat}) (gerekirse), takas için ise {swapFee} ({swapFeeFiat}) olarak tahmin edilmektedir.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "İşlem ücretleri için fon kalmadı. Lütfen takas tutarını maksimum {max} {symbol} tutarına düşürün", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} geçersizdir", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", @@ -903,7 +964,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Sabit oranlar, takasın sonunda elinize tam olarak ne kadar geçeceğini gösterir; oranı seçtiğiniz zaman ile işleminizin tamamlandığı zaman arasında tutar değişmez. Gösterilen tutar size garanti edilir ancak bu oranlar genellikle daha az cömerttir, yani paranız çok fazla kripto satın almaz.", "TR_EXCHANGE_FLOAT": "Değişken oranlı teklif", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Değişken oranlar, oranı seçtiğiniz zaman ile işleminizin tamamlandığı zaman arasında piyasadaki dalgalanmalar nedeniyle alacağınız nihai tutarın biraz değişebileceği anlamına gelir. Bu oranlar genellikle daha yüksektir, yani sonuçta daha fazla kripto elde edebilirsiniz.", - "TR_EXCHANGE_PROVIDER": "Sağlayıcı", "TR_EXCHANGE_RATE": "Fiyat", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Alma hesabı Suite dışında.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Bu, coin'lerinizin alınacağı belirli alfasayısal adrestir.", @@ -930,23 +990,16 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Lütfen bir sayı girin.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "İstediğiniz kayma değerini girin.", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Takas teklif tutarı", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Kayma özeti", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Kayma toleransı", - "TR_EXCHANGE_TRANS_ID": "İşlem Kimliği:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Suite'te olmayan bir hesap ({symbol}) kullanın", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Alma adresi", - "TR_EXCHANGE_VIEW_DETAILS": "Ayrıntıları görüntüle", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "Otomatik Trezor Suite güncellemeleri", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite, en son sürümü arka planda otomatik olarak indirir ve uygulama yeniden başlatılırken yükler. Bu sayede en son özellikler ve güvenlik yamalarıyla her zaman güncel kalırsınız. Güncellemeler izniniz istenmeden gerçekleşir.", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "BNB Smart Chain", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "Geçmiş dahili işlemler olmadan BNB Smart Chain'i etkinleştirin.", "TR_EXPERIMENTAL_FEATURES": "Deneysel", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Deneysel özellikler", "TR_EXPERIMENTAL_FEATURES_WARNING": "Yalnızca deneyimli kullanıcılara yöneliktir. Riski üstlenerek kullanın. Bu özellikler test aşamasında olup istikrarsız olabilir ve bunlar için uzun vadede destek sunulmayabilir.", "TR_EXPERIMENTAL_PASSWORD_MANAGER": "Dropbox şifrelerini taşı", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Dropbox üzerinde saklanan ve Trezor tarafından güvence altına alınan şifreleri almaya yönelik bir yardımcı program. Trezor Password Manager'ın eski Chrome uzantısının kullanıcıları için tasarlanmıştır.", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "Dropbox'ta saklanan ve Trezor tarafından güvence altına alınan şifreleri almak için bu yardımcı programı kullanın. Trezor Password Manager Chrome uzantısının eski kullanıcıları için tasarlanmıştır.", "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake, sansürlü web sitelerine ve uygulamalara erişim sağlayan bir sistemdir.", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Kısıtlamaları aşmak için tasarlanmış bir sistem olan Tor Snowflake'i kullanarak sansürlü web sitelerine ve uygulamalara erişin.", "TR_EXPORT_AS": "{as} olarak dışa aktar ", "TR_EXPORT_FAIL": "Dışa aktarma başarısız.", "TR_EXPORT_TO_FILE": "Dosyaya aktar", @@ -984,6 +1037,7 @@ "TR_FIRMWARE_NEW_FW_DESCRIPTION": "Yeni bellenim şimdi mevcut. Cihazınızı şimdi güncelleyin veya daha sonra yapmayı seçin.", "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "Cihazınız zaten en son bellenime güncellenmiştir. Gerekirse bellenimi yeniden yükleyebilirsiniz.", "TR_FIRMWARE_REVISION_CHECK_FAILED": "Ürün yazılımı revizyonu kontrolü başarısız oldu. Trezor'unuz sahte olabilir.", + "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "Bellenim revizyonu kontrolü gerçekleştirilemedi.", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "Tamamlandı", "TR_FIRMWARE_SUBHEADING_BITCOIN": "Yalnızca Bitcoin işlemlerini destekleyen hafif bellenim.", "TR_FIRMWARE_SUBHEADING_NONE": "Trezor'unuz bellenimi olmadan gönderilir. Cihazınızı güvenle kullanabilmek için en son bellenimi yükleyin. Bitcoin-only kullanıcılarının, yüklemelerini öneririz.", @@ -1021,7 +1075,7 @@ "TR_GOT_IT_BUTTON": "Anladım", "TR_GO_TO_ONBOARDING": "Kuruluma başla", "TR_GO_TO_SETTINGS": "Ayarlara git", - "TR_GO_TO_SUITE": "Suite'e eriş", + "TR_GO_TO_SUITE": "Trezor Suite'e gidin", "TR_GRAPH_LINEAR": "Doğrusal", "TR_GRAPH_LOGARITHMIC": "Logaritmik", "TR_GRAPH_MISSING_DATA": "XRP, SOL ve tüm token tutarları portföy bakiyesinde yer alır ancak şu anda grafik görünümünde desteklenmemektedir.", @@ -1040,7 +1094,7 @@ "TR_GUIDE_FORUM": "Trezor Forumu", "TR_GUIDE_FORUM_LABEL": "Trezor topluluğu ile bağlantı kurun", "TR_GUIDE_SUGGESTION_LABEL": "Ne durumdayız?", - "TR_GUIDE_SUPPORT": "Destek ekibiyle iletişime geçin", + "TR_GUIDE_SUPPORT": "Trezor Support ile iletişime geçin", "TR_GUIDE_SUPPORT_AND_FEEDBACK": "Destek ve Geri Bildirim", "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "Destek ve Geri Bildirim", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "İyileştirmemize yardımcı olun", @@ -1172,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "Yerel dosya sistemi", "TR_LOG": "Uygulama günlüğü", "TR_LOGIN_PROCEED": "Devam et", - "TR_LOG_DESCRIPTION": "Günlük, Trezor Suite hakkında gerekli tüm teknik bilgileri içerir. Trezor Support ile bağlantı kurarken gerekli olabilir.", + "TR_LOG_DESCRIPTION": "Bu günlük, Trezor Suite hakkında önemli teknik bilgiler içerir ve Trezor Support ile iletişime geçerken gerekli olabilir.", "TR_LOOKING_FOR_COINJOIN_ROUND": "Tur bekleniyor", "TR_LOW_ANONYMITY_WARNING": "Çok düşük gizlilik. Bu eşiğin altındaki herhangi bir şey gizli olmadığından, en az 5'te 1'i kullanmanızı öneririz.", "TR_LTC_ADDRESS_INFO": "Litecoin adres biçimini değiştirdi. Adresinizi nasıl dönüştüreceğiniz hakkında daha fazla bilgiyi blogumuzda bulabilirsiniz. {TR_LEARN_MORE}", @@ -1202,6 +1256,7 @@ "TR_MULTI_SHARE_BACKUP_EXPLANATION_2": "Mevcut yedeklemeniz hâlâ fonlarınızı kurtarmanıza izin verir. Çok Paylaşımlı Yedekleme paylaşımlarınızdan ayrı olarak, güvenli ve emniyetli bir yerde saklayın.", "TR_MULTI_SHARE_BACKUP_GREAT": "Harika!", "TR_MULTI_SHARE_BACKUP_IN_PROGRESS": "Çok Paylaşımlı Yedekleme oluşturma işlemi devam ediyor", + "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION": "Devam etmeden önce Çoklu Paylaşım Yedekleme paylaşımlarınızı oluşturmayı bitirmeniz gerekir. Trezor'unuzun ekranındaki talimatları izleyin.", "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING": "Tekrar hoş geldiniz! Kaldığınız yerden devam edelim.", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP": "Cüzdan yedeklemenizi mi kaybettiniz?", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT": "Cüzdanınızı kurtarmak için hiçbir seçenek olmayabilir. Trezor Support ile iletişime geçin.", @@ -1222,6 +1277,7 @@ "TR_MY_PORTFOLIO": "Portföy", "TR_NAV_ANONYMIZE": "Coin'leri özel yap", "TR_NAV_BUY": "Satın al", + "TR_NAV_DCA": "DCA", "TR_NAV_DETAILS": "Ayrıntılar", "TR_NAV_RECEIVE": "Al", "TR_NAV_SELL": "Sat", @@ -1263,6 +1319,7 @@ "TR_NETWORK_LITECOIN": "Litecoin", "TR_NETWORK_NAMECOIN": "Namecoin", "TR_NETWORK_NEM": "NEM", + "TR_NETWORK_OP": "Optimizm", "TR_NETWORK_POLYGON": "Polygon PoS", "TR_NETWORK_SOLANA_DEVNET": "Solana Devnet", "TR_NETWORK_SOLANA_MAINNET": "Solana", @@ -1275,9 +1332,10 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Yeni", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Yeni Trezor Bridge mevcut.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Yeni Trezor bellenimi mevcut! Lütfen cihazınızı güncelleyin.", "TR_NEXT_UP": "Sonraki", "TR_NONCE": "Nonce", + "TR_NON_ASCII_CHAR": "{label} (tavsiye edilmeyen \"{char}\" ile)", + "TR_NON_ASCII_CHARS": "{label} (tavsiye edilmeyen karakterlerle)", "TR_NORMAL_ACCOUNTS": "Varsayılan hesaplar", "TR_NORTH": "Kuzey", "TR_NOTHING_TO_ANONYMIZE": "Özel yapılacak bir şey yok", @@ -1295,16 +1353,12 @@ "TR_NO_PASSPHRASE_WALLET": "Standart cüzdan", "TR_NO_SEARCH_RESULTS": "Arama kriteriniz için sonuç yok", "TR_NO_SPENDABLE_UTXOS": "Hesabınızda harcanabilir UTXO bulunmamaktadır.", - "TR_NO_TRANSPORT": "Tarayıcı, cihaz ile iletişim kuramıyor", + "TR_NO_TRANSPORT": "Tarayıcınız, cihazınız ile iletişim kuramıyor", "TR_NO_TRANSPORT_DESKTOP": "Uygulama, cihaz ile iletişim kuramıyor", "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {hesap} other {hesaplar}} • {fiatValue}", "TR_N_MIN": "{n} min.", "TR_N_TRANSACTIONS": "{value} {value, plural, one {işlem} other {işlemler}}", "TR_OFF": "kapalı", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Seçilen {amount} tutarı, kabul edilen maksimum değer olan {max} değerinden yüksektir.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Seçilen {amount} tutarı, kabul edilen maksimum değer olan {max} değerinden yüksektir.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Seçilen {amount} tutarı, kabul edilen minimum değer olan {min} değerinden düşüktür.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Seçilen {amount} tutarı, kabul edilen minimum değer olan {min} değerinden düşüktür.", "TR_OFFICIAL_LANGUAGES": "Resmi", "TR_OK": "Tamam", "TR_ON": "açık", @@ -1363,40 +1417,18 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Diğer girdi ve çıktılar", "TR_OUTGOING": "Giden", "TR_OUTPUTS": "Çıktılar", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Bu kullanıcının {symbol} satmaya istekli olduğu minimum ve maksimum tutar.", - "TR_P2P_GET_STARTED_INTRO": "İşlemi şurada başlatmanız gerekir: {providerName}. Aşağıdaki adımları dikkatlice izlediğinizden emin olun.", - "TR_P2P_GET_STARTED_ITEM_1": "İş ortağımızın web sitesine yönlendirilmek için \"Şuraya git: {providerName}\" öğesini seçin.", - "TR_P2P_GET_STARTED_ITEM_3": "{providerName} sizden bir çıkış adresi istediğinde, buraya geri dönün ve devam edin.", - "TR_P2P_GET_STARTED_ITEM_4": "Neredeyse bitti! Adresinizi açın ve kopyalayın, {providerName}'daki \"Çıkış adresi\" alanına yapıştırın ve işlemi tamamlayın.", - "TR_P2P_GO_TO_PROVIDER": "Şuraya git: {providerName}", - "TR_P2P_INFO": "{peerToPeer} (P2P) teknolojisiyle ne alıcı ne de satıcı tarafında herhangi bir KYC doğrulaması söz konusu değildir. Tüm taraflar güvenli {multisigEscrow} ile dolandırıcılığa karşı korunur.", - "TR_P2P_MODAL_CONFIRM": "Satın almaya hazırım", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "{provider} ile Eşler Arası {cryptocurrency} Satın Alma", - "TR_P2P_MODAL_LEGAL_HEADER": "Yasal uyarı", - "TR_P2P_MODAL_SECURITY_HEADER": "Trezor'unuzla önce güvenlik", - "TR_P2P_MODAL_TERMS_1": "Kimlik doğrulaması olmadan Eşler Arası (P2P) teknolojisini kullanarak seçtiğiniz başka bir kişiden kripto para satın almak için buradasınız. Bu siteye başka bir nedenle yönlendirildiyseniz devam etmeden önce lütfen destek ekibiyle iletişime geçin.", - "TR_P2P_MODAL_TERMS_2": "Kripto para işlemlerinin geri dönüşü olmadığını ve iade edilemeyeceğini anlıyorsunuz. Bu nedenle, hileli veya kazara oluşan kayıplar telafi edilemez olabilir.", - "TR_P2P_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorsunuz. Hizmet, {provider} şartlarına göre yönetilir.", - "TR_P2P_MODAL_TERMS_5": "Bu özelliği kumar, dolandırıcılık veya Invity'nin veya sağlayıcının hizmet şartlarını ya da geçerli düzenlemeleri ihlal etmek için kullanmayacaksınız.", - "TR_P2P_MODAL_TERMS_6": "Kripto paraların gelişmekte olan bir finansal araç olduğunu ve düzenlemelerin farklı yargı bölgelerinde değişiklik gösterebileceğini anlıyorsunuz. Bu durum sizi daha yüksek dolandırıcılık, hırsızlık veya piyasa istikrarsızlığı riskiyle karşı karşıya bırakabilir.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Invity tarafından doğrulanmış iş ortakları", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "İşlemin, {providerName} sitesinde bir sözleşme oluşturulmasından itibaren sayılan bu süre içinde tamamlanması gerekir.", - "TR_P2P_PRICE": "1 {symbol} için fiyat", - "TR_P2P_PRICE_TOOLTIP": "Bu kullanıcı tarafından sunulan {symbol} fiyatı.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Merhaba, ben {providerName} kullanıyorum!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Seçilen {amount} tutarı, kabul edilen maksimum değer olan {maximum} değerinden yüksektir.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Seçilen {amount} tutarı, kabul edilen minimum değer olan {minimum} değerinden düşüktür.", "TR_PAGINATION_NEWER": "Daha yeni", "TR_PAGINATION_OLDER": "Daha eski", "TR_PASSPHRASE_CASE_SENSITIVE": "Not: Parola büyük/küçük harfe duyarlıdır.", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "Öncelikle bir parolanın nasıl çalıştığını öğrenmek önemlidir", "TR_PASSPHRASE_DESCRIPTION_ITEM2": "Bir parola, bu kelime grubu ile korunan bir cüzdanı açar", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Trezor support dahil hiç kimse onu kurtaramaz", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "Trezor Support dahil hiç kimse onu kurtaramaz", "TR_PASSPHRASE_HIDDEN_WALLET": "Gizli cüzdan", "TR_PASSPHRASE_MISMATCH": "Parola uyumsuzluğu", "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "Parolalar birbiriyle uyumlu değil. Güvenlik için baştan başlayın ve doğru şekilde girin.", "TR_PASSPHRASE_MISMATCH_START_OVER": "Baştan başla", + "TR_PASSPHRASE_NON_ASCII_CHARS": "ABC, abc, 123, boşluk veya bu özel karakterleri kullanmanızı öneririz", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "Listelenmemiş özel karakterlerin kullanılması gelecekteki uyumluluğu riske atar", "TR_PASSPHRASE_TOO_LONG": "Parola uzunluğu izin verilen sınırı aşıyor.", "TR_PASSPHRASE_WALLET": "Gizli cüzdan #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "Parolanın nasıl çalıştığını öğrenin", @@ -1438,15 +1470,30 @@ "TR_PIN_SUBHEADING": "Güçlü bir PIN kodu kullanmak Trezor'unuzu yetkisiz fiziksel erişime karşı korur.", "TR_PLAY_IT_SAFE": "Güvende olalım", "TR_PLEASE_ALLOW_YOUR_CAMERA": "Lütfen QR kodlarını taramak için kameranızı etkinleştirin.", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "Doğrulama işlemine devam etmek için lütfen cihazınızı bağlayın.", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "Doğrulama işlemine devam etmek için Trezor'unuzu bağlayın.", "TR_PLEASE_ENABLE_PASSPHRASE": "Doğrulama işlemine devam etmek için lütfen parola özelliğini etkinleştirin.", "TR_POLICY_ID_ADDRESS": "Politika kimliği:", "TR_PRIMARY_FIAT": "Fiat para birimi", "TR_PRIVATE": "Özel", "TR_PRIVATE_DESCRIPTION": "En azından {targetAnonymity} gizliliği", + "TR_PROCEED_UNVERIFIED_ADDRESS": "Doğrulanmamış adresle devam et", "TR_PROMO_BANNER_DASHBOARD": "Kriptonuzu güvenli bir şekilde yönetmek için en kullanışlı donanım cüzdanı", "TR_QR_RECEIVE_ADDRESS_CONFIRM": "Taramadan önce Trezor'da onaylayın", "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "Güvenilir ekranı ele geçirilemeyeceğinden, lütfen önce Trezor cihazınızdaki alma adresini onaylayın.", + "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "Etkin", + "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "Az önce güncellendi ({currentVersion})", + "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "Güncellemek için yeniden başlat", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Trezor cihazı", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", + "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "Güncelleme mevcut ({newVersion})", + "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "Güncel ({currentVersion})", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Trezor Suite yeni bir güncelleme indirdi.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "Trezor Suite güncellendi.", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "Trezor Suite güncellemesi şimdi mevcut", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "Yeniden başlat ve güncelle", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "Güncellemeyi başlat", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "Trezor güncellemesi şimdi mevcut", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "Yenilikler", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "Kurtarma parolanızın bir parçası olmayan bazı kelimeler yazmanız istenebilir.", "TR_RANGE": "aralık", "TR_READ_AND_UNDERSTOOD": "Yukarıdakileri okudum ve anladım", @@ -1498,7 +1545,7 @@ "TR_SAFETY_CHECKS_MODAL_TITLE": "Güvenlik kontrolleri", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "İstem", "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "Trezor'unuzda manuel olarak onaylayarak anahtarları uyumsuz hale getirmek veya aşırı ücretlere izin vermek gibi potansiyel olarak güvenli olmayan eylemlere izin verin.", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Ne yaptığınızı bilmiyorsanız bunu değiştirmeyin!", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "Bunu sadece ne yaptığınızı biliyorsanız değiştirin!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "Sıkı", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "Tam Trezor güvenliği.", "TR_SCAN_QR_CODE": "QR kodunu tara", @@ -1508,9 +1555,10 @@ "TR_SEARCH_TRANSACTIONS": "İşlem ara", "TR_SEARCH_UTXOS": "Belirli bir adresi, işlem kimliğini veya etiketi arayın", "TR_SECURITY_CHECKPOINT_GOT_SEED": "Cüzdan yedeklemeniz var mı?", + "TR_SECURITY_CHECK_HOLOGRAM": "Hologramlar ve güvenlik mühürleri de dahil olmak üzere cihaz ambalajlarının zaman içinde güncellendiğini lütfen unutmayın. Ambalaj ayrıntılarını buradan doğrulayabilirsiniz. Cihazınızın resmi Trezor Shop'dan veya güvenilir satıcılarımızdan birinden satın alındığından emin olun. Aksi takdirde, cihazınızın sahte olma riski vardır. Cihazınızın orijinal olmadığından şüpheleniyorsanız lütfen Trezor Support ile iletişime geçin.", "TR_SECURITY_FEATURES_COMPLETED_N": "Güvenlik ({n}/{m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Parolasız modda ayarlanmış cihazlar Trezor Suite'e erişemez. Bunun amacı, yanlış amaçla yanlış ayarlanmış bir cihaz kullanıldığında meydana gelen, geri dönüşü olmayan coin kaybını önlemektir.", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Parolasız kurulum Trezor Suite tarafından desteklenmez", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "Parolasız modda ayarlanmış cihazlar, bir cihazın yanlış kullanılması durumunda oluşabilecek geri dönüşü olmayan coin kaybını önlemek için Trezor Suite'e erişemez.", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Parolasız kurulum Trezor Suite'te desteklenmez", "TR_SEED_BACKUP_LENGTH": "Kurtarma parolanız 12, 18 veya 24 kelime içerebilir.", "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "Kurtarma parolanız 12, 18, 20, 24 veya 33 kelime içerebilir.", "TR_SEED_CHECK_FAIL_TITLE": "Kurtarma parolası kontrolü başarısız", @@ -1520,12 +1568,15 @@ "TR_SEED_WORDS_ENTER_COMPUTER": "Kurtarma parolanızdaki kelimeleri Trezor'unuzda görüntülenen sırayla girin.", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "Dokunmatik ekranı kullanarak, tamamlanıncaya kadar tüm kelimeleri doğru sırada girin.", "TR_SEE_DETAILS": "Ayrıntıları gör", + "TR_SEE_IF_ISSUE_PERSISTS": "Sorunun devam edip etmediğine bakın.", "TR_SELECTED": "{amount} seçildi", "TR_SELECT_COIN_FOR_SETTINGS": "Ayarları değiştirmek için etkin coin'i seçin", "TR_SELECT_DEVICE": "Cihaz seç", + "TR_SELECT_NAME_OR_ADDRESS": "Ad, sembol, ağ veya sözleşme adresine göre arama yapın", "TR_SELECT_NUMBER_OF_WORDS": "Kurtarma parolanızdaki kelime sayısını seçin", "TR_SELECT_PASSPHRASE_SOURCE": "\"{deviceLabel}\" üzerinde parolanın girileceği yeri seçin.", "TR_SELECT_RECOVERY_METHOD": "Kurtarma yöntemi seç", + "TR_SELECT_TOKEN": "Token seçin", "TR_SELECT_TREZOR": "Trezor'u seç", "TR_SELECT_TREZOR_TO_CONTINUE": "Devam etmek için Trezor'unuzu seçin.", "TR_SELECT_TYPE": "Tür seç", @@ -1556,7 +1607,7 @@ "TR_SELL_MODAL_FOR_YOUR_SAFETY": "{provider} ile {cryptocurrency} sat", "TR_SELL_MODAL_LEGAL_HEADER": "Yasal uyarı", "TR_SELL_MODAL_SECURITY_HEADER": "Trezor'unuzla önce güvenlik", - "TR_SELL_MODAL_TERMS_1": "Kripto para satmak için buradasınız. Bu siteye başka bir nedenle yönlendirildiyseniz devam etmeden önce lütfen destek ekibiyle iletişime geçin.", + "TR_SELL_MODAL_TERMS_1": "Kripto para satmak için buradasınız. Bu siteye başka bir nedenle yönlendirildiyseniz devam etmeden önce lütfen Trezor Support ile iletişime geçin.", "TR_SELL_MODAL_TERMS_2": "Kendi hesabınız için kripto para satıyorsunuz. Sağlayıcının politikalarının kimlik doğrulaması gerektirebileceğini kabul edersiniz.", "TR_SELL_MODAL_TERMS_3": "Kripto para işlemlerinin geri dönüşü olmadığını ve iade edilemeyeceğini anlıyorsunuz. Bu nedenle, hileli veya kazara oluşan kayıplar telafi edilemez olabilir.", "TR_SELL_MODAL_TERMS_4": "Invity'nin bu hizmeti sağlamadığını anlıyorsunuz. Hizmet, {provider} şartlarına göre yönetilir.", @@ -1569,10 +1620,6 @@ "TR_SELL_STATUS_ERROR": "Reddedildi", "TR_SELL_STATUS_PENDING": "Beklemede", "TR_SELL_STATUS_SUCCESS": "Onaylandı", - "TR_SELL_TRANS_ID": "İşlem Kimliği:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Maksimum {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Minimum {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "Ayrıntıları görüntüle", "TR_SENDFORM_LABELING_EXAMPLE_1": "Tasarruflar", "TR_SENDFORM_LABELING_EXAMPLE_2": "Kirala", "TR_SENDING_SYMBOL": "{multiple, select, true {Birden fazla token} false {{symbol}} other {{symbol}}} gönderiliyor", @@ -1627,6 +1674,8 @@ "TR_SHOW_LOG": "Günlüğü göster", "TR_SHOW_MORE": "Daha fazla göster", "TR_SHOW_MORE_ADDRESSES": "Daha fazla göster ({count})", + "TR_SHOW_ON_TRAY": "Simgeyi tepside göster", + "TR_SHOW_ON_TRAY_DESCRIPTION": "Trezor Suite'in arka planda çalışıp çalışmadığını izleyin.", "TR_SHOW_UNVERIFIED_ADDRESS": "Doğrulanmamış adresi göster", "TR_SHOW_UNVERIFIED_XPUB": "Doğrulanmamış genel anahtarı göster", "TR_SIDEBAR_ADD_COIN": "Coin ekle", @@ -1639,7 +1688,9 @@ "TR_SIZE": "Boyut", "TR_SKIP": "Atla", "TR_SKIP_BACKUP": "Yedeklemeyi atla", + "TR_SKIP_BACKUP_DESCRIPTION": "Bir cüzdan yedekleme, Trezor'unuzun kaybolması, çalınması veya zarar görmesi durumunda fonlarınızı kurtarmanızı sağlar. Bir yedekleme olmazsa kripto paranıza erişimi kalıcı olarak kaybedebilirsiniz.", "TR_SKIP_PIN": "PIN kodunu atla", + "TR_SKIP_PIN_DESCRIPTION": "Bir cihaz PIN'i, Trezor'unuza yetkisiz erişimi önler. Bu olmadan, cihazınıza sahip olan herkes fonlarınıza erişebilir.", "TR_SKIP_ROUNDS": "Tur atlanıyor", "TR_SKIP_ROUNDS_DESCRIPTION": "Turların atlanmasına izin vererek, girdileriniz arasındaki herhangi bir ilişkiyi kanıtlamayı daha zor hale getirirsiniz. Bu, fonların kaynağını daha da gizleyebileceğiniz anlamına gelir.", "TR_SKIP_ROUNDS_HEADING": "Trezor'un turları atlamasına izin ver", @@ -1652,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "Stake havuzuna ekleniyor", "TR_STAKE_ANY_AMOUNT_ETH": "En az {amount} {symbol} tutarını stake edin ve ödüller kazanmaya başlayın. Mevcut %{apyPercent} APY oranımızla ödülleriniz de kazandırıyor!", "TR_STAKE_APY": "Yıllık Getiri Yüzdesi", + "TR_STAKE_APY_ABBR": "APY", "TR_STAKE_APY_DESC": "*Yıllık Getiri Yüzdesi", "TR_STAKE_AVAILABLE": "Mevcut", "TR_STAKE_CAN_CLAIM_WARNING": "Şimdiden {amount} {symbol} talep edebilirsiniz. {br}Lütfen talepte bulunun ya da yeni unstake etme işlemi gerçekleştirilene kadar bekleyin.", @@ -1661,13 +1713,16 @@ "TR_STAKE_CLAIM_AFTER_UNSTAKING": "Unstake etme süresi tamamlandığında talepte bulunabilirsiniz.", "TR_STAKE_CLAIM_IN_NEXT_BLOCK": "sonraki blokta", "TR_STAKE_CLAIM_PENDING": "Talep beklemede", + "TR_STAKE_CLAIM_UNSTAKED": "Unstake edilen {symbol} paraları talep et", "TR_STAKE_CONFIRM_AND_STAKE": "Onayla ve stake et", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "Giriş süresini onayla", "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "Everstake ile stake etmeyi kabul ediyorum ve onaylıyorum", "TR_STAKE_DAYS": "{days} gün", "TR_STAKE_DELEGATED": "Stake delegasyonu", "TR_STAKE_DEREGISTERED": "Bir stake adresinin kaydının silinmesi", + "TR_STAKE_EARN_REWARDS_WEEKLY": "Haftalık ödüller kazanın", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "Stake havuzuna girmek {days} gün kadar sürebilir", + "TR_STAKE_ENTER_THE_STAKING_POOL": "Stake havuzuna gir", "TR_STAKE_ETH": "Ethereum stake et", "TR_STAKE_ETH_CARD_TITLE": "{symbol} kazanmanın en kolay yolu.", "TR_STAKE_ETH_EARN_REPEAT": "Stake edin. Ödüller kazanın. Tekrarlayın.", @@ -1685,12 +1740,16 @@ "TR_STAKE_EVERSTAKE_MANAGES": "Everstake, akıllı sözleşmeleri, altyapısı ve teknolojisi ile stake edilmiş {symbol} tutarınızı yönetir ve korur.", "TR_STAKE_INSTANT": "Anında", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "{amount} {symbol} tutarını \"Anında\" aldınız. {days, plural, =0 {} one {Tutarın geri kalanı # gün içinde ödenecektir.} other { Tutarın geri kalanı # gün içinde ödenecektir}}", + "TR_STAKE_IN_ACCOUNT": "{symbol} hesapta", "TR_STAKE_LEARN_MORE": "Daha fazla bilgi edinin", + "TR_STAKE_LEAVE_STAKING_POOL": "Stake havuzundan çık", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "Para çekme ücretlerini ödeyebilmeniz için {amount} {symbol} tutarını dışarıda bıraktık.", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "Para çekme ücretlerini ödeyebilmeniz için {symbol} gibi küçük bir tutarı dışarıda bıraktık.", "TR_STAKE_MAX": "Maks.", "TR_STAKE_MAX_FEE_DESC": "Maksimum ücret, işleminizin gerçekleştirildiğinden emin olmak için ağ üzerinden ödeme yapmak istediğiniz ağ işlem ücretidir.", "TR_STAKE_MAX_REWARD_DAYS": "Maks. {count, plural, one { gün #} other { gün #}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "Stake edilecek minimum tutar {amount} {symbol}", + "TR_STAKE_MONTHLY": "Her ay", "TR_STAKE_NEXT_PAYOUT": "Bir sonraki ödül ödemesi", "TR_STAKE_NOT_ENOUGH_FUNDS": "Ağ ücretlerini ödemek için yeterli {symbol} yok", "TR_STAKE_ONLY_REWARDS": "Yalnızca ödüller", @@ -1702,6 +1761,8 @@ "TR_STAKE_REGISTERED": "Bir stake adresinin kaydedilmesi", "TR_STAKE_RESTAKED_BADGE": "Yeniden stake edildi", "TR_STAKE_REWARDS": "Ödüller", + "TR_STAKE_SIGN_TRANSACTION": "İşlemi imzala", + "TR_STAKE_SIGN_UNSTAKING_TRANSACTION": "Unstake etme işlemini imzala", "TR_STAKE_STAKE": "Stake et", "TR_STAKE_STAKED_AMOUNT": "Tutar stake edildi", "TR_STAKE_STAKED_AND_EARNING": "Stake edildi ve ödüller kazanılıyor", @@ -1709,6 +1770,7 @@ "TR_STAKE_STAKE_MORE": "Daha fazla stake et", "TR_STAKE_STAKING_IN_A_NUTSHELL": "Özetle Stake Etme", "TR_STAKE_STAKING_IS": "Stake etmek, blok zincirinin çalışmasını desteklemek için Ethereum varlıklarınızı geçici olarak kilitlediğiniz dostane bir jest gibidir. Tatlı bir ödül olarak, karşılığında daha fazla {symbol} kazanacaksınız!", + "TR_STAKE_STAKING_PROCESS": "Stake etme süreci", "TR_STAKE_START_STAKING": "Stake etmeye başla", "TR_STAKE_TIME_TO_CLAIM": "Talep etme zamanı", "TR_STAKE_TOTAL_PENDING": "Toplam stake beklemede:", @@ -1717,23 +1779,35 @@ "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "Unstake edildi ve talep etmeye hazır", "TR_STAKE_UNSTAKE_TO_CLAIM": "Talep etmek için unstake et", "TR_STAKE_UNSTAKING": "Unstake ediliyor", + "TR_STAKE_UNSTAKING_APPROXIMATE": "Anında kullanılabilir yaklaşık {symbol} miktarı", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "Stake havuzunun likiditesi bazı fonların anında unstake edilmesine olanak tanıyabilir. Kalan fonlar unstake etme dönemini takip edecektir.", "TR_STAKE_UNSTAKING_PERIOD": "Unstake etme süresi", + "TR_STAKE_UNSTAKING_PROCESS": "Unstake etme süreci", "TR_STAKE_UNSTAKING_TAKES": "Unstake etme işlemi genellikle yaklaşık 3 gün sürer. İşlem tamamlandıktan sonra takas edebilir veya gönderebilirsiniz.", + "TR_STAKE_WEEKLY": "Her hafta", "TR_STAKE_WHAT_IS_STAKING": "Stake etme nedir?", + "TR_STAKE_YEARLY": "Her yıl", "TR_STAKE_YOUR_FUNDS_MAINTAINED": "Stake edilen fonlarınız Everstake tarafından korunur", + "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "{amount} {symbol} anında stake edildi!", + "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "{amount} {symbol} anında unstake edildi!", + "TR_STAKING_CONSOLIDATING_FUNDS": "{symbol} sizin için konsolide ediliyor", "TR_STAKING_DELEGATE": "Delege et", "TR_STAKING_DEPOSIT": "İade edilebilir Depozito", "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "Depozito ücreti {feeAmount} ADA'dır ve stake etmeye başlamak için adresinizi kaydetmeniz gerekir. Cardano'nuzu unstake etmeyi seçerseniz depozitoyu geri alırsınız.", + "TR_STAKING_ESTIMATED_GAINS": "Tahmini kazanç", "TR_STAKING_FEE": "Ücret", + "TR_STAKING_GETTING_READY": "{symbol} çalışmaya hazırlanıyor", "TR_STAKING_INSTANTLY_STAKED": "{amount} {symbol} tutarını anında stake ettiniz. {days, plural, =0 {} one {Geri kalan {symbol} tutarı # gün içinde stake edilecektir.} other { Geri kalan {symbol} tutarı # gün içinde stake edilecektir}}", "TR_STAKING_IS_NOT_SUPPORTED": "Stake etme bu ağda desteklenmiyor.", "TR_STAKING_NOT_ENOUGH_FUNDS": "Hesabınızda yeterli fon yok.", + "TR_STAKING_ONCE_YOU_CONFIRM": "Onayladıktan sonra", "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "Bir Trezor stake havuzunda stake ederek Trezor'u ve Trezor Suite içindeki Cardano ekosistemini doğrudan desteklemiş olursunuz.", "TR_STAKING_ON_3RD_PARTY_TITLE": "Bir üçüncü taraf stake havuzunda delegasyon yapıyorsunuz", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "Delege ettiğiniz stake havuzu aşırı doymuş durumda. Lütfen stake ödüllerinizi en üst düzeye çıkarmak için stakenizi yeniden delege edin", "TR_STAKING_POOL_OVERSATURATED_TITLE": "Stake havuzu aşırı doymuş durumda", "TR_STAKING_REDELEGATE": "Yeniden delege et", "TR_STAKING_REWARDS": "Mevcut Ödüller", + "TR_STAKING_REWARDS_ARE_RESTAKED": "Ödüller otomatik olarak yeniden stake edilir", "TR_STAKING_REWARDS_DESCRIPTION": "İlk stake kaydı ve delegasyonundan sonra ödüllerinizi almaya başlamanızın 20 güne kadar sürebileceğini lütfen unutmayın. Bu süre tamamlandıktan sonra ödülünüzü her 5 günde bir alacaksınız.", "TR_STAKING_REWARDS_TITLE": "Cardano Staking Etkin", "TR_STAKING_STAKE_ADDRESS": "Stake adresiniz", @@ -1742,6 +1816,9 @@ "TR_STAKING_TREZOR_POOL_FAIL": "Delegasyon yapılacak Trezor stake havuzuna ulaşılamadı.", "TR_STAKING_TX_PENDING": "İşleminiz ({txid}) blok zincirine başarıyla gönderildi ve onay bekleniyor.", "TR_STAKING_WITHDRAW": "Çek", + "TR_STAKING_YOUR_EARNINGS": "Kazançlarınız otomatik olarak yeniden stake edilir ve bileşik faiz kazanmanıza olanak tanır.", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "Unstake edilen {symbol} hazır", + "TR_STAKING_YOU_ARE_HERE": "Buradasınız", "TR_STANDARD_WALLET_DESCRIPTION": "Parola yok", "TR_START": "Başlat", "TR_START_AGAIN": "Tekrar başlat", @@ -1750,6 +1827,7 @@ "TR_START_COINJOIN": "Coinjoin'i başlat", "TR_START_RECOVERY": "Kurtarmayı başlat", "TR_STEP": "Adım {number}", + "TR_STEP_OF_TOTAL": "Adım {index} / {total}", "TR_STILL_DONT_SEE_YOUR_TREZOR": "Trezor'unuzu hala göremiyor musunuz?", "TR_STOP": "Durdur", "TR_STOPPING": "Durduruluyor", @@ -1758,7 +1836,7 @@ "TR_SUITE_META_DESCRIPTION": "Trezor donanım cüzdanları için yeni masaüstü ve tarayıcı uygulaması. Trezor Suite, üç temel unsurumuz olan kullanılabilirlik, güvenlik ve gizlilik konularında önemli iyileştirmeler sunar.", "TR_SUITE_STORAGE": "Uygulama depolama", "TR_SUITE_VERSION": "Trezor Suite sürümü", - "TR_SWITCH_DEVICE": "Cihazı değiştir", + "TR_SWITCH_DEVICE": "Cihazı değiştirin", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_CANCEL_BUTTON": "İptal et", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DESCRIPTION": "Cihazınızı yeniden bağlayana kadar fonlarınız ve işlemleriniz görünmez.", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_DESCRIPTION": "Cihazınızı yeniden bağlayana kadar fonlarınız ve işlemleriniz görünmez.", @@ -1801,7 +1879,11 @@ "TR_TOKENS_EMPTY": "Token yok... henüz.", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "Token yok. Gizli olabilirler.", "TR_TOKENS_SEARCH_TOOLTIP": "Token, sembol veya sözleşme adresine göre arama yapın.", + "TR_TOKEN_NOT_FOUND": "Token bulunamadı", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "{networkName} ağında token bulunamadı.", "TR_TOKEN_TRANSFERS": "{standard} Token Transferleri", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "Farklı bir arama deneyin.", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "Farklı bir arama deneyin veya başka bir ağa geçin.", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "Tanınmayan token'lar", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "Tanınmayan token'lar potansiyel risk oluşturur. Dikkatli kullanın.", "TR_TOO_LONG": "Mesaj çok uzun", @@ -1813,18 +1895,24 @@ "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "Yolu güncelle", "TR_TOR_DESCRIPTION": "Trezor Suite'in tüm trafiğini Tor ağı üzerinden yönlendirerek gizliliğinizi ve güvenliğinizi artırın. Tor'un yüklenmesi ve bağlantı kurulması biraz zaman alabilir.", "TR_TOR_DISABLE": "Tor'u devre dışı bırak", + "TR_TOR_DISABLED": "Devre dışı", "TR_TOR_DISABLE_ONIONS_ONLY": "Onion olmayan özel arka uçlar eksik", "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "Bu davranışı önlemek için lütfen onion olmayan özel arka uç adresleri ekleyin.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "Tor'u şimdi güvenle devre dışı bırakabilirsiniz.", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "Özel arka uçlar artık yalnızca onion adreslerini kullanmıyor.", "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "Tor'u devre dışı bırak", "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "Tor'u şimdi devre dışı bırakmak, tüm Onion arka uçlarını varsayılan Trezor sunucularına sıfırlayacaktır.", + "TR_TOR_DISABLING": "Devre dışı bırakılıyor", "TR_TOR_ENABLE": "Tor'u etkinleştir", + "TR_TOR_ENABLED": "Etkin", "TR_TOR_ENABLE_AND_CONFIRM": "Tor'u etkinleştir ve onayla", "TR_TOR_ENABLE_TITLE": "Tor'u etkinleştir", + "TR_TOR_ENABLING": "Etkinleştiriliyor", + "TR_TOR_ERROR": "Hata", "TR_TOR_IS_SLOW_MESSAGE": "Tor ağa bağlanıyor.

Biraz daha bekleyin.", "TR_TOR_KEEP_RUNNING": "Tor'u çalıştırmaya devam et", "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "Lütfen devam etmek için \"Tor'u çalıştırmaya devam et\" veya coinjoin işleminden çıkmak için \"Tor'u durdur\" öğesini seçin.", + "TR_TOR_MISBEHAVING": "Uygunsuz davranma", "TR_TOR_REMOVE_ONION_AND_DISABLE": "Tor'u devre dışı bırak ve varsayılan arka uçlara geç", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "Ayrıl", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "Lütfen devam etmek için \"Tor'u Etkinleştir\" veya işlemden çıkmak için \"Ayrıl\" öğesini seçin.", @@ -1839,11 +1927,7 @@ "TR_TO_BTC": "BTC'ye", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Etiketlerinizi tutarlı ve farklı cihazlarda kullanılabilir hale getirmek için bir bulut depolama sağlayıcısına bağlanın.", "TR_TO_SATOSHIS": "Sat'a", - "TR_TRADE_BUYS": "satın alır", - "TR_TRADE_ENTER_COIN": "Kripto adı veya sembolünü girin...", - "TR_TRADE_EXCHANGES": "takas işlemi yapar", "TR_TRADE_REDIRECTING": "Yeniden yönlendiriliyor...", - "TR_TRADE_SELLS": "satar", "TR_TRANSACTIONS_NOT_AVAILABLE": "İşlem geçmişi mevcut değil", "TR_TRANSACTIONS_SEARCH_TIP_1": "İpucu: İşlem kimliklerini, adresleri, token'ları, etiketleri, tutarları ve tarihleri arayabilirsiniz.", "TR_TRANSACTIONS_SEARCH_TIP_10": "İpucu: Daha karmaşık aramalar için AND (&) ve OR (|) işleçlerini birleştirin. Örneğin, > {lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31, {lastYear} Ocak veya Aralık ayındaki tüm işlemleri gösterecektir.", @@ -1858,6 +1942,7 @@ "TR_TRANSACTIONS_SEARCH_TOOLTIP": "İşlem kimliği, etiket veya tutara göre arama yapın veya < > | & = != gibi işleçleri kullanın.", "TR_TRANSACTION_DETAILS": "Ayrıntılar", "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor Bridge {version} sürümünü çalıştırıyor", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "Eğitim iptal edildi", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "Eğitim tamamlandı", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "Kısa bir eğitim yardımıyla cihazınızı nasıl kullanacağınızı öğrenin", @@ -1865,32 +1950,40 @@ "TR_TROUBLESHOOTING_CLOSE_TABS": "Trezor'unuzu kullanıyor olabilecek diğer sekmeleri ve pencereleri kapatın", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "Diğer sekmeleri ve pencereleri kapattıktan sonra bu sayfayı yenilemeyi deneyin.", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "Diğer tarayıcı sekmelerini ve pencerelerini kapattıktan sonra Trezor Suite'ten çıkmayı ve yeniden açmayı deneyin.", - "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "İletişimi etkinleştirmek için gereken adımlar", + "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "Bu sorunu çözmek için aşağıdaki adımları deneyin.", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": " Trezor Bridge durum sayfasını ziyaret edin", "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "Trezor Bridge işleminin çalıştığından emin olun", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Şu anda yalnızca Chromium tabanlı tarayıcılar USB cihazlarıyla doğrudan iletişime izin verir", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "Şu anda yalnızca Chromium tabanlı tarayıcılar USB cihazlarıyla doğrudan iletişime izin verir.", "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "Chromium tabanlı bir tarayıcı kullanın", "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "Kablo tamamen takılı olmalıdır. USB-C bağlantılı bir cihaz olması durumunda, kablo yerine oturmalıdır.", "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "Farklı bir kablo deneyin", "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "Trezor Bridge yüklü olarak.", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "Mümkünse farklı bir bilgisayar kullanmayı deneyin", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Her ihtimale karşı", - "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Bilgisayarınızı yeniden başlatmayı deneyin", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "Bilgisayarınızı yeniden başlatmak tarayıcınız ve cihazınız arasındaki iletişim sorununu çözebilir.", + "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "Bilgisayarınızı yeniden başlatın", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "Trezor Suite masaüstü uygulamasını çalıştır", "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "Trezor Suite masaüstü uygulamasını kullan", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "Alternatif bir bridge uygulamasına değiştirmek için tıklayın. Mevcut sürüm: ({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "Trezor Bridge'in başka bir sürümünü kullanın", "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": " Udev kurallarını yüklemeyi deneyin. Açmadan önce masaüstüne kaydedildiğinden emin olun.", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "Cihazınızın ürün yazılımını en son 2019'da veya daha önce güncellediyseniz lütfen Bilgi Bankasındaki talimatları izleyin", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "Eski bir Trezor modeli kullanıyor olabilirsiniz.", "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "Doğrudan bilgisayarınıza bağlayın (USB hub olmadan).", "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "Farklı bir USB bağlantı noktası deneyin", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Kuralları otomatik olarak yükle", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Eksik udev kuralları", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Beklenmeyen durum: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Cihazınız düzgün bir şekilde bağlandı ancak tarayıcınız şu anda onunla iletişim kuramıyor. Trezor Bridge'i yüklemeniz gerekiyor.", "TR_TRY_AGAIN": "Tekrar deneyin", "TR_TXID": "İşlem kimliği", "TR_TXID_RBF": "Değiştirilecek orijinal işlem kimliği", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "İşlem onaylandı", "TR_TX_CONFIRMING": "İşlem onaylanıyor", + "TR_TX_DATA_FUNCTION": "Fonksiyon", + "TR_TX_DATA_INPUT_DATA": "Giriş verisi", + "TR_TX_DATA_METHOD": "Giriş verisi", + "TR_TX_DATA_METHOD_NAME": "Yöntem adı", + "TR_TX_DATA_PARAMS": "Parametreler", "TR_TX_DEPOSIT": "Depozito", "TR_TX_FEE": "Ücret", "TR_TX_TAB_AMOUNT": "Tutar", @@ -1930,6 +2023,8 @@ "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "Bellenim güncellemesi gerekli. Ana ekranınızı daha sonra ayarlar sayfasından değiştirebilirsiniz", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "Ana ekranınızı değiştirmek için belleniminizi güncelleyin", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "Güncelleme mevcut", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "Otomatik güncellemeleri etkinleştir", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "Yeni", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "Yeniden başlat ve güncelle", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "Güncellemeyi şimdi yüklemek ister misiniz?", "TR_UPDATE_MODAL_NOT_NOW": "Şimdi değil", @@ -1937,6 +2032,9 @@ "TR_UPDATE_MODAL_START_DOWNLOAD": "Güncellemeyi başlat", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "Güncelleme indirildi", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "Çıkarken güncelle", + "TR_UPDATE_MODAL_WHATS_NEW": "Yenilikler", + "TR_UPDATE_MODAL_YOUR_VERSION": "Sürümünüz: v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "Bu hesaba erişmek için belleniminizi yükseltin. Yukarıdaki mavi başlığa bakın.", "TR_UP_TO": "şuna kadar:", "TR_UP_TO_DATE": "Güncel", "TR_UP_TO_DAYS": "{days} güne kadar", @@ -1990,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "Gizli cüzdana eriş", "TR_WALLET_SELECTION_HIDDEN_WALLET": "Gizli cüzdan", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "Yeni bir cüzdan oluşturun veya kurtarma parolanızı kullanarak bir yedeklemeden bir cüzdan kurtarın.", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "Trezor deneyiminizi iyileştirmek için sürekli çalışıyoruz. Yenilikler şunlardır:", "TR_WEST": "Batı", "TR_WHAT_DATA_WE_COLLECT": "Hangi verileri topluyoruz?", "TR_WHAT_IS_PASSPHRASE": "Fark hakkında daha fazla bilgi edinin", diff --git a/packages/suite-data/files/translations/uk.json b/packages/suite-data/files/translations/uk.json index 52e75337c0a..16f2649709b 100644 --- a/packages/suite-data/files/translations/uk.json +++ b/packages/suite-data/files/translations/uk.json @@ -191,7 +191,6 @@ "TR_404_GO_TO_DASHBOARD": "Перейти на головну", "TR_404_TITLE": "Помилка 404: Посилання не знайдено", "TR_7D_CHANGE": "Протягом 7 днів ", - "TR_ABORT": "Скасувати", "TR_ACCESS_HIDDEN_WALLET": "Увійти в прихований гаманець", "TR_ACCESS_STANDARD_WALLET": "Увійти в стандартний гаманець", "TR_ACCOUNT_DETAILS_HEADER": "Деталі рахунка", @@ -209,7 +208,7 @@ "TR_ACCOUNT_EXCEPTION_DISCOVERY_EMPTY_DESC": "Всі монети зараз відключені. Будь ласка, додайте новий аккаунт або ввімкніть монети в налаштуваннях.", "TR_ACCOUNT_EXCEPTION_DISCOVERY_ERROR": "Помилка виявлення аккаунту", "TR_ACCOUNT_EXCEPTION_NOT_ENABLED": "{networkName} вимкнено в налаштуваннях.", - "TR_ACCOUNT_EXCEPTION_NOT_EXIST": "Аккаунт не існує", + "TR_ACCOUNT_EXCEPTION_NOT_EXIST": "Рахунка не існує", "TR_ACCOUNT_IMPORTED_ANNOUNCEMENT": "Рахунок тільки для спостереження є публічною адресою, яку ви імпортували в ваш гаманець, що дозволяє гаманцю дивитися на виходи (outputs), але не витрачати їх.", "TR_ACCOUNT_IS_EMPTY_DESCRIPTION": "Почніть з отримання транзакцій або купівлі {network}.", "TR_ACCOUNT_IS_EMPTY_TITLE": "Ще немає транзакцій.", @@ -343,9 +342,7 @@ "TR_BUG": "Баг", "TR_BUMP_FEE": "Змінити комісію", "TR_BUY": "Купити", - "TR_BUY_ACCOUNT_TRANSACTIONS": "Торговельні операції", "TR_BUY_BUY": "Купити", - "TR_BUY_BUY_AGAIN": "Купити знову", "TR_BUY_CONFIRMED_ON_TREZOR": "Підтверджено на Trezor", "TR_BUY_DETAIL_ERROR_BUTTON": "Повернутися до рахунка", "TR_BUY_DETAIL_ERROR_SUPPORT": "Перейти до служби підтримки провайдера", @@ -390,12 +387,10 @@ "TR_BUY_STATUS_PENDING": "В очікуванні", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "В очікуванні", "TR_BUY_STATUS_SUCCESS": "Схвалено", - "TR_BUY_TRANS_ID": "ID транзакції:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "Максимум - {maximum}", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "Максимум становить - {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "Мінімум - {minimum}", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "Мінімум становить - {maximum} {currency}", - "TR_BUY_VIEW_DETAILS": "Переглянути деталі", "TR_BYTES": "байтів", "TR_CAMERA_NOT_RECOGNIZED": "Камера не розпізнана.", "TR_CAMERA_PERMISSION_DENIED": "У доступі до камери було відмовлено.", @@ -414,7 +409,6 @@ "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Сума Trezor", "TR_CHAINED_TXS": "Ланцюгові транзакції", "TR_CHANGELOG": "Список змін", - "TR_CHANGELOG_ON_GITHUB": "Список змін на GitHub", "TR_CHANGE_ADDRESS_TOOLTIP": "Ця адреса була створена як невитрачений залишок (\"change\") попередньої транзакції.", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "Ви можете змінити тип прошивки в налаштуваннях в будь-який час.", "TR_CHANGE_HOMESCREEN": "Змінити заставку", @@ -430,8 +424,6 @@ "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "Виконайте симуляцію відновлення, щоб перевірити вашу мнемонічну фразу.", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "Введіть слова з резервної копії гаманця в тому порядку, в якому вони відображаються на вашому пристрої. Можливо, вас попросять ввести деякі слова, які не входять до резервної копії гаманця, як додатковий захід безпеки.", "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "Ваша фраза відновлення (резервна копія гаманця) вводиться за допомогою кнопок Trezor Model T. Це дає змогу уникнути розкриття будь-якої конфіденційної інформації на потенційно небезпечному комп'ютері або веб-браузері.", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "Резервна копія вашого гаманця вводиться за допомогою сенсорного екрана. Це дозволяє уникнути потрапляння вашої конфіденційної інформації до потенційно небезпечного комп'ютера або веб-браузера.", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "Резервна копія вашого гаманця вводиться за допомогою сенсорного екрана. Це дозволяє уникнути потрапляння вашої конфіденційної інформації до потенційно небезпечного комп'ютера або веб-браузера.", "TR_CHECK_SEED": "Перевірити резервну копію", "TR_CHECK_YOUR_DEVICE": "Перевірте екран свого Trezor", "TR_CHOOSE_RECOVERY_TYPE": "Виберіть тип відновлення", @@ -484,12 +476,6 @@ "TR_COINJOIN_TILE_3_DESCRIPTION": "Ваш Bitcoin завжди знаходиться під Вашим контролем", "TR_COINJOIN_TILE_3_TITLE": "Protected by your Trezor", "TR_COINJOIN_TRANSACTION_BATCH": "Транзакції coinjoin", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "Автоматичне перезавантаження через", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "Повернутися до транзакції", - "TR_COINMARKET_NO_OFFERS_HEADER": "Немає пропозицій", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "На жаль, на даний момент у нас немає пропозицій через проблеми з підключенням до сервера.", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "На жаль, у нас немає для Вас жодної пропозиції на даний момент. Спробуйте перезавантажити сторінку або змінити ваш запит.", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "Перезавантажити сторінку", "TR_COINS": "Криптовалюта", "TR_COIN_CONTROL": "Coin Control", "TR_COIN_CONTROL_TOOLTIP": "Coin control дає змогу вручну обирати UTXO, які будуть використовуватися як входи для транзакції.", @@ -553,7 +539,6 @@ "TR_COPY_ADDRESS_POLICY_ID": "Ніколи не надсилайте активи на ідентифікаційну адресу полісу.", "TR_COPY_AND_CLOSE": "Копіювати та Закрити", "TR_COPY_SIGNED_MESSAGE": "Копіювати підписане повідомлення", - "TR_COPY_TO_CLIPBOARD_TX_ID": "Копіювати", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "Не вдалося отримати список змін", "TR_COULD_NOT_RETRIEVE_DATA": "Не вдалося отримати дані", "TR_COUNT_WALLETS": "{count} {count, plural, one {гаманець} few {гаманців} many {гаманців} other {гаманці}}", @@ -709,7 +694,6 @@ "TR_DOWNLOADING": "Завантаження", "TR_DOWNLOAD_LATEST_BRIDGE": "Завантажити останню версію Bridge {version}", "TR_DO_NOT_DISCONNECT_DEVICE": "Не вимикайте свій пристрій", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "Ви дійсно хочете пропустити цей крок?", "TR_DROPBOX": "Dropbox", "TR_DROPZONE": "Перетягніть файл сюди або клацніть, щоб вибрати з файлів", "TR_DROPZONE_ERROR": "Помилка імпорту: {error}", @@ -789,7 +773,6 @@ "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "Схваліть тільки точну суму, необхідну для цього обміну. Вам потрібно буде заплатити додаткову комісію, якщо ви захочете здійснити подібний обмін ще раз.", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "Скасувати попередній дозвіл", "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "Виконає операцію, яка видалить попереднє схвалення контракту з {provider}.", - "TR_EXCHANGE_BUY": "Для", "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "Підтвердити на Trezor і відправити", "TR_EXCHANGE_CONFIRM_SEND_STEP": "Підтвердити та відправити", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "Створити схвалення", @@ -811,8 +794,6 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "Ваша транзакція пройшла успішно.", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "Схвалено", "TR_EXCHANGE_DEX": "Пропозиція децентралізованої біржі", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "Комісія за виконання цього обміну оцінюється в {approvalFee} ({approvalFeeFiat}) на затвердження (якщо потрібно) і {swapFee} ({swapFeeFiat}) на заміну.", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "Немає коштів для оплати комісії за транзакцію. Будь ласка, знизьте суму обміну до максимальної: {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} є недійсним", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "", @@ -822,7 +803,6 @@ "TR_EXCHANGE_FIXED_OFFERS_INFO": "Фіксовані курси показують, скільки саме ви отримаєте в кінці обміну - сума не зміниться з моменту вибору курсу до завершення транзакції. Ви гарантовано отримаєте вказану суму, але ці курси зазвичай менш вигідні, що означає, що на ці кошти ви не купите стільки криптовалюти, скільки планували.", "TR_EXCHANGE_FLOAT": "Пропозиція з плаваючою ставкою", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "Плаваючі курси означають, що кінцева сума, яку ви отримаєте, може дещо змінитися через коливання на ринку між моментом вибору курсу і завершенням транзакції. Ці курси зазвичай вищі, а це означає, що в результаті ви можете отримати більше криптовалюти.", - "TR_EXCHANGE_PROVIDER": "Провайдер", "TR_EXCHANGE_RATE": "Ціна", "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "Рахунок отримувача знаходиться поза межами Suite.", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "Це конкретна буквено-цифрова адреса, на яку надійдуть ваші монети.", @@ -849,12 +829,9 @@ "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "Будь ласка, введіть номер.", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "Введіть бажане відхилення від курсу", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "Сума пропозиції свопу", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "Підсумки відхилення від курсу", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "Допустимість відхилення від курсу", - "TR_EXCHANGE_TRANS_ID": "ID транзакції:", "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "Використовуйте обліковий запис ({symbol}), якого немає в Suite", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "Адреса отримувача", - "TR_EXCHANGE_VIEW_DETAILS": "Переглянути деталі", "TR_EXPERIMENTAL_FEATURES": "Експериментальні функції", "TR_EXPERIMENTAL_FEATURES_ALLOW": "Дозволити експериментальні функції", "TR_EXPERIMENTAL_FEATURES_WARNING": "Довготривала підтримка цих функцій не гарантується.", @@ -1181,7 +1158,6 @@ "TR_NETWORK_ZCASH": "Zcash", "TR_NEW_FEE": "Новий", "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "Доступний новий Trezor Bridge.", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "Доступна нова прошивка Trezor! Будь ласка, оновіть свій пристрій.", "TR_NEXT_UP": "Далі", "TR_NONCE": "Nonce", "TR_NORMAL_ACCOUNTS": "Рахунок за замовчуванням", @@ -1205,10 +1181,6 @@ "TR_N_MIN": "{n} min", "TR_N_TRANSACTIONS": "{value} {value, plural, one {транзакція} few {транзакції} many {транзакції} other {транзакції}}", "TR_OFF": "вимк", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "Обрана сума {amount} більша, ніж допустимий максимум {max}.", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "Обрана сума {amount} більша, ніж допустимий максимум {max}.", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "Обрана сума {amount} є меншою, ніж прийнятний мінімум {min}.", - "TR_OFFER_ERROR_MINIMUM_FIAT": "Обрана сума {amount} менша, ніж прийнятий мінімум {min}.", "TR_OFFICIAL_LANGUAGES": "Офіційний", "TR_OK": "OK", "TR_ON": "увімк.", @@ -1265,30 +1237,6 @@ "TR_OTHER_INPUTS_AND_OUTPUTS": "Інші вхідні та вихідні дані", "TR_OUTGOING": "Вихідні", "TR_OUTPUTS": "Виходи", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "Мінімальна і максимальна сума, за яку цей користувач готовий продати {symbol}.", - "TR_P2P_GET_STARTED_INTRO": "Вам необхідно ініціювати транзакцію на сайті {providerName} - уважно виконайте наступні кроки.", - "TR_P2P_GET_STARTED_ITEM_1": "Виберіть \"Перейти на сайт {providerName}\" для перенаправлення на сайт наших партнерів.", - "TR_P2P_GET_STARTED_ITEM_3": "Щойно {providerName} попросить у вас \"release address\", поверніться сюди і продовжуйте.", - "TR_P2P_GET_STARTED_ITEM_4": "Майже готово! Відкрийте та скопіюйте свою адресу, вставте її в поле \"release address\" на сайті {providerName} і завершіть транзакцію.", - "TR_P2P_GO_TO_PROVIDER": "Перейти на сайт {providerName}", - "TR_P2P_INFO": "При використанні технології {peerToPeer} (P2P) немає необхідності в перевірці KYC ні з боку покупця, ні з боку продавця. Усі сторони захищені від шахрайства надійним {multisigEscrow}.", - "TR_P2P_MODAL_CONFIRM": "Я готовий купити", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "P2P покупка {cryptocurrency} с {provider}", - "TR_P2P_MODAL_LEGAL_HEADER": "Юридичне повідомлення", - "TR_P2P_MODAL_SECURITY_HEADER": "Безпека перш за все з Вашим Trezor", - "TR_P2P_MODAL_TERMS_1": "Ви тут, щоб купити криптовалюту у іншої особи, яку ви обрали, використовуючи технологію Peer-to-Peer (P2P) без перевірки особи. Якщо ви потрапили на цей сайт з будь-якої іншої причини, зверніться до служби підтримки, перш ніж продовжити.", - "TR_P2P_MODAL_TERMS_2": "Ви розумієте, що операції з криптовалютою незворотні і не підлягають поверненню. Таким чином, шахрайські або випадкові втрати можуть бути непоправними.", - "TR_P2P_MODAL_TERMS_4": "Ви розумієте, що Invity не надає цю послугу. Умови компанії {provider} регулюють цю послугу.", - "TR_P2P_MODAL_TERMS_5": "Ви не використовуєте цю функцію для азартних ігор, шахрайства або будь-якого іншого порушення Умов надання послуг Invity або провайдера, а також будь-яких застосовних нормативних актів.", - "TR_P2P_MODAL_TERMS_6": "Ви розумієте, що криптовалюти є фінансовим інструментом, який розвивається, і що в різних юрисдикціях можуть діяти різні нормативні акти. Це може піддати Вас підвищеному ризику шахрайства, крадіжки або нестабільності ринку.", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "Перевірені партнери від Invity", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "Угода має бути завершена протягом цього терміну, рахуючи з моменту створення контракту на сайті {providerName} .", - "TR_P2P_PRICE": "Ціна за 1 {symbol}", - "TR_P2P_PRICE_TOOLTIP": "{symbol} ціна, запропонована цим користувачем.", - "TR_P2P_TITLE_NOT_AVAILABLE": "Привіт, я використовую {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}, {numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "Обрана сума {amount} вища, ніж допустимий максимум {maximum}.", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "Обрана кількість {amount} менше допустимого мінімуму {minimum}.", "TR_PAGINATION_NEWER": "Новіші", "TR_PAGINATION_OLDER": "Старіші", "TR_PASSPHRASE_CASE_SENSITIVE": "Увага: кодову фразу потрібно вводити з урахуванням регістра.", @@ -1470,10 +1418,6 @@ "TR_SELL_STATUS_ERROR": "Відхилено", "TR_SELL_STATUS_PENDING": "В очікуванні", "TR_SELL_STATUS_SUCCESS": "Схвалено", - "TR_SELL_TRANS_ID": "ID транзакції:", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "Максимум становить - {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "Мінімум становить - {maximum} {currency}", - "TR_SELL_VIEW_DETAILS": "Переглянути деталі", "TR_SENDFORM_LABELING_EXAMPLE_1": "Заощадження", "TR_SENDFORM_LABELING_EXAMPLE_2": "Оренда", "TR_SENDING_SYMBOL": "Надсилання {multiple, select, true {декількох токенів} false {{symbol}} other {{symbol}}}", @@ -1732,10 +1676,7 @@ "TR_TO_BTC": "В BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "Щоб зробити ваші мітки узгодженими та доступними на різних пристроях, під’єднайтеся до постачальника хмарного сховища.", "TR_TO_SATOSHIS": "В sat", - "TR_TRADE_BUYS": "покупки", - "TR_TRADE_EXCHANGES": "обміни", "TR_TRADE_REDIRECTING": "Перенаправлення...", - "TR_TRADE_SELLS": "продажі", "TR_TRANSACTIONS_NOT_AVAILABLE": "Історія транзакцій недоступна", "TR_TRANSACTIONS_SEARCH_TIP_1": "Порада: Ви можете шукати ID транзакцій, адрес, мітки, суми та дат.", "TR_TRANSACTIONS_SEARCH_TIP_10": "Порада: Для складнішого пошуку можна комбінувати оператори AND (&) та OR (|). Наприклад, >> 2020-01-01 & < 2020-01-31 | > 2020-12-01 & < 2020-12-31 покаже всі транзакції за січень 2020 року або грудень 2020 року.", @@ -1770,7 +1711,6 @@ "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "Встановлення правил автоматично", "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "Відсутні правила udev", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "Неочікуваний стан: {error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "Ваш пристрій підключено належним чином, але інтернет-браузер не може встановити з ним зв'язок. Вам потрібно встановити Trezor Bridge.", "TR_TRY_AGAIN": "Спробуйте ще раз", "TR_TXID": "TX ID", "TR_TXID_RBF": "Оригінальний TX ID потрібно замінити", diff --git a/packages/suite-data/files/translations/zh.json b/packages/suite-data/files/translations/zh.json index c9aba071cee..b9d063e0268 100644 --- a/packages/suite-data/files/translations/zh.json +++ b/packages/suite-data/files/translations/zh.json @@ -1,8 +1,9 @@ { "AMOUNT": "金额", + "AMOUNT_EXCEEDS_MAX": "该金额超出了 {maxAmount} 的最大允许值。", "AMOUNT_IS_BELOW_DUST": "金额必须至少为 {dust}", "AMOUNT_IS_LESS_THAN_RESERVE": "接收账户需要最低保留 {reserve} XRP 才能激活", - "AMOUNT_IS_MORE_THAN_RESERVE": "金额超过规定的不可动用储备金({reserve} XRP)。", + "AMOUNT_IS_MORE_THAN_RESERVE": "金额超过规定的不可动用储备金 ({reserve} XRP)。", "AMOUNT_IS_NOT_ENOUGH": "资金不足", "AMOUNT_IS_NOT_INTEGER": "金额不是整数", "AMOUNT_IS_NOT_IN_RANGE_DECIMALS": "最多允许 {decimals} 位小数", @@ -15,105 +16,105 @@ "BACKUP_BACKUP_ALREADY_FINISHED_DESCRIPTION": "连接的设备已备份。你应该把恢复种子写下来,藏在安全的地方。", "BACKUP_BACKUP_ALREADY_FINISHED_HEADING": "备份已完成", "BROADCAST": "广播", - "BROADCAST_TOOLTIP": "向网络广播该交易。", - "BROADCAST_TOOLTIP_DISABLED_LOCKTIME": "锁定时间设置超过当前区块或时间戳的交易将被网络拒绝。", + "BROADCAST_TOOLTIP": "向网络广播此交易。", + "BROADCAST_TOOLTIP_DISABLED_LOCKTIME": "时间锁设置超过当前区块或时间戳的交易将被网络拒绝。", "COPY_TRANSACTION_TO_CLIPBOARD": "复制原始交易", - "CUSTOM_FEE_IS_NOT_INTEGER": "输入一个整数", - "CUSTOM_FEE_IS_NOT_SET": "输入你希望的费用", - "CUSTOM_FEE_LIMIT_BELOW_RECOMMENDED": "Gas费率限额太低", + "CUSTOM_FEE_IS_NOT_INTEGER": "请输入一个整数", + "CUSTOM_FEE_IS_NOT_SET": "请输入您愿意使用的费率来完整此交易", + "CUSTOM_FEE_LIMIT_BELOW_RECOMMENDED": "燃料费上限太低", "CUSTOM_FEE_LIMIT_USE_RECOMMENDED": "使用推荐值", - "CUSTOM_FEE_NOT_IN_RANGE": "输入介于 {minFee} 和 {maxFee} 之间的费率", + "CUSTOM_FEE_NOT_IN_RANGE": "请输入介于 {minFee} 和 {maxFee} 之间的费率", "DATA_ETH": "数据", "DATA_ETH_ADD": "添加数据", "DATA_ETH_ADD_TOOLTIP": "以太坊交易的交易数据。", "DATA_HEX_TOO_BIG": "超出数据限制", - "DATA_NOT_SET": "数据未设置", + "DATA_NOT_SET": "未设置数据", "DATA_NOT_VALID_HEX": "不是有效的十六进制值", "DESTINATION_TAG": "目的地标签", "DESTINATION_TAG_IS_NOT_NUMBER": "目的地标签不是一个数字", - "DESTINATION_TAG_IS_NOT_VALID": "目的地标签是无效的", - "DESTINATION_TAG_NOT_SET": "目的地标签未设置", - "DESTINATION_TAG_TOOLTIP": "目的地标签是一个独特的识别码、用于识别交易的收款者。", - "DISCONNECT_DEVICE_DESCRIPTION": "您的设备被擦除,不再拥有任何私钥。", + "DESTINATION_TAG_IS_NOT_VALID": "目的地标签无效", + "DESTINATION_TAG_NOT_SET": "未设置目的地标签", + "DESTINATION_TAG_TOOLTIP": "目的地标签是一个独特的识别码,用于识别交易的接收者。", + "DISCONNECT_DEVICE_DESCRIPTION": "您的设备数据已被清除,不再拥有任何私钥。", "DOWNLOAD_TRANSACTION": "下载为 .txt", "ESTIMATED_TIME": "预计时间", "EVENT_DEVICE_CONNECT": "设备 {label} 已连接", "EVENT_DEVICE_CONNECT_UNACQUIRED": "{label} 已连接", - "EVENT_WALLET_CREATED": "新钱包已创建", - "EXPECTED_FEE": "预计费用", - "FAILED_TO_DISABLE_TOR": "禁用Tor失败", - "FAILED_TO_ENABLE_TOR": "启用Tor失败", + "EVENT_WALLET_CREATED": "已成功添加钱包", + "EXPECTED_FEE": "预计矿工费", + "FAILED_TO_DISABLE_TOR": "禁用 Tor 已失败", + "FAILED_TO_ENABLE_TOR": "启用 Tor 已失败", "FEE": "矿工费", "FEE_LEVEL_CUSTOM": "自定义", "FEE_LEVEL_HIGH": "高", "FEE_LEVEL_LOW": "低", - "FEE_LEVEL_NORMAL": "正常", - "FIRMWARE_CONNECT_IN_NORMAL_MODEL_NO_BUTTON": "连接USB数据线时请不要按住任何按钮。", - "FIRMWARE_USER_HAS_SEED_CHECKBOX_DESC": "是的、我有备份!", - "FIRMWARE_USER_TAKES_RESPONSIBILITY_CHECKBOX_DESC": "我接受风险", + "FEE_LEVEL_NORMAL": "普通", + "FIRMWARE_CONNECT_IN_NORMAL_MODEL_NO_BUTTON": "连接 USB 数据线时请不要按住任何按钮。", + "FIRMWARE_USER_HAS_SEED_CHECKBOX_DESC": "是的,我已有备份!", + "FIRMWARE_USER_TAKES_RESPONSIBILITY_CHECKBOX_DESC": "我愿意承担这些风险", "FW_CAPABILITY_CONNECT_OUTDATED": "需要更新应用程序", - "FW_CAPABILITY_UPDATE_REQUIRED": "需要更新", - "IMAGE_VALIDATION_ERROR_INVALID_COLOR_COMBINATION": "无效的图像颜色。图像只能包含黑白 (而不是灰度)。", + "FW_CAPABILITY_UPDATE_REQUIRED": "需要更新设备固件", + "IMAGE_VALIDATION_ERROR_INVALID_COLOR_COMBINATION": "无效的图像颜色。只能包含黑白 (非灰度)。", "IMAGE_VALIDATION_ERROR_INVALID_DIMENSIONS": "无效的尺寸 (图像必须是 {width} x {height} px)。", "IMAGE_VALIDATION_ERROR_INVALID_FORMAT_ONLY_JPG": "选择的文件是无效的。必须是 .jpg", "IMAGE_VALIDATION_ERROR_INVALID_FORMAT_ONLY_PNG_JPG": "选择的文件是无效的。必须是 .jpg 或 .png", - "IMAGE_VALIDATION_ERROR_INVALID_SIZE_JPG": "无效的大小 (图像必须小于16KB)。", - "IMAGE_VALIDATION_ERROR_PROGRESSIVE_JPG": "不支持逐行JPG图像格式。\n", - "IMAGE_VALIDATION_ERROR_UNEXPECTED_ALPHA": "无效的图像格式。它必须不包含透明度。", + "IMAGE_VALIDATION_ERROR_INVALID_SIZE_JPG": "无效的尺寸 (图像必须小于 16 KB)。", + "IMAGE_VALIDATION_ERROR_PROGRESSIVE_JPG": "不支持逐行 JPG 图像格式。\n", + "IMAGE_VALIDATION_ERROR_UNEXPECTED_ALPHA": "无效的图像格式。不能包含透明度。", "IMPORT_CSV": "导入", "INCLUDING_FEE": "包含矿工费", "LABELING_ACCOUNT": "{networkName} #{index}", - "LOCKTIME_ADD": "添加锁定期", - "LOCKTIME_ADD_TOOLTIP": "锁定期设定了交易被挖掘到区块的最早时间。", - "LOCKTIME_BLOCKHEIGHT": "锁定期的区块高度", - "LOCKTIME_IS_NOT_INTEGER": "锁定期不是一个整数", - "LOCKTIME_IS_NOT_SET": "未设置锁定期", + "LOCKTIME_ADD": "添加时间锁", + "LOCKTIME_ADD_TOOLTIP": "时间锁设定了最早交易可以被挖掘到区块的时间。", + "LOCKTIME_BLOCKHEIGHT": "时间锁的区块高度", + "LOCKTIME_IS_NOT_INTEGER": "时间锁不是一个整数", + "LOCKTIME_IS_NOT_SET": "时间锁未设置", "LOCKTIME_IS_TOO_BIG": "时间戳太大", - "LOCKTIME_IS_TOO_LOW": "锁定期太低", - "LOCKTIME_SCHEDULE_SEND": "锁定期", - "LOCKTIME_TIMESTAMP": "锁定期的时间戳", - "LOG_DESCRIPTION": "该日志包含有关Trezor Suite的重要技术信息、联系Trezor客服时可能需要这些信息。", - "LOG_INCLUDE_BALANCE_DESCRIPTION": "启用此选项会在应用程序日志中包含余额、交易ID、设备标签、设备ID和公共地址等敏感信息。如果问题与此无关、请禁用该选项。", - "LOG_INCLUDE_BALANCE_TITLE": "包含敏感数据", + "LOCKTIME_IS_TOO_LOW": "时间锁太短", + "LOCKTIME_SCHEDULE_SEND": "时间锁", + "LOCKTIME_TIMESTAMP": "时间锁的时间戳", + "LOG_DESCRIPTION": "该日志包含了有关 Trezor Suite 的重要技术信息,联系 Trezor 客服时可能需要这些信息。", + "LOG_INCLUDE_BALANCE_DESCRIPTION": "启用此选项会在应用程序日志中包含余额、交易ID、设备标签、设备ID和公共地址等敏感信息。如果问题与此无关,请禁用该选项。", + "LOG_INCLUDE_BALANCE_TITLE": "包含钱包余额的敏感数据", "MAX_FEE": "矿工费率最大值", - "METADATA_MODAL_DESCRIPTION": "选择如何同步您的标签。您的数据由Trezor加密。", + "METADATA_MODAL_DESCRIPTION": "选择如何同步您的标签。您的数据由 Trezor 设备加密。", "METADATA_MODAL_HEADING": "保存标签", - "METADATA_PROVIDER_AUTH_ERROR": "与云存储提供商 {provider} 同步标签失败。用户已注销。", - "METADATA_PROVIDER_NOT_FOUND_ERROR": "无法在云提供商中找到元数据。", - "METADATA_PROVIDER_UNEXPECTED_ERROR": "与云存储提供商 {provider} 同步标签失败。用户已注销。", - "MODAL_ADD_ACCOUNT_COINJOIN_DESKTOP_ONLY": "混币账户仅适用于Trezor Suite桌面应用程序。", - "MODAL_ADD_ACCOUNT_COINJOIN_LIMIT_EXCEEDED": "每个钱包只能拥有一个混币账户。", - "MODAL_ADD_ACCOUNT_COINJOIN_NO_SUPPORT": "请更新您的固件以使用混币功能", - "MODAL_ADD_ACCOUNT_COINJOIN_UPDATE_REQUIRED": "请更新您的固件以启用混币器。", - "MODAL_ADD_ACCOUNT_LIMIT_EXCEEDED": "已创建的账户数已达到允许的上限。", + "METADATA_PROVIDER_AUTH_ERROR": "无法与云存储提供商 {provider} 同步标签。用户已注销。", + "METADATA_PROVIDER_NOT_FOUND_ERROR": "无法在云存储提供商中找到元数据。", + "METADATA_PROVIDER_UNEXPECTED_ERROR": "无法与云存储提供商 {provider} 同步标签。用户已注销。", + "MODAL_ADD_ACCOUNT_COINJOIN_DESKTOP_ONLY": "CoinJoin 混币器账户仅适用于 Trezor Suite。", + "MODAL_ADD_ACCOUNT_COINJOIN_LIMIT_EXCEEDED": "每个钱包只能拥有一个 CoinJoin 混币器账户。", + "MODAL_ADD_ACCOUNT_COINJOIN_NO_SUPPORT": "请更新您的设备固件以使用 CoinJoin 混币器", + "MODAL_ADD_ACCOUNT_COINJOIN_UPDATE_REQUIRED": "请更新您的设备固件以启用 CoinJoin 混币器功能。", + "MODAL_ADD_ACCOUNT_LIMIT_EXCEEDED": "已达到账户创建数量的上限。", "MODAL_ADD_ACCOUNT_NO_ACCOUNT": "账户搜索错误", "MODAL_ADD_ACCOUNT_NO_EMPTY_ACCOUNT": "没有可用的空帐户。", "MODAL_ADD_ACCOUNT_PREVIOUS_EMPTY": "上一个帐户是空的", "NEXT_PAGE": "下一页", - "NOTIFICATIONS_ALL_TITLE": "所有活动", - "NOTIFICATIONS_EMPTY_DESC": "一旦有重要的通知、您将在这里看到它们。", + "NOTIFICATIONS_ALL_TITLE": "所有动态", + "NOTIFICATIONS_EMPTY_DESC": "您将在这里看到所有的重要通知。", "NOTIFICATIONS_EMPTY_TITLE": "还没有通知", "NOTIFICATIONS_IMPORTANT_TITLE": "通知", "NOTIFICATIONS_SEEN_TITLE": "全部已读", "NOTIFICATIONS_TITLE": "通知", - "NOTIFICATIONS_UNSEEN_TITLE": "{count} 未读", - "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_HEADING": "您正在使用一个不同的Trezor设备", - "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_P1": "这不是您一直使用的那个Trezor设备。请重新连接正确的那个。", - "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_P2": "如果您想使用该设备、请重新开始。", - "OP_RETURN": "OP RETURN", - "OP_RETURN_ADD": "添加OP Return", - "OP_RETURN_TOOLTIP": "用于将交易输出标记为无效的脚本操作码。由于带有 OP_RETURN 的输出都会被证明是无法花费的,OP_RETURN 的输出可以用来烧毁比特币。", + "NOTIFICATIONS_UNSEEN_TITLE": "{count} 个未读", + "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_HEADING": "您正在使用一台不同的 Trezor 设备", + "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_P1": "这不是您以往使用的那台 Trezor 设备。请重新连接正确的设备。", + "ONBOARDING_UNEXPECTED_DEVICE_DIFFERENT_P2": "如果您想使用该设备,请重新开始。", + "OP_RETURN": "OP_RETURN", + "OP_RETURN_ADD": "添加 OP_RETURN", + "OP_RETURN_TOOLTIP": "OP_RETURN 可用于数字资产所有权证明,有时也用于传递发送交易所需的附加信息。", "RAW_TRANSACTION": "原始交易", "RAW_TX_NOT_SET": "交易未设置", - "RBF": "交易费用替代", + "RBF": "费用替代", "RECEIVE_ADDRESS": "地址", - "RECEIVE_ADDRESS_COINJOIN_DISALLOW": "要为混币账户创建其他地址、您必须确保已在初始地址收到比特币。", + "RECEIVE_ADDRESS_COINJOIN_DISALLOW": "要为 CoinJoin 混币器账户创建其他地址,您必须确保已在初始地址收到比特币。", "RECEIVE_ADDRESS_FRESH": "新地址", - "RECEIVE_ADDRESS_LIMIT_REACHED": "您已经达到21个未使用过的地址上限", + "RECEIVE_ADDRESS_LIMIT_REACHED": "您已经达到 21 个未使用过的地址上限", "RECEIVE_ADDRESS_REVEAL": "显示完整地址", - "RECEIVE_ADDRESS_UNAVAILABLE": "无法使用", - "RECEIVE_DESC_BITCOIN": "要接收任何资金、您需要获得一个未使用过的接收地址。我们建议始终使用新的地址、因为这可以防止其他人追踪您的交易。当然您也可以重复使用一个地址、但我们建议除非绝对必要、否则不要这样做。", - "RECEIVE_DESC_ETHEREUM": "也可使用此地址接收代币。", + "RECEIVE_ADDRESS_UNAVAILABLE": "无法提供", + "RECEIVE_DESC_BITCOIN": "要接收任何资金,您需要获得一个未使用过的接收地址。我们建议始终使用新的地址,因为这可以防止其他人追踪您的交易。当然您也可以重复使用一个地址,但我们建议除非绝对必要,否则不要这样做。\n", + "RECEIVE_DESC_ETHEREUM": "也可使用此地址来接收代币。", "RECEIVE_TABLE_ADDRESS": "地址", "RECEIVE_TABLE_NOT_USED": "未使用", "RECEIVE_TABLE_RECEIVED": "总共接收", @@ -128,21 +129,21 @@ "RECIPIENT_REQUIRES_UPDATE": "您的固件版本不支持主根地址。请更新您的设备固件。", "RECIPIENT_SCAN": "扫描", "REFRESH": "刷新", - "REMAINING_BALANCE_LESS_THAN_RENT": "发送此金额后、您的账户将有 {remainingSolBalance} SOL的余额。非空账户的余额必须超过 {rent} SOL。", + "REMAINING_BALANCE_LESS_THAN_RENT": "发送此金额后,您的账户将有 {remainingSolBalance} SOL 的余额。非空账户的余额必须超过 {rent} SOL。", "REVIEW_AND_SEND_TRANSACTION": "审核并发送", "SEND_RAW": "发送原始", "SEND_RAW_TRANSACTION_TOOLTIP": "您可以自行提供交易的所有原始数据。", "SEND_TRANSACTION": "发送", - "SETTINGS_ADV_COIN_BLOCKBOOK_DESCRIPTION": "Trezor Suite使用Blockbook作为钱包的后端。您也可以使用您自己的自定义区块簿。", - "SETTINGS_ADV_COIN_BLOCKFROST_DESCRIPTION": "Trezor Suite使用Blockfrost websocket-link作为钱包的后端。", - "SETTINGS_ADV_COIN_CONN_INFO_BLOCK_HASH": "区块哈希: {hash}", - "SETTINGS_ADV_COIN_CONN_INFO_BLOCK_HEIGHT": "区块高度: {height}", + "SETTINGS_ADV_COIN_BLOCKBOOK_DESCRIPTION": "Trezor Suite 使用 Blockbook 作为钱包的后端。您也可以使用您自己的自定义区块簿。", + "SETTINGS_ADV_COIN_BLOCKFROST_DESCRIPTION": "Trezor Suite 使用 Blockfrost websocket 连接作为钱包的后端。", + "SETTINGS_ADV_COIN_CONN_INFO_BLOCK_HASH": "区块哈希:{hash}", + "SETTINGS_ADV_COIN_CONN_INFO_BLOCK_HEIGHT": "区块高度:{height}", "SETTINGS_ADV_COIN_CONN_INFO_NO_CONNECTED": "未连接到后端。请尝试重新连接设备。另外、请检查您的互联网连接或自定义后端的网址。", "SETTINGS_ADV_COIN_CONN_INFO_TITLE": "连接信息", "SETTINGS_ADV_COIN_CONN_INFO_URL": "当前已连接到 {url}", "SETTINGS_ADV_COIN_CONN_INFO_VERSION": "后端版本:{version}", - "SETTINGS_ADV_COIN_URL_INPUT_PLACEHOLDER": "例如: {url}", - "SETTINGS_UPDATE_AVAILABLE": "获取最新版本", + "SETTINGS_ADV_COIN_URL_INPUT_PLACEHOLDER": "例如:{url}", + "SETTINGS_UPDATE_AVAILABLE": "获取最新的版本", "SETTINGS_UPDATE_CHECK": "检查更新", "SETTINGS_UPDATE_CHECKING": "正在检查...", "SETTINGS_UPDATE_DOWNLOADING": "正在下载...", @@ -151,7 +152,7 @@ "TOAST_ACQUIRE_ERROR": "获取错误 {error}", "TOAST_AUTH_CONFIRM_ERROR": "密码短语确认错误:{error}", "TOAST_AUTH_CONFIRM_ERROR_DEFAULT": "无效的密码短语", - "TOAST_AUTH_FAILED": "授权错误: {error}", + "TOAST_AUTH_FAILED": "授权错误:{error}", "TOAST_AUTO_UPDATER_ERROR": "自动程序更新错误 ({state})", "TOAST_AUTO_UPDATER_NEW_VERSION_FIRST_RUN": "新版本 ({version}) 已成功安装。", "TOAST_AUTO_UPDATER_NO_NEW": "没有新的更新", @@ -161,18 +162,19 @@ "TOAST_COIN_SCHEME_PROTOCOL_ACTION": "自动填充发送表单", "TOAST_COIN_SCHEME_PROTOCOL_HEADER": "前往一个帐户去发送", "TOAST_COPY_TO_CLIPBOARD": "已复制到剪贴板", - "TOAST_DEVICE_WIPED": "设备擦除成功", - "TOAST_DISCOVERY_ERROR": "账户发现错误: {error}", + "TOAST_DEVICE_WIPED": "设备数据自毁成功", + "TOAST_DISCOVERY_ERROR": "账户发现错误:{error}", "TOAST_ESTIMATED_FEE_ERROR": "网络矿工费用估算失败。将使用备用值。", "TOAST_GENERIC_ERROR": "错误:{error}", "TOAST_PIN_CHANGED": "PIN码修改成功。", "TOAST_QR_INCORRECT_ADDRESS": "二维码包含该账户的无效地址", "TOAST_QR_INCORRECT_COIN_SCHEME_PROTOCOL": "二维码是为 {coin} 账户定义的", + "TOAST_QR_UNKNOWN_SCHEME_PROTOCOL": "未知协议方案:\"{scheme}\"。请重试或手动输入地址。", "TOAST_RAW_TX_SENT": "交易已发送。交易ID: {txid}", "TOAST_SETTINGS_APPLIED": "设置修改成功", - "TOAST_SIGN_MESSAGE_ERROR": "留言签署出错: {error}", + "TOAST_SIGN_MESSAGE_ERROR": "留言签署出错:{error}", "TOAST_SIGN_MESSAGE_SUCCESS": "留言签署成功", - "TOAST_SIGN_TX_ERROR": "交易签署出错: {error}", + "TOAST_SIGN_TX_ERROR": "交易签署出错:{error}", "TOAST_SUCCESSFUL_CLAIM": "成功认领 {symbol} ", "TOAST_TX_BUTTON": "查看详情", "TOAST_TX_CLAIMED": "已认领 {amount} ", @@ -181,104 +183,104 @@ "TOAST_TX_SENT": "从 {account} 发送出 {amount}", "TOAST_TX_STAKED": "从 {account} 质押 {amount}", "TOAST_TX_UNSTAKED": "已解除质押 {amount} ", - "TOAST_VERIFY_ADDRESS_ERROR": "地址验证出错: {error}", - "TOAST_VERIFY_MESSAGE_ERROR": "消息验证出错: {error}", + "TOAST_VERIFY_ADDRESS_ERROR": "地址验证出错:{error}", + "TOAST_VERIFY_MESSAGE_ERROR": "消息验证出错:{error}", "TOAST_VERIFY_MESSAGE_SUCCESS": "消息验证成功", - "TOAST_VERIFY_XPUB_ERROR": "公钥验证出错: {error}", + "TOAST_VERIFY_XPUB_ERROR": "公钥验证出错:{error}", "TOAST_WIPE_CODE_CHANGED": "成功修改自毁PIN码", "TOAST_WIPE_CODE_REMOVED": "成功删除自毁PIN码", "TOKEN_BALANCE": "余额: {balance}", "TOTAL_SENT": "总共发送出", - "TR_404_DESCRIPTION": "哎呀!看起来像一个错误的网址或不完整链接。o", + "TR_404_DESCRIPTION": "哎呀!看起来像一个错误的网址或不完整链接。", "TR_404_GO_TO_DASHBOARD": "前往控制面板", - "TR_404_TITLE": "错误 404:未找到链接", - "TR_7D_CHANGE": "一星期的变化\n", - "TR_ABORT": "终止", - "TR_ACCESS_HIDDEN_WALLET": "进入隐藏钱包", + "TR_404_TITLE": "错误 404: 未找到链接", + "TR_7D_CHANGE": "7天的变化\n", + "TR_ACCESS_HIDDEN_WALLET": "访问隐藏钱包", "TR_ACCESS_STANDARD_WALLET": "访问标准钱包", "TR_ACCOUNT_DETAILS_HEADER": "账户详情", - "TR_ACCOUNT_DETAILS_PATH_DESC": "派生路径是在分层确定性钱包的组织结构中浏览和生成特定密钥的一种方法。", + "TR_ACCOUNT_DETAILS_PATH_DESC": "派生路径是在 “分层确定性” 钱包的组织结构中浏览和生成特定密钥的一种方法。", "TR_ACCOUNT_DETAILS_PATH_HEADER": "派生路径", "TR_ACCOUNT_DETAILS_TYPE_HEADER": "账户类型", - "TR_ACCOUNT_DETAILS_XPUB": "谨慎地处理您的账户扩展公钥 (XPUB)。一旦暴露、第三方将能够看到您的整个交易历史。", + "TR_ACCOUNT_DETAILS_XPUB": "谨慎地处理您的账户扩展公钥。一旦暴露,第三方将能够看到您的整个交易历史。", "TR_ACCOUNT_DETAILS_XPUB_BUTTON": "显示扩展公钥", "TR_ACCOUNT_DETAILS_XPUB_HEADER": "扩展公钥 (XPUB)", "TR_ACCOUNT_ENABLE_PASSPHRASE": "启用密码短语", "TR_ACCOUNT_EXCEPTION_AUTH_ERROR": "授权错误", - "TR_ACCOUNT_EXCEPTION_AUTH_ERROR_DESC": "此设备的授权进程失败。请点击“重试”或重新连接您的Trezor设备。", + "TR_ACCOUNT_EXCEPTION_AUTH_ERROR_DESC": "此设备的授权进程失败。请点击 “重试” 或重新连接您的 Trezor 设备。", "TR_ACCOUNT_EXCEPTION_DISCOVERY_DESCRIPTION": "我们无法发现您的帐户。", "TR_ACCOUNT_EXCEPTION_DISCOVERY_EMPTY": "在设置中禁用所有的加密货币。", - "TR_ACCOUNT_EXCEPTION_DISCOVERY_EMPTY_DESC": "目前所有加密货币均已被禁用。请在“设置”中启用。", + "TR_ACCOUNT_EXCEPTION_DISCOVERY_EMPTY_DESC": "目前所有加密货币均已被禁用。请在 “设置” 中启用。", "TR_ACCOUNT_EXCEPTION_DISCOVERY_ERROR": "账户发现错误", "TR_ACCOUNT_EXCEPTION_NOT_ENABLED": "{networkName} 未在设置中启用。", "TR_ACCOUNT_EXCEPTION_NOT_EXIST": "账户不存在", - "TR_ACCOUNT_IMPORTED_ANNOUNCEMENT": "观察钱包是个已经导入到您的钱包中的公共地址、允许钱包监视加密货币输出、但无权发送它们。", + "TR_ACCOUNT_IMPORTED_ANNOUNCEMENT": "“仅供查看” 账户是个已经导入到您的钱包中的公共地址,允许钱包监视加密货币输出,但无权发送它们。", "TR_ACCOUNT_IS_EMPTY_DESCRIPTION": "从接收或购买 {network} 开始。", "TR_ACCOUNT_IS_EMPTY_TITLE": "还没有交易记录...", - "TR_ACCOUNT_NO_ACCOUNTS": "没有账号", - "TR_ACCOUNT_OUT_OF_SYNC": "正在进行帐户同步、请稍等...", + "TR_ACCOUNT_NO_ACCOUNTS": "没有账户", + "TR_ACCOUNT_OUT_OF_SYNC": "正在进行帐户同步,请稍等...", "TR_ACCOUNT_PASSPHRASE_DISABLED": "更改密码短语的设置来使用该设备", "TR_ACCOUNT_SEARCH_NO_RESULTS": "没有结果", "TR_ACCOUNT_TYPE": "帐户类型", - "TR_ACCOUNT_TYPE_BIP44_DESC": "遗留或支付公钥哈希地址使用更简单的交易格式、但可能会导致更高的交易费用、并且缺乏新地址类型中的效率和功能。", - "TR_ACCOUNT_TYPE_BIP44_NAME": "遗留", + "TR_ACCOUNT_TYPE_BIP44_DESC": "传统钱包地址使用较为简单的交易格式,但可能需要支付更高的矿工费,而且缺乏新型钱包地址类型的效率和功能。", + "TR_ACCOUNT_TYPE_BIP44_NAME": "传统", "TR_ACCOUNT_TYPE_BIP44_TECH": "BIP44, P2PKH, Base58", - "TR_ACCOUNT_TYPE_BIP49_DESC": "嵌套隔离见证地址得到广泛的支持、比遗留地址更高效 (节省矿工费)、并且与遗留和原生隔离见证地址兼容", + "TR_ACCOUNT_TYPE_BIP49_DESC": "嵌套隔离见证钱包地址得到普遍支持,比传统钱包地址更高效 (节省矿工费)、并且与传统和原生隔离见证钱包地址兼容。", "TR_ACCOUNT_TYPE_BIP49_NAME": "嵌套隔离见证", "TR_ACCOUNT_TYPE_BIP49_TECH": "BIP49, P2SH-P2WPKH, Base58", - "TR_ACCOUNT_TYPE_BIP84_DESC": "原生隔离见证是Trezor Suite的默认地址类型。它可以减少交易规模、提高交易容量、增强可扩展性、同时降低矿工费用、但可能无法与某些旧服务一起使用。", + "TR_ACCOUNT_TYPE_BIP84_DESC": "原生隔离见证是 Trezor Suite 的默认钱包地址类型。它可以减少交易数据体积、提高容量、增强可扩展性、同时降低矿工费用,但可能无法与某些传统服务一起使用。", "TR_ACCOUNT_TYPE_BIP84_NAME": "原生隔离见证", "TR_ACCOUNT_TYPE_BIP84_TECH": "BIP84, P2WPKH, Bech32", - "TR_ACCOUNT_TYPE_BIP86_DESC": "主根是目前最新的地址类型、可以增强隐私和网络效率。请注意、某些服务可能尚不支持主根地址。", + "TR_ACCOUNT_TYPE_BIP86_DESC": "主根是一个能够增强隐私和网络效率的新钱包地址类型。注意某些服务可能尚不支持主根地址。", "TR_ACCOUNT_TYPE_BIP86_NAME": "主根", "TR_ACCOUNT_TYPE_BIP86_TECH": "BIP86, P2TR, Bech32m", "TR_ACCOUNT_TYPE_CARDANO_DESC": "当前最广泛接受的生成和管理卡达尔诺地址的方法可确保互操作性、安全性以及对所有类型代币的支持。", - "TR_ACCOUNT_TYPE_COINJOIN": "混币", + "TR_ACCOUNT_TYPE_COINJOIN": "CoinJoin 混币器", "TR_ACCOUNT_TYPE_DEFAULT": "默认", "TR_ACCOUNT_TYPE_IMPORTED": "已导入", "TR_ACCOUNT_TYPE_LEDGER": "Ledger", - "TR_ACCOUNT_TYPE_LEDGER_DESC": "Ledger账户与Ledger Live派生路径兼容、可顺利从Ledger迁移到Trezor。", - "TR_ACCOUNT_TYPE_LEGACY": "遗留", - "TR_ACCOUNT_TYPE_LEGACY_DESC": "遗留账户与Ledger传统衍生路径兼容、可从Ledger顺利迁移到Trezor。", + "TR_ACCOUNT_TYPE_LEDGER_DESC": "Ledger 账户与 Ledger Live 派生路径兼容,可从 Ledger 顺利迁移到 Trezor。", + "TR_ACCOUNT_TYPE_LEGACY": "传统", + "TR_ACCOUNT_TYPE_LEGACY_DESC": "传统账户与传统 Ledger 派生路径兼容,可从 Ledger 顺利迁移到 Trezor。", "TR_ACCOUNT_TYPE_NORMAL_EVM_DESC": "当前最广为接受的生成和管理 {value} 地址的方法可确保互操作性、安全性并支持所有类型的代币。", - "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "当前最广为接受的生成和管理Solana地址的方法可确保互操作性、安全性以及对SOL和SPL代币的支持。", + "TR_ACCOUNT_TYPE_NORMAL_SOLANA_DESC": "当前最广为接受的生成和管理 Solana 地址的方法可确保互操作性、安全性以及对 SOL 和 SPL 代币的支持。", "TR_ACCOUNT_TYPE_NO_CAPABILITY": "不支持。", - "TR_ACCOUNT_TYPE_NO_SUPPORT": "该Trezor型号不支持此账户类型。", + "TR_ACCOUNT_TYPE_NO_SUPPORT": "该 Trezor 型号不支持此账户类型。", "TR_ACCOUNT_TYPE_SEGWIT": "嵌套隔离见证", "TR_ACCOUNT_TYPE_SHELLEY": "雪莱", "TR_ACCOUNT_TYPE_SHELLEY_DESC": "雪莱时代的地址引入了一种新型钱包、可以支持权益委托和赚取奖励。", - "TR_ACCOUNT_TYPE_SLIP25_DESC": "2024年6月1日、混币功能被停用。您的混币帐户和资金仍然可以使用、但您将无法发起新一轮的混币请求。", - "TR_ACCOUNT_TYPE_SLIP25_NAME": "混币", + "TR_ACCOUNT_TYPE_SLIP25_DESC": "2024 年 6 月 1 日,CoinJoin 混币器功能被停用。您的混币帐户和资金仍然可以使用,但您将无法发起新一回合的混币请求。", + "TR_ACCOUNT_TYPE_SLIP25_NAME": "CoinJoin 混币器", "TR_ACCOUNT_TYPE_SLIP25_TECH": "SLIP25、P2TR、Bech32m", - "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_DESC": "Bip44修改账户", - "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_NAME": "Bip44修改", - "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44、Base58", + "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_DESC": "BIP44找零账户", + "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_NAME": "BIP44找零", + "TR_ACCOUNT_TYPE_SOLANA_BIP44_CHANGE_TECH": "BIP44, Base58", "TR_ACCOUNT_TYPE_TAPROOT": "主根", "TR_ACCOUNT_TYPE_UPDATE_REQUIRED": "请您更新设备固件以启用此账户类型。", - "TR_ACCOUNT_TYPE_XRP_DESC": "瑞波币 (XRP) 是一种加密货币、无需依赖传统的挖矿技术、利用共识分类账即可实现快速、低成本的跨境支付、从而快速确认交易。", - "TR_ACQUIRE_DEVICE": "在此处使用Trezor", + "TR_ACCOUNT_TYPE_XRP_DESC": "瑞波币 (XRP) 是一种加密货币,无需依赖传统的挖矿技术,利用共识分类账即可实现快速、低成本的跨境支付,从而快速确认交易。", + "TR_ACQUIRE_DEVICE": "在此处使用 Trezor", "TR_ACQUIRE_DEVICE_TITLE": "另一个会话正在运行", "TR_ACTIVATED_COINS": "已激活的加密货币", "TR_ACTIVE": "激活", + "TR_ADD": "添加", "TR_ADDRESSES": "地址", "TR_ADDRESSES_CHANGE": "找零地址", - "TR_ADDRESSES_FRESH": "新的地址", - "TR_ADDRESSES_USED": "用过的地址", + "TR_ADDRESSES_FRESH": "新地址", + "TR_ADDRESSES_USED": "使用过的地址", "TR_ADDRESS_DISPLAY": "显示地址", - "TR_ADDRESS_DISPLAY_DESCRIPTION": "显示连续地址 (bc1wetes...v54d8d) 或间隔地址 (bc1w etes ... v54d 8d)。", + "TR_ADDRESS_DISPLAY_DESCRIPTION": "显示连续式地址 (bc1wetes...v54d8d) 或间隔式地址 (bc1w etes ... v54d 8d)。", "TR_ADDRESS_MODAL_CLIPBOARD": "复制地址", "TR_ADDRESS_MODAL_TITLE": "{networkName} 接收地址", "TR_ADDRESS_MODAL_TITLE_EXCHANGE": "{networkCurrencyName} 接受地址在 {networkName} 网络", - "TR_ADDRESS_PHISHING_WARNING": "为了防止网络钓鱼攻击、您应该验证您的Trezor设备上的地址。 {claim}", + "TR_ADDRESS_PHISHING_WARNING": "为了防止网络钓鱼攻击,您应该验证您的 Trezor 设备上的地址。 {claim}", "TR_ADD_ACCOUNT": "添加帐户", - "TR_ADD_HIDDEN_WALLET": "添加隐藏钱包", + "TR_ADD_HIDDEN_WALLET": "隐藏钱包", "TR_ADD_NETWORK_ACCOUNT": "添加 {network} 账户", "TR_ADD_NEW_BLOCKBOOK_BACKEND": "添加新的", "TR_ADD_TOKEN_ADDRESS_DUPLICATE": "代币地址已被添加", "TR_ADD_TOKEN_ADDRESS_NOT_VALID": "无效地址", "TR_ADD_TOKEN_LABEL": "代币地址", "TR_ADD_TOKEN_SUBMIT": "添加代币", - "TR_ADD_TOKEN_TITLE": "添加ERC20代币", + "TR_ADD_TOKEN_TITLE": "添加 ERC20 代币", "TR_ADD_TOKEN_TOAST_ERROR": "操作失败:{error}", "TR_ADD_TOKEN_TOAST_SUCCESS": "已添加代币", "TR_ADD_TOKEN_TOKEN_NOT_VALID": "这看起来不像一个有效的代币", @@ -286,92 +288,92 @@ "TR_ADVANCED": "危险区域", "TR_ADVANCED_RECOVERY": "高级恢复", "TR_ADVANCED_RECOVERY_NOT_SURE": "不知道高级钱包恢复方法是如何运作的?", - "TR_ADVANCED_RECOVERY_OPTION": "使用Trezor设备拼写和输入恢复种子的每个助记词。", - "TR_ADVANCED_RECOVERY_TEXT": "根据字母在Trezor屏幕上的位置、使用下面的键盘拼写钱包备份的每个单词。", + "TR_ADVANCED_RECOVERY_OPTION": "使用 Trezor 设备拼写和输入恢复种子的每个助记词。", + "TR_ADVANCED_RECOVERY_TEXT": "根据字母在 Trezor 屏幕上的位置,使用下面的键盘拼写钱包备份的每个单词。", "TR_AFFECTED_TXS": "此操作将从内存池中删除以下交易", "TR_AFFECTED_TXS_HEADER": "链式的交易是从初始交易的输出所创建的", "TR_AFFECTED_TXS_OTHERS": "从其它账户创建的交易", "TR_AFFECTED_TXS_OWN": "您的交易记录", "TR_ALL": "全部", "TR_ALLOW_ANALYTICS": "用户使用数据", - "TR_ALLOW_ANALYTICS_DESCRIPTION": "所有用户使用数据都是严格匿名的; 我们只用它来改善Trezor的生态系统。", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "自动更新Trezor Suite", - "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite会在后台自动下载最新版本、并在重启应用程序时安装。这可确保您始终使用最新功能和安全补丁。更新无需经过您的许可。", + "TR_ALLOW_ANALYTICS_DESCRIPTION": "所有用户使用数据都是严格匿名的;我们只用它来改善 Trezor 的生态系统。", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES": "自动更新 Trezor Suite", + "TR_ALLOW_AUTOMATIC_SUITE_UPDATES_DESCRIPTION": "Trezor Suite 将在后台自动下载最新版本、并在重启应用程序时安装。这可确保您始终使用最新功能和安全补丁。更新无需经过您的许可。", + "TR_ALL_NETWORKS": "所有网络", + "TR_ALL_NETWORKS_TOOLTIP": "查看所有 {networkCount} 网络上的代币。使用右侧的按钮按照热门网络进行筛选。", "TR_ALL_TRANSACTIONS": "所有交易", "TR_AMOUNT_SENT": "已发送的金额", - "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "无法用于混币 — 金额太大", - "TR_AMOUNT_TOO_SMALL_FOR_COINJOIN": "无法用于混币 — 金额太小", + "TR_AMOUNT_TOO_BIG_FOR_COINJOIN": "无法用于 CoinJoin 混币器 —— 金额太大", + "TR_AMOUNT_TOO_SMALL_FOR_COINJOIN": "无法用于 CoinJoin 混币器 —— 金额太小", "TR_ANALYZE_IN_BLOCKBOOK": "在Trezor Blockbook中进行分析", - "TR_ANALYZE_IN_BLOCKBOOK_DESC": "查看Trezor Blockbook中的输入和输出、因为它可能更容易分析。", + "TR_ANALYZE_IN_BLOCKBOOK_DESC": "查看 Trezor Blockbook 中的输入和输出、因为它可能更容易分析。", "TR_ANALYZE_IN_BLOCKBOOK_OPEN": "打开", - "TR_ANONYMITY_SET_ERROR": "计算匿名性时发生错误。 请稍后再试", - "TR_ANONYMIZATION_OPTION_1": "加强比特币的隐私性", - "TR_ANONYMIZATION_OPTION_2": "在中选择“未消费交易输出”", - "TR_ANONYMIZATION_OPTION_3": "减少匿名程度", - "TR_ANONYMIZATION_PAUSED": "混币已暂停", + "TR_ANONYMITY_SET_ERROR": "计算您的匿名性时出错。 请稍后再试", + "TR_ANONYMIZATION_OPTION_1": "匿名化更多比特币", + "TR_ANONYMIZATION_OPTION_2": "在中选择 “未消费交易输出”", + "TR_ANONYMIZATION_OPTION_3": "降低匿名程度", + "TR_ANONYMIZATION_PAUSED": "CoinJoin 混币器已被暂停", "TR_ANONYMIZING": "运行中", "TR_APP": "应用", "TR_APPLICATION": "应用程序", - "TR_ASCII_ONLY": "只允许使用ASCII字符", + "TR_ASCII_ONLY": "只允许使用 ASCII 字符", "TR_ASSETS": "加密货币", - "TR_AUTHENTICATE_DEVICE_DESCRIPTION": "我们只是想确保您的Trezor是正版的。", + "TR_AUTHENTICATE_DEVICE_DESCRIPTION": "我们只是想确保您的 Trezor 是正版的。", "TR_AUTH_CONFIRM_FAILED_RETRY": "重试", "TR_AUTH_CONFIRM_FAILED_TITLE": "错误的密码短语", - "TR_AUTOSTOP_COINJOIN_ENABLED": "本回合结束后、将停止混币", - "TR_AUTO_START": "自动打开Trezor Suite", - "TR_AUTO_START_DESCRIPTION": "在登陆电脑时、自动在后台打开Trezor Suite", - "TR_AUTO_STOP_TOOLTIP": "混币正处于签署阶段。点击即可在本轮结束后停止。", + "TR_AUTOSTOP_COINJOIN_ENABLED": "本回合结束后,将停止 CoinJoin 混币器", + "TR_AUTO_START": "自动打开 Trezor Suite", + "TR_AUTO_START_DESCRIPTION": "在登陆电脑时、自动在后台打开 Trezor Suite", + "TR_AUTO_STOP_TOOLTIP": "CoinJoin 混币器正处于签署阶段。点击即可在本轮结束后停止。", "TR_AVAILABLE_NOW_FOR": "现可供", "TR_AVOID_USING_DEVICE": "避免使用该设备或向其发送任何资金。", "TR_BACK": "后退", "TR_BACKENDS": "后端", "TR_BACKEND_CUSTOM_SERVERS": "自定义 {type} 服务器", - "TR_BACKEND_DEFAULT_SERVERS": "Trezor服务器 (默认)", - "TR_BACKEND_DISCONNECTED": "后端断开连接", + "TR_BACKEND_DEFAULT_SERVERS": "Trezor 服务器 (默认)", + "TR_BACKEND_DISCONNECTED": "后端已断开连接", "TR_BACKEND_RECONNECTING": "在 {time} 秒内重新连接...", "TR_BACKGROUND_DOWNLOAD": "在后台进行下载", "TR_BACKSPACE": "退格", "TR_BACKUP": "钱包备份", - "TR_BACKUP_CHECKBOX_1_DESCRIPTION": "确保按照Trezor设备屏幕上所给的顺序写下每个助记词。妥善保管钱包备份卡片。", - "TR_BACKUP_CHECKBOX_1_TITLE": "您的钱包助记词备份可让您在Trezor设备丢失或损坏的情况下恢复资金。", - "TR_BACKUP_CHECKBOX_2_DESCRIPTION": "不要把您的钱包助记词备份保存在您的手机或任何可能被黑的联网的电子设备上、包括云储存服务。", + "TR_BACKUP_CHECKBOX_1_DESCRIPTION": "确保按照 Trezor 设备屏幕上所给的顺序写下每个助记词。妥善保管钱包备份卡片。", + "TR_BACKUP_CHECKBOX_1_TITLE": "您的钱包助记词备份可让您在 Trezor 设备丢失或损坏的情况下恢复资金。", + "TR_BACKUP_CHECKBOX_2_DESCRIPTION": "不要把您的钱包助记词备份保存在您的手机或任何可能被黑的联网的电子设备上,包括云储存服务。", "TR_BACKUP_CHECKBOX_2_TITLE": "切勿拍摄钱包助记词备份或将其存储在任何数字媒体", "TR_BACKUP_CHECKBOX_3_DESCRIPTION": "将钱包助记词备份藏好、并使用适当的物理访问控制、以确保您是唯一能够看到您的钱包助记词的人。", - "TR_BACKUP_CHECKBOX_3_TITLE": "将您的钱包助记词备份安全的存储、切勿与任何人共享。", + "TR_BACKUP_CHECKBOX_3_TITLE": "将您的钱包助记词备份安全的存储,切勿与任何人共享。", "TR_BACKUP_CREATED": "钱包备份完成", "TR_BACKUP_FAILED": "备份失败", "TR_BACKUP_FINISHED_BUTTON": "继续到PIN码", - "TR_BACKUP_FINISHED_TEXT": "如果您已写下了您的钱包助记词备份、那么您的Trezor设备即将准备就绪。不要丢失您的备份、否则您将无法恢复您的资金。", + "TR_BACKUP_FINISHED_TEXT": "如果您已写下了您的钱包助记词备份,那么您的 Trezor 设备即将准备就绪。不要丢失您的备份,否则您将无法恢复您的资金。", "TR_BACKUP_RECOVERY_SEED": "备份", "TR_BACKUP_RECOVERY_SEED_FAILED_DESC": "备份过程失败了。强烈建议您备份您的钱包。请关注链接以了解如何创建钱包备份。", "TR_BACKUP_RECOVERY_SEED_FAILED_TITLE": "备份失败", - "TR_BACKUP_SEED_IS_ULTIMATE": "Trezor设备生成的助记词是您的钱包钥匙。 如果你丢失了它、它就永远消失了、没有其他办法可以还原丢失的钱包钥匙。", - "TR_BACKUP_SUBHEADING_1": "您的Trezor将生成您需要写下的单词列表。此信息是保护您的Trezor的最重要部分、它是您的Trezor和与此相关的所有钱包的唯一脱机备份。", + "TR_BACKUP_SEED_IS_ULTIMATE": "Trezor 设备生成的助记词是您的钱包钥匙。 如果你丢失了它,它就永远消失了,没有其他办法可以还原丢失的钱包钥匙。", + "TR_BACKUP_SUBHEADING_1": "您的 Trezor 将生成您需要写下的单词列表。此信息是保护您的 Trezor 的最重要部分、它是您的 Trezor 和与此相关的所有钱包的唯一脱机备份。", "TR_BACKUP_SUCCESSFUL": "备份成功", "TR_BALANCE": "余额", "TR_BASIC_RECOVERY": "标准恢复", "TR_BASIC_RECOVERY_OPTION": "请在您的电脑上逐字逐句地输入您的钱包助记词备份。", - "TR_BCH_ADDRESS_INFO": "比特币现金 (BCH) 将其地址的格式改为cashaddr。在我们的博客上可以找到更多关于如何转换地址的信息。 {TR_LEARN_MORE}", + "TR_BCH_ADDRESS_INFO": "比特现金 (BCH) 将其地址的格式改为 cashaddr。在我们的博客上可以找到更多关于如何转换地址的信息。 {TR_LEARN_MORE}", "TR_BEFORE_ANY_FURTHER_ACTIONS": "虽然可能性不大、但如果在固件更新时出现问题、可能需要使用您的钱包助记词备份来恢复您的资金。", "TR_BIP_SIG_FORMAT": "Trezor", - "TR_BITCOIN_ONLY_UNAVAILABLE": "在当您切换到 {bitcoinOnly} 之前、需要将您的Trezor设备固件升级到最新版本。", + "TR_BITCOIN_ONLY_UNAVAILABLE": "在当您切换到 {bitcoinOnly} 之前,需要将您的 Trezor 设备固件升级到最新版本。", "TR_BREAKING_ANONYMITY_CHECKBOX": "我理解我正在破坏我的匿名性", - "TR_BRIDGE": "Trezor网桥", - "TR_BRIDGE_DEV_MODE_START": "在21324端口启动Trezor网桥", - "TR_BRIDGE_DEV_MODE_STOP": "在默认端口启动Trezor网桥", - "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "您确定吗?您的设备一次只能被一个应用程序所使用。如果您正在用 Trezor设备使用其他应用程序、请先结束该会议。", - "TR_BRIDGE_NEEDED_DESCRIPTION": "您的浏览器不被支持。为获得最佳体验、请下载并在后台运行Trezor Suite 桌面应用程序、或使用与WebUSB兼容的Chromium浏览器。", - "TR_BRIDGE_REQUESTED_DESCRIPTION": "另一个应用程序请求了Trezor Suite与您的Trezor设备连接。保持Trezor Suit在后台运行、然后在另外的应用程序中重新尝试操作。", + "TR_BRIDGE": "Trezor 网桥", + "TR_BRIDGE_DEV_MODE_START": "在 21324 端口启动 Trezor 网桥", + "TR_BRIDGE_DEV_MODE_STOP": "在默认端口启动 Trezor 网桥", + "TR_BRIDGE_GO_TO_WALLET_DESCRIPTION": "您确定吗?您的设备只能被一个应用程序所使用。如果您正在用 Trezor 设备使用其他应用程序,请先结束该会议。", + "TR_BRIDGE_NEEDED_DESCRIPTION": "您的浏览器不被支持。为获得最佳体验,请下载并在后台运行 Trezor Suite 桌面应用程序,或使用与 WebUSB 兼容的 Chromium 浏览器。", + "TR_BRIDGE_REQUESTED_DESCRIPTION": "另一个应用程序请求了 Trezor Suite 与您的 Trezor 设备连接。保持 Trezor Suite 在后台运行,然后在另外的应用程序中重新尝试操作。", + "TR_BRIDGE_TIP_AUTOSTART": "提示:启用自动启动功能,让网桥始终在后台运行。", "TR_BTC_UNITS": "比特币单位", "TR_BUG": "程序错误", "TR_BUMP_FEE": "追加矿工费", "TR_BUMP_FEE_DISABLED_TOOLTIP": "为加快您的交易速度、请追加队列中最早的待处理交易的矿工费。交易必须按顺序确认。", "TR_BUY": "购买", - "TR_BUY_ACCOUNT_TRANSACTIONS": "交易记录", "TR_BUY_BUY": "购买", - "TR_BUY_BUY_AGAIN": "再次购买", - "TR_BUY_CONFIRMED_ON_TREZOR": "已在Trezor设备上确认", - "TR_BUY_CONFIRM_ON_TREZOR": "在Trezor设备上确认", + "TR_BUY_CONFIRMED_ON_TREZOR": "已在 Trezor 设备上确认", "TR_BUY_DETAIL_ERROR_BUTTON": "返回账户", "TR_BUY_DETAIL_ERROR_SUPPORT": "前往供应商客服", "TR_BUY_DETAIL_ERROR_TEXT": "对不起、您的交易失败了或被拒绝。您的付款方式没有被扣除费用。", @@ -387,27 +389,27 @@ "TR_BUY_DETAIL_WAITING_FOR_USER_GATE": "前往供应商的网站", "TR_BUY_DETAIL_WAITING_FOR_USER_TEXT": "{providerName} 需要一些最后的细节来完成这项交易。请访问他们的网站以继续。", "TR_BUY_DETAIL_WAITING_FOR_USER_TITLE": "完成您的交易", - "TR_BUY_FOOTER_TEXT_1": "Invity是一个比较工具、将您与最优惠的交易供应商联系起来。他们只使用您提供的所在地、以显示最相关的报价。", - "TR_BUY_FOOTER_TEXT_2": "Invity不会看到您的任何付款或实名认证信息; 您只在选择完成交易时与交易供应商分享这些信息。", + "TR_BUY_FOOTER_TEXT_1": "Invity 是一个比较工具,将您与最优惠的交易供应商联系起来。他们只使用您提供的所在地、以显示最相关的报价。", + "TR_BUY_FOOTER_TEXT_2": "Invity 不会看到您的任何付款或实名认证信息;您只在选择完成交易时与交易供应商分享这些信息。", "TR_BUY_GO_TO_PAYMENT": "完成交易", "TR_BUY_LEARN_MORE": "了解更多", "TR_BUY_MODAL_CONFIRM": "我准备购买", "TR_BUY_MODAL_FOR_YOUR_SAFETY": "通过 {provider} 购买 {cryptocurrency}", "TR_BUY_MODAL_LEGAL_HEADER": "法律声明", - "TR_BUY_MODAL_SECURITY_HEADER": "Trezor一直把安全放在第一位", + "TR_BUY_MODAL_SECURITY_HEADER": "Trezor 一直把安全放在第一位", "TR_BUY_MODAL_TERMS_1": "我来这里是为了购买加密货币。如果我因其他原因被引导到此网站、我会在继续操作之前联系 {provider} 的客服。", "TR_BUY_MODAL_TERMS_2": "我正在使用此功能购买将发送到我自己账户的加密货币。", "TR_BUY_MODAL_TERMS_3": "我理解加密货币交易是最终交易、无法撤销或退款。因欺诈或错误造成的损失可能无法挽回。", - "TR_BUY_MODAL_TERMS_4": "我理解Invity不提供此项服务。它是受 {provider} 的条款和条件约束。", - "TR_BUY_MODAL_TERMS_5": "我不会使用此功能进行赌博、欺诈或任何违反Invity或提供商的服务条款或任何适用法律的活动。", + "TR_BUY_MODAL_TERMS_4": "我理解 Invity 不提供此项服务。它是受 {provider} 的条款和条件约束。", + "TR_BUY_MODAL_TERMS_5": "我不会使用此功能进行赌博、欺诈或任何违反 Invity 或提供商的服务条款或任何适用法律的活动。", "TR_BUY_MODAL_TERMS_6": "我理解加密货币是一种新兴的金融工具、不同地区的监管规定可能有所不同。这可能会增加欺诈、盗窃或市场不稳定的风险。", - "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "经Invity验证的合作伙伴", + "TR_BUY_MODAL_VERIFIED_PARTNERS_HEADER": "已经过 Invity 认证的合作伙伴", "TR_BUY_NETWORK": "购买 {network}", "TR_BUY_NOT_TRANSACTIONS": "还没有交易记录。", "TR_BUY_PROVIDED_BY_INVITY": "供应商为", "TR_BUY_PROVIDER": "供应商", "TR_BUY_RECEIVE_ACCOUNT_QUESTION_TOOLTIP": "这是您钱包里的账户、一旦交易完成、您会在这里找到您的币。", - "TR_BUY_RECEIVE_ADDRESS_QUESTION_TOOLTIP": "这是将接收您的币的专用字母和数字地址。请在您的Trezor设备上验证此地址。", + "TR_BUY_RECEIVE_ADDRESS_QUESTION_TOOLTIP": "这是将接收您的币的专用字母和数字地址。请在您的 Trezor 设备上验证此地址。", "TR_BUY_RECEIVING_ACCOUNT": "接收账户", "TR_BUY_RECEIVING_ADDRESS": "接收地址", "TR_BUY_STATUS_ACTION_REQUIRED": "需要进行操作", @@ -415,139 +417,137 @@ "TR_BUY_STATUS_PENDING": "待定的", "TR_BUY_STATUS_PENDING_GO_TO_GATEWAY": "待定的", "TR_BUY_STATUS_SUCCESS": "已批准", - "TR_BUY_TRANS_ID": "交易ID:", "TR_BUY_VALIDATION_ERROR_MAXIMUM_CRYPTO": "最大值为 {maximum} ", "TR_BUY_VALIDATION_ERROR_MAXIMUM_FIAT": "最大值为 {maximum} {currency}", "TR_BUY_VALIDATION_ERROR_MINIMUM_CRYPTO": "最小值为 {minimum} ", "TR_BUY_VALIDATION_ERROR_MINIMUM_FIAT": "最小值为 {minimum} {currency}", - "TR_BUY_VIEW_DETAILS": "查看详细信息", "TR_BYTES": "字节", "TR_CAMERA_NOT_RECOGNIZED": "无法识别摄像头。", "TR_CAMERA_PERMISSION_DENIED": "访问摄像头的权限被拒绝。", "TR_CANCEL": "取消", "TR_CANCELLED": "已取消", - "TR_CANCEL_COINJOIN": "取消混币", - "TR_CANCEL_COINJOIN_NO": "否、继续", - "TR_CANCEL_COINJOIN_QUESTION": "确认想取消该混币任务吗?", - "TR_CANCEL_COINJOIN_YES": "是的、取消", - "TR_CANDIDATE_TRANSACTION": "混币候选人", - "TR_CANDIDATE_TRANSACTION_DESCRIPTION": "您已签名、等待其他人", - "TR_CANDIDATE_TRANSACTION_EXPLANATION": "您已经签署了交易、但仍需要所有参与者的签名。除非每个人都签名、否则我们无法保证交易的处理。", + "TR_CANCEL_COINJOIN": "取消 CoinJoin 混币器", + "TR_CANCEL_COINJOIN_NO": "不,继续", + "TR_CANCEL_COINJOIN_QUESTION": "确认想取消该 CoinJoin 混币任务吗?", + "TR_CANCEL_COINJOIN_YES": "是的,取消", + "TR_CANDIDATE_TRANSACTION": "CoinJoin 混币器候选人", + "TR_CANDIDATE_TRANSACTION_DESCRIPTION": "您已签名,正在等待其他参与者", + "TR_CANDIDATE_TRANSACTION_EXPLANATION": "您已经签署了交易,但仍需要所有参与者的签名。除非大家都签署,否则我们无法保证交易的处理。", "TR_CANDIDATE_TRANSACTION_HEADER": "候选交易", "TR_CARDANO_FINGERPRINT_HEADLINE": "指纹", - "TR_CARDANO_LEDGER_ACCOUNTS": "Ledger账户", - "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor金额", + "TR_CARDANO_LEDGER_ACCOUNTS": "Ledger 账户", + "TR_CARDANO_TREZOR_AMOUNT_HEADLINE": "Trezor 金额", "TR_CHAINED_TXS": "链式交易", "TR_CHANGELOG": "更新日志", - "TR_CHANGELOG_ON_GITHUB": "GitHub上的更新日志", "TR_CHANGE_ADDRESS_TOOLTIP": "这是从上次发送创建的找零地址。", "TR_CHANGE_FIRMWARE_TYPE_ANYTIME": "您可以随时在设置中更改您的固件类型。", "TR_CHANGE_HOMESCREEN": "更改主屏幕图片", - "TR_CHANGE_PIN": "", + "TR_CHANGE_PIN": "修改PIN码", "TR_CHANGE_WIPE_CODE": "修改自毁PIN码", + "TR_CHECKED_BALANCES_ON": "已核对的余额在", "TR_CHECKING_YOUR_DEVICE": "检查您的设备", "TR_CHECKSUM_CONVERSION_INFO": "已转换为校验和。了解更多", - "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "我们将验证Trezor设备的完整性、确保其安全性并确认芯片的真实性。", + "TR_CHECK_DEVICE_ORIGIN_DESCRIPTION": "我们将验证 Trezor 设备的完整性,确保其安全性并确认芯片的真实性。", "TR_CHECK_DEVICE_ORIGIN_TITLE": "检查设备", "TR_CHECK_FINGERPRINT": "检查设备指纹", - "TR_CHECK_FOR_DEVICES": "寻找Trezor设备", + "TR_CHECK_FOR_DEVICES": "寻找 Trezor 设备", "TR_CHECK_ORIGIN": "检查设备", "TR_CHECK_RECOVERY_SEED": "检查备份", "TR_CHECK_RECOVERY_SEED_DESCRIPTION": "执行模拟恢复以检查您的钱包助记词备份。", "TR_CHECK_RECOVERY_SEED_DESC_T1B1": "在此按照设备上显示的顺序输入恢复种子中的单词。作为额外的安全措施、您可能会被要求输入一些不属于恢复您的钱包的助记词。", - "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "使用2个按钮键盘输入您的恢复钱包助记词备份。 通过这样做、您将保持所有敏感信息的安全性和声音、远离任何阴影或不安全的计算机或网络浏览器。", - "TR_CHECK_RECOVERY_SEED_DESC_T2T1": "您的钱包助记词备份是用触摸屏输入的。 这将避免您的敏感信息暴露在可能不安全的计算机或网络浏览器中。", - "TR_CHECK_RECOVERY_SEED_DESC_T3T1": "您的钱包助记词备份是用触摸屏输入的。 这将避免您的敏感信息暴露在可能不安全的计算机或网络浏览器中。", + "TR_CHECK_RECOVERY_SEED_DESC_T2B1": "使用 2 个按钮键盘输入您的恢复钱包助记词备份。 通过这样做,您将保持所有敏感信息的安全性和声音,远离任何阴影或不安全的计算机或网络浏览器。", + "TR_CHECK_RECOVERY_SEED_DESC_TOUCHSCREEN": "您的钱包助记词备份是通过触摸屏输入的。这样可以避免将您的任何敏感信息暴露给可能不安全的电脑或网络浏览器。", "TR_CHECK_SEED": "检查钱包助记词备份", - "TR_CHECK_YOUR_DEVICE": "检查您的Trezor屏幕", + "TR_CHECK_YOUR_DEVICE": "检查您的 Trezor 屏幕", "TR_CHOOSE_RECOVERY_TYPE": "选择恢复类型", - "TR_CHUNKED_ADDRESS": "空格", - "TR_CLAIM_FROM_EVERSTAKE": "向Everstake认领 {symbol}?", + "TR_CHUNKED_ADDRESS": "间隔式", + "TR_CLAIM_FROM_EVERSTAKE": "从 Everstake 认领 {symbol}?", "TR_CLEAR_ALL": "全部清除", "TR_CLEAR_STORAGE": "重置应用程序", "TR_CLEAR_STORAGE_DESCRIPTION": "重置应用程序将其恢复到默认设置; 这是任何故障排除的首选的第一步。在这个过程结束时、该应用程序将自行重新启动。", "TR_CLOSE": "关闭", - "TR_COINJOIN_ACCOUNTS": "混币账户", + "TR_COINJOIN_ACCOUNTS": "CoinJoin 混币器账户", "TR_COINJOIN_ACCOUNT_RESCAN_ACTION": "重新扫描账户", - "TR_COINJOIN_ACCOUNT_RESCAN_DESCRIPTION": "如果您的交易历史记录不正确、您可以完全重新扫描您的帐户、无需优化。 在这种情况下、需要的时间会比平时更长。", + "TR_COINJOIN_ACCOUNT_RESCAN_DESCRIPTION": "如果交易历史记录不正确,可以在不进行优化的情况下重新扫描账户。这可能需要比平时更长的时间。", "TR_COINJOIN_ACCOUNT_RESCAN_TITLE": "重新扫描账户", "TR_COINJOIN_ANONYMITY_LEVEL_SETUP_DESCRIPTION": "显示与您的资源无法区分的人数。", - "TR_COINJOIN_ANONYMITY_LEVEL_SETUP_TITLE": "所需的隐私级别", - "TR_COINJOIN_CEX_WARNING": "如果您使用混币、交易所可能不会为您服务。", + "TR_COINJOIN_ANONYMITY_LEVEL_SETUP_TITLE": "想要的匿名程度", + "TR_COINJOIN_CEX_WARNING": "如果您使用 CoinJoin 混币器,交易所可能不会为您服务。", "TR_COINJOIN_COMPLETED": "已成功的完成混币!", - "TR_COINJOIN_COMPLETED_DESCRIPTION": "所有资金已成功匿名化", + "TR_COINJOIN_COMPLETED_DESCRIPTION": "您的所有资金已成功被 Coinjoin 混合", "TR_COINJOIN_DISCOVERY_BLOCK_FETCHING": "获取区块筛选器", "TR_COINJOIN_DISCOVERY_BLOCK_PROGRESS": "{current} 在扫描的 {total} 个区块中", - "TR_COINJOIN_DISCOVERY_MEMPOOL_FETCHING": "获取内存池过滤器", - "TR_COINJOIN_DISCOVERY_MEMPOOL_PROGRESS": "{current} 在 {total} 所分析的交易中", - "TR_COINJOIN_ENDED": "混币结束", - "TR_COINJOIN_EXPLANATION_TITLE": "它是如何工作的", - "TR_COINJOIN_INTERRUPTED_ERROR": "混币因外部错误而被中止", + "TR_COINJOIN_DISCOVERY_MEMPOOL_FETCHING": "正在获取内存池过滤器", + "TR_COINJOIN_DISCOVERY_MEMPOOL_PROGRESS": "{current} / {total} 的交易已进过分析", + "TR_COINJOIN_ENDED": "CoinJoin 混币器已结束运行", + "TR_COINJOIN_EXPLANATION_TITLE": "工作原理说明", + "TR_COINJOIN_INTERRUPTED_ERROR": "CoinJoin 混币器因外部错误而被中止", "TR_COINJOIN_LOGS_ACTION": "查找日志", "TR_COINJOIN_LOGS_DESCRIPTION": "打开包含日志文件的文件夹。", - "TR_COINJOIN_LOGS_TITLE": "混币调试日志", + "TR_COINJOIN_LOGS_TITLE": "CoinJoin 混币器调试日志", "TR_COINJOIN_PHASE_0_MESSAGE": "收集输入", "TR_COINJOIN_PHASE_1_MESSAGE": "正在建立连接", "TR_COINJOIN_PHASE_2_MESSAGE": "注册输出", - "TR_COINJOIN_PHASE_3_MESSAGE": "签名交易", - "TR_COINJOIN_PHASE_4_MESSAGE": "该回合结束", + "TR_COINJOIN_PHASE_3_MESSAGE": "签署交易", + "TR_COINJOIN_PHASE_4_MESSAGE": "正在结束该回合", "TR_COINJOIN_RECEIVE_WARNING_TITLE": "您应该知道", "TR_COINJOIN_ROUND_COUNTDOWN_OVERTIME": "片刻", - "TR_COINJOIN_ROUND_COUNTDOWN_PLURAL": "下一笔交易签名从 {value} 开始", - "TR_COINJOIN_RUNNING": "混币器正在运行", - "TR_COINJOIN_SESSION_COUNTDOWN_PLURAL": "{firstValue, plural, other {剩余: }} {value}", - "TR_COINJOIN_SETUP": "您的比特币将与其他人的比特币进行混合、以保护隐私。", - "TR_COINJOIN_SETUP_HEADING": "混币设置", - "TR_COINJOIN_STEP_1_DESCRIPTION": "给自己发送一些比特币、以进行隐私化。", + "TR_COINJOIN_ROUND_COUNTDOWN_PLURAL": "下一笔交易签名开始于 {value}", + "TR_COINJOIN_RUNNING": "CoinJoin 混币器正在运行中", + "TR_COINJOIN_SESSION_COUNTDOWN_PLURAL": "{firstValue, plural, other {剩余:}} {value}", + "TR_COINJOIN_SETUP": "您的比特币将与其他用户的比特币进行混合,以实现匿名性。", + "TR_COINJOIN_SETUP_HEADING": "CoinJoin 混币器设置", + "TR_COINJOIN_STEP_1_DESCRIPTION": "给自己发送一些比特币,以进行匿名化。", "TR_COINJOIN_STEP_1_TITLE": "添加比特币", - "TR_COINJOIN_STEP_2_DESCRIPTION": "点击按钮并在Trezor设备上确认。", - "TR_COINJOIN_STEP_3_DESCRIPTION": "您的钱币将与其他人的钱币混合、以保护隐私。", + "TR_COINJOIN_STEP_2_DESCRIPTION": "点击按钮并在 Trezor 设备上确认。", + "TR_COINJOIN_STEP_3_DESCRIPTION": "您的比特币将与其他用户的比特币进行混合,以实现匿名性。", "TR_COINJOIN_STEP_3_TITLE": "等待魔法的到来", - "TR_COINJOIN_TILE_1_DESCRIPTION": "您可以锁定您的笔记本电脑和Trezor设备", + "TR_COINJOIN_TILE_1_DESCRIPTION": "您可以锁定您的电脑和 Trezor 设备", "TR_COINJOIN_TILE_1_TITLE": "需要几个小时", - "TR_COINJOIN_TILE_2_DESCRIPTION": "您可以安全地暂停混币", + "TR_COINJOIN_TILE_2_DESCRIPTION": "您可以安全地暂停 CoinJoin 混币器", "TR_COINJOIN_TILE_2_TITLE": "连接后即可使用", "TR_COINJOIN_TILE_3_DESCRIPTION": "您的比特币始终由您掌控", - "TR_COINJOIN_TILE_3_TITLE": "由您的Trezor设备所保护", - "TR_COINJOIN_TRANSACTION_BATCH": "混币交易", + "TR_COINJOIN_TILE_3_TITLE": "由您的 Trezor 设备所保护", + "TR_COINJOIN_TRANSACTION_BATCH": "CoinJoin 混币器交易", "TR_COINMARKET_BEST_RATE": "最优惠的报价", "TR_COINMARKET_BUY_AND_SELL": "买入和卖出", + "TR_COINMARKET_BUY_AND_SELL_COUNTER": "{totalBuys, plural, =0 {{totalBuys} 次买入} other {{totalBuys} 次买入} } • {totalSells, plural, =0 {{totalSells} 次卖出} other {{totalSells} 次卖出} }", "TR_COINMARKET_CEX_TOOLTIP": "中心化加密货币交易所", "TR_COINMARKET_CHANGE_AMOUNT_OR_CURRENCY": "跟换金额或币种", "TR_COINMARKET_COMPARE_OFFERS": "对比所有的报价", "TR_COINMARKET_COUNTRY": "居住地国家", - "TR_COINMARKET_DCA_DOWNLOAD": "下载Invity的APP、开始囤比特币", + "TR_COINMARKET_DCA_DOWNLOAD": "下载 Invity 的应用程序,开始囤比特币", "TR_COINMARKET_DCA_FEATURE_1_DESCRIPTION": "一个即安全又简单的平均成本法托管储蓄计划。", - "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "由SatoshiLabs所开发的", + "TR_COINMARKET_DCA_FEATURE_1_SUBHEADING": "由 SatoshiLabs 所开发的", "TR_COINMARKET_DCA_FEATURE_2_DESCRIPTION": "提取到自己管理的钱包无需额外的费用", "TR_COINMARKET_DCA_FEATURE_2_SUBHEADING": "免费提取", "TR_COINMARKET_DCA_FEATURE_3_DESCRIPTION": "一个快速、简洁、用户友好的界面。", "TR_COINMARKET_DCA_FEATURE_3_SUBHEADING": "操作简单", "TR_COINMARKET_DCA_FEATURE_4_DESCRIPTION": "观察您的投资历史、金额与频率", "TR_COINMARKET_DCA_FEATURE_4_SUBHEADING": "平均成本计算概况", - "TR_COINMARKET_DCA_HEADING": "使用Invity的APP来帮您囤比特币", + "TR_COINMARKET_DCA_HEADING": "使用 Invity 的应用程序来帮您囤比特币", "TR_COINMARKET_DEX_TOOLTIP": "去中心化加密货币交易所", "TR_COINMARKET_ENTER_AMOUNT_IN": "输入金额 {currency}", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_KYC_ALL": "所有实名认证选择", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_NO_KYC": "永不要求实名认证", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_ALL": "所有中心化与去中心化交易所的报价", "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_DEX": "去中心化交易所", - "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "固定利率的中心化交易所", - "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "浮动利率的中心化交易所", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FIXED_CEX": "固定汇率的中心化交易所", + "TR_COINMARKET_EXCHANGE_COMPARATOR_FILTER_RATE_FLOATING_CEX": "浮动汇率的中心化交易所", "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING": "去中心化交易所", - "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "去中心化交易所 —— 通过区块链交易进行加密货币交易、无需中心化中介。", - "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "固定利率的中心化交易所", - "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "浮动利率的中心化交易所", - "TR_COINMARKET_FEATURED_OFFERS_HEADING": "精选的报价", + "TR_COINMARKET_EXCHANGE_DEX_OFFERS_HEADING_TOOLTIP": "去中心化交易所允许您直接在区块链上交易加密货币,无需中央机构或中介。", + "TR_COINMARKET_EXCHANGE_FIXED_OFFERS_HEADING": "固定汇率的中心化交易所", + "TR_COINMARKET_EXCHANGE_FLOAT_OFFERS_HEADING": "浮动汇率的中心化交易所", + "TR_COINMARKET_FEATURED_OFFERS_HEADING": "精选报价", "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_BUY_LABEL": "支付: ", - "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "接收方式: ", + "TR_COINMARKET_FEATURED_OFFER_PAYMENT_METHOD_SELL_LABEL": "接收方式:", "TR_COINMARKET_FEES_INCLUDED": "已包括矿工费", - "TR_COINMARKET_FEES_NOT_INCLUDED": "没有包括矿工费", + "TR_COINMARKET_FEES_NOT_INCLUDED": "并不包括矿工费", "TR_COINMARKET_FEES_ON_WEBSITE": "有些费用并没有包括在显示的价格; 最终的价格会在供应商网站上显示。", - "TR_COINMARKET_FIX_RATE": "固定的价格", - "TR_COINMARKET_FIX_RATE_DESCRIPTION": "通过支付更高的费用、将您的利率锁定至15分钟。", - "TR_COINMARKET_FLOATING_RATE": "浮动价格", - "TR_COINMARKET_FLOATING_RATE_DESCRIPTION": "获得一个估算价格可能会随着实时市场变化而调整。", + "TR_COINMARKET_FIX_RATE": "固定汇率", + "TR_COINMARKET_FIX_RATE_DESCRIPTION": "通过支付更高的费用,将您的汇率锁定至 15 分钟。", + "TR_COINMARKET_FLOATING_RATE": "浮动汇率", + "TR_COINMARKET_FLOATING_RATE_DESCRIPTION": "获得一个可以根据实时市场变化进行调整的估计汇率。", "TR_COINMARKET_KYC_DEX": "从不需要实名认证。去中心化加密货币交易所兑换要么成功、要么失败。👍", "TR_COINMARKET_KYC_NO_KYC": "从不需要实名认证。特殊情况自动退款。👍", "TR_COINMARKET_KYC_NO_REFUND": "特殊情况下需要实名认证。退款需要实名认证。👈", @@ -556,72 +556,69 @@ "TR_COINMARKET_KYC_YES_REFUND": "特殊情况下需要实名认证。退款无需实名认证。🤝", "TR_COINMARKET_LAST_TRANSACTIONS": "最近的交易", "TR_COINMARKET_NETWORK_FEE": "网络矿工费", - "TR_COINMARKET_NETWORK_TOKENS": "{networkName} 代币", + "TR_COINMARKET_NETWORK_TOKENS": "{networkName} 的代币", "TR_COINMARKET_NO_CEX_PROVIDER_FOUND": "未找到中心化加密货币交易所的供应商", - "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "未找到中心化加密货币交易所的供应商", + "TR_COINMARKET_NO_DEX_PROVIDER_FOUND": "未找到去中心化交易所的供应商", "TR_COINMARKET_NO_METHODS_AVAILABLE": "没有可用的方法", - "TR_COINMARKET_NO_OFFERS_AUTORELOADING_IN": "自动重新加载于", - "TR_COINMARKET_NO_OFFERS_BACK_BUTTON": "回到交易", - "TR_COINMARKET_NO_OFFERS_HEADER": "没有报价", - "TR_COINMARKET_NO_OFFERS_LOADING_FAILED_MESSAGE": "抱歉、由于服务器连接问题、我们目前没有任何报价。", - "TR_COINMARKET_NO_OFFERS_MESSAGE": "抱歉、我们目前没有任何报价。请尝试重新加载页面或修改您的查询。", - "TR_COINMARKET_NO_OFFERS_RELOAD_PAGE_BUTTON": "重新加载页面", "TR_COINMARKET_OFFERS_EMPTY": "没有符合您要求的报价。更改国家或购买金额。", "TR_COINMARKET_OFFERS_REFRESH": "报价刷新在", "TR_COINMARKET_OFFERS_SELECT": "选择", "TR_COINMARKET_OFFER_LOOKING": "正在帮您寻找最优惠的报价", "TR_COINMARKET_OFFER_NO_FOUND": "暂无适合您所要求的报价。", - "TR_COINMARKET_ON_NETWORK_CHAIN": "在 {networkName} 链", + "TR_COINMARKET_ON_NETWORK_CHAIN": "在 {networkName} 链上", "TR_COINMARKET_OTHER_CURRENCIES": "其他货币", "TR_COINMARKET_PAYMENT_METHOD": "支付方式", - "TR_COINMARKET_POPULAR_CURRENCIES": "热门加密货币", - "TR_COINMARKET_RATE": "利率", + "TR_COINMARKET_POPULAR_CURRENCIES": "热门货币", + "TR_COINMARKET_RATE": "汇率", "TR_COINMARKET_RECEIVE_METHOD": "接收方法", "TR_COINMARKET_SELL": "卖出", "TR_COINMARKET_SHOW_OFFERS": "比较报价", "TR_COINMARKET_SWAP": "兑换", "TR_COINMARKET_SWAP_AMOUNT": "兑换金额", - "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "我已经准备好兑换了", + "TR_COINMARKET_SWAP_COUNTER": "{totalSwaps, plural, =0 {{totalSwaps} 次兑换} other {{totalSwaps} 次兑换} }", + "TR_COINMARKET_SWAP_DEX_MODAL_CONFIRM": "我已经准备好兑换", "TR_COINMARKET_SWAP_DEX_MODAL_FOR_YOUR_SAFETY": "使用 {provider} 来兑换 {fromCrypto} 到 {toCrypto}", "TR_COINMARKET_SWAP_DEX_MODAL_LEGAL_HEADER": "法律声明", - "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Trezor一直把安全放在第一位", + "TR_COINMARKET_SWAP_DEX_MODAL_SECURITY_HEADER": "Trezor 一直把安全放在第一位", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_1": "我想使用 {provider} 去中心化交易所的智能合约来兑换加密货币。", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_2": "我想为自己的账户兑换加密货币。我知道提供商的政策可能要求实名认证。", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_3": "我知道加密货币交易是最终交易、不能撤销或退款。因欺诈或错误操作所造成的损失可能无法追回。", - "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "我知道Invity不提供此项服务。它受 {provider} 的条款和条件管辖。", + "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_4": "我知道 Invity 不提供此项服务。它受 {provider} 的条款和条件管辖。", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_5": "我不会将此功能用于赌博、欺诈或任何违反Invity或供应商服务条款或任何适用法律的活动。", "TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6": "我知道加密货币是一种新兴的金融工具、不同地区的法规可能有所不同。这可能会增加欺诈、盗窃或市场不稳定的风险。", - "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "经Invity认证的合作伙伴", - "TR_COINMARKET_SWAP_MODAL_CONFIRM": "我已经准备好了兑换", + "TR_COINMARKET_SWAP_DEX_MODAL_VERIFIED_PARTNERS_HEADER": "已经过 Invity 认证的合作伙伴", + "TR_COINMARKET_SWAP_MODAL_CONFIRM": "我已经准备好兑换", "TR_COINMARKET_SWAP_MODAL_FOR_YOUR_SAFETY": "使用 {provider} 来兑换 {fromCrypto} 到 {toCrypto}", "TR_COINMARKET_SWAP_MODAL_LEGAL_HEADER": "法律声明", - "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Trezor一直把安全放在第一位", - "TR_COINMARKET_SWAP_MODAL_TERMS_1": "我是来兑换加密货币的。如果我是出于其他原因被引导到这个网站、我会在继续之前联系Trezor的客服。", + "TR_COINMARKET_SWAP_MODAL_SECURITY_HEADER": "Trezor 一直把安全放在第一位", + "TR_COINMARKET_SWAP_MODAL_TERMS_1": "我是来兑换加密货币的。如果我是出于其他原因被引导到这个网站,我会在继续之前联系 Trezor 的客服。", "TR_COINMARKET_SWAP_MODAL_TERMS_2": "我想为自己的账户兑换加密货币。我了解提供商的政策可能要求实名认证。", "TR_COINMARKET_SWAP_MODAL_TERMS_3": "我知道加密货币交易是最终交易、不能撤销或退款。因欺诈或错误造成的损失可能无法追回。", - "TR_COINMARKET_SWAP_MODAL_TERMS_4": "我知道Invity不提供此项服务。它受 {provider} 的条款和条件管辖。", - "TR_COINMARKET_SWAP_MODAL_TERMS_5": "我不会将此功能用于赌博、欺诈或任何违反Invity或供应商服务条款或任何适用法律的活动。", + "TR_COINMARKET_SWAP_MODAL_TERMS_4": "我知道 Invity 不提供此项服务。它受 {provider} 的条款和条件管辖。", + "TR_COINMARKET_SWAP_MODAL_TERMS_5": "我不会将此功能用于赌博、欺诈或任何违反 Invity 或供应商服务条款或任何适用法律的活动。", "TR_COINMARKET_SWAP_MODAL_TERMS_6": "我知道加密货币是一种新兴的金融工具、不同地区的法规可能有所不同。这可能会增加欺诈、盗窃或市场不稳定的风险。", - "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "经Invity验证的合作伙伴", + "TR_COINMARKET_SWAP_MODAL_VERIFIED_PARTNERS_HEADER": "已经过 Invity 认证的合作伙伴", "TR_COINMARKET_TOKEN_NETWORK": "{tokenName} 在 {networkName} 网络", "TR_COINMARKET_TRADE_FEE": "交易费", - "TR_COINMARKET_UNKNOWN_PROVIDER": "佚名供应商", - "TR_COINMARKET_YOUR_BEST_OFFER": "您最优惠的报价", - "TR_COINMARKET_YOU_BUY": "您将购买", - "TR_COINMARKET_YOU_GET": "您将获得", + "TR_COINMARKET_TRANS_ID": "交易ID: ", + "TR_COINMARKET_UNKNOWN_PROVIDER": "未知提供商", + "TR_COINMARKET_VIEW_DETAILS": "查看详情", + "TR_COINMARKET_YOUR_BEST_OFFER": "您的最优惠报价", + "TR_COINMARKET_YOU_BUY": "您购买", + "TR_COINMARKET_YOU_GET": "您获得", "TR_COINMARKET_YOU_PAY": "您支付", "TR_COINMARKET_YOU_RECEIVE": "您收到", - "TR_COINMARKET_YOU_SELL": "您将卖出", + "TR_COINMARKET_YOU_SELL": "您卖出", "TR_COINMARKET_YOU_WILL_GET": "您将获得", "TR_COINMARKET_YOU_WILL_PAY": "您将支付", "TR_COINS": "加密货币", - "TR_COIN_CONTROL": "货币控制", - "TR_COIN_CONTROL_TOOLTIP": "硬币控制使得手动选择未消费的交易输出可以用作交易的输入。", + "TR_COIN_CONTROL": "币控制", + "TR_COIN_CONTROL_TOOLTIP": "“币控制” 功能可手动选择用作交易输入的 “未消费交易输出”。", "TR_COIN_DISCOVERY_LOADER_DESCRIPTION": "检查隐藏钱包的余额和交易情况", - "TR_COIN_SETTINGS": "货币设置", - "TR_COLOR_SCHEME": "配色方案", - "TR_COLOR_SCHEME_DARK": "黑夜模式", - "TR_COLOR_SCHEME_DESCRIPTION": "您可以选择应用程序是使用黑暗元素在浅色背景上、还是使用黑暗背景下的浅色元素。", + "TR_COIN_SETTINGS": "加密货币设置", + "TR_COLOR_SCHEME": "颜色主题", + "TR_COLOR_SCHEME_DARK": "深色", + "TR_COLOR_SCHEME_DESCRIPTION": "您可以选择应用程序是使用深色元素在浅色背景上、还是使用深色背景下的浅色元素。", "TR_COLOR_SCHEME_LIGHT": "浅色", "TR_COMMUNITY_LANGUAGES": "社区", "TR_COMPATIBILITY_SIG_FORMAT": "Electrum", @@ -629,49 +626,49 @@ "TR_CONFIRM": "确认", "TR_CONFIRMED_TX": "已确认", "TR_CONFIRMING_TX": "确认交易", - "TR_CONFIRM_ACTION_ON_YOUR": "按照您的Trezor设备屏幕上的指示操作", + "TR_CONFIRM_ACTION_ON_YOUR": "请按照您的 Trezor 设备屏幕上的指示来操作", "TR_CONFIRM_ADDRESS": "确认地址", - "TR_CONFIRM_BEFORE_COPY": "复制前在Trezor设备上确认", - "TR_CONFIRM_CONDITIONS": "在继续之前确认条款。", - "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "确认在 \"{deviceLabel}\" 设备上的隐藏钱包为空。", - "TR_CONFIRM_EVM_EXPLANATION_RECEIVE_DESCRIPTION_ETH": "请您确保只通过以太坊网络接收加密货币。如果加密货币或代币被发送到以太坊网络之外 (如Polygon或Avalanche)、您可能无法访问它们。", + "TR_CONFIRM_BEFORE_COPY": "复制之前,请在 Trezor 设备上确认", + "TR_CONFIRM_CONDITIONS": "请在继续之前确认条款。", + "TR_CONFIRM_EMPTY_HIDDEN_WALLET_ON": "请确认在 \"{deviceLabel}\" 设备上的密码短语钱包为空。", + "TR_CONFIRM_EVM_EXPLANATION_RECEIVE_DESCRIPTION_ETH": "请您确保只通过以太坊网络接收加密货币。如果加密货币或代币被发送到以太坊网络之外 (如 Polygon 或 Avalanche 网络),您可能无法访问它们。", "TR_CONFIRM_EVM_EXPLANATION_RECEIVE_DESCRIPTION_OTHER": "请您确保只通过 {network} 网络接收加密货币。如果加密货币币或代币被发送 {network} 网络之外、您可能无法访问它们。", "TR_CONFIRM_EVM_EXPLANATION_RECEIVE_TITLE": "通过 {network} 网络接收", "TR_CONFIRM_EVM_EXPLANATION_SEND_DESCRIPTION": "请您确保只通过 {network} 网络发送加密货币。如果在 {network} 网络之外发送加密货币或代币、接收方可能无法访问它们。", "TR_CONFIRM_EVM_EXPLANATION_SEND_TITLE": "通过 {network} 网络发送", - "TR_CONFIRM_ON_TREZOR": "在Trezor设备上确认", - "TR_CONFIRM_PASSPHRASE": "确认 密码短语", - "TR_CONFIRM_PASSPHRASE_SOURCE": "确认在 \"{deviceLabel}\" 设备上的空隐藏钱包 密码短语来源。", - "TR_CONFIRM_PASSPHRASE_WITHOUT_ADVICE_DESCRIPTION": "输入您的密码以授权此操作。", + "TR_CONFIRM_ON_TREZOR": "请在 Trezor 设备上确认", + "TR_CONFIRM_PASSPHRASE": "请确认密码短语", + "TR_CONFIRM_PASSPHRASE_SOURCE": "请在 \"{deviceLabel}\" 设备上确认隐藏钱包的密码短语源。", + "TR_CONFIRM_PASSPHRASE_WITHOUT_ADVICE_DESCRIPTION": "输入您的密码短语以授权此操作。", "TR_CONGRATS": "恭喜!", "TR_CONNECT": "连接", "TR_CONNECTED": "已连接", "TR_CONNECTED_DIFFERENT_DEVICE": "已连接了其一个设备吗?", "TR_CONNECTED_TO_PROVIDER": "以 {user} 的身份连接到 {provider}", "TR_CONNECTED_TO_PROVIDER_LOCALLY": "本地保存标签", - "TR_CONNECTION_LOST": "连接丢失", + "TR_CONNECTION_LOST": "连接中断", "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_BUTTON": "管理", - "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "启用密码短语输入对话框、以便在打开Trezor Suite时打开。", - "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "您是否主要使用密码?", - "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "在Trezor设备上验证以确认接收地址。不建议在未确认的情况下继续操作。", + "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_DESCRIPTION": "启用密码短语输入对话框,以便在启动 Trezor Suite 时打开。", + "TR_CONNECT_DEVICE_PASSPHRASE_BANNER_TITLE": "您是否主要使用密码短语?", + "TR_CONNECT_DEVICE_RECEIVE_PROMO_DESCRIPTION": "请在 Trezor 设备上验证以确认接收地址。不建议在未确认的情况下继续操作。", "TR_CONNECT_DEVICE_RECEIVE_PROMO_TITLE": "无法验证接收地址", - "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "若要发送加密货币、请连接您的Trezor设备。", - "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "您的Trezor设备未连接", - "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "若要发送加密货币、请连接您的Trezor设备。", - "TR_CONNECT_YOUR_DEVICE": "连接并解锁您的Trezor设备", - "TR_CONTACT_SUPPORT": "联系客服", - "TR_CONTACT_TREZOR_SUPPORT": "联系Trezor客服", + "TR_CONNECT_DEVICE_SEND_PROMO_DESCRIPTION": "若要发送加密货币,请连接您的 Trezor 设备。", + "TR_CONNECT_DEVICE_SEND_PROMO_TITLE": "您的 Trezor 设备未连接", + "TR_CONNECT_TREZOR_TO_SEND_BUTTON": "连接您的 Trezor 设备以发送", + "TR_CONNECT_YOUR_DEVICE": "请连接并解锁您的 Trezor 设备", + "TR_CONTACT_SUPPORT": "联系 Trezor 客服", + "TR_CONTACT_TREZOR_SUPPORT": "联系 Trezor 客服", "TR_CONTINUE": "继续", "TR_CONTINUE_ANYWAY": "仍然继续", "TR_CONTINUE_ONLY_WITH_SEED": "仅当您有钱包助记词备份时才继续", - "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION": "如果您没有恢复种子、甚至Trezor Support 也不能帮助您恢复您的资金,如果您的设备被重置。 如果您有多个恢复种子、请确保您有正确的一个准备好并易于恢复此特定的Trezor设备。", - "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION_2": "在继续之前、 中检查您的备份。这是检查和验证恢复种子的简单方法。", - "TR_CONTINUE_ON_TREZOR": "在Trezor设备上继续", + "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION": "如果您没有恢复种子,甚至 Trezor Support 也不能帮助您恢复您的资金,如果您的设备被重置。 如果您有多个恢复种子,请确保您有正确的一个准备好并易于恢复此特定的 Trezor 设备。", + "TR_CONTINUE_ONLY_WITH_SEED_DESCRIPTION_2": "在继续之前,。这是检查和验证钱包助记词备份的简单方法。", + "TR_CONTINUE_ON_TREZOR": "在 Trezor 设备上继续", "TR_CONTINUE_TO_BACKUP": "继续备份", "TR_CONTINUE_TO_PIN": "创建PIN码", - "TR_CONTRACT": "智能合约", - "TR_CONTRACT_ADDRESS": "合约地址: ", - "TR_CONTRACT_TRANSACTION": "智能合约交易", + "TR_CONTRACT": "合约", + "TR_CONTRACT_ADDRESS": "合约地址:", + "TR_CONTRACT_TRANSACTION": "合约交易", "TR_CONVERT_TO_CHECKSUM_ADDRESS": "转换为校验地址", "TR_CONVERT_TO_LOWERCASE": "转换为小写", "TR_COPY_ADDRESS_CONTRACT": "切勿向合约地址汇款。", @@ -679,24 +676,24 @@ "TR_COPY_ADDRESS_POLICY_ID": "切勿向政策ID地址汇款。", "TR_COPY_AND_CLOSE": "复制并关闭", "TR_COPY_SIGNED_MESSAGE": "复制签署的留言", - "TR_COPY_TO_CLIPBOARD_TX_ID": "复制", + "TR_COPY_TO_CLIPBOARD": "复制", "TR_COULD_NOT_RETRIEVE_CHANGELOG": "无法获取更新日志", "TR_COULD_NOT_RETRIEVE_DATA": "无法获取数据", - "TR_COUNT_WALLETS": "{count} {count, plural, one {钱包} other {钱包}}", + "TR_COUNT_WALLETS": "{count} {count, plural, other {个钱包}}", "TR_CREATE_BACKUP": "创建备份", - "TR_CREATE_MULTI_SHARE_BACKUP": "创建多共享备份", - "TR_CREATE_MULTI_SHARE_BACKUP_CREATED": "已创建了多共享备份", - "TR_CREATE_MULTI_SHARE_BACKUP_CREATED_INFO_TEXT": "您已经在增强安全性方面迈出了重要一步。现在、请选择值得信赖的个人或安全地点来存储您的多共享备份份额。", - "TR_CREATE_SHARES": "在Trezor设备上创建多共享备份份额", - "TR_CREATE_SHARES_CARD_1": "拿上纸和笔、记录在 备份卡片、或使用 Trezor Keep Metal。", - "TR_CREATE_SHARES_CARD_2": "不要拍照或制作您的钱包备份的数码备份", - "TR_CREATE_SHARES_CARD_3": "确保只有您自己一人、没有好奇的旁观者", - "TR_CREATE_SHARES_EXAMPLE": "例如: 共有5份备份份额、找回您的钱包需要任意3份", - "TR_CREATE_SHARES_EXPLANATION": "选择总份额数、然后选择恢复Trezor所需的最小份额数。", + "TR_CREATE_MULTI_SHARE_BACKUP": "创建秘密共享备份", + "TR_CREATE_MULTI_SHARE_BACKUP_CREATED": "已创建了秘密共享备份", + "TR_CREATE_MULTI_SHARE_BACKUP_CREATED_INFO_TEXT": "您已经在增强安全性方面迈出了重要一步。现在、请选择值得信赖的个人或安全地点来存储您的秘密共享份额。", + "TR_CREATE_SHARES": "在 Trezor 设备上创建秘密共享份额", + "TR_CREATE_SHARES_CARD_1": "拿上纸和笔,记录在 备份卡片、或使用 Trezor Keep Metal。", + "TR_CREATE_SHARES_CARD_2": "不要拍照或制作钱包备份的数码副本", + "TR_CREATE_SHARES_CARD_3": "确保只有您自己一人,周边没有旁观的闲人", + "TR_CREATE_SHARES_EXAMPLE": "例如:共有 5 份秘密共享份额,找回您的钱包需要其中任意的 3 份", + "TR_CREATE_SHARES_EXPLANATION": "选择总份额数,然后选择恢复钱包所需的最小份额数。", "TR_CREATE_WALLET": "创建新钱包", - "TR_CREATE_WALLET_DEFAULT_OPTION_DISABLED_TOOLTIP": "更新您的Trezor设备固件以启用单份备份功能。", + "TR_CREATE_WALLET_DEFAULT_OPTION_DISABLED_TOOLTIP": "更新您的 Trezor 设备固件以启用单份备份功能。", "TR_CREATE_WALLET_DEFAULT_OPTION_TOOLTIP": "建议选择简洁、灵活的设置。", - "TR_CURRENT_FEE": "当前", + "TR_CURRENT_FEE": "当前的费率", "TR_CUSTOM": "自定义", "TR_CUSTOM_BACKEND": "自定义后端", "TR_CUSTOM_BACKEND_BACKEND_ALREADY_ADDED": "后端已添加", @@ -713,12 +710,13 @@ "TR_DASHBOARD_ASSET_FAILED": "资产未正确加载", "TR_DASHBOARD_DISCOVERY_ERROR": "发现错误", "TR_DASHBOARD_DISCOVERY_ERROR_PARTIAL_DESC": "帐户未正确加载 {details}", + "TR_DATA": "数据", "TR_DATABASE_UPGRADE_BLOCKED": "数据库升级被另一个应用程序实例阻止", "TR_DATA_ANALYTICS_CATEGORY_1": "平台", - "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "操作系统,Trezor型号,版本等。", - "TR_DATA_ANALYTICS_CATEGORY_2": "使用数据", - "TR_DATA_ANALYTICS_CATEGORY_2_ITEM_1": "您如何使用Suite", - "TR_DATA_ANALYTICS_CATEGORY_3": "受众", + "TR_DATA_ANALYTICS_CATEGORY_1_ITEM_1": "操作系统、Trezor 设备型号、Suite 版本等。", + "TR_DATA_ANALYTICS_CATEGORY_2": "用户使用数据", + "TR_DATA_ANALYTICS_CATEGORY_2_ITEM_1": "您如何使用 Suite", + "TR_DATA_ANALYTICS_CATEGORY_3": "观众", "TR_DATA_ANALYTICS_CATEGORY_3_ITEM_1": "语言、用户数量等。", "TR_DATE_DAY_LONG": "1天", "TR_DATE_DAY_SHORT": "1天", @@ -736,180 +734,186 @@ "TR_DEFAULT_WALLET_LOADING_PASSPHRASE": "密码短语", "TR_DEFAULT_WALLET_LOADING_STANDARD": "标准", "TR_DELIVERY": "交付时间", - "TR_DESKTOP_APP_PROMO_GET": "获取桌面版", - "TR_DESKTOP_APP_PROMO_HEADING": "充分利用Trezor Suite", - "TR_DESKTOP_APP_PROMO_TEXT": "直接在桌面上轻松的进行加密货币管理", + "TR_DESKTOP_APP_PROMO_GET": "获取桌面应用程序", + "TR_DESKTOP_APP_PROMO_HEADING": "充分利用 Trezor Suite", + "TR_DESKTOP_APP_PROMO_TEXT": "直接在电脑上轻松地进行加密货币管理", "TR_DETAIL": "详情", "TR_DEVICE": "设备", "TR_DEVICE_AUTHENTICITY_ERROR": "我们无法验证您的设备的真实性", "TR_DEVICE_AUTHENTICITY_ITEM_1": "此检查是确保设备的可靠性、完整性和安全使用必须执行的步骤。", - "TR_DEVICE_AUTHENTICITY_ITEM_2": "这可以确认您的硬件钱包内的芯片是正品并且来自Trezor。", - "TR_DEVICE_AUTHENTICITY_ITEM_3": "一旦您的设备通过了健康检查、您就可以放心地使用Trezor了。", + "TR_DEVICE_AUTHENTICITY_ITEM_2": "这可以确认您的硬件钱包内的芯片是正品并且来自 Trezor。", + "TR_DEVICE_AUTHENTICITY_ITEM_3": "一旦您的设备通过了健康检查、您就可以放心地使用 Trezor 了。", "TR_DEVICE_AUTHENTICITY_OPT_OUT_BUTTON": "关闭", "TR_DEVICE_AUTHENTICITY_OPT_OUT_BUTTON_DISABLED": "开启", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_DESCRIPTION": "设备检查是一项重要的安全功能、可确保您的安全、避免使用假冒或受损设备。我们不建议将其关闭。", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_DESCRIPTION_DISABLED": "设备检查是一项重要的安全功能、可确保您的安全、避免使用假冒或受损设备。我们强烈建议打开它。", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_DESCRIPTION": "设备检查是一项重要的安全功能,可确保您的安全,避免使用假冒或受损设备。我们不建议将其关闭。", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_DESCRIPTION_DISABLED": "设备检查是一项重要的安全功能,可确保您的安全,避免使用假冒或受损设备。我们强烈建议开启它。", "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_BUTTON": "关闭", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_CHECKBOX_TITLE": "我已阅读并理解上述内容", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_DESCRIPTION_1": "只有在完全清楚自己在做什么并有明确理由的情况下、才能关闭设备检查。如果您不确定、请联系Trezor客服寻求帮助。", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_CHECKBOX_TITLE": "我已阅读并理解上述的内容", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_DESCRIPTION_1": "只有在完全清楚自己在做什么并有明确理由的情况下,才能关闭设备检查。如果您不确定,请联系 Trezor 客服寻求帮助。", "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_DESCRIPTION_2": "除非您的设备事先已成功通过检查、否则不要关闭此功能。使用未经验证的设备可能会导致资金损失。", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_DESCRIPTION_3": "Trezor客服绝不会要求您关闭设备检查功能。该功能旨在确保您的安全。", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_TITLE": "禁用设备检查", - "TR_DEVICE_AUTHENTICITY_OPT_OUT_TITLE_DISABLED": "启用设备检查", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_MODAL_DESCRIPTION_3": "Trezor 客服绝不会要求您关闭设备检查功能。该功能旨在确保您的安全。", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_TITLE": "禁用设备检查功能", + "TR_DEVICE_AUTHENTICITY_OPT_OUT_TITLE_DISABLED": "启用设备检查功能", "TR_DEVICE_AUTHENTICITY_SUCCESS": "设备真实性检查通过。", "TR_DEVICE_AUTHENTICITY_SUCCESS_DESCRIPTION": "您的 {deviceName} 已准备就绪!", "TR_DEVICE_COMPROMISED_HEADING": "您的设备可能不安全", - "TR_DEVICE_COMPROMISED_TEXT": "请联系Trezor客服、了解您的设备出现了什么问题、以及下一步该怎么做。", - "TR_DEVICE_COMPROMISED_TEXT_SOFT": "在您开始使用之前、我们希望确保您的设备处于最佳状态。请联系Trezor客服、了解下一步操作。", + "TR_DEVICE_COMPROMISED_TEXT": "请联系 Trezor 客服,了解您的设备出现了什么问题,以及下一步该怎么做。", + "TR_DEVICE_COMPROMISED_TEXT_SOFT": "在您开始使用之前,我们希望确保您的设备处于最佳状态。请联系 Trezor 客服、了解下一步操作。", "TR_DEVICE_CONNECTED": "设备已连接", "TR_DEVICE_CONNECTED_BOOTLOADER": "设备已连接到加载引导程序", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT": "不小心进入了加载引导模式?", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_BUTTON": "连接电缆时不要按住任何按钮。", "TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH": "在不接触屏幕的情况下重新连接设备。", + "TR_DEVICE_CONNECTED_UNACQUIRED": "此设备正在其他地方使用。", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION": "应用程序 {transportSessionOwner} 当前可能正在使用该设备。如有需要,您可以控制该设备。", + "TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP": "当前可能有其他应用程序正在使用该设备。如有需要,您可以控制该设备。", "TR_DEVICE_CONNECTED_WRONG_STATE": "检测到设备处于不正确的状态", - "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "您的Trezor设备在备份过程中断开连接。我们强烈建议您使用设备设置中的恢复出厂设置选项来清除设备数据并重新启动钱包备份过程。", + "TR_DEVICE_DISCONNECTED_DURING_ACTION_DESCRIPTION": "您的 Trezor 设备在备份过程中断开连接。我们强烈建议您使用设备设置中的恢复出厂设置选项来清除设备数据并重新启动钱包备份过程。", + "TR_DEVICE_FIRMWARE_HASH_CHECK_HASH_MISMATCH": "固件哈希检查失败。您的 Trezor 设备可能是假冒产品。", + "TR_DEVICE_FIRMWARE_HASH_CHECK_OTHER_ERROR": "无法进行固件哈希检查。您的 Trezor 设备可能是假冒产品。", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON": "关闭", "TR_DEVICE_FIRMWARE_REVISION_CHECK_BUTTON_DISABLED": "开启", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION": "固件修订检查是一项至关重要的安全功能。我们强烈建议保持开启状态。", "TR_DEVICE_FIRMWARE_REVISION_CHECK_DESCRIPTION_DISABLED": "固件修订检查是一项至关重要的安全功能。我们强烈建议保持开启状态。", "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_BUTTON": "关闭", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_1": "只有在完全了解风险并有正当理由的情况下、才能关闭固件版本检查。如果不确定、请联系Trezor支持部门寻求帮助。", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_2": "只有当您的Trezor设备之前已成功通过检查时、才可关闭此功能。使用未经验证的设备可能会导致资金损失。", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_3": "Trezor客服绝不会要求您关闭固件版本检查。该功能旨在保护您的安全。", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_1": "只有在完全了解风险并有正当理由的情况下,才能关闭固件版本检查。如果不确定,请联系 Trezor 客服寻求帮助。", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_2": "只有当您的 Trezor 设备之前已成功通过检查时,才可关闭此功能。使用未经验证的设备可能会导致资金损失。", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_MODAL_DESCRIPTION_3": "Trezor 客服绝不会要求您关闭固件版本检查。该功能旨在保护您的安全。", "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE": "关闭固件修订检查功能", "TR_DEVICE_FIRMWARE_REVISION_CHECK_TITLE_DISABLED": "开启固件修订检查功能", - "TR_DEVICE_FIRMWARE_REVISION_CHECK_UNABLE_TO_PERFORM": "无法进行固件版本检查。请上网验证您的Trezor设备的固件版本。", + "TR_DEVICE_FIRMWARE_REVISION_CHECK_UNABLE_TO_PERFORM": "无法进行固件版本检查。请上网验证您的 Trezor 设备的固件版本。", "TR_DEVICE_FW_UNKNOWN": "未知", - "TR_DEVICE_IN_BOOTLOADER": "设备处于引导加载模式。", - "TR_DEVICE_IN_RECOVERY_MODE": "您的设备处于恢复模式。", + "TR_DEVICE_IN_BOOTLOADER": "设备处于引导加载器模式。", + "TR_DEVICE_IN_RECOVERY_MODE": "您的设备已进入恢复模式。", "TR_DEVICE_IN_RECOVERY_MODE_DESC": "该设备处于恢复模式。点击按钮、继续。", "TR_DEVICE_LABEL_IS_NOT_BACKED_UP": "设备 \"{deviceLabel}\" 未备份", "TR_DEVICE_LABEL_IS_NOT_CONNECTED": "设备 \"{deviceLabel}\" 未连接", "TR_DEVICE_LABEL_IS_UNAVAILABLE": "设备 \"{deviceLabel}\" 不可用", + "TR_DEVICE_MAYBE_COMPROMISED_HEADING": "设备验证失败", + "TR_DEVICE_MAYBE_COMPROMISED_TEXT": "请重新连接设备并尝试再次验证。如果问题仍然存在,请联系 Trezor 客服,了解设备出现了什么问题以及下一步该怎么做。", "TR_DEVICE_NOT_CONNECTED": "设备未连接", - "TR_DEVICE_NOT_INITIALIZED": "Trezor设备未设置", + "TR_DEVICE_NOT_INITIALIZED": "Trezor 设备尚未设置", "TR_DEVICE_NOT_INITIALIZED_TEXT": "我们将指导您完成整个流程、并让您立即开始使用。", "TR_DEVICE_SECURITY": "安全", "TR_DEVICE_SETTINGS_AFTER_DELAY": "延迟后", "TR_DEVICE_SETTINGS_AUTO_LOCK": "自动锁定设备时间", - "TR_DEVICE_SETTINGS_AUTO_LOCK_SUBHEADING": "设备自动锁定的之前的时长。", + "TR_DEVICE_SETTINGS_AUTO_LOCK_SUBHEADING": "设置设备自动锁定前的时间。", "TR_DEVICE_SETTINGS_BRIGHTNESS_BUTTON": "调节亮度", "TR_DEVICE_SETTINGS_BRIGHTNESS_DESC": "启用设备显示屏亮度自定义功能", "TR_DEVICE_SETTINGS_BRIGHTNESS_TITLE": "显示亮度", "TR_DEVICE_SETTINGS_BUTTON_WIPE_DEVICE": "恢复出厂设置", - "TR_DEVICE_SETTINGS_CHANGE_PIN_DESC": "如果您的PIN码已被泄露或您因任何原因想要更改它、您可以在此处进行更改。", + "TR_DEVICE_SETTINGS_CHANGE_PIN_DESC": "如果您的PIN码已经被泄漏或您想要修改它,您可以在这里进行。", "TR_DEVICE_SETTINGS_CHANGE_PIN_TITLE": "修改PIN码", "TR_DEVICE_SETTINGS_CUSTOM_FIRMWARE_BUTTON": "安装固件", - "TR_DEVICE_SETTINGS_CUSTOM_FIRMWARE_DESCRIPTION": "您可以在Trezor设备上安装自定义固件、但这样做会清除其数据内存、可能导致其无法使用。除非您真的知道您在做什么、否则永远不要使用该功能!", + "TR_DEVICE_SETTINGS_CUSTOM_FIRMWARE_DESCRIPTION": "您可以在 Trezor 设备上安装自定义固件,但这样做会清除其数据内存,可能导致其无法使用。除非您真的知道您在做什么,否则永远不要使用该功能!", "TR_DEVICE_SETTINGS_CUSTOM_FIRMWARE_TITLE": "安装自定义固件", - "TR_DEVICE_SETTINGS_DEFAULT_WALLET_LOADING_DESC": "将 “标准” 或 “隐藏” 设为Trezor Suite启动时的默认钱包选项。选择 “隐藏”会在打开应用程序时显示密码短语输入框。", - "TR_DEVICE_SETTINGS_DEFAULT_WALLET_LOADING_TITLE": "启动时要打开的钱包类型", + "TR_DEVICE_SETTINGS_DEFAULT_WALLET_LOADING_DESC": "将 “标准” 或 “隐藏” 设为 Trezor Suite 启动时的默认钱包选项。选择 “隐藏” 会在打开应用程序时显示密码短语输入框。", + "TR_DEVICE_SETTINGS_DEFAULT_WALLET_LOADING_TITLE": "启动时打开的钱包类型", "TR_DEVICE_SETTINGS_DEVICE_EDIT_LABEL": "编辑名称", "TR_DEVICE_SETTINGS_DEVICE_LABEL": "设备名称", - "TR_DEVICE_SETTINGS_DISPLAY_ROTATION": "旋转显示屏", + "TR_DEVICE_SETTINGS_DISPLAY_ROTATION": "显示屏旋转方向", "TR_DEVICE_SETTINGS_ENABLE_VIEW_ONLY_CHANGE_BUTTON": "修改", - "TR_DEVICE_SETTINGS_ENABLE_VIEW_ONLY_DESC": "连接Trezor设备来移动或交易加密货币。", - "TR_DEVICE_SETTINGS_ENABLE_VIEW_ONLY_TITLE": "启用 “仅限查看” 功能、即使Trezor设备已断开连接、仍可在应用程序中查看您的余额", + "TR_DEVICE_SETTINGS_ENABLE_VIEW_ONLY_DESC": "连接 Trezor 设备来移动或交易加密货币。", + "TR_DEVICE_SETTINGS_ENABLE_VIEW_ONLY_TITLE": "启用 “仅供查看” 功能、即使 Trezor 设备已断开连接、仍可在应用程序中查看您的余额", "TR_DEVICE_SETTINGS_HAPTIC_FEEDBACK_DESC": "为设备互动开启触觉反馈功能", "TR_DEVICE_SETTINGS_HAPTIC_FEEDBACK_TITLE": "触觉反馈", "TR_DEVICE_SETTINGS_HOMESCREEN_EDITOR": "主屏幕编辑器", - "TR_DEVICE_SETTINGS_HOMESCREEN_IMAGE_SETTINGS_BW_128x64": "支持PNG或JPG、128 x 64px、只使用黑白 (而不是灰度)。", - "TR_DEVICE_SETTINGS_HOMESCREEN_IMAGE_SETTINGS_COLOR_240x240": "支持JPG、240 x 240px、最大允许大小为16KB。", + "TR_DEVICE_SETTINGS_HOMESCREEN_IMAGE_SETTINGS_BW_128x64": "支持 PNG 或 JPG,128 x 64 px,只使用黑白 (而不是灰度)。", + "TR_DEVICE_SETTINGS_HOMESCREEN_IMAGE_SETTINGS_COLOR_240x240": "支持 JPG,240 x 240 px,最大允许大小为 16 KB。", "TR_DEVICE_SETTINGS_HOMESCREEN_SELECT_FROM_GALLERY": "从图库中选择", "TR_DEVICE_SETTINGS_HOMESCREEN_TITLE": "主屏幕", "TR_DEVICE_SETTINGS_HOMESCREEN_UPLOAD_IMAGE": "上传图像", - "TR_DEVICE_SETTINGS_PASSPHRASE_DESC": "密码短语会将自定义短语 (如单词、句子或字符串) 添加到您现有的钱包备份中、从而创建密码短语钱包。每个密码短语钱包都有自己的密码短语。您的标准钱包无需密码短语即可访问。不要忘记您的密码短语。与常规密码不同、密码短语无法恢复。如果您丢失了密码短语、您的资金将永久丢失。", + "TR_DEVICE_SETTINGS_PASSPHRASE_DESC": "密码短语会将自定义短语 (如单词、句子或字符串) 添加到您现有的钱包备份中,从而创建隐藏钱包。每个隐藏钱包都有自己的密码短语。您的标准钱包无需密码短语即可访问。不要忘记您的密码短语。与常规密码不同,密码短语无法恢复。如果您丢失了密码短语,您的资金将永久丢失。", "TR_DEVICE_SETTINGS_PASSPHRASE_TITLE": "密码短语", - "TR_DEVICE_SETTINGS_PIN_PROTECTION_DESC": "设置一个强大的PIN码是确保设备安全、防止未经授权的物理访问和保护资金的最佳方法之一。", - "TR_DEVICE_SETTINGS_PIN_PROTECTION_TITLE": "PIN 码", + "TR_DEVICE_SETTINGS_PIN_PROTECTION_DESC": "设置一个较长的PIN码是确保设备安全,防止未经授权的物理访问和保护资金的最佳方法之一。", + "TR_DEVICE_SETTINGS_PIN_PROTECTION_TITLE": "PIN码", "TR_DEVICE_SETTINGS_SAFETY_CHECKS_BUTTON": "编辑", "TR_DEVICE_SETTINGS_SAFETY_CHECKS_DESC": "安全检查可防止您执行非标准交易。如果需要执行此类交易、可以暂时禁用安全检查。", "TR_DEVICE_SETTINGS_SAFETY_CHECKS_TITLE": "安全检查", "TR_DEVICE_SETTINGS_WALLET_LOADING": "正在加载钱包", - "TR_DEVICE_SETTINGS_WIPE_CODE_DESC": "自毁PIN码是一项高级功能、它是一串用户预先选择的数字、一旦输入、就会触发删除Trezor设备上存储的钱包备份数据。", + "TR_DEVICE_SETTINGS_WIPE_CODE_DESC": "“自毁PIN码” 是一项高级功能,它是一串用户预先选择的数字,一旦输入,就会触发删除 Trezor 设备上存储的钱包数据。", "TR_DEVICE_SETTINGS_WIPE_CODE_TITLE": "设置自毁PIN码", - "TR_DISABLED_ANONYMITY_CHANGE_MESSAGE": "运行混币时禁止编辑。", - "TR_DISABLED_SWITCH_TOOLTIP": "连接并解锁设备以进行编辑", + "TR_DISABLED_ANONYMITY_CHANGE_MESSAGE": "运行 CoinJoin 混币器时禁止编辑。", + "TR_DISABLED_SWITCH_TOOLTIP": "请连接并解锁设备以进行编辑", "TR_DISABLE_AUTOSTOP_COINJOIN": "不要停止", - "TR_DISABLE_WEBUSB_TRY_BRIDGE": "禁用WebUSB并使用网桥", - "TR_DISABLING_TOR": "正在禁用Tor", + "TR_DISABLE_WEBUSB_TRY_BRIDGE": "禁用 WebUSB 并使用网桥", + "TR_DISABLING_TOR": "正在禁用 Tor", "TR_DISCONNECT": "断开连接", "TR_DISCONNECTED": "已断开连接", "TR_DISCONNECT_DEVICE": "断开设备与电脑的连接。", - "TR_DISCONNECT_YOUR_DEVICE": "断开您的Trezor设备连接", + "TR_DISCONNECT_YOUR_DEVICE": "断开您的 Trezor 设备连接", "TR_DISCOVERY_NEW_COINS": "激活加密货币", "TR_DISCOVERY_NEW_COINS_TEXT": "激活加密货币后看不到账户?", - "TR_DISCREET": "谨慎", - "TR_DISMISS": "解散", + "TR_DISCREET": "隐秘", + "TR_DISMISS": "略过", "TR_DONT_HAVE_BACKUP": "我没有钱包助记词备份", "TR_DONT_SKIP": "不要跳过", "TR_DOWNLOAD": "下载", "TR_DOWNLOADING": "正在下载", "TR_DOWNLOAD_LATEST_BRIDGE": "下载最新的网桥 {version}", - "TR_DO_NOT_DISCONNECT_DEVICE": "不要断开您的Trezor设备连接", + "TR_DO_NOT_DISCONNECT_DEVICE": "不要断开您的 Trezor 设备连接", "TR_DO_NOT_SHOW_AGAIN": "不再显示", - "TR_DO_YOU_REALLY_WANT_TO_SKIP": "您真的想要跳过这个步骤吗?", "TR_DROPBOX": "Dropbox", - "TR_DROPZONE": "拖放文件到此处、或者点击从文件中选择", - "TR_DROPZONE_ERROR": "导入失败: {error}", + "TR_DROPZONE": "拖放文件到此处,或者点击从文件中选择", + "TR_DROPZONE_ERROR": "导入失败:{error}", "TR_DROPZONE_ERROR_EMPTY": "未选择文件", "TR_DROPZONE_ERROR_FILETYPE": "文件类型不正确", "TR_DROP_IMAGE": "拖放图像", - "TR_DRY_RUN_CHECK_ITEM_DESCRIPTION": "请注意、该测试与正常钱包恢复过程完全相同。您只能相信Trezor屏幕上显示的信息和说明。", - "TR_DRY_RUN_CHECK_ITEM_TITLE": "我知道这只是模拟检查、不会影响到我的设备", - "TR_DUST": "无法使用的输出 (灰尘)", + "TR_DRY_RUN_CHECK_ITEM_DESCRIPTION": "请注意,该测试与正常钱包恢复过程完全相同。您只能相信 Trezor 屏幕上显示的信息和说明。", + "TR_DRY_RUN_CHECK_ITEM_TITLE": "我知道这只是模拟检查,它不会影响到我的设备", + "TR_DUST": "无法使用的输出 (灰尘 —— “未消费交易输出” 的金额低于矿工费)", "TR_DUST_DESCRIPTION": "这些输出很可能低于所需支付的费用。", "TR_EARLY_ACCESS": "抢先体验计划", "TR_EARLY_ACCESS_CHECK_UPDATE": "立即检查更新", - "TR_EARLY_ACCESS_DESCRIPTION": "在我们向所有Trezor用户发布最新产品功能之前、加入来测试这些功能。", + "TR_EARLY_ACCESS_DESCRIPTION": "在我们向所有 Trezor 用户发布最新产品功能之前、加入来测试这些功能。", "TR_EARLY_ACCESS_DESCRIPTION_ENABLED": "如果您不再想抢先体验新功能、请点击离开。", "TR_EARLY_ACCESS_DISABLE": "离开", - "TR_EARLY_ACCESS_DISABLE_CONFIRM_DESCRIPTION": "点击“离开”将停止寻找测试版", + "TR_EARLY_ACCESS_DISABLE_CONFIRM_DESCRIPTION": "点击 “离开” 将停止寻找测试版本", "TR_EARLY_ACCESS_DISABLE_CONFIRM_TITLE": "您确定要退出抢先体验计划吗?", "TR_EARLY_ACCESS_ENABLE": "加入", "TR_EARLY_ACCESS_ENABLED": "启用抢先体验计划", "TR_EARLY_ACCESS_ENABLE_CONFIRM": "加入", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "我知道这允许我测试预发布的软件、它可能包含影响Trezor Suite正常运行的缺陷。", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_CHECK": "我知道这允许我测试预发布的软件,它可能包含影响 Trezor Suite 正常运行的缺陷。", "TR_EARLY_ACCESS_ENABLE_CONFIRM_DESCRIPTION": "您可以随时关闭它。", "TR_EARLY_ACCESS_ENABLE_CONFIRM_TITLE": "在向公众发布之前体验最新的产品功能。", - "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "首请先检查上面的字段", + "TR_EARLY_ACCESS_ENABLE_CONFIRM_TOOLTIP": "首先请检查上面的字段", "TR_EARLY_ACCESS_JOINED_DESCRIPTION": "您可以现在或在下次发布时查看测试版更新。", "TR_EARLY_ACCESS_JOINED_TITLE": "已启用抢先体验计划!", - "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "要降级到最新的Trezor Suite稳定版,请点击“下载稳定版”并重新安装应用程序。", + "TR_EARLY_ACCESS_LEFT_DESCRIPTION": "要降级到最新的 Trezor Suite 稳定版本,请点击 “下载稳定版本” 并重新安装应用程序。", "TR_EARLY_ACCESS_LEFT_TITLE": "您已经离开了抢先体验计划。不再提供测试版。", "TR_EARLY_ACCESS_MENU": "抢先体验计划", - "TR_EARLY_ACCESS_REINSTALL": "下载稳定版", + "TR_EARLY_ACCESS_REINSTALL": "下载稳定版本", "TR_EARLY_ACCESS_SKIP_CHECK": "在下次启动时检查", "TR_EARLY_ACCESS_SKIP_REINSTALL": "关闭", "TR_EARLY_ACCESS_STAY_IN": "留在", "TR_EAST": "东", "TR_EJECT_HEADING": "弹出", "TR_EMPTY_ACCOUNT_TITLE": "没有资金", - "TR_EMPTY_COINJOIN_ACCOUNT_SUBTITLE": "接收一些资金并开始混币。", - "TR_ENABLE_AUTOSTOP_COINJOIN": "本回合结束后将停止混币", + "TR_EMPTY_COINJOIN_ACCOUNT_SUBTITLE": "接收一些比特币并开始使用 CoinJoin 混币器。", + "TR_ENABLE_AUTOSTOP_COINJOIN": "本回合结束后将停止 CoinJoin 混币器", "TR_ENABLE_MORE_COINS": "启用更多的加密货币", "TR_ENABLE_NETWORK_BUTTON": "查找我的 {networkName} 帐户", - "TR_ENABLING_TOR": "正在启用Tor", - "TR_ENABLING_TOR_FAILED": "启用Tor失败", + "TR_ENABLING_TOR": "正在启用 Tor", + "TR_ENABLING_TOR_FAILED": "启用 Tor 已失败", "TR_ENTERED_PIN_NOT_CORRECT": "\"{deviceLabel}\" 的PIN码不正确", - "TR_ENTER_ALL_WORDS_IN_CORRECT": "按照正确顺序输入所有单词", - "TR_ENTER_EXISTING_BACKUP": "在Trezor设备上输入当前钱包的助记词备份", + "TR_ENTER_ALL_WORDS_IN_CORRECT": "请按照正确顺序输入所有单词", + "TR_ENTER_EXISTING_BACKUP": "在 Trezor 设备上输入当前钱包的助记词备份", "TR_ENTER_PASSPHRASE": "输入密码短语", - "TR_ENTER_PASSPHRASE_ON_DEVICE": "在Trezor设备上输入密码短语", - "TR_ENTER_PASSPHRASE_ON_DEVICE_LABEL": "在 {deviceLabel} 上输入密码短语", - "TR_ENTER_PIN": "输入PIN码", - "TR_ENTER_SEED_WORDS_INSTRUCTION": "在此按照Trezor设备上显示的顺序输入您钱包助记词备份中的单词。", - "TR_ENTER_SEED_WORDS_ON_DEVICE": "出于安全考虑、钱包助记词将在Trezor设备上输入。请按正确顺序输入单词。", + "TR_ENTER_PASSPHRASE_ON_DEVICE": "在 Trezor 设备上输入密码短语", + "TR_ENTER_PASSPHRASE_ON_DEVICE_LABEL": "请在 {deviceLabel} 上输入密码短语", + "TR_ENTER_PIN": "请输入PIN码", + "TR_ENTER_SEED_WORDS_INSTRUCTION": "请在此按照 Trezor 设备上显示的顺序输入您钱包助记词备份中的单词。", + "TR_ENTER_SEED_WORDS_ON_DEVICE": "出于安全考虑,钱包助记词将在 Trezor 设备上输入。请按正确顺序输入单词。", "TR_ENTER_WIPECODE": "输入自毁PIN码", "TR_ERROR": "错误", "TR_ERROR_CARDANO_DELEGATE": "账户余额不足", "TR_ERROR_CARDANO_WITHDRAWAL": "账户余额不足", "TR_ETH_ADDRESS_CANT_VERIFY_HISTORY": "无法验证地址历史记录。检查地址是否正确。", - "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "地址没有交易历史记录、也没有校验和。检查地址是否正确。", - "TR_EVM_EXPLANATION_DESCRIPTION": "它与以太坊共享相同的地址样式、但有自己独特的加密货币和代币、不能在其他网络上使用。", - "TR_EVM_EXPLANATION_EXCHANGE_DESCRIPTION": "您在 {network} 网络上选择了{coin}、但您似乎没有任何 {networkSymbol} 。您是想在其他网络上选择 {coin} 吗?", - "TR_EVM_EXPLANATION_RECEIVE_DESCRIPTION": "此接收地址仅用于 {network} 以及该网络上的代币。如果有人从 {network} 网络之外向您发送加密货币、您可能无法收到它们。", - "TR_EVM_EXPLANATION_SEND_DESCRIPTION": "仅通过 {network} 网络发送。该地址必须在{network} 网络上、才能接收已发送的加密货币。", + "TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED": "地址没有交易历史记录,也没有校验和。检查地址是否正确。", + "TR_EVM_EXPLANATION_DESCRIPTION": "它与以太坊共享相同的地址样式,但有自己独特的加密货币和代币,不能在其他网络上使用。", + "TR_EVM_EXPLANATION_EXCHANGE_DESCRIPTION": "您在 {network} 网络上选择了{coin},但您似乎没有任何 {networkSymbol} 。您是想在其他网络上选择 {coin} 吗?", + "TR_EVM_EXPLANATION_RECEIVE_DESCRIPTION": "此接收地址仅用于 {network} 以及该网络上的代币。如果有人从 {network} 网络之外向您发送加密货币,您可能无法收到它们。", + "TR_EVM_EXPLANATION_SEND_DESCRIPTION": "仅通过 {network} 网络发送。该地址必须在 {network} 网络上、才能接收已发送的加密货币。", "TR_EVM_EXPLANATION_SEND_MODAL_DESCRIPTION": "您只能将 {network} 的代币发送到 {network} 网络上的接收地址,否则您的代币可能会丢失。", "TR_EVM_EXPLANATION_TITLE": "{network} 是其自身的网络", "TR_EXCEEDS_MAX": "超出最大长度", @@ -918,20 +922,19 @@ "TR_EXCHANGE_APPROVAL_FAILED": "批准交易失败了。", "TR_EXCHANGE_APPROVAL_NOT_REQUIRED": "{send} 无需批准交易。", "TR_EXCHANGE_APPROVAL_PREAPPROVED": "合约已被批准。", - "TR_EXCHANGE_APPROVAL_PROCEED": "继续兑换、无需批准交易。", + "TR_EXCHANGE_APPROVAL_PROCEED": "继续兑换,无需批准交易。", "TR_EXCHANGE_APPROVAL_SEND_TO": "{send} 合约", "TR_EXCHANGE_APPROVAL_SUCCESS": "批准交易已确认。", "TR_EXCHANGE_APPROVAL_TO_SWAP_BUTTON": "继续兑换", "TR_EXCHANGE_APPROVAL_TXID": "批准交易ID", "TR_EXCHANGE_APPROVAL_VALUE": "批准价值", "TR_EXCHANGE_APPROVAL_VALUE_INFINITE": "无限价值", - "TR_EXCHANGE_APPROVAL_VALUE_INFINITE_INFO": "创建一个单一的批准交易、简化 {send} 与 {provider} 的多次交易。这样可以节省费用、但万一 {provider} 的合同有缺陷、您的资金就会面临风险。", - "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL": "{value} {send} 的必备值", + "TR_EXCHANGE_APPROVAL_VALUE_INFINITE_INFO": "创建一个单一的批准交易,简化 {send} 与 {provider} 的多次交易。这样可以节省费用,但万一 {provider} 的合同有缺陷,您的资金就会面临风险。", + "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL": "{value} {send} 的必要值", "TR_EXCHANGE_APPROVAL_VALUE_MINIMAL_INFO": "只批准此兑换所需的确切金额。 如果你想再次进行类似的兑换、你需要支付额外的费用。", "TR_EXCHANGE_APPROVAL_VALUE_ZERO": "撤销先前的批准", - "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "执行一项交易、将移除先前与 {provider} 的合同批准。", - "TR_EXCHANGE_BUY": "对于", - "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "在Trezor设备上确认并发送", + "TR_EXCHANGE_APPROVAL_VALUE_ZERO_INFO": "执行一项交易,取消之前与 {provider} 的合同批准。", + "TR_EXCHANGE_CONFIRM_ON_TREZOR_SEND": "请在 Trezor 设备上确认并发送", "TR_EXCHANGE_CONFIRM_SEND_STEP": "确认并发送", "TR_EXCHANGE_CREATE_APPROVAL_STEP": "创建批准", "TR_EXCHANGE_CREATE_SUITE_ACCOUNT": "创建一个新的 {symbol} 帐户", @@ -939,12 +942,12 @@ "TR_EXCHANGE_DETAIL_CONVERTING_TITLE": "转换中", "TR_EXCHANGE_DETAIL_ERROR_BUTTON": "返回账户", "TR_EXCHANGE_DETAIL_ERROR_SUPPORT": "打开合作伙伴的客服网站", - "TR_EXCHANGE_DETAIL_ERROR_TEXT": "抱歉、您的交易失败或被拒绝。您的加密货币尚未被兑换。", - "TR_EXCHANGE_DETAIL_ERROR_TITLE": "交易失败", + "TR_EXCHANGE_DETAIL_ERROR_TEXT": "抱歉,您的交易失败或被拒绝。您的加密货币尚未被兑换。", + "TR_EXCHANGE_DETAIL_ERROR_TITLE": "交易失败了", "TR_EXCHANGE_DETAIL_KYC_BUTTON": "返回账户", "TR_EXCHANGE_DETAIL_KYC_INFO_LINK": "前往提供商了解实名认证的详情", "TR_EXCHANGE_DETAIL_KYC_SUPPORT": "前往提供商的客服", - "TR_EXCHANGE_DETAIL_KYC_TEXT": "提供商已将这笔交易标为“可疑”交易; 您可能需要完成他们的实名认证流程才能完成交易。 请联系提供商的客服以继续。", + "TR_EXCHANGE_DETAIL_KYC_TEXT": "提供商已将这笔交易标为 “可疑” 交易; 您可能需要完成他们的实名认证流程才能完成交易。 请联系提供商的客服以继续。", "TR_EXCHANGE_DETAIL_KYC_TITLE": "实名认证请求", "TR_EXCHANGE_DETAIL_SENDING_SUPPORT": "前往提供商的客服", "TR_EXCHANGE_DETAIL_SENDING_TITLE": "待定的", @@ -952,20 +955,17 @@ "TR_EXCHANGE_DETAIL_SUCCESS_TEXT": "您的交易已成功。", "TR_EXCHANGE_DETAIL_SUCCESS_TITLE": "已批准", "TR_EXCHANGE_DEX": "去中心化交易所报价", - "TR_EXCHANGE_DEX_OFFER_FEE_INFO": "进行这项兑换的批准费用估算为: {approvalFee} ({approvalFeeFiat})。兑换费用估算为: {swapFee} ({swapFeeFiat})。", - "TR_EXCHANGE_DEX_OFFER_NO_FUNDS_FEES": "没有剩余资金支付此交易费用。请将兑换金额降至最大值 {symbol} {max}", "TR_EXCHANGE_EXTRA_FIELD": "{extraFieldName}", "TR_EXCHANGE_EXTRA_FIELD_INVALID": "{extraFieldName} 是无效的", "TR_EXCHANGE_EXTRA_FIELD_QUESTION_TOOLTIP": "{extraFieldDescription}", "TR_EXCHANGE_EXTRA_FIELD_REQUIRED": "{extraFieldName} 是必需的", - "TR_EXCHANGE_FEES_INFO": "包括所有费用; 交易费估算为 {feeAmount} ({feeAmountFiat})。", + "TR_EXCHANGE_FEES_INFO": "包括所有费用;交易费估算为 {feeAmount} ({feeAmountFiat})。", "TR_EXCHANGE_FIXED": "固定汇率报价", - "TR_EXCHANGE_FIXED_OFFERS_INFO": "固定汇率会准确地显示您在交易结束时会得到多少金额 —— 从您选择的汇率到交易完成之间的金额不会发生变化。可以保证您得到所显示的金额、但固定汇率通常比浮动汇率低、这意味着您的钱买不到那么多加密货币。", + "TR_EXCHANGE_FIXED_OFFERS_INFO": "固定汇率会准确地显示您在交易结束时会得到的金额 —— 从您选择的汇率到交易完成之间的金额不会发生变化。可以保证您得到所显示的金额,但固定汇率通常比浮动汇率低,这意味着您的钱买不到那么多加密货币。", "TR_EXCHANGE_FLOAT": "浮动汇率报价", "TR_EXCHANGE_FLOAT_OFFERS_INFO": "浮动汇率意味着,从您选择的汇率到您的交易完成,由于市场的波动,您将得到的最终金额可能会略有变化。浮动汇率通常比固定汇率高,意味着你最终可能得到更多的加密货币。", - "TR_EXCHANGE_PROVIDER": "供应商", "TR_EXCHANGE_RATE": "价格", - "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "接收账户是在Suite之外。", + "TR_EXCHANGE_RECEIVE_NON_SUITE_ACCOUNT_QUESTION_TOOLTIP": "接收账户是在 Suite 之外。", "TR_EXCHANGE_RECEIVE_NON_SUITE_ADDRESS_QUESTION_TOOLTIP": "这是将收到您的加密货币的具体字母数字地址。", "TR_EXCHANGE_RECEIVING_ADDRESS": "接收地址", "TR_EXCHANGE_RECEIVING_ADDRESS_INFO": "您的地址是您将收到您的 {symbol} 的地址。", @@ -984,36 +984,29 @@ "TR_EXCHANGE_SWAP_SLIPPAGE": "滑点", "TR_EXCHANGE_SWAP_SLIPPAGE_AMOUNT": "滑点最大值", "TR_EXCHANGE_SWAP_SLIPPAGE_CUSTOM": "自定义", - "TR_EXCHANGE_SWAP_SLIPPAGE_INFO": "汇率不断变化、所以您在这个报价中接受的金额和最终在区块链上确认的金额可能不同; 这就是滑点。滑点容忍度设定了您可能因滑点而损失的交易比例; 换句话说、您设定了您最终愿意接受的最低金额。如果滑点容忍度太高、您可能会收到比报价少得多的金额。如果滑点容忍度太低、您的交易可能会失败 (逆转)、但您仍将支付交易费。", + "TR_EXCHANGE_SWAP_SLIPPAGE_INFO": "汇率不断变化,所以您在这个报价中接受的金额和最终在区块链上确认的金额可能不同;这就是滑点。滑点容忍度设定了您可能因滑点而损失的交易比例;换句话说,您设定了您最终愿意接受的最低金额。如果滑点容忍度太高,您可能会收到比报价少得多的金额。如果滑点容忍度太低,您的交易可能会失败(逆转),但您仍将支付交易费。", "TR_EXCHANGE_SWAP_SLIPPAGE_MINIMUM": "最低接收金额", - "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_IN_RANGE": "滑点必须在0.01% — 50%的范围内", + "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_IN_RANGE": "滑点必须在 0.01% - 50% 的范围内", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_NUMBER": "请输入一个数字。", "TR_EXCHANGE_SWAP_SLIPPAGE_NOT_SET": "输入你想要的滑点。", "TR_EXCHANGE_SWAP_SLIPPAGE_OFFERED": "兑换报价金额", - "TR_EXCHANGE_SWAP_SLIPPAGE_SUMMARY": "滑点总结", "TR_EXCHANGE_SWAP_SLIPPAGE_TOLERANCE": "滑点容忍度", - "TR_EXCHANGE_TRANS_ID": "交易ID: ", - "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "使用一个不在Suite中的账户 {symbol}", + "TR_EXCHANGE_USE_NON_SUITE_ACCOUNT": "使用一个不在 Suite 中的账户 {symbol}", "TR_EXCHANGE_VERIFY_ADDRESS_STEP": "接收地址", - "TR_EXCHANGE_VIEW_DETAILS": "查看详情", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE": "自动更新Trezor Suite", - "TR_EXPERIMENTAL_AUTOMATIC_UPDATE_DESCRIPTION": "Trezor Suite会在后台自动下载最新版本、并在重启应用程序时安装。这可确保您始终使用最新功能和安全补丁。更新无需经过您的许可。", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN": "币安智能链", - "TR_EXPERIMENTAL_BNB_SMART_CHAIN_DESCRIPTON": "启用币安智能链网络、但无内部交易数据历史记录。", "TR_EXPERIMENTAL_FEATURES": "实验性", - "TR_EXPERIMENTAL_FEATURES_ALLOW": "实验功能", - "TR_EXPERIMENTAL_FEATURES_WARNING": "仅供有经验的用户使用。使用风险自负。这些功能正在测试中、可能不稳定、也可能得不到长期支持。", - "TR_EXPERIMENTAL_PASSWORD_MANAGER": "迁移Dropbox密码", - "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "该工具用于找回存储在Dropbox中并由Trezor保护的密码。专为Trezor密码管理器的Chrome浏览器扩展用户设计。", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake", - "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake是一个允许访问被墙的网站和应用程序的系统。", + "TR_EXPERIMENTAL_FEATURES_ALLOW": "实验性功能", + "TR_EXPERIMENTAL_FEATURES_WARNING": "仅供有经验的用户使用。使用风险自负。这些功能正在测试中,可能不稳定,也可能得不到长期支持。", + "TR_EXPERIMENTAL_PASSWORD_MANAGER": "迁移 Dropbox 密码", + "TR_EXPERIMENTAL_PASSWORD_MANAGER_DESCRIPTION": "该工具用于找回存储在 Dropbox 中并由 Trezor 保护的密码。专为 Trezor 密码管理器的 Chrome 浏览器扩展用户设计。", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE": "Tor Snowflake 插件 ", + "TR_EXPERIMENTAL_TOR_SNOWFLAKE_DESCRIPTION": "Tor Snowflake 是一个允许访问被 “防火长城” 所墙的网站和应用程序的插件。", "TR_EXPORT_AS": "导出为 {as}", "TR_EXPORT_FAIL": "导出失败。", "TR_EXPORT_TO_FILE": "导出到文件", "TR_FAILED": "失败", - "TR_FAILED_BACKUP": "备份失败。请清除您的Trezor设备数据并重新开始设置过程。", + "TR_FAILED_BACKUP": "备份失败。请清除您的 Trezor 设备数据并重新开始设置过程。", "TR_FAILED_TRANSACTION": "失败的交易", - "TR_FEEDBACK_ANALYTICS_ITEM_APP": "Trezor Suite版本", + "TR_FEEDBACK_ANALYTICS_ITEM_APP": "Trezor Suite 版本", "TR_FEEDBACK_ANALYTICS_ITEM_BROWSER": "浏览器", "TR_FEEDBACK_ANALYTICS_ITEM_FW": "设备固件版本", "TR_FEEDBACK_ANALYTICS_ITEM_OS": "操作系统", @@ -1026,120 +1019,120 @@ "TR_FEEDBACK_CATEGORY_SETTINGS": "设置", "TR_FEEDBACK_CATEGORY_TRADE": "交易", "TR_FEE_RATE": "矿工费率", - "TR_FEE_RATE_CHANGED": "为了完成交易、矿工费率已更改。", - "TR_FEE_ROUNDING_BASEFEE_WARNING": "已提高 {feeRate} 的矿工费率、以支付内存池内的链式交易", - "TR_FEE_ROUNDING_DEFAULT_WARNING": "由于费用的四舍五入、矿工费率已提高至{feeRate}。", + "TR_FEE_RATE_CHANGED": "为了完成交易,矿工费率已更改。", + "TR_FEE_ROUNDING_BASEFEE_WARNING": "已提高 {feeRate} 的矿工费率,以支付内存池内的链式交易", + "TR_FEE_ROUNDING_DEFAULT_WARNING": "由于费用的四舍五入,矿工费率已提高至 {feeRate}。", "TR_FIAT_RATES_NOT_AVAILABLE": "没有法定货币汇率。", "TR_FIAT_RATES_NOT_AVAILABLE_TOOLTIP": "目前尚无法定货币汇率。", "TR_FINAL_HEADING": "设置完成!", "TR_FINGERPRINT_ADDRESS": "指纹: ", - "TR_FIRMWARE": "固件版本", + "TR_FIRMWARE": "固件", "TR_FIRMWARE_CHECK_AUTHENTICITY_SUCCESS": "固件为正品", - "TR_FIRMWARE_HASH_MISMATCH": "您的Trezor设备正在运行非官方固件。请立即联系help@trezor.io。", + "TR_FIRMWARE_HASH_MISMATCH": "您的 Trezor 设备正在运行非官方固件。请立即联系 help@trezor.io。", "TR_FIRMWARE_INSTALLED_TEXT": "该设备已安装了 {type}{version} 的固件。", - "TR_FIRMWARE_IS_POTENTIALLY_RISKY": "更新固件存在潜在风险。如果出现任何问题 (如使用有损坏的USB数据线)、设备的数据可能会被清除、这也意味着您必须使用钱包助记词备份来恢复钱包。", + "TR_FIRMWARE_IS_POTENTIALLY_RISKY": "更新固件存在潜在风险。如果出现任何问题 (如使用有问题的 USB 数据线),设备的数据可能会被清除,这也意味着您必须使用钱包助记词备份来恢复钱包。", "TR_FIRMWARE_IS_UP_TO_DATE": "固件准备就绪", "TR_FIRMWARE_LANGUAGE_CHANGED": "已成功修改设备语言", "TR_FIRMWARE_LANGUAGE_FETCH_ERROR": "翻译文件下载失败", "TR_FIRMWARE_NEW_FW_DESCRIPTION": "新固件现已发布。立即更新您的设备。", - "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "您的设备已经更新到最新的固件。如果需要、您可以重新安装固件。", - "TR_FIRMWARE_REVISION_CHECK_FAILED": "固件修订检查失败。您的Trezor设备可能是假冒产品。", + "TR_FIRMWARE_REINSTALL_FW_DESCRIPTION": "您的设备已经更新到最新的固件。如果需要,您可以重新安装固件。", + "TR_FIRMWARE_REVISION_CHECK_FAILED": "固件修订检查失败。您的 Trezor 设备可能是假冒产品。", "TR_FIRMWARE_REVISION_CHECK_OTHER_ERROR": "无法进行固件修订检查。", "TR_FIRMWARE_STATUS_INSTALLATION_COMPLETED": "已完成", "TR_FIRMWARE_SUBHEADING_BITCOIN": "仅支持比特币操作的轻量级固件。", - "TR_FIRMWARE_SUBHEADING_NONE": "您的Trezor设备出厂时未安装固件。请安装最新固件、以便安全使用该设备。对于仅使用比特币的用户、我们建议安装。", - "TR_FIRMWARE_SUBHEADING_NONE_BITCOIN_ONLY_DEVICE": "您的设备已准备好安装最新固件、以便安全使用。对于比特币爱好者、我们提供了仅适用于比特币的固件。", - "TR_FIRMWARE_SUBHEADING_UNKNOWN": "您的Trezor设备出厂时未安装固件。请安装最新固件、以便安全使用该设备。对于仅使用比特币的用户、我们建议安装。", - "TR_FIRMWARE_SUBHEADING_UNKNOWN_BITCOIN_ONLY_DEVICE": "仅支持比特币操作的轻量级固件。", + "TR_FIRMWARE_SUBHEADING_NONE": "您的 Trezor 设备出厂时未安装固件。请安装最新固件,以便安全使用该设备。对于仅使用比特币的用户,我们建议安装。", + "TR_FIRMWARE_SUBHEADING_NONE_BITCOIN_ONLY_DEVICE": "您的设备已准备好安装最新固件,以便安全使用。对于比特币爱好者,我们提供了仅适用于比特币的固件。", + "TR_FIRMWARE_SUBHEADING_UNKNOWN": "您的 Trezor 设备出厂时未安装固件。请安装最新固件,以便安全使用该设备。对于仅使用比特币的用户,我们建议安装。", + "TR_FIRMWARE_SUBHEADING_UNKNOWN_BITCOIN_ONLY_DEVICE": "一个轻量级的固件支持于仅限比特币的操作。", "TR_FIRMWARE_SWITCH_WARNING_1": "切换固件会清除所有设备数据、包括钱包、密钥和账户。", - "TR_FIRMWARE_SWITCH_WARNING_2": "要重新访问您的加密货币、您必须使用钱包助记词备份来恢复您的钱包。确保您的钱包助记词备份可访问且清晰可辨。", - "TR_FIRMWARE_SWITCH_WARNING_3": "如果您没有您的钱包助记词备份、您无法恢复您的加密货币!", + "TR_FIRMWARE_SWITCH_WARNING_2": "要重新访问您的加密货币,您必须使用钱包助记词备份来恢复您的钱包。确保您的钱包助记词备份可访问且清晰可辨。", + "TR_FIRMWARE_SWITCH_WARNING_3": "如果您没有您的钱包助记词备份,您将无法恢复您的加密货币!", "TR_FIRMWARE_TYPE": "类型", "TR_FIRMWARE_TYPE_BITCOIN_ONLY": "仅限比特币", "TR_FIRMWARE_TYPE_REGULAR": "通用", "TR_FIRMWARE_UPDATE_REQUIRED_EXPLAINED": "您的设备有不再被支持的固件。您需要更新它。", - "TR_FIRMWARE_VALIDATION_T1_V2": "您需要先升级到引导程序1.8.0版本", + "TR_FIRMWARE_VALIDATION_T1_V2": "您需要先升级到引导加载程序 1.8.0", "TR_FIRMWARE_VALIDATION_TOO_OLD": "您的设备固件版本太低", "TR_FIRMWARE_VALIDATION_UNMATCHING_DEVICE": "固件与您的设备不匹配", "TR_FIRMWARE_VALIDATION_UNRECOGNIZED_FORMAT": "固件镜像类型无法识别", - "TR_FIRMWARE_VERSION": "版本 ", - "TR_FIRST_SEEN": "首次出现", - "TR_FOLLOW_INSTRUCTIONS_ON_DEVICE": "检查您的Trezor设备的屏幕", + "TR_FIRMWARE_VERSION": "版本", + "TR_FIRST_SEEN": "首次出现于", + "TR_FOLLOW_INSTRUCTIONS_ON_DEVICE": "检查您的 Trezor 设备的屏幕", "TR_FORMAT": "格式", - "TR_FORMAT_TOOLTIP": " Trezor — 根据BIP137的标准签名格式 Electrum — 兼容签名格式 ", + "TR_FORMAT_TOOLTIP": " Trezor —— 根据 BIP137 的标准签名格式 Electrum —— 兼容签名格式 ", "TR_FOR_EASIER_AND_SAFER_INPUT": "请将二维码对准电脑摄像头。", "TR_FRACTION_BUTTONS_ALL": "全部", "TR_FROM": "从", - "TR_FW_INSTALLATION_FAILED": "安装失败", - "TR_GAS_LIMIT": "Gas限额", - "TR_GAS_PRICE": "Gas价格", - "TR_GAS_USED": "使用的Gas", - "TR_GATHERING_INFO": "获取信息、请稍等...", + "TR_FW_INSTALLATION_FAILED": "安装失败了", + "TR_GAS_LIMIT": "燃料费上限", + "TR_GAS_PRICE": "燃料费", + "TR_GAS_USED": "已使用的燃料费", + "TR_GATHERING_INFO": "正在获取信息,请稍等...", "TR_GENERAL": "应用程序", - "TR_GENERIC_ERROR_TITLE": "哎呀!出了错!", + "TR_GENERIC_ERROR_TITLE": "哎呀!出错了!", "TR_GOOGLE_DRIVE": "谷歌云端硬盘", - "TR_GOT_IT": "明白了!", + "TR_GOT_IT": "知道了!", "TR_GOT_IT_BUTTON": "明白", "TR_GO_TO_ONBOARDING": "开始设置", "TR_GO_TO_SETTINGS": "前往设置", - "TR_GO_TO_SUITE": "访问Suite", + "TR_GO_TO_SUITE": "访问 Suite", "TR_GRAPH_LINEAR": "线性", "TR_GRAPH_LOGARITHMIC": "对数", - "TR_GRAPH_MISSING_DATA": "XRP、SOL和所有代币金额都在投资组合余额中、但目前在图表视图中不支持。", + "TR_GRAPH_MISSING_DATA": "XRP、SOL 和所有代币金额都在投资组合余额中,但目前在图表视图中不支持。", "TR_GRAPH_VIEW": "图表视图", "TR_GUIDE_ARTICLES": "文章", - "TR_GUIDE_BUG_LABEL": "出什么问题了吗?", + "TR_GUIDE_BUG_LABEL": "有什么不对吗?", "TR_GUIDE_DASHBOARD": "控制面板", - "TR_GUIDE_FEEDBACK_BUG_TEXT_HEADLINE": "有什么问题?", + "TR_GUIDE_FEEDBACK_BUG_TEXT_HEADLINE": "有什么问题吗?", "TR_GUIDE_FEEDBACK_CATEGORY_HEADLINE": "应用程序中的位置", "TR_GUIDE_FEEDBACK_ERROR": "服务器出现错误。请稍后再试。", - "TR_GUIDE_FEEDBACK_RATING_HEADLINE": "喜欢使用Suite吗?", + "TR_GUIDE_FEEDBACK_RATING_HEADLINE": "喜欢使用 Suite 吗?", "TR_GUIDE_FEEDBACK_SEND_REPORT": "提交", - "TR_GUIDE_FEEDBACK_SENT": "信息已发送。谢谢!", + "TR_GUIDE_FEEDBACK_SENT": "留言已发送。谢谢!", "TR_GUIDE_FEEDBACK_SUGGESTION_TEXT_HEADLINE": "我们该如何改进?", - "TR_GUIDE_FEEDBACK_SYSTEM_INFO_NOTICE": "您的基本系统信息将被匿名共享", - "TR_GUIDE_FORUM": "Trezor论坛", - "TR_GUIDE_FORUM_LABEL": "与Trezor社区互动", - "TR_GUIDE_SUGGESTION_LABEL": "我们做得怎样?", + "TR_GUIDE_FEEDBACK_SYSTEM_INFO_NOTICE": "您的基本系统信息将被匿名分享", + "TR_GUIDE_FORUM": "Trezor 论坛", + "TR_GUIDE_FORUM_LABEL": "与 Trezor 社区互动", + "TR_GUIDE_SUGGESTION_LABEL": "我们做的怎么样?", "TR_GUIDE_SUPPORT": "联系客服", - "TR_GUIDE_SUPPORT_AND_FEEDBACK": "帮助与反馈", - "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "帮助与反馈", + "TR_GUIDE_SUPPORT_AND_FEEDBACK": "支持与反馈", + "TR_GUIDE_VIEW_HEADLINES_SUPPORT_FEEDBACK_SELECTION": "支持与反馈", "TR_GUIDE_VIEW_HEADLINE_HELP_US_IMPROVE": "帮助我们改进", - "TR_GUIDE_VIEW_HEADLINE_LEARN_AND_DISCOVER": "Suite使用指南", - "TR_GUIDE_VIEW_HEADLINE_NEED_HELP": "需要帮助?", + "TR_GUIDE_VIEW_HEADLINE_LEARN_AND_DISCOVER": "Suite 使用指南", + "TR_GUIDE_VIEW_HEADLINE_NEED_HELP": "需要帮助吗?", "TR_GUIDE_VIEW_HEADLINE_REPORT_BUG": "报告一个软件缺陷", - "TR_GUIDE_VIEW_HEADLINE_SUGGEST": "反馈意见", - "TR_HELP": "帮助", + "TR_GUIDE_VIEW_HEADLINE_SUGGEST": "用户反馈", + "TR_HELP": "技术支持", "TR_HEX_FORMAT": "十六进制格式", - "TR_HIDDEN": "隐蔽", - "TR_HIDDEN_TOKENS": "已隐蔽的代币", - "TR_HIDDEN_TOKENS_EMPTY": "您没持有已隐蔽的代币", - "TR_HIDDEN_WALLET_DESCRIPTION": "需要密码短语", - "TR_HIDDEN_WALLET_TOOLTIP": "密码短语在您的钱包助记词备份中添加了一个自定义短语 (例如、一个单词、句子或一串字符)。这将创建一个隐藏钱包; 每个隐藏钱包可以使用自己的密码短语。您的标准钱包仍然可以在没有密码短语的情况下访问。", + "TR_HIDDEN": "已隐藏", + "TR_HIDDEN_TOKENS": "已隐藏的代币", + "TR_HIDDEN_TOKENS_EMPTY": "您没有被隐藏的代币", + "TR_HIDDEN_WALLET_DESCRIPTION": "需要提供密码短语", + "TR_HIDDEN_WALLET_TOOLTIP": "密码短语在您的钱包助记词备份中添加了一个自定义的短语 (例如,一个单词、句子或一串字符)。这将创建一个隐藏钱包;每个隐藏钱包可以使用自己的密码短语。您的标准钱包仍然可以在没有密码短语的情况下访问。", "TR_HIDE_BALANCES": "隐藏余额", "TR_HIDE_GRAPH": "隐藏图表", - "TR_HIDE_TOKEN": "隐藏代币", - "TR_HOLOGRAM_STEP_HEADING": "验证您的封条", - "TR_HOLOGRAM_STEP_SUBHEADING": "确保保护您的设备的全息密封条是完整的。", - "TR_HOLOGRAM_T2B1_NEW_SEAL": "Trezor Safe 3的全息印章于2024年4月更新。在此日期之后生产的设备采用了更新后的封条、如图片底部所示。不过、较早生产的设备可能仍带有原来的安全印章。", + "TR_HIDE_TOKEN": "隐藏此代币", + "TR_HOLOGRAM_STEP_HEADING": "请核对设备的全息密封条", + "TR_HOLOGRAM_STEP_SUBHEADING": "请确保设备的全息密封封条完好无损。", + "TR_HOLOGRAM_T2B1_NEW_SEAL": "Trezor Safe 3 的全息密封条于 2024 年 4 月更新。在此日期之后生产的设备采用了新版的密封条,如图片底部所示。不过,较早生产的设备可能仍带有原版的全息密封条。", "TR_HOMESCREEN_GALLERY": "主屏幕图库", "TR_HOW_PIN_WORKS": "更多关于您的PIN码的信息", - "TR_IF_YOUR_DEVICE_IS_EVER_LOST": "如果您的Trezor设备丢失或损坏、您的资金将不可逆转地丢失。", - "TR_IMPORTANT": "重要!", + "TR_IF_YOUR_DEVICE_IS_EVER_LOST": "如果您的 Trezor 设备遗失或损坏,您的资金将不可逆转地丢失。", + "TR_IMPORTANT": "重要事项!", "TR_IMPORT_CSV_FROM_FILE": "从文件导入", - "TR_IMPORT_CSV_FROM_TEXT": "以文本形式导入", + "TR_IMPORT_CSV_FROM_TEXT": "作为文本形式导入", "TR_IMPORT_CSV_MODAL_DELIMITER_CUSTOM": "自定义分隔符", "TR_IMPORT_CSV_MODAL_DELIMITER_DEFAULT": "自动检测分隔符", - "TR_IMPORT_CSV_MODAL_HIDE_EXAMPLE": "隐藏实例", - "TR_IMPORT_CSV_MODAL_SHOW_EXAMPLE": "显示CSV示例", - "TR_IMPORT_CSV_MODAL_TITLE": "从CSV导入地址", + "TR_IMPORT_CSV_MODAL_HIDE_EXAMPLE": "隐藏示例", + "TR_IMPORT_CSV_MODAL_SHOW_EXAMPLE": "显示 CSV 示例", + "TR_IMPORT_CSV_MODAL_TITLE": "从 CSV 导入地址", "TR_INACTIVE_COINS": "尚未激活", "TR_INCLUDING_FEE": "包括矿工费\n", "TR_INCLUDING_TOKENS": "包括代币", - "TR_INCLUDING_TOKENS_AND_STAKING": "包括代币和质押", - "TR_INCOMING": "传入", - "TR_INCREASED_FEE": "新的交易费", - "TR_INCREASE_FEE_BY": "增加您的交易费", + "TR_INCLUDING_TOKENS_AND_STAKING": "包括其代币与质押", + "TR_INCOMING": "接收到", + "TR_INCREASED_FEE": "新的矿工费", + "TR_INCREASE_FEE_BY": "追加您的矿工费", "TR_INPUTS": "输入", "TR_INPUTS_OUTPUTS": "输入、输出", "TR_INSTALL": "安装固件", @@ -1150,18 +1143,18 @@ "TR_INSTALL_FW_DISABLED_MULTIPLE_DEVICES": "不允许在连接多个设备的情况下安装固件。", "TR_INSTALL_LATEST_FW": "安装最新的固件", "TR_INSTALL_REGULAR": "安装 {regular} 固件", - "TR_INSTANT_STAKING": "立刻质押", - "TR_INSTANT_UNSTAKING": "立刻解除质押", + "TR_INSTANT_STAKING": "已即时质押", + "TR_INSTANT_UNSTAKING": "已即时解除质押", "TR_INTERNAL_TRANSACTIONS": "内部转账", - "TR_IN_PENDING_TRANSACTION": "待处理交易中", - "TR_I_AM_IN_SAFE_PRIVATE_OR": "您在一个安全的私人空间里、或在一个远离摄像头的公共空间里。", - "TR_I_HAVE_DOUBTS": "我有疑问", + "TR_IN_PENDING_TRANSACTION": "在待处理的交易中", + "TR_I_AM_IN_SAFE_PRIVATE_OR": "你在一个安全的私人空间或远离摄像头的公共空间", + "TR_I_HAVE_DOUBTS": "我有疑虑", "TR_I_HAVE_ENOUGH_TIME_TO_DO": "你有足够的时间来备份你的钱包助记词", - "TR_I_HAVE_NOT_USED_IT": "不、我未使用过", + "TR_I_HAVE_NOT_USED_IT": "不,我未使用过", "TR_I_UNDERSTAND_PASSPHRASE": "我知道密码短语是无法找回的。", "TR_I_UNDERSTAND_SEED_IS_IMPORTANT": "钱包备份安全是自己的责任", - "TR_JOINT_TRANSACTION": "混币交易", - "TR_JOINT_TRANSACTION_TARGET": "{inMy}/{in} 的输入、{outMy}/{out} 的输出", + "TR_JOINT_TRANSACTION": "CoinJoin 混币器交易", + "TR_JOINT_TRANSACTION_TARGET": "{inMy} / {in} 的输入,{outMy} / {out} 的输出", "TR_KEEP_RUNNING_IN_BACKGROUND": "保持在后台运行", "TR_LABELING": "标签功能", "TR_LABELING_ADD_ACCOUNT": "重命名", @@ -1169,14 +1162,14 @@ "TR_LABELING_ADD_LABEL": "添加标签", "TR_LABELING_ADD_OUTPUT": "添加标签", "TR_LABELING_ADD_WALLET": "重命名", - "TR_LABELING_EDITED_LABEL": "已改名", + "TR_LABELING_EDITED_LABEL": "已重命名", "TR_LABELING_EDIT_ACCOUNT": "重命名", "TR_LABELING_EDIT_ADDRESS": "编辑标签", "TR_LABELING_EDIT_LABEL": "编辑标签", "TR_LABELING_EDIT_OUTPUT": "编辑标签", "TR_LABELING_EDIT_WALLET": "编辑标签", "TR_LABELING_ENABLED": "标签功能", - "TR_LABELING_FEATURE_ALLOWS": "重新命名钱包、账户和地址。标签通过与Dropbox或谷歌云储存同步。", + "TR_LABELING_FEATURE_ALLOWS": "重新命名钱包、账户和地址。标签通过与 Dropbox 或谷歌云储存同步。", "TR_LABELING_NOT_SYNCED": "标签未同步", "TR_LABELING_REMOVE_ACCOUNT": "移除标签", "TR_LABELING_REMOVE_ADDRESS": "移除标签", @@ -1184,48 +1177,48 @@ "TR_LABELING_REMOVE_OUTPUT": "移除标签", "TR_LABELING_REMOVE_WALLET": "移除标签", "TR_LABEL_ERROR_CHARACTERS": "不支持的字符", - "TR_LABEL_ERROR_LENGTH": "不得超过 {length} 个字符", - "TR_LABEL_REQUIREMENTS": "名称不得超过 {length} 个字符、且只能包含英文字母。", + "TR_LABEL_ERROR_LENGTH": "不能超过 {length} 个字符", + "TR_LABEL_REQUIREMENTS": "名称不能超过 {length} 个字符,且只能包含英文字母。", "TR_LANGUAGE": "语言", - "TR_LANGUAGE_CREDITS": "查看制作人员名单", - "TR_LANGUAGE_DESCRIPTION": "衷心感谢Trezor社区在翻译过程中提供的帮助。如有需要、您可以随时参考官方语言。", + "TR_LANGUAGE_CREDITS": "查看社区翻译志愿者名单", + "TR_LANGUAGE_DESCRIPTION": "衷心感谢 Trezor 社区在翻译过程中提供的帮助。如有需要,您可以随时参考官方语言。", "TR_LAST_UPDATE": "价格已更新 {value}", "TR_LEARN": "学习", "TR_LEARN_ADVANCED_RECOVERY": "学习如何输入字母", "TR_LEARN_MORE": "了解更多", "TR_LEFT": "左", - "TR_LEGACY_ACCOUNTS": "遗留账户", + "TR_LEGACY_ACCOUNTS": "传统账户", "TR_LEGACY_SEGWIT_ACCOUNTS": "嵌套隔离见证帐户", "TR_LETS_CHECK_YOUR_DEVICE": "让我们检查您的设备", "TR_LOADING": "正在加载...", "TR_LOADING_ACCOUNTS": "正在加载账户...", "TR_LOADING_ACCOUNTS_DESCRIPTION": "您可以在账户加载后更改这些设置。", - "TR_LOADING_FACT_0": "您的混币账户未被自动检测到。如果您没有启用该钱包的只读模式、则需要在重新连接Trezor设备后手动重新添加账户。", - "TR_LOADING_FACT_1": "Trezor Model One 是全球首款硬件钱包、于2014年7月29日发布。后续的 Trezor Model T 于2018年推出。", - "TR_LOADING_FACT_11": "“隐私不是秘密\"。—— 埃里克·休斯", - "TR_LOADING_FACT_12": "就像现金一样、你不会把你的全部净资产都放在口袋里、你会随身携带钱、以备不时之需 —— 中本聪", - "TR_LOADING_FACT_13": "“隐私不是秘密\"。—— 埃里克·休斯", + "TR_LOADING_FACT_0": "您的 CoinJoin 混币器账户未被自动检测到。如果您没有启用该钱包的 “仅供查看” 模式,则需要在重新连接 Trezor 设备后手动重新添加账户。", + "TR_LOADING_FACT_1": "Trezor Model One 是全球首款硬件钱包,于2014 年 7 月 29 日发布。后续的 Trezor Model T 于 2018 年推出。", + "TR_LOADING_FACT_11": "“隐私不是秘密”。—— 埃里克·休斯", + "TR_LOADING_FACT_12": "就像现金一样,你不会把你的全部净资产都放在口袋里,你会随身携带钱,以备不时之需 —— 中本聪", + "TR_LOADING_FACT_13": "“隐私不是秘密”。—— 埃里克·休斯", "TR_LOADING_FACT_14": "隐私是有选择地向世界展示自己的能力 —— 埃里克·休斯", "TR_LOADING_FACT_15": "隐私是有选择地向世界展示自己的能力 —— 埃里克·休斯", - "TR_LOADING_FACT_16": "混币账户使用主根地址", - "TR_LOADING_FACT_17": "2021年9月、萨尔瓦多成为第一个接受比特币为法定货币的国家。", - "TR_LOADING_FACT_18": "Laszlo Hanyecz首次进行了商业比特币交易。他用一万枚比特币买了2个披萨。", - "TR_LOADING_FACT_19": "最后一个比特币应该在2140年左右被挖出来。", - "TR_LOADING_FACT_2": "您的混币账户未被自动检测到。如果您没有启用该钱包的只读模式、则需要在重新连接Trezor设备后手动重新添加账户。", - "TR_LOADING_FACT_20": "人称“吓人辣妹”的梅尔-B (Mel B) 是第一位接受比特币支付单曲的主流音乐人。", + "TR_LOADING_FACT_16": "CoinJoin 混币器账户使用主根地址", + "TR_LOADING_FACT_17": "2021 年 9 月,萨尔瓦多成为第一个接受比特币为法定货币的国家。", + "TR_LOADING_FACT_18": "Laszlo Hanyecz 首次进行了商业比特币交易。他用一万枚比特币买了 2 个披萨。", + "TR_LOADING_FACT_19": "最后一个比特币应该在 2140 年左右被挖出来。", + "TR_LOADING_FACT_2": "您的 CoinJoin 混币器账户未被自动检测到。如果您没有启用该钱包的 “仅供查看” 模式,则需要在重新连接 Trezor 设备后手动重新添加账户。", + "TR_LOADING_FACT_20": "人称 “吓人辣妹” 的梅尔-B (Mel B) 是第一位接受比特币支付单曲的主流音乐人。", "TR_LOADING_FACT_21": "比特币交易以区块为单位。这些区块在区块链上按时间顺序排列。", - "TR_LOADING_FACT_22": "所谓的“减半”、是指开采比特币的奖励每四年减半一次。一开始、挖一个区块可以获得50比特币、而到2020年、挖一个区块可以获得6.25比特币", - "TR_LOADING_FACT_23": "比特币的最大上限为2100万个。", - "TR_LOADING_FACT_24": "“隐私不仅仅是我有权享有的东西、它是绝对的先决条件\"。—— 马龙·白兰度", - "TR_LOADING_FACT_25": "2011年2月、比特币首次达到与美元平价的水平", - "TR_LOADING_FACT_26": "Slush Pool是历史最悠久的比特币矿池、也是第一个公开的比特币矿池。如今、它由Braiins Pool运营。", - "TR_LOADING_FACT_3": "为了保护您的隐私、Suite不会记住您的混币账户、除非您明确选择记住钱包。", - "TR_LOADING_FACT_4": "约有20%的比特币因用户丢失私钥而丢失在钱包中。这相当于大约367万枚比特币。", - "TR_LOADING_FACT_5": "2013年10月29日、第一台比特币自动取款机在加拿大温哥华的一家咖啡店里诞生。到2022年、全球共有超过3.8万台比特币自动取款机。", - "TR_LOADING_FACT_6": "比特币是在2008年金融危机之后由一个化名为中本聪的匿名者 (甚至是一群人) 创建的。", + "TR_LOADING_FACT_22": "所谓的 “减半”、是指开采比特币的奖励每四年减半一次。一开始,挖一个区块可以获得 50 枚比特币,而到 2020 年,挖一个区块可以获得 6.25 枚比特币", + "TR_LOADING_FACT_23": "比特币的最大上限为 2100 万枚。", + "TR_LOADING_FACT_24": "“隐私不仅仅是我有权享有的东西、它是绝对的先决条件”。—— 马龙·白兰度", + "TR_LOADING_FACT_25": "2011 年 2 月,比特币首次达到与美元平价的水平", + "TR_LOADING_FACT_26": "Slush Pool 是历史最悠久的比特币矿池,也是第一个公开的比特币矿池。如今,它由 Braiins Pool 运营。", + "TR_LOADING_FACT_3": "除非您明确选择记住钱包,否则 Suite 不会记住您的 CoinJoin 混币器帐户以保护您的隐私。", + "TR_LOADING_FACT_4": "约有 20% 的比特币因用户丢失私钥而丢失在钱包中。这相当于大约 367 万枚比特币。", + "TR_LOADING_FACT_5": "2013 年 10 月 29 日,第一台比特币自动取款机在加拿大温哥华的一家咖啡店里诞生。到2022年,全球共有超过 3.8 万台比特币自动取款机。", + "TR_LOADING_FACT_6": "比特币是在 2008 年金融危机之后由一个化名为中本聪的匿名者 (甚至是一群人) 创建的。", "TR_LOADING_FACT_7": "一个比特币相当于1亿个聪。", - "TR_LOADING_FACT_8": "2022年、只有不到2%的人口拥有比特币。", - "TR_LOADING_FACT_9": "“隐私不仅仅是我有权享有的东西、它是绝对的先决条件\"。—— 马龙·白兰度", + "TR_LOADING_FACT_8": "在2022 年,只有不到 2% 的人口拥有比特币。", + "TR_LOADING_FACT_9": "“隐私不仅仅是我有权享有的东西,它是绝对的先决条件”。—— 马龙·白兰度", "TR_LOADING_FACT_TITLE": "您知道吗?", "TR_LOADING_FUNDS": "正在加载资金...", "TR_LOADING_TRANSACTION_DETAILS": "正在加载交易详情...", @@ -1233,7 +1226,7 @@ "TR_LOCAL_FILE_SYSTEM": "本地文件系统", "TR_LOG": "应用程序日志", "TR_LOGIN_PROCEED": "继续操作", - "TR_LOG_DESCRIPTION": "日志包含Trezor Suite的所有必要技术信息。在与Trezor客服连接时可能需要使用。", + "TR_LOG_DESCRIPTION": "日志包含 Trezor Suite 的所有必要技术信息。在与 Trezor 客服连接时可能需要使用。", "TR_LOOKING_FOR_COINJOIN_ROUND": "正在寻找下一回合。", "TR_LOW_ANONYMITY_WARNING": "匿名性极低。 我们建议至少使用五分之一、因为低于此值的任何内容都不具有匿名性。", "TR_LTC_ADDRESS_INFO": "莱特币 (LTC) 改变了地址的格式。在我们的博客上找到更多关于如何转换地址的信息。 {TR_LEARN_MORE}", @@ -1243,15 +1236,15 @@ "TR_MAX_MINING_FEE": "最大值挖矿费", "TR_MAYBE_LATER": "以后再说", "TR_MESSAGE": "留言", - "TR_MINED_TIME": "出矿时间", + "TR_MINED_TIME": "区块挖出时间", "TR_MINING_FEE_NOTE": "挖矿费可能更低。选定的费用是最大值。", "TR_MISSING_TO_FEE": "没有足够的资金来支付交易费", "TR_MISSING_TO_INPUT": "您的输入缺少 {amount} (不包括手续费)", "TR_MOBILE_APP_PROMO_TEXT": "具备更多安全功能", "TR_MOBILE_APP_PROMO_TEXT_FOOTER": "使用 Trezor Suite Lite 在手机上同步并跟踪", "TR_MORE_ROUNDS_NEEDED": "还需要几个回合", - "TR_MORE_ROUNDS_NEEDED_DESCRIPTION": "我们无法在预留回合内达到您所需的匿名级别。请再运行一回合的混币。您无需支付两次的服务费。", - "TR_MULTI_SHARE_BACKUP": "多共享助记词备份", + "TR_MORE_ROUNDS_NEEDED_DESCRIPTION": "我们无法在预留回合内达到您所需的匿名程度。请再运行一回合的混币。您无需支付两次的服务费。", + "TR_MULTI_SHARE_BACKUP": "秘密共享备份", "TR_MULTI_SHARE_BACKUP_BACKUPS": "我的助记词备份", "TR_MULTI_SHARE_BACKUP_CALLOUT_1": "它是如何运作的?", "TR_MULTI_SHARE_BACKUP_CALLOUT_2": "那我当前的钱包备份怎么办?", @@ -1260,26 +1253,26 @@ "TR_MULTI_SHARE_BACKUP_CHECKBOX_2": "我当前的助记词备份仍然能够恢复我的钱包", "TR_MULTI_SHARE_BACKUP_DESCRIPTION": "生成多个20个单词的助记词份额来恢复您的钱包。要重新获得钱包使用权、您需要您所设置的最低助记词份额数。", "TR_MULTI_SHARE_BACKUP_EXPLANATION_1": "生成多个20个单词的助记词份额来恢复您的钱包。要重新获得钱包使用权、您需要您所设置的最低助记词份额数。", - "TR_MULTI_SHARE_BACKUP_EXPLANATION_2": "您当前的助记词备份仍允许您恢复您的钱包。将其存储在一个安全可靠的地方、与您其他的多共享备份份额分开。", + "TR_MULTI_SHARE_BACKUP_EXPLANATION_2": "您当前的助记词备份仍允许您恢复您的钱包。将其存储在一个安全可靠的地方,与您其他的秘密共享份额分开放。", "TR_MULTI_SHARE_BACKUP_GREAT": "太好了!", - "TR_MULTI_SHARE_BACKUP_IN_PROGRESS": "多共享备份生成正在进行中", - "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION": "在继续之前、有必要先完成多共享助记词备份份额的生成。请按照Trezor屏幕上的说明操作。", + "TR_MULTI_SHARE_BACKUP_IN_PROGRESS": "正在生成秘密共享备份", + "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION": "在继续之前,有必要先完成秘密共享份额的生成。请按照 Trezor 屏幕上的说明操作。", "TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING": "欢迎回来!让我们继续你刚才的话题。", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP": "丢失了您的钱包备份?", - "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT": "可能无法恢复钱包。请联系Trezor客服。", - "TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR": "丢了您的Trezor设备?", + "TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT": "可能无法恢复钱包。请联系 Trezor 客服。", + "TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR": "丢了您的 Trezor 设备?", "TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR_INFO_TEXT": "使用您的助记词备份来恢复对您的钱包的访问权。", "TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT": "我先前的备份", "TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE1": "依然能找回您的钱包", "TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE2": "请您安全存放、与新钱包的备份分开", - "TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT": "我新的多共享备份", + "TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT": "我新的秘密共享备份", "TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE1": "用您设定的最低备份份额来找回您的钱包", - "TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE2": "与您信任的人共享或存储在安全和不同的地理位置", + "TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE2": "与受信任的个人分享或存储在安全、分开的位置", "TR_MULTI_SHARE_BACKUP_SUCCESS_WHY_IS_BACKUP_IMPORTANT": "为什么您的钱包的助记词备份是至关重要的?", "TR_MULTI_SHARE_TIPS_ON_STORING_BACKUP": "存储钱包助记词备份的小窍门", "TR_MY_ACCOUNTS": "我的账户", "TR_MY_ASSETS": "资产", - "TR_MY_COINS": "我的硬币", + "TR_MY_COINS": "隐私", "TR_MY_INPUTS_AND_OUTPUTS": "我的输入和输出", "TR_MY_PORTFOLIO": "资产组合", "TR_NAV_ANONYMIZE": "匿名化加密货币", @@ -1296,59 +1289,61 @@ "TR_NAV_TOKENS": "代币", "TR_NAV_TRADE": "兑换", "TR_NAV_TRANSACTIONS": "概览", - "TR_NEEDS_ATTENTION_BOOTLOADER": "Trezor设备处于加载引导模式。", + "TR_NEEDS_ATTENTION_BOOTLOADER": "Trezor 设备处于加载引导模式。", "TR_NEEDS_ATTENTION_FIRMWARE_REQUIRED": "需要固件更新。", - "TR_NEEDS_ATTENTION_INITIALIZE": "Trezor设备尚未设置。", - "TR_NEEDS_ATTENTION_SEEDLESS": "Trezor设备未备份过。", - "TR_NEEDS_ATTENTION_UNACQUIRED": "Trezor设备已在另一个窗口使用。", - "TR_NEEDS_ATTENTION_UNAVAILABLE": "Trezor设备无法使用。", - "TR_NEEDS_ATTENTION_UNREADABLE": "Trezor设备无法读取。", - "TR_NEEDS_ATTENTION_USED_IN_OTHER_WINDOW": "Trezor设备已使用。", - "TR_NEEDS_ATTENTION_WAS_USED_IN_OTHER_WINDOW": "Trezor设备已使用。", - "TR_NETWORK_BITCOIN": "比特币 (BTC)", - "TR_NETWORK_BITCOIN_CASH": "比特币现金 (BCH)", - "TR_NETWORK_BITCOIN_GOLD": "比特币黄金 (BTG)", + "TR_NEEDS_ATTENTION_INITIALIZE": "Trezor 设备尚未设置。", + "TR_NEEDS_ATTENTION_SEEDLESS": "Trezor 设备未备份过。", + "TR_NEEDS_ATTENTION_UNACQUIRED": "Trezor 设备已在另一个窗口使用。", + "TR_NEEDS_ATTENTION_UNAVAILABLE": "Trezor 设备无法使用。", + "TR_NEEDS_ATTENTION_UNREADABLE": "Trezor 设备无法读取。", + "TR_NEEDS_ATTENTION_USED_IN_OTHER_WINDOW": "Trezor 设备已使用。", + "TR_NEEDS_ATTENTION_WAS_USED_IN_OTHER_WINDOW": "Trezor 设备已使用。", + "TR_NETWORK_BITCOIN": "比特币", + "TR_NETWORK_BITCOIN_CASH": "比特现金", + "TR_NETWORK_BITCOIN_GOLD": "比特黄金", "TR_NETWORK_BITCOIN_REGTEST": "比特币回归测试代币", "TR_NETWORK_BITCOIN_TESTNET": "比特币测试代币", - "TR_NETWORK_BNB": "币安智能链 (BNB)", - "TR_NETWORK_CARDANO": "卡达尔诺 (ADA)", + "TR_NETWORK_BNB": "币安智能链", + "TR_NETWORK_CARDANO": "卡达尔诺", "TR_NETWORK_CARDANO_TESTNET": "卡达尔诺测试代币", - "TR_NETWORK_COINJOIN_BITCOIN": "混币", - "TR_NETWORK_COINJOIN_BITCOIN_REGTEST": "混币器回归测试代币\n", - "TR_NETWORK_COINJOIN_BITCOIN_TESTNET": "混币器测试代币", - "TR_NETWORK_DASH": "达世币 (DASH)", - "TR_NETWORK_DIGIBYTE": "极特币 (DGB)", - "TR_NETWORK_DOGECOIN": "狗狗币 (DOGE)", - "TR_NETWORK_ETHEREUM": "以太坊 (ETH)", - "TR_NETWORK_ETHEREUM_CLASSIC": "以太坊经典 (ETC)", - "TR_NETWORK_ETHEREUM_HOLESKY": "以太坊Holesky测试代币", - "TR_NETWORK_ETHEREUM_SEPOLIA": "以太坊Sepolia测试代币", - "TR_NETWORK_LITECOIN": "莱特币 (LTC)", - "TR_NETWORK_NAMECOIN": "域名币 (NMC)", - "TR_NETWORK_NEM": "新经币 (XEM)", - "TR_NETWORK_POLYGON": "Polygon权益证明链 (POL)", - "TR_NETWORK_SOLANA_DEVNET": "Solana开发网络", - "TR_NETWORK_SOLANA_MAINNET": "Solana (SOL)", - "TR_NETWORK_STELLAR": "恒星币 (XLM)", - "TR_NETWORK_TEZOS": "Tezos(XTZ)", + "TR_NETWORK_COINJOIN_BITCOIN": "CoinJoin 混币器", + "TR_NETWORK_COINJOIN_BITCOIN_REGTEST": "CoinJoin 混币器回归测试代币\n", + "TR_NETWORK_COINJOIN_BITCOIN_TESTNET": "CoinJoin 混币器测试网代币", + "TR_NETWORK_DASH": "达世币", + "TR_NETWORK_DIGIBYTE": "极特币", + "TR_NETWORK_DOGECOIN": "狗狗币", + "TR_NETWORK_ETHEREUM": "以太坊", + "TR_NETWORK_ETHEREUM_CLASSIC": "以太经典", + "TR_NETWORK_ETHEREUM_HOLESKY": "以太坊 Holesky 测试代币", + "TR_NETWORK_ETHEREUM_SEPOLIA": "以太坊 Sepolia 测试代币", + "TR_NETWORK_LITECOIN": "莱特币", + "TR_NETWORK_NAMECOIN": "域名币", + "TR_NETWORK_NEM": "新经币", + "TR_NETWORK_OP": "Optimism", + "TR_NETWORK_POLYGON": "Polygon 权益证明链", + "TR_NETWORK_SOLANA_DEVNET": "Solana 开发网络", + "TR_NETWORK_SOLANA_MAINNET": "Solana", + "TR_NETWORK_STELLAR": "恒星币", + "TR_NETWORK_TEZOS": "Tezos", "TR_NETWORK_UNKNOWN": "未知", - "TR_NETWORK_VERTCOIN": "绿币 (VTC)", - "TR_NETWORK_XRP": "瑞波币 (XRP)", + "TR_NETWORK_VERTCOIN": "绿币", + "TR_NETWORK_XRP": "瑞波币", "TR_NETWORK_XRP_TESTNET": "瑞波币测试代币", - "TR_NETWORK_ZCASH": "大零币 (ZEC)", - "TR_NEW_FEE": "新", - "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "新的Trezor网桥可供使用。", - "TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT": "新的Trezor固件已发布!请立即更新您的设备。", + "TR_NETWORK_ZCASH": "大零币", + "TR_NEW_FEE": "新的费率", + "TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE": "新的 Trezor 网桥可供使用。", "TR_NEXT_UP": "接下来", "TR_NONCE": "随机数", + "TR_NON_ASCII_CHAR": "{label} (带有非不推荐的 \"{char}\")", + "TR_NON_ASCII_CHARS": "{label} (带有不推荐的字符)", "TR_NORMAL_ACCOUNTS": "默认账户", "TR_NORTH": "北", "TR_NOTHING_TO_ANONYMIZE": "无需匿名化", - "TR_NOTIFICATIONS": "活动", + "TR_NOTIFICATIONS": "动态", "TR_NOT_ENOUGH_ANONYMIZED_FUNDS": "没有足够具有隐私性的资产", - "TR_NOT_ENOUGH_ANONYMIZED_FUNDS_RBF_WARNING": "具有隐私性的比特币不足。您可以提高您的比特币隐私性或者降低隐私级别。", + "TR_NOT_ENOUGH_ANONYMIZED_FUNDS_RBF_WARNING": "具有隐私性的比特币不足。您可以提高您的比特币匿名性或者降低匿名程度。", "TR_NOT_ENOUGH_ANONYMIZED_FUNDS_TOOLTIP": "您可以: ", - "TR_NOT_ENOUGH_ANONYMIZED_FUNDS_WARNING": "具有隐私性的比特币不足。您可以提高您的比特币隐私性、在“货币控制”中手动选择“消费的交易输出”(UTXOs)、或者降低隐私级别。", + "TR_NOT_ENOUGH_ANONYMIZED_FUNDS_WARNING": "具有隐私性的比特币不足。您可以提高您的比特币匿名性、在 “币控制” 中手动选择 “未消费交易输出”、或者降低匿名程度。", "TR_NOT_ENOUGH_SELECTED": "没有选择足够的资金", "TR_NOT_PRIVATE": "不具有隐私性的", "TR_NOT_PRIVATE_DESCRIPTION": "匿名性低于 {targetAnonymity}", @@ -1357,127 +1352,101 @@ "TR_NO_FIRMWARE_EXPLAINED": "您需要安装固件后才能使用您的设备。", "TR_NO_PASSPHRASE_WALLET": "标准钱包", "TR_NO_SEARCH_RESULTS": "没有符合您搜索标准的结果", - "TR_NO_SPENDABLE_UTXOS": "您的帐户里没有可使用的未消费的交易输出。", + "TR_NO_SPENDABLE_UTXOS": "您的帐户里没有可使用的”未消费交易输出”。", "TR_NO_TRANSPORT": "浏览器无法与设备进行通信", "TR_NO_TRANSPORT_DESKTOP": "应用程序无法与设备进行通信", - "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, one {account} other {accounts}} • {fiatValue}", + "TR_NUM_ACCOUNTS_FIAT_VALUE": "{accountsCount} {accountsCount, plural, other {个账户}} • {fiatValue}", "TR_N_MIN": "{n} 最小值", - "TR_N_TRANSACTIONS": "{value} {value, plural, one {transaction} other {transactions}}", + "TR_N_TRANSACTIONS": "{value} {value, plural, other {笔交易}}", "TR_OFF": "关闭", - "TR_OFFER_ERROR_MAXIMUM_CRYPTO": "选择的 {amount} 金额高于 {max} 的最大值。", - "TR_OFFER_ERROR_MAXIMUM_FIAT": "选择的 {amount} 金额高于 {max} 的最大值。", - "TR_OFFER_ERROR_MINIMUM_CRYPTO": "所选的 {amount} 金额低于所接受的最小值 {min}。", - "TR_OFFER_ERROR_MINIMUM_FIAT": "所选的 {amount} 金额低于所接受的最小值 {min}。", "TR_OFFICIAL_LANGUAGES": "官方", "TR_OK": "确定", "TR_ON": "开机", "TR_ONBOARDING_ADVANCED": "高级", "TR_ONBOARDING_ALLOW_ANALYTICS": "允许匿名数据收集", "TR_ONBOARDING_BACKUP_CATEGORY_20_WORD_BACKUPS": "20个单词的助记词备份类型", - "TR_ONBOARDING_BACKUP_LEGACY_WARNING": "这不能直接升级为多共享备份。为实现无缝升级、请使用默认的单份备份。", + "TR_ONBOARDING_BACKUP_LEGACY_WARNING": "这不能直接升级为秘密共享备份。为实现无缝升级,请使用默认的单份备份。", "TR_ONBOARDING_BACKUP_OLDER_BACKUP_TYPES_SHORT": "旧版备份类型", - "TR_ONBOARDING_BACKUP_SHAMIR_WARNING": "这种备份类型允许未来升级、但它包含20个单词、所以无法完全记录在随设备包装的12个单词的助记词纸卡上。请您把它们按编号顺序写在耐用的纸张上、不要用数码方式存储。", - "TR_ONBOARDING_BACKUP_SUBHEADING": "您的钱包备份是由Trezor设备生成的单词列表组成。请准确记下并妥善保存。这是找回您的钱包的唯一方法。", + "TR_ONBOARDING_BACKUP_SHAMIR_WARNING": "这种备份类型允许未来升级、但它包含 20 个单词、所以无法完全记录在随设备包装的 12 个单词的助记词纸卡上。请您把它们按编号顺序写在耐用的纸张上、不要用数码方式存储。", + "TR_ONBOARDING_BACKUP_SUBHEADING": "您的钱包备份是由 Trezor 设备生成的单词列表组成。请准确记下并妥善保存。这是找回您的钱包的唯一方法。", "TR_ONBOARDING_BACKUP_TYPE": "备份类型", - "TR_ONBOARDING_BACKUP_TYPE_12_DEFAULT_TOOLTIP": "对于Trezor设备、建议使用默认选项。", + "TR_ONBOARDING_BACKUP_TYPE_12_DEFAULT_TOOLTIP": "对于 Trezor 设备、建议使用默认选项。", "TR_ONBOARDING_BACKUP_TYPE_12_WORDS_DEFAULT_NOTE": "生成一组12个单词的助记词、适合写在设备的钱包备份卡上。", "TR_ONBOARDING_BACKUP_TYPE_ADVANCED": "高级", "TR_ONBOARDING_BACKUP_TYPE_DEFAULT": "默认", - "TR_ONBOARDING_BACKUP_TYPE_UPGRADABLE_TO_MULTI": "可升级至多共享备份", - "TR_ONBOARDING_CANNOT_SELECT_SEED_TYPE": "Trezor设备将为您创建一个新钱包。", + "TR_ONBOARDING_BACKUP_TYPE_UPGRADABLE_TO_MULTI": "可升级至秘密共享备份", + "TR_ONBOARDING_CANNOT_SELECT_SEED_TYPE": "Trezor 设备将为您创建一个新钱包。", "TR_ONBOARDING_CLICK_TO_CONFIRM": "点击确认您理解下面的说明", "TR_ONBOARDING_COINS_STEP": "激活币种", - "TR_ONBOARDING_COINS_STEP_DESCRIPTION": "选择要在Trezor Suite中显示的加密货币类型。您可以随时更改此设置。", - "TR_ONBOARDING_COINS_STEP_DESCRIPTION_BITCOIN_ONLY": "选择是否要在Trezor Suite中显示比特币。您可以随时更改此设置。", + "TR_ONBOARDING_COINS_STEP_DESCRIPTION": "选择要在 Trezor Suite 中显示的加密货币类型。您可以随时更改此设置。", + "TR_ONBOARDING_COINS_STEP_DESCRIPTION_BITCOIN_ONLY": "选择是否要在 Trezor Suite 中显示比特币。您可以随时更改此设置。", "TR_ONBOARDING_CREATE_NEW_WALLET": "创建一个新的钱包", "TR_ONBOARDING_CURRENT_VERSION": "当前版本", "TR_ONBOARDING_DATA_COLLECTION_DESCRIPTION": "所有收集到的数据都是匿名的、用于提高产品性能和开发。更多信息请参见技术文档条款与条件。", "TR_ONBOARDING_DATA_COLLECTION_HEADING": "匿名数据收集", "TR_ONBOARDING_DEVICE_CHECK": "设备安全性检查", "TR_ONBOARDING_DEVICE_CHECK_1": "我的 全息图 完好无损、没有被篡改的痕迹。", - "TR_ONBOARDING_DEVICE_CHECK_2": "我的设备是从Trezor官方商店或值得信赖的经销商
处购买的。", + "TR_ONBOARDING_DEVICE_CHECK_2": "我的设备是从Trezor 官方商店或值得信赖的经销商
处购买的。", "TR_ONBOARDING_DEVICE_CHECK_3": "设备包装完好无损、没有被篡改的痕迹。", - "TR_ONBOARDING_DEVICE_CHECK_4": "已在连接的Trezor设备上安装完固件。只有在使用过该Trezor的情况下才能继续设置。", + "TR_ONBOARDING_DEVICE_CHECK_4": "已在连接的 Trezor 设备上安装完固件。只有在使用过该 Trezor 的情况下才能继续设置。", "TR_ONBOARDING_DOWNLOAD_DESKTOP_APP": "下载桌面应用程序", "TR_ONBOARDING_FINAL_CHANGE_HOMESCREEN": "更改主屏幕", - "TR_ONBOARDING_NEW_FW_DESCRIPTION": "有新的固件版本可用。立即更新设备或在完成设备设置后在Trezor Suite中进行更新。", + "TR_ONBOARDING_NEW_FW_DESCRIPTION": "有新的固件版本可用。立即更新设备或在完成设备设置后在 Trezor Suite 中进行更新。", "TR_ONBOARDING_NEW_VERSION": "最新版本", "TR_ONBOARDING_SEED_TYPE_12_WORDS": "12个单词的助记词备份", "TR_ONBOARDING_SEED_TYPE_24_WORDS": "24个单词的助记词备份", - "TR_ONBOARDING_SEED_TYPE_ADVANCED": "多共享备份", + "TR_ONBOARDING_SEED_TYPE_ADVANCED": "秘密共享备份", "TR_ONBOARDING_SEED_TYPE_ADVANCED_DESCRIPTION": "生成多个20个单词的助记词备份份额来恢复您的钱包。设置最低恢复份额数量、然后将份额分发给受信任的人或安全地隐藏起来。需要时、收集所需数量的备份份额、重新获得钱包访问权限。", "TR_ONBOARDING_SEED_TYPE_SINGLE_SEED": "单份备份", - "TR_ONBOARDING_SEED_TYPE_SINGLE_SEED_DESCRIPTION": "生成一组包含20个单词的助记词备份、用于恢复钱包。该备份类型可随时升级为多共享备份。", + "TR_ONBOARDING_SEED_TYPE_SINGLE_SEED_DESCRIPTION": "生成一组包含 20 个英文单词的助记词,用于恢复钱包。该备份类型可随时升级为秘密共享备份。", "TR_ONBOARDING_SELECTED_DEFAULT_BACKUP_TYPE": "我们已根据您的设备为您的钱包选择了默认备份类型。", "TR_ONBOARDING_SELECTED_OPTIMAL_BACKUP_TYPE": "我们已为您的钱包选择了最佳备份类型。", "TR_ONBOARDING_SELECT_SEED_TYPE_CONFIRM": "创建钱包", "TR_ONBOARDING_STEP_WALLET": "钱包", - "TR_ONBOARDING_TREZOR_WILL_DISPLAY_BACKUP": "Trezor设备将显示您的钱包备份。请准确记下并妥善保管。这是找回钱包的唯一方法。", + "TR_ONBOARDING_TREZOR_WILL_DISPLAY_BACKUP": "Trezor 设备将显示您的钱包备份。请准确记下并妥善保管。这是找回钱包的唯一方法。", "TR_ONBOARDING_TROUBLESHOOTING_FAILED": "还是没有用?", - "TR_ONBOARDING_WILL_CREATE_BACKUP_TYPE": "Trezor设备将根据

所选的钱包备份类型创建钱包。", + "TR_ONBOARDING_WILL_CREATE_BACKUP_TYPE": "Trezor 设备将根据

所选的钱包备份类型创建钱包。", "TR_ONCE_YOU_BEGIN_THIS_PROCESS": "这个过程只需要几分钟、但一旦开始、您就不能暂停或重新开始。", - "TR_ONION_BACKEND_TOR_NEEDED": "您为您的后端使用了洋葱地址。要使用洋葱地址、你需要有访问Tor网络的权限。", + "TR_ONION_BACKEND_TOR_NEEDED": "您使用了一个洋葱地址作为您的后端。要使用洋葱地址,您需要访问 Tor 网络。", "TR_ONION_LINKS_DESCRIPTION": "启用该设置后、所有 trezor.io 的链接将以 .onion 链接的形式打开。", "TR_ONION_LINKS_TITLE": "将 trezor.io 链接用 .onion 链接打开", - "TR_OPEN_TREZOR_SUITE_DESKTOP": "打开Trezor Suite桌面应用程序", + "TR_OPEN_TREZOR_SUITE_DESKTOP": "打开 Trezor Suite 桌面应用程序", "TR_ORDER_NOW": "立即订购", - "TR_ORIGINAL_ADDRESS": "持续的", + "TR_ORIGINAL_ADDRESS": "连续式", "TR_OTHER_COINS_USE_DEFAULT_BACKEND": "其他币种使用其默认的后端", "TR_OTHER_INPUTS_AND_OUTPUTS": "其他输入和输出", - "TR_OUTGOING": "传出", + "TR_OUTGOING": "发送出", "TR_OUTPUTS": "输出", - "TR_P2P_AMOUNT_RANGE_TOOLTIP": "该用户愿意出售 {symbol} 的最低和最高的价格。", - "TR_P2P_GET_STARTED_INTRO": "您需要在 {providerName} 上启动交易——确保仔细遵循下面的步骤。", - "TR_P2P_GET_STARTED_ITEM_1": "选择 “前往 {providerName}”、转到我们合作伙伴的网站。", - "TR_P2P_GET_STARTED_ITEM_3": "一旦 {providerName} 请求您的发布地址、返回此处并继续。", - "TR_P2P_GET_STARTED_ITEM_4": "差不多了!显示并复制您的地址、粘贴到 {providerName} 上的 “发布地址 ”字段、然后完成交易。", - "TR_P2P_GO_TO_PROVIDER": "前往 {providerName}", - "TR_P2P_INFO": "有了 {peerToPeer} (P2P) 技术、买卖双方都无需进行实名认证。通过安全的多重签名托管 {multisigEscrow}、所有各方都能免受欺诈。", - "TR_P2P_MODAL_CONFIRM": "我准备购买了", - "TR_P2P_MODAL_FOR_YOUR_SAFETY": "点对点购买 {provider} 的 {cryptocurrency} ", - "TR_P2P_MODAL_LEGAL_HEADER": "法律声明", - "TR_P2P_MODAL_SECURITY_HEADER": "Trezor一直把安全放在第一位", - "TR_P2P_MODAL_TERMS_1": "您在此使用点对点 (P2P) 技术向您选择的另一人购买加密货币、无需身份验证。如果您是因其他原因被引导至本网站、请在继续操作前联系技术支持。", - "TR_P2P_MODAL_TERMS_2": "您需要明白加密货币交易是不可逆的、是无法退还的。因此、欺诈性或意外损失是无法恢复的。", - "TR_P2P_MODAL_TERMS_4": "您理解Invity不提供此服务。{provider} 的条款管辖该服务。", - "TR_P2P_MODAL_TERMS_5": "您不得将此功能用于赌博、欺诈或任何其他违反Invity或供应商服务条款或任何适用法规的行为。", - "TR_P2P_MODAL_TERMS_6": "您明白加密货币是一种新兴的金融工具、不同司法管辖区的法规可能有所不同。这可能会使您面临更高的欺诈、盗窃或市场不稳定风险。", - "TR_P2P_MODAL_VERIFIED_PARTNERS_HEADER": "经过Invity验证的合作伙伴", - "TR_P2P_PAYMENT_WINDOW_TOOLTIP": "交易需要在此时间限制内完成、从创建一个 {providerName} 站点的合同。", - "TR_P2P_PRICE": "1 {symbol} 的价格", - "TR_P2P_PRICE_TOOLTIP": "此用户提供的 {symbol} 价格。", - "TR_P2P_TITLE_NOT_AVAILABLE": "你好、我正在使用 {providerName}!", - "TR_P2P_USER_REPUTATION": "({rating}、{numberOfTrades})", - "TR_P2P_WARNING_AMOUNT_RANGE_MAXIMUM": "选择的 {amount} 金额高于接受的 {maximum} 上限。", - "TR_P2P_WARNING_AMOUNT_RANGE_MINIMUM": "选择的金额 {amount} 低于接受的最小值 {minimum}。", "TR_PAGINATION_NEWER": "较新的", "TR_PAGINATION_OLDER": "较旧的", - "TR_PASSPHRASE_CASE_SENSITIVE": "注意: 密码短语是区分大小写的。", + "TR_PASSPHRASE_CASE_SENSITIVE": "注意:密码短语是区分大小写的。", "TR_PASSPHRASE_DESCRIPTION_ITEM1": "首先了解密码短语的工作原理是非常重要", - "TR_PASSPHRASE_DESCRIPTION_ITEM2": "通过密码短语打开由该密码短语保护的钱包", - "TR_PASSPHRASE_DESCRIPTION_ITEM3": "没人能恢复它、即使是Trezor客服也不行", + "TR_PASSPHRASE_DESCRIPTION_ITEM2": "通过密码短语打开由该密码短语保护的隐藏钱包", + "TR_PASSPHRASE_DESCRIPTION_ITEM3": "没人能恢复它,即使是 Trezor 客服也不行", "TR_PASSPHRASE_HIDDEN_WALLET": "隐藏钱包", "TR_PASSPHRASE_MISMATCH": "密码不匹配", - "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "密码不匹配。为了安全起见、请重新开始并正确输入。", - "TR_PASSPHRASE_MISMATCH_START_OVER": "从头再来", - "TR_PASSPHRASE_TOO_LONG": "密码短语的长度已经超过了允许的限制。", + "TR_PASSPHRASE_MISMATCH_DESCRIPTION": "密码不匹配。为了安全起见,请重新开始并正确输入。", + "TR_PASSPHRASE_MISMATCH_START_OVER": "重新开始", + "TR_PASSPHRASE_NON_ASCII_CHARS": "我们建议使用 ABC, abc, 123, 空格这些特殊字符", + "TR_PASSPHRASE_NON_ASCII_CHARS_WARNING": "使用未列出的特殊字符会给未来的兼容性带来风险", + "TR_PASSPHRASE_TOO_LONG": "密码短语长度已超出允许的限制(50个字节)。", "TR_PASSPHRASE_WALLET": "隐藏钱包 #{id}", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT": "了解密码短语的工作原理", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_HINT_LINK": "去", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_OPEN_UNUSED_WALLET_BUTTON": "是、打开", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_OPEN_UNUSED_WALLET_BUTTON": "是的,打开", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_OPEN_UNUSED_WALLET_DESCRIPTION": "这个钱包是空的、从未使用过。你想打开它吗?", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_OPEN_WITH_FUNDS_BUTTON": "重试", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_OPEN_WITH_FUNDS_DESCRIPTION": "期待一个带有资金的隐藏钱包?", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_TITLE": "此密码钱包是空的", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_BUTTON": "知道了、继续", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM1_DESCRIPTION": "将密码短语写在纸上、远离任何电子设备 (云端、USB、互联网、电话)。", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM2_DESCRIPTION": "将其存放在安全的地方、与钱包备份和Trezor设备分开。", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM3_DESCRIPTION": "切勿与任何人分享、即使是Trezor客服也不行。", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP1_TITLE": "此隐藏钱包是空的", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_BUTTON": "明白了,继续", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM1_DESCRIPTION": "将密码短语写在纸上,远离任何电子设备 (云存储、U盘、互联网、手机)。", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM2_DESCRIPTION": "请将其存放在安全的地方,与钱包备份和 Trezor 设备分开存放。", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_ITEM3_DESCRIPTION": "切勿与任何人分享,即使是 Trezor 客服也不行。", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_TITLE": "密码短语最佳实践", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_WARNING": "没有人能恢复它、即使是Trezor客服也不行。", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP2_WARNING": "没有人能恢复它,即使是 Trezor 客服也没办法。", "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP3_TITLE": "确认密码短语", - "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP3_WARNING": "写在纸上、远离任何电子设备、并将其存放在安全的地方。没有人能恢复它、即使是Trezor客服。", - "TR_PASSWORD_MANAGER": "迁移Dropbox密码", + "TR_PASSPHRASE_WALLET_CONFIRMATION_STEP3_WARNING": "写在纸上,远离任何电子设备,并将其存放在安全的地方。没有人能恢复它,即使是 Trezor 客服也不行。", + "TR_PASSWORD_MANAGER": "迁移 Dropbox 密码", "TR_PAUSED": "已暂停", "TR_PAYMENT_METHOD_ACH": "自动转账", "TR_PAYMENT_METHOD_BANKTRANSFER": "银行转帐", @@ -1488,66 +1457,72 @@ "TR_PAYMENT_METHOD_TEN31SEPA": "TEN31 SEPA", "TR_PAYMENT_METHOD_UNKNOWN": "未知", "TR_PAYMENT_METHOD_WORLDPAYCREDIT": "Worldpay Credit", - "TR_PENDING_TX_HEADING": "待定的 {count, plural, other {交易}}", + "TR_PENDING_TX_HEADING": "待办中 {count, plural, one {transaction} other {transactions}}", "TR_PERSONALIZATION": "自定义", "TR_PIN": "PIN码", "TR_PIN_HEADING_FIRST": "设置新的PIN码", "TR_PIN_HEADING_INITIAL": "设置PIN码", "TR_PIN_HEADING_REPEAT": "重复PIN码", "TR_PIN_HEADING_SUCCESS": "PIN码已设置", - "TR_PIN_MATRIX_DISPLAYED_ON_TREZOR": "这些数字已显示在您的Trezor设备上", + "TR_PIN_MATRIX_DISPLAYED_ON_TREZOR": "这些数字已显示在您的 Trezor 设备上", "TR_PIN_MISMATCH_HEADING": "PIN码不匹配!", - "TR_PIN_SET_SUCCESS": "写下您的PIN码并妥善保管、与您的恢复种子分开存放。当您需要访问您的资金时、用它来解锁您的Trezor设备。", - "TR_PIN_SUBHEADING": "使用较长的PIN码可以保护您的Trezor设备免受未经授权的物理访问。", + "TR_PIN_SET_SUCCESS": "请写下您的PIN码并妥善保管。需要访问您的资金时,用它来解锁您的 Trezor 设备。", + "TR_PIN_SUBHEADING": "使用较长的PIN码可以保护您的 Trezor 设备免受未经授权的物理访问。", "TR_PLAY_IT_SAFE": "让我们稳妥行事", "TR_PLEASE_ALLOW_YOUR_CAMERA": "请允许您的摄像头被使用来扫描二维码。", - "TR_PLEASE_CONNECT_YOUR_DEVICE": "请连接您的Trezor设备以继续验证过程。", + "TR_PLEASE_CONNECT_YOUR_DEVICE": "请连接您的 Trezor 设备以继续验证过程。", "TR_PLEASE_ENABLE_PASSPHRASE": "请启用密码短语功能以继续验证过程。", "TR_POLICY_ID_ADDRESS": "政策ID: ", "TR_PRIMARY_FIAT": "法定货币", - "TR_PRIVATE": "私人的", - "TR_PRIVATE_DESCRIPTION": "隐私性至少 {targetAnonymity}", + "TR_PRIVATE": "具有隐私性的", + "TR_PRIVATE_DESCRIPTION": "匿名性至少 {targetAnonymity}", "TR_PROCEED_UNVERIFIED_ADDRESS": "使用未经验证的地址", "TR_PROMO_BANNER_DASHBOARD": "最便利的 硬件钱包来管理您的加密货币", - "TR_QR_CODE": "二维码", - "TR_QR_RECEIVE_ADDRESS_CONFIRM": "扫描二维码前先请在Trezor设备上确认", - "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "请先在Trezor设备上确认接收地址、因为它的显示屏是权威性的、无法被黑客或病毒篡改。", + "TR_QR_RECEIVE_ADDRESS_CONFIRM": "扫描二维码之前先请在 Trezor 设备上确认", + "TR_QR_RECEIVE_ADDRESS_CONFIRM_EXPLANATION": "请先在 Trezor 设备上确认接收地址、因为它的显示屏是权威性的、无法被黑客或病毒篡改。", "TR_QUICK_ACTION_DEBUG_EAP_EXPERIMENTAL_ENABLED": "已启用", "TR_QUICK_ACTION_TOOLTIP_JUST_UPDATED": "刚刚更新 {currentVersion}", "TR_QUICK_ACTION_TOOLTIP_RESTART_TO_UPDATE": "重新启动以更新", - "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Trezor设备", + "TR_QUICK_ACTION_TOOLTIP_TREZOR_DEVICE": "Trezor 设备", "TR_QUICK_ACTION_TOOLTIP_TREZOR_SUITE": "Trezor Suite", "TR_QUICK_ACTION_TOOLTIP_UPDATE_AVAILABLE": "可提供的更新 {newVersion}", "TR_QUICK_ACTION_TOOLTIP_UP_TO_DATE": "您已使用最新的版本{currentVersion}", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_DOWNLOADED": "Suite 已下载了新的 Trezor 更新!", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_HAS_BEEN_UPDATED": "应用程序已更新!", + "TR_QUICK_ACTION_UPDATE_POPOVER_APP_UPDATE_AVAILABLE": "应用程序更新可用", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_RESTART_AND_UPDATE": "点击重新启动并更新", + "TR_QUICK_ACTION_UPDATE_POPOVER_CLICK_TO_START_UPDATE": "点击开始更新", + "TR_QUICK_ACTION_UPDATE_POPOVER_TREZOR_UPDATE_AVAILABLE": "有 Trezor 更新可用", + "TR_QUICK_ACTION_UPDATE_POPOVER_WHATS_NEW": "有什么新变化?", "TR_RANDOM_SEED_WORDS_DISCLAIMER": "您可能会被要求输入一些不属于您的钱包助记词备份的单词。", "TR_RANGE": "时间范围", "TR_READ_AND_UNDERSTOOD": "我已经阅读并理解了上述内容", - "TR_REBOOT_INTO_BOOTLOADER": "更新Trezor设备固件", + "TR_REBOOT_INTO_BOOTLOADER": "更新 Trezor 设备固件", "TR_RECEIVE": "接收", "TR_RECEIVED": "已接收", "TR_RECEIVED_SYMBOL": "已收到 {multiple, select, true {多个代币} false {{symbol}} other {{symbol}}}", "TR_RECEIVE_ADDRESS": "接收地址", - "TR_RECEIVE_ADDRESS_MATCH": "接收地址是否匹配?", + "TR_RECEIVE_ADDRESS_MATCH": "接收地址是否一致?", "TR_RECEIVE_ADDRESS_SECURITY_CHECK_FAILED": "您的设备可能不安全。请勿向其发送资金。", "TR_RECEIVE_NETWORK": "接收 {network}", "TR_RECEIVING_SYMBOL": "接收{multiple, select, true {多个代币} false {{symbol}} other {{symbol}}}", "TR_RECIPIENT_ADDRESS": "接收人地址", "TR_RECIPIENT_ADDRESS_MATCH": "接收地址是否匹配?", "TR_RECOMMENDED": "推荐的", - "TR_RECONNECT_DEVICE_DESCRIPTION": "如果关闭标签页并刷新此页面没有帮助、请尝试重新连接您的Trezor设备。", - "TR_RECONNECT_DEVICE_DESCRIPTION_DESKTOP": "如果关闭标签页并重新打开Trezor Suite应用程序没有帮助、请尝试重新连接您的Trezor设备。", - "TR_RECONNECT_HEADER": "重新连接您的Trezor设备", - "TR_RECONNECT_IN_BOOTLOADER": "在加载引导模式下重新连接您的Trezor设备", + "TR_RECONNECT_DEVICE_DESCRIPTION": "如果关闭标签页并刷新此页面没有帮助、请尝试重新连接您的 Trezor 设备。", + "TR_RECONNECT_DEVICE_DESCRIPTION_DESKTOP": "如果关闭标签页并重新打开 Trezor Suite 应用程序没有帮助、请尝试重新连接您的 Trezor 设备。", + "TR_RECONNECT_HEADER": "重新连接您的 Trezor 设备", + "TR_RECONNECT_IN_BOOTLOADER": "在加载引导模式下重新连接您的 Trezor 设备", "TR_RECONNECT_IN_BOOTLOADER_SUCCESS": "设备现在已经准备就绪", "TR_RECONNECT_IN_NORMAL": "重新连接您的设备", - "TR_RECONNECT_YOUR_DEVICE": "重新连接您的Trezor设备", - "TR_RECOVERY_ERROR": "设备恢复失败:{error}", + "TR_RECONNECT_YOUR_DEVICE": "重新连接您的 Trezor 设备", + "TR_RECOVERY_ERROR": "设备恢复失败: {error}", "TR_RECOVERY_FAILED": "恢复失败", - "TR_RECOVERY_MATRIX_DISPLAYED_ON_TREZOR": "这些字母显示在您的Trezor设备上", - "TR_RECOVERY_TYPES_DESCRIPTION": "这两种方法都是安全的; 高级恢复让您在Trezor设备上输入钱包助记词、只不过需要稍微长一些时间。", - "TR_RECOVER_SUBHEADING_BUTTONS": "整个恢复钱包的过程在Trezor设备上通过按钮输入完成。", + "TR_RECOVERY_MATRIX_DISPLAYED_ON_TREZOR": "这些字母显示在您的 Trezor 设备上", + "TR_RECOVERY_TYPES_DESCRIPTION": "这两种方法都是安全的; 高级恢复让您在 Trezor 设备上输入钱包助记词、只不过需要稍微长一些时间。", + "TR_RECOVER_SUBHEADING_BUTTONS": "整个恢复钱包的过程在 Trezor 设备上通过按钮输入完成。", "TR_RECOVER_SUBHEADING_COMPUTER": "如果您想恢复一个现有的钱包、您可以用您的钱包助记词备份来进行。选择您的钱包助记词的单词数量。", - "TR_RECOVER_SUBHEADING_TOUCH": "使用Trezor Model T、整个钱包恢复过程是在设备的触摸屏上完成的。", + "TR_RECOVER_SUBHEADING_TOUCH": "使用 Trezor Model T、整个钱包恢复过程是在设备的触摸屏上完成的。", "TR_RECOVER_YOUR_WALLET_FROM": "使用助记词恢复钱包", "TR_REDUCE_FROM": "从 {value} 减少", "TR_REMEMBER_WALLET_DESCRIPTION": "它仍可私下加载、但加载速度更快、并可保存自定义设置。", @@ -1564,12 +1539,12 @@ "TR_RETRYING_DOT_DOT": "重试中...", "TR_REVEAL_ADDRESS": "显示地址", "TR_REWARDS_WITHDRAWAL": "提取奖励", - "TR_RUNNING_MULTIPLE_INSTANCES": "您似乎正在运行多个应用实例。 如果您在其他窗口或标签页中使用Trezor Suite、请关闭它们并刷新应用程序。", + "TR_RUNNING_MULTIPLE_INSTANCES": "您似乎正在运行多个应用实例。 如果您在其他窗口或标签页中使用 Trezor Suite、请关闭它们并刷新应用程序。", "TR_SAFETY_CHECKS_BANNER_CHANGE": "设置", "TR_SAFETY_CHECKS_DISABLED_WARNING": "安全检查已禁用。", "TR_SAFETY_CHECKS_MODAL_TITLE": "安全检查", "TR_SAFETY_CHECKS_PROMPT_LEVEL": "提示", - "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "允许潜在不安全的行为、例如通过在Trezor设备上手动批准不匹配的代币密钥或极端的费率。", + "TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC": "允许潜在不安全的行为、例如通过在 Trezor 设备上手动批准不匹配的代币密钥或极端的费率。", "TR_SAFETY_CHECKS_PROMPT_LEVEL_WARNING": "除非您知道您在做什么、否则不要改变这个!", "TR_SAFETY_CHECKS_STRICT_LEVEL": "严格", "TR_SAFETY_CHECKS_STRICT_LEVEL_DESC": "全面设备安全。", @@ -1580,27 +1555,30 @@ "TR_SEARCH_TRANSACTIONS": "搜索交易", "TR_SEARCH_UTXOS": "搜索具体地址,交易ID或标签", "TR_SECURITY_CHECKPOINT_GOT_SEED": "您有自己钱包助记词的备份吗?", - "TR_SECURITY_CHECK_HOLOGRAM": "请注意、设备包装 (包括全息图和安全封条) 已随着时间的推移而更新。您可以在此处验证包装细节。确保您的设备是从Trezor官方商店或我们信任的卖家处购买的。否则,您的设备有可能是假冒产品。如果您怀疑您的设备不是正品、请联系Trezor客服。", + "TR_SECURITY_CHECK_HOLOGRAM": "请注意、设备包装 (包括全息图和安全封条) 已随着时间的推移而更新。您可以在此处验证包装细节。确保您的设备是从 Trezor 官方商店或我们信任的卖家处购买的。否则,您的设备有可能是假冒产品。如果您怀疑您的设备不是正品、请联系 Trezor 客服。", "TR_SECURITY_FEATURES_COMPLETED_N": "安全 ({n} / {m})", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "被设置为无种子模式的设备不能访问Trezor Suite。这是为了避免灾难性的加密货币损失、以防不适当设置的设备被用于错误的目的。", - "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Trezor Suite不支持无种子模式设置", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_DESCRIPTION": "被设置为无种子模式的设备不能访问 Trezor Suite。这是为了避免灾难性的加密货币损失、以防不适当设置的设备被用于错误的目的。", + "TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE": "Trezor Suite 不支持无种子模式设置", "TR_SEED_BACKUP_LENGTH": "您的钱包助记词可能包含12,18个或24个单词。", - "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "您的钱包助记词可能包含12,18,20,24或33个单词。", + "TR_SEED_BACKUP_LENGTH_INCLUDING_SHAMIR": "您的钱包助记词可能包含 12,18,20,24 或 33 个单词。", "TR_SEED_CHECK_FAIL_TITLE": "恢复钱包助记词备份检查失败!", "TR_SEED_CHECK_SUCCESS_DESC": "您的钱包助记词是有效的、并且刚刚被成功检查。请妥善保管它并且把它藏在您能找到的地方。", "TR_SEED_CHECK_SUCCESS_TITLE": "钱包助记词备份已成功通过检查!", "TR_SEED_WORDS_ENTER_BUTTONS": "使用设备上的按钮、按照设备显示的顺序输入钱包助记词中的单词。", - "TR_SEED_WORDS_ENTER_COMPUTER": "按照Trezor设备上显示的顺序输入钱包助记词备份中的单词。", + "TR_SEED_WORDS_ENTER_COMPUTER": "按照 Trezor 设备上显示的顺序输入钱包助记词备份中的单词。", "TR_SEED_WORDS_ENTER_TOUCHSCREEN": "使用触摸屏、按正确顺序输入所有单词、直到完成。", "TR_SEE_DETAILS": "查看详情", + "TR_SEE_IF_ISSUE_PERSISTS": "查看问题是否仍然存在。", "TR_SELECTED": " 已选择 {amount} ", "TR_SELECT_COIN_FOR_SETTINGS": "选择活跃的加密货币来更改设置", "TR_SELECT_DEVICE": "选择设备", + "TR_SELECT_NAME_OR_ADDRESS": "通过名称、符号、网络或合约地址来进行搜索", "TR_SELECT_NUMBER_OF_WORDS": "选择您钱包助记词备份中的单词数", "TR_SELECT_PASSPHRASE_SOURCE": "在 “{deviceLabel}” 里选择输入密码短语的地方。", "TR_SELECT_RECOVERY_METHOD": "选择恢复钱包的方式", - "TR_SELECT_TREZOR": "选择Trezor设备", - "TR_SELECT_TREZOR_TO_CONTINUE": "选择您的Trezor设备以继续。", + "TR_SELECT_TOKEN": "选择一个代币", + "TR_SELECT_TREZOR": "选择 Trezor 设备", + "TR_SELECT_TREZOR_TO_CONTINUE": "选择您的 Trezor 设备以继续。", "TR_SELECT_TYPE": "选择类型", "TR_SELL_ADD_BANK_ACCOUNT": "添加银行账户", "TR_SELL_BANK_ACCOUNT": "您的银行账户", @@ -1608,7 +1586,7 @@ "TR_SELL_BANK_ACCOUNT_STEP": "银行账户", "TR_SELL_BANK_ACCOUNT_TOOLTIP": "您在提供商注册的银行账户", "TR_SELL_BANK_ACCOUNT_VERIFIED": "已验证", - "TR_SELL_CONFIRM_ON_TREZOR_SEND": "在Trezor设备上确认并发送", + "TR_SELL_CONFIRM_ON_TREZOR_SEND": "请在 Trezor 设备上确认并发送", "TR_SELL_CONFIRM_SEND_STEP": "确认并发送", "TR_SELL_DETAIL_ERROR_BUTTON": "返回账户", "TR_SELL_DETAIL_ERROR_SUPPORT": "打开合作伙伴的客服网站", @@ -1628,24 +1606,20 @@ "TR_SELL_MODAL_CONFIRM": "我已准备出售", "TR_SELL_MODAL_FOR_YOUR_SAFETY": "通过 {provider} 出售 {cryptocurrency}", "TR_SELL_MODAL_LEGAL_HEADER": "法律声明", - "TR_SELL_MODAL_SECURITY_HEADER": "Trezor一直把安全放在第一位", + "TR_SELL_MODAL_SECURITY_HEADER": "Trezor 一直把安全放在第一位", "TR_SELL_MODAL_TERMS_1": "您是来出售加密货币的。如果您是因其他原因被引导至本网站、请在继续操作前联系客服。", "TR_SELL_MODAL_TERMS_2": "您正在为自己的账户出售加密货币。您承认提供商的政策可能要求验证身份。", - "TR_SELL_MODAL_TERMS_3": "您需要理解加密货币交易是不可逆的、是无法退还的。因此、欺诈性或意外损失也是无法恢复的。", - "TR_SELL_MODAL_TERMS_4": "您理解Invity不提供此服务。{provider} 的条款管辖该服务。", - "TR_SELL_MODAL_TERMS_5": "您不得将此功能用于赌博、欺诈或任何其他违反Invity或供应商服务条款或任何适用法规的行为。", - "TR_SELL_MODAL_TERMS_6": "您了解加密货币是一种新兴的金融工具、不同司法管辖区的法规可能有所不同。这可能会使您面临更高的欺诈、盗窃或市场不稳定的风险。", - "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "经过Invity验证的合作伙伴", + "TR_SELL_MODAL_TERMS_3": "您需要知道加密货币交易是不可逆的、是无法退还的。因此、欺诈性或意外损失也是无法恢复的。", + "TR_SELL_MODAL_TERMS_4": "您知道 Invity 不提供此服务。{provider} 的条款管辖该服务。", + "TR_SELL_MODAL_TERMS_5": "您不得将此功能用于赌博、欺诈或任何其他违反 Invity 或供应商服务条款或任何适用法规的行为。", + "TR_SELL_MODAL_TERMS_6": "您知道加密货币是一种新兴的金融工具、不同司法管辖区的法规可能有所不同。这可能会使您面临更高的欺诈、盗窃或市场不稳定的风险。", + "TR_SELL_MODAL_VERIFIED_PARTNERS_HEADER": "已经过 Invity 认证的合作伙伴", "TR_SELL_REGISTER": "注册", "TR_SELL_SEND_FROM": "发送自", "TR_SELL_SEND_TO": "发送至 {providerName} 的地址", "TR_SELL_STATUS_ERROR": "已拒绝", "TR_SELL_STATUS_PENDING": "待定的", "TR_SELL_STATUS_SUCCESS": "已批准", - "TR_SELL_TRANS_ID": "交易ID: ", - "TR_SELL_VALIDATION_ERROR_MAXIMUM_FIAT": "最大值为 {maximum} {currency}", - "TR_SELL_VALIDATION_ERROR_MINIMUM_FIAT": "最小值为 {minimum} {currency}", - "TR_SELL_VIEW_DETAILS": "查看详情", "TR_SENDFORM_LABELING_EXAMPLE_1": "储蓄", "TR_SENDFORM_LABELING_EXAMPLE_2": "租", "TR_SENDING_SYMBOL": "发送{multiple, select, true {多个代币} false {{symbol}} other {{symbol}}}", @@ -1659,10 +1633,10 @@ "TR_SENT_TO_SELF": "发送给自己", "TR_SERVICE_FEE": "一次性服务费", "TR_SERVICE_FEE_NOTE": "注意: 您也在支付挖矿费", - "TR_SESSION_ERROR_PHASE_AFFILIATE_SERVERS_OFFLINE": "混币服务暂时不可用", + "TR_SESSION_ERROR_PHASE_AFFILIATE_SERVERS_OFFLINE": "CoinJoin 混币器服务暂时不可用", "TR_SESSION_ERROR_PHASE_BLOCKED_UTXOS": "比特币尚未准备好。请稍候重试。", - "TR_SESSION_ERROR_PHASE_CRITICAL_ERROR": "严重错误、正在停止混币", - "TR_SESSION_ERROR_PHASE_MISSING_UTXOS": "寻找可用的比特币", + "TR_SESSION_ERROR_PHASE_CRITICAL_ERROR": "严重错误!正在停止 CoinJoin 混币器", + "TR_SESSION_ERROR_PHASE_MISSING_UTXOS": "寻找可使用的”未消费交易输出”", "TR_SESSION_ERROR_PHASE_REGISTRATION_FAILED": "验证失败、重试", "TR_SESSION_ERROR_PHASE_RETRYING_PAIRING": "正在重试配对", "TR_SESSION_ERROR_PHASE_SKIPPING_ROUND": "跳过回合", @@ -1689,7 +1663,7 @@ "TR_SETTINGS_DEVICE_BANNER_TITLE_REMEMBERED": "连接设备以便更改设备设置", "TR_SETTINGS_DEVICE_BANNER_TITLE_UNAVAILABLE": "检测到的设备状态不正确", "TR_SETTINGS_SAME_AS_SYSTEM": "系统", - "TR_SETUP_MY_TREZOR": "设置我的Trezor设备", + "TR_SETUP_MY_TREZOR": "设置我的 Trezor 设备", "TR_SETUP_WIPE_CODE": "设置自毁PIN码", "TR_SET_PIN": "设置PIN码", "TR_SHOW_BALANCES": "显示余额", @@ -1700,6 +1674,8 @@ "TR_SHOW_LOG": "显示日志", "TR_SHOW_MORE": "显示更多", "TR_SHOW_MORE_ADDRESSES": "显示更多 ({count})", + "TR_SHOW_ON_TRAY": "在系统托盘中显示图标", + "TR_SHOW_ON_TRAY_DESCRIPTION": "监控 Trezor Suite 是否正在后台运行。", "TR_SHOW_UNVERIFIED_ADDRESS": "显示未验证的地址", "TR_SHOW_UNVERIFIED_XPUB": "显示未验证的扩展公钥", "TR_SIDEBAR_ADD_COIN": "添加加密货币", @@ -1712,12 +1688,14 @@ "TR_SIZE": "大小", "TR_SKIP": "跳过", "TR_SKIP_BACKUP": "跳过备份", + "TR_SKIP_BACKUP_DESCRIPTION": "如果您的 Trezor 设备丢失、被盗或损坏、钱包助记词备份可让您恢复资金。如果没有备份、您可能会永远失去对加密货币的访问权限。", "TR_SKIP_PIN": "跳过PIN码", + "TR_SKIP_PIN_DESCRIPTION": "设备PIN码可防止他人未经授权访问您的 Trezor 设备。如果不设置设备PIN码、任何拥有您设备的人都可以访问您的资金。", "TR_SKIP_ROUNDS": "跳过这一轮", "TR_SKIP_ROUNDS_DESCRIPTION": "允许跳过混币回合、使得别人更难证明输入之间的任何关系。 这意味着您可以进一步混淆资金的来源。", - "TR_SKIP_ROUNDS_HEADING": "允许Trezor跳过混币回合", + "TR_SKIP_ROUNDS_HEADING": "允许 Trezor 跳过混币回合", "TR_SKIP_UPDATE": "跳过更新", - "TR_SOLANA_DEVNET_SHORTCUT_WARNING": "由于网络限制、DSOL会在Trezor设备上显示SOL。如果需要验证交易网络,请查看Devnet探索工具上提供的blockhash。", + "TR_SOLANA_DEVNET_SHORTCUT_WARNING": "由于网络限制、DSOL 会在 Trezor 设备上显示 SOL。如果需要验证交易网络,请查看 Devnet 探索工具上提供的 blockhash。", "TR_SOLANA_TX_CONFIRMATION_MAY_TAKE_UP_TO_1_MIN": "交易确认可能需要1分钟。", "TR_SOLVE_ISSUE": "刷新", "TR_SOUTH": "南", @@ -1725,6 +1703,7 @@ "TR_STAKE_ADDING_TO_POOL": "添加到质押矿池", "TR_STAKE_ANY_AMOUNT_ETH": "最低的质押金额为 {amount} {symbol} 并开始赚取奖励。我们目前的年利率为{apyPercent}%、您赚取的奖励也能赚!", "TR_STAKE_APY": "年利率", + "TR_STAKE_APY_ABBR": "年利率", "TR_STAKE_APY_DESC": "*年利率", "TR_STAKE_AVAILABLE": "可选", "TR_STAKE_CAN_CLAIM_WARNING": "您已可以认领 {amount} {symbol}。{br}请认领或等待、直到新的解除质押处理完毕。", @@ -1737,18 +1716,18 @@ "TR_STAKE_CLAIM_UNSTAKED": "认领解除质押的 {symbol}", "TR_STAKE_CONFIRM_AND_STAKE": "确认并质押", "TR_STAKE_CONFIRM_ENTRY_PERIOD": "确认参与期限", - "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "我知道并同意使用Everstake进行质押", + "TR_STAKE_CONSENT_TO_STAKING_WITH_EVERSTAKE": "我知道并同意使用 Everstake 进行质押", "TR_STAKE_DAYS": "{count, plural, other {#天}}", "TR_STAKE_DELEGATED": "委托质押", "TR_STAKE_DEREGISTERED": "撤销质押地址的注册", - "TR_STAKE_EARN_REWARDS_WEEKLY": "每周赚取奖励", + "TR_STAKE_EARN_REWARDS_WEEKLY": "每星期赚取奖励", "TR_STAKE_ENTERING_POOL_MAY_TAKE": "进入质押矿池可能最多需要{count, plural, other {#天}}。", "TR_STAKE_ENTER_THE_STAKING_POOL": "参与此质押矿池", "TR_STAKE_ETH": "质押以太坊", "TR_STAKE_ETH_CARD_TITLE": "赚取 {symbol} 最简单的方法", "TR_STAKE_ETH_EARN_REPEAT": "质押。获得奖励。重复。", - "TR_STAKE_ETH_EVERSTAKE": "Trezor和Everstake", - "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake是质押技术里的国际领导者和供应商", + "TR_STAKE_ETH_EVERSTAKE": "Trezor 和 Everstake", + "TR_STAKE_ETH_EVERSTAKE_DESC": "Everstake 是质押技术里的国际领导者和供应商", "TR_STAKE_ETH_LOCK_FUNDS": "灵活锁定资金", "TR_STAKE_ETH_LOCK_FUNDS_DESC": "质押会锁定您的资金、但您可以随时选择解除质押。", "TR_STAKE_ETH_MAXIMIZE_REWARDS": "最大化您的奖励", @@ -1756,23 +1735,25 @@ "TR_STAKE_ETH_REWARDS_EARN": "您的奖励也可以赚。继续质押、看着您的 {symbol} 奖励飙升。", "TR_STAKE_ETH_REWARDS_EARN_APY": "您的 {symbol} 奖励也可赚取年利率。保留您的资金或增加资金以提高奖励。", "TR_STAKE_ETH_SEE_MONEY_DANCE": "看着您的钱舞动", - "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "使用Trezor设备来为您的以太坊进行质押、赚取 {apyPercent}% 年利率 。", + "TR_STAKE_ETH_SEE_MONEY_DANCE_DESC": "使用 Trezor 设备来为您的以太坊进行质押、赚取 {apyPercent}% 年利率 。", "TR_STAKE_ETH_WILL_BE_BLOCKED": "在此期间、您的 {symbol} 将被封锁、并且您无法取消此操作。了解更多", - "TR_STAKE_EVERSTAKE_MANAGES": "Everstake利用其智能合约、基础设施与技术来维护和保护您质押的 {symbol} 。", + "TR_STAKE_EVERSTAKE_MANAGES": "Everstake 利用其智能合约、基础设施与技术来维护和保护您质押的 {symbol} 。", "TR_STAKE_INSTANT": "立刻", "TR_STAKE_INSTANTLY_UNSTAKED_WITH_DAYS": "您立刻收到了{amount} {symbol}{days, plural, =0 {} other {、其余的将在#天内支付完毕}}", - "TR_STAKE_IN_ACCOUNT": "{symbol} 在账号", + "TR_STAKE_IN_ACCOUNT": "{symbol} 在账户", "TR_STAKE_LEARN_MORE": "了解更多", "TR_STAKE_LEAVE_STAKING_POOL": "离开质押矿池", "TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL": "我们未填写 {amount} {symbol}、以便您支付提款手续费用。", + "TR_STAKE_LEFT_SMALL_AMOUNT_FOR_WITHDRAWAL": "我们预留了少量的 {symbol},以便您支付提款费用。", "TR_STAKE_MAX": "最多", "TR_STAKE_MAX_FEE_DESC": "最高费率是您为了确保您的交易能得到处理而愿意支付的网络交易费用。", "TR_STAKE_MAX_REWARD_DAYS": "最多{count, plural, other {#天}}", "TR_STAKE_MIN_AMOUNT_TOOLTIP": "最少质押金额为 {amount} {symbol}", + "TR_STAKE_MONTHLY": "每月", "TR_STAKE_NEXT_PAYOUT": "下一次奖励支付", "TR_STAKE_NOT_ENOUGH_FUNDS": "没有足够的 {symbol} 来支付网络交易费用", "TR_STAKE_ONLY_REWARDS": "只有奖励", - "TR_STAKE_ON_EVERSTAKE": "在Everstake上质押 {symbol}?", + "TR_STAKE_ON_EVERSTAKE": "在 Everstake 上质押 {symbol}?", "TR_STAKE_OTHER_AMOUNT": "其他金额", "TR_STAKE_PAID_FROM_BALANCE": "从您的余额中支付", "TR_STAKE_PROVIDED_BY": "供应商为", @@ -1793,61 +1774,74 @@ "TR_STAKE_START_STAKING": "开始质押", "TR_STAKE_TIME_TO_CLAIM": "认领奖励时间", "TR_STAKE_TOTAL_PENDING": "总共待处理的质押: ", - "TR_STAKE_TREZOR_NO_LIABILITY": "在质押时、您的资金安全责任将从您的Trezor设备转移到Everstake。", + "TR_STAKE_TREZOR_NO_LIABILITY": "在质押时、您的资金安全责任将从您的 Trezor 设备转移到 Everstake。", "TR_STAKE_UNSTAKE": "解除质押", "TR_STAKE_UNSTAKED_AND_READY_TO_CLAIM": "解除质押、随时可认领", "TR_STAKE_UNSTAKE_TO_CLAIM": "解除质押来认领", "TR_STAKE_UNSTAKING": "解除质押", + "TR_STAKE_UNSTAKING_APPROXIMATE": "大约即是可用的 {symbol}", + "TR_STAKE_UNSTAKING_APPROXIMATE_DESCRIPTION": "质押矿池的流动性允许即时解除部分资金的质押。其余资金将跟随在解除质押期之后。", "TR_STAKE_UNSTAKING_PERIOD": "解除质押期", "TR_STAKE_UNSTAKING_PROCESS": "解除质押过程", "TR_STAKE_UNSTAKING_TAKES": "解除质押目前需要{count, plural, other {#天}}。一旦完成、您就可以进行交易或发送您的资产。", + "TR_STAKE_WEEKLY": "每星期", "TR_STAKE_WHAT_IS_STAKING": "什么是质押?", - "TR_STAKE_YOUR_FUNDS_MAINTAINED": "您的质押资金由Everstake维护", + "TR_STAKE_YEARLY": "每年", + "TR_STAKE_YOUR_FUNDS_MAINTAINED": "您的质押资金由 Everstake 维护", "TR_STAKING_AMOUNT_STAKED_INSTANTLY": "已立刻质押 {amount}{symbol}", "TR_STAKING_AMOUNT_UNSTAKED_INSTANTLY": "已立刻解除质押 {amount}{symbol}!", + "TR_STAKING_CONSOLIDATING_FUNDS": "正在为您合并您的 {symbol}", "TR_STAKING_DELEGATE": "委托", "TR_STAKING_DEPOSIT": "可退还的押金", - "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "押金费用为 {feeAmount} ADA并需要注册您的地址才能开始押质押。如果您选择解除质押您的艾达币、您将获得押金返还。", + "TR_STAKING_DEPOSIT_FEE_DECRIPTION": "押金费用为 {feeAmount} ADA、并需要注册您的地址才能开始押质押。如果您选择解除质押您的艾达币、您将获得押金返还。", + "TR_STAKING_ESTIMATED_GAINS": "预计收益", "TR_STAKING_FEE": "质押费用", + "TR_STAKING_GETTING_READY": "您的 {symbol} 正在准备开始工作", "TR_STAKING_INSTANTLY_STAKED": "您已立刻质押了{amount} {symbol}{days, plural, =0 {} other {、剩余的{symbol}将在#天内质押。}}", "TR_STAKING_IS_NOT_SUPPORTED": "该网络不支持质押。", "TR_STAKING_NOT_ENOUGH_FUNDS": "您的账户上没有足够的资金。", - "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "通过在Trezor的质押矿池进行质押、您直接支持Trezor和Trezor Suite的卡尔达诺生态系统。", + "TR_STAKING_ONCE_YOU_CONFIRM": "一旦您确认", + "TR_STAKING_ON_3RD_PARTY_DESCRIPTION": "通过在 Trezor 的质押矿池进行质押、您直接支持 Trezor 和 Trezor Suite 的卡尔达诺生态系统。", "TR_STAKING_ON_3RD_PARTY_TITLE": "您正在委托第三方的质押矿池", "TR_STAKING_POOL_OVERSATURATED_DESCRIPTION": "您正在委托的质押矿池已过度饱和。请重新委托其他质押矿池、以最大限度地增加您的质押奖励", "TR_STAKING_POOL_OVERSATURATED_TITLE": "质押矿池已过度饱和", "TR_STAKING_REDELEGATE": "重新委托", "TR_STAKING_REWARDS": "可用的奖励", + "TR_STAKING_REWARDS_ARE_RESTAKED": "奖励会自动被再质押", "TR_STAKING_REWARDS_DESCRIPTION": "请注意、在您最初注册和授权后、最多需要20天时间才能开始认领奖励。 在此时间段结束后、您将每5天收到奖励。", "TR_STAKING_REWARDS_TITLE": "卡尔达诺质押已激活", "TR_STAKING_STAKE_ADDRESS": "您的质押地址", "TR_STAKING_STAKE_DESCRIPTION": "质押卡尔达诺是赚取艾达币质押奖励的好方法、是持有卡尔达诺的一种被动收入形式。{br}通过质押您的艾达币、您积极支持卡尔达诺网络、并为网络的稳定做出贡献。", "TR_STAKING_STAKE_TITLE": "卡尔达诺质押未激活", - "TR_STAKING_TREZOR_POOL_FAIL": "无法连接到Trezor的质押矿池以进行委托。", + "TR_STAKING_TREZOR_POOL_FAIL": "无法连接到 Trezor 的质押矿池以进行委托。", "TR_STAKING_TX_PENDING": "您的交易 {txid} 已成功发送到区块链并等待确认。", "TR_STAKING_WITHDRAW": "提取", + "TR_STAKING_YOUR_EARNINGS": "您的收益将自动再质押,让您赚取复利。", + "TR_STAKING_YOUR_UNSTAKED_FUNDS": "您的已被解除质押的 {symbol} 已准备就绪", + "TR_STAKING_YOU_ARE_HERE": "您在这里", "TR_STANDARD_WALLET_DESCRIPTION": "无密码短语", "TR_START": "开始", "TR_START_AGAIN": "重新开始", "TR_START_BACKUP": "开始备份", "TR_START_CHECK": "开始", - "TR_START_COINJOIN": "开始混币\n", + "TR_START_COINJOIN": "开始 CoinJoin 混币器\n", "TR_START_RECOVERY": "开始恢复", "TR_STEP": "步骤 {number}", - "TR_STILL_DONT_SEE_YOUR_TREZOR": "仍然看不到您的Trezor设备?", + "TR_STEP_OF_TOTAL": "步骤: {index} / {total} ", + "TR_STILL_DONT_SEE_YOUR_TREZOR": "仍然看不见您的 Trezor 设备?", "TR_STOP": "停止", "TR_STOPPING": "正在停止", "TR_STORAGE_CLEARED": "缓存清理完成", "TR_SUGGESTION": "用户反馈", - "TR_SUITE_META_DESCRIPTION": "新的桌面和浏览器应用程序配合Trezor硬件钱包。Trezor Suite在我们的三个关键支柱、即可用性、安全性和隐私性方面带来了巨大改进。", + "TR_SUITE_META_DESCRIPTION": "用于 Trezor 硬件钱包的全新桌面和浏览器应用程序。Trezor Suite 在可用性、安全性和隐私性三大支柱方面都有重大改进。", "TR_SUITE_STORAGE": "应用存储", - "TR_SUITE_VERSION": "Trezor Suite版本", + "TR_SUITE_VERSION": "Trezor Suite 版本", "TR_SWITCH_DEVICE": "切换设备", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_CANCEL_BUTTON": "取消", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DESCRIPTION": "在重新连接设备之前、您的资金和交易将无法显示。", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_DESCRIPTION": "在重新连接设备之前、您的资金和交易将无法显示。", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_PRIMARY_BUTTON": "禁用和弹出", - "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_TITLE": "禁用只供查看功能将弹出该钱包", + "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_DISABLE_VIEW_ONLY_TITLE": "禁用 “仅供查看” 功能将弹出该钱包", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_PRIMARY_BUTTON": "弹出", "TR_SWITCH_DEVICE_EJECT_CONFIRMATION_TITLE": "弹出此钱包?", "TR_SWITCH_FIRMWARE": "切换固件", @@ -1856,26 +1850,26 @@ "TR_SWITCH_FIRMWARE_TO": "将固件切换到 {firmwareType}", "TR_SWITCH_TO_BITCOIN_ONLY": "切换到 {bitcoinOnly}", "TR_SWITCH_TO_BITCOIN_ONLY_DESCRIPTION": "{bitcoinOnly} 固件仅适用于比特币交易。如果您想访问和管理所有的加密货币、只需使用钱包助记词备份将设备固件切换回 {regular} 即可。", - "TR_SWITCH_TO_BOOTLOADER_HOLD_BOTH_BUTTONS": "连接USB数据线时长按两个按钮不放。", - "TR_SWITCH_TO_BOOTLOADER_HOLD_LEFT_BUTTON": "连接USB数据线时长按左侧按钮不放。", - "TR_SWITCH_TO_BOOTLOADER_SWIPE_YOUR_FINGERS": "在连接USB数据线时、用手指在触摸屏上滑动。", + "TR_SWITCH_TO_BOOTLOADER_HOLD_BOTH_BUTTONS": "连接 USB 数据线时长按两个按钮不放。", + "TR_SWITCH_TO_BOOTLOADER_HOLD_LEFT_BUTTON": "连接 USB 数据线时长按左侧按钮不放。", + "TR_SWITCH_TO_BOOTLOADER_SWIPE_YOUR_FINGERS": "在连接 USB 数据线时、用手指在触摸屏上滑动。", "TR_SWITCH_TO_REGULAR": "切换到 {regular}", "TR_SWITCH_TO_REGULAR_DESCRIPTION": "{regular} 固件允许您的设备访问和管理所有的加密货币。{bitcoinOnly} 固件仅适用于比特币交易。您可以使用钱包助记词备份随时更改设备固件。", "TR_TAKES_N_MINUTES": "大约需要15分钟", - "TR_TAKE_ME_BACK_TO_WALLET": "带我回到Suite", + "TR_TAKE_ME_BACK_TO_WALLET": "带我回到 Suite", "TR_TAPROOT_ACCOUNTS": "主根账户", "TR_TAPROOT_BANNER_POINT_1": "仅限小写字母: 降低任何审查错误的机会", "TR_TAPROOT_BANNER_POINT_2": "提高所有比特币交易的隐私性", "TR_TAPROOT_BANNER_TITLE": "主根账户", - "TR_TERMS_AND_PRIVACY_CONFIRMATION": "我同意 Trezor Suite的条款 “简洁非交互式零知识证明”协调员的条款", + "TR_TERMS_AND_PRIVACY_CONFIRMATION": "我同意 Trezor Suite 的条款 “简洁非交互式零知识证明” 协调员的条款", "TR_TERMS_OF_USE_INVITY": "使用条款", "TR_TESTNET_COINS": "测试网代币", "TR_TESTNET_COINS_DESCRIPTION": "这些加密货币只用于测试、不具备任何价值。", "TR_TESTNET_COINS_LABEL": "测试网代币", - "TR_THESE_WONT_ALLOW_YOU_UPGRADE": "生成一组12或24个助记词、用于恢复钱包。传统备份方案无法轻松地升级为多共享备份。了解更多", - "TR_THESE_WONT_ALLOW_YOU_UPGRADE_HEADER": "安全又可靠、但不易升级到多共享备份", + "TR_THESE_WONT_ALLOW_YOU_UPGRADE": "生成一组 12 或 24 个英文单词的助记词,用于恢复钱包。该传统备份方案无法轻松地升级为秘密共享备份。了解更多", + "TR_THESE_WONT_ALLOW_YOU_UPGRADE_HEADER": "安全又可靠,但不易升级到秘密共享备份", "TR_THE_PIN_LAYOUT_IS_DISPLAYED": "检查您的 {deviceLabel} 屏幕上的虚拟键盘布局。", - "TR_THIS_HIDDEN_WALLET_IS_EMPTY_SOURCE": "这个隐藏钱包是空的。为了确保您进入了正确的隐藏钱包、请在Trezor设备上再次输入您的密码短语。", + "TR_THIS_HIDDEN_WALLET_IS_EMPTY_SOURCE": "这个隐藏钱包是空的。为了确保您进入了正确的隐藏钱包、请在 Trezor 设备上再次输入您的密码短语。", "TR_THIS_INSTANCE_IS_BLOCKING": "这个实例阻碍着数据库的升级", "TR_TIMER_PAST_DEADLINE": "即将完成...", "TR_TO": "到", @@ -1885,159 +1879,173 @@ "TR_TOKENS_EMPTY": "还没有代币...", "TR_TOKENS_EMPTY_CHECK_HIDDEN": "没有代币。它们可以已被隐藏。", "TR_TOKENS_SEARCH_TOOLTIP": "按代币、符号或合约地址搜索。", + "TR_TOKEN_NOT_FOUND": "未能找到此代币", + "TR_TOKEN_NOT_FOUND_ON_NETWORK": "未能在 {networkName} 网络上找到此代币", "TR_TOKEN_TRANSFERS": "{standard} 代币转让", + "TR_TOKEN_TRY_DIFFERENT_SEARCH": "请尝试不同的搜索。", + "TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH": "请尝试不同的搜索或切换到另一个网络。", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR": "未识别的代币", "TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP": "未识别的代币会带来潜在的风险。谨慎使用。", "TR_TOO_LONG": "留言太长", - "TR_TOR": "洋葱路由 (Tor)", - "TR_TOR_CONFIG_SNOWFLAKE_DESCRIPTION": "输入您系统中Tor Snowflake二进制文件的路径。更改前请确保Tor已被禁用。", - "TR_TOR_CONFIG_SNOWFLAKE_DISABLE_LABEL": "禁用Tor Snowflake", + "TR_TOR": "洋葱路由网络 (Tor)", + "TR_TOR_CONFIG_SNOWFLAKE_DESCRIPTION": "输入您系统中 Tor Snowflake 二进制文件的路径。更改前请确保 Tor 已被禁用。", + "TR_TOR_CONFIG_SNOWFLAKE_DISABLE_LABEL": "禁用 Tor Snowflake", "TR_TOR_CONFIG_SNOWFLAKE_ERROR_PATH": "必须是一个有效的完整路径。", - "TR_TOR_CONFIG_SNOWFLAKE_TITLE": "Tor Snowflake二进制路径", + "TR_TOR_CONFIG_SNOWFLAKE_TITLE": "Tor Snowflake 二进制路径", "TR_TOR_CONFIG_SNOWFLAKE_UPDATE_LABEL": "更新路径", - "TR_TOR_DESCRIPTION": "启用这个功能将使Suite的所有流量通过Tor网络。所有对Trezor基础设施的请求都将指向我们的Tor服务、增加您的隐私和安全。", - "TR_TOR_DISABLE": "禁用Tor", + "TR_TOR_DESCRIPTION": "通过 Tor 路由所有 Trezor Suite 的流量,从而增强您的匿名性。 Tor 可能需要一段时间才能加载并建立连接。", + "TR_TOR_DISABLE": "禁用 Tor", "TR_TOR_DISABLED": "禁用", - "TR_TOR_DISABLE_ONIONS_ONLY": "缺少非onion自定义后端", - "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "请添加非onion的自定义后端地址、以防止这种行为。", - "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "您现在可以安全地禁用Tor。", + "TR_TOR_DISABLE_ONIONS_ONLY": "缺少非 .onion 自定义后端", + "TR_TOR_DISABLE_ONIONS_ONLY_DESCRIPTION": "请添加非 .onion 的自定义后端地址、以防止这种行为。", + "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_DESCRIPTION": "您现在可以安全地禁用 Tor。", "TR_TOR_DISABLE_ONIONS_ONLY_NO_MORE_TITLE": "自定义后端不再仅使用 .onion 地址。", - "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "禁用Tor", - "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "禁用Tor现在将会将所有.onion 的后端重置为默认的Trezor服务器。", + "TR_TOR_DISABLE_ONIONS_ONLY_RESOLVED": "禁用 Tor", + "TR_TOR_DISABLE_ONIONS_ONLY_TITLE": "现在禁用 Tor 会将所有洋葱地址后端重置为默认的 Trezor 服务器。", "TR_TOR_DISABLING": "禁用", - "TR_TOR_ENABLE": "启用Tor", + "TR_TOR_ENABLE": "启用 Tor", "TR_TOR_ENABLED": "已启用", - "TR_TOR_ENABLE_AND_CONFIRM": "启用Tor并确认", - "TR_TOR_ENABLE_TITLE": "启用Tor", - "TR_TOR_ENABLING": "启用着", + "TR_TOR_ENABLE_AND_CONFIRM": "启用 Tor 并确认", + "TR_TOR_ENABLE_TITLE": "启用 Tor", + "TR_TOR_ENABLING": "正在启用中", "TR_TOR_ERROR": "错误", - "TR_TOR_IS_SLOW_MESSAGE": "Tor正在连接网络。

稍等片刻。", - "TR_TOR_KEEP_RUNNING": "继续运行Tor", - "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "请选择 “继续运行Tor” 继续或 “停止Tor” 退出混币进程。", + "TR_TOR_IS_SLOW_MESSAGE": "Tor 正在连接中。

稍等片刻。", + "TR_TOR_KEEP_RUNNING": "继续运行 Tor", + "TR_TOR_KEEP_RUNNING_FOR_COIN_JOIN_SUBTITLE": "请选择 “继续运行 Tor” 以继续或 “停止运行 Tor” 以退出 CoinJoin 混币进程。", "TR_TOR_MISBEHAVING": "发生故障", - "TR_TOR_REMOVE_ONION_AND_DISABLE": "禁用Tor并切换到默认后端", + "TR_TOR_REMOVE_ONION_AND_DISABLE": "禁用 Tor 并切换到默认后端", "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_LEAVE": "离开", - "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "请选择 “启用Tor” 继续、或 “离开” 退出进程。", - "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_TITLE": "运行混币功能时、必须启用 Tor 以保持私密性。", - "TR_TOR_STOP": "停止Tor", - "TR_TOR_TITLE": "洋葱路由 (Tor)", + "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_SUBTITLE": "请选择 “启用 Tor” 以继续,或 “离开” 以退出进程。", + "TR_TOR_REQUEST_ENABLE_FOR_COIN_JOIN_TITLE": "在运行 CoinJoin 混币器功能时,必须启用 Tor 以保持隐私。", + "TR_TOR_STOP": "停止运行 Tor", + "TR_TOR_TITLE": "洋葱路由网络 (Tor)", "TR_TOTAL": "总计", "TR_TOTAL_AMOUNT": "总金额", "TR_TO_ACCESS_OTHER_WALLETS": "连接您的设备以添加其他钱包", "TR_TO_ADD_NEW_ACCOUNT_PLEASE_CONNECT": "连接您的设备以添加新帐户。", "TR_TO_ADD_NEW_ACCOUNT_WAIT_FOR_DISCOVERY": "帐户仍在加载。请等待添加新帐户。", - "TR_TO_BTC": "到BTC", + "TR_TO_BTC": "到 BTC", "TR_TO_MAKE_YOUR_LABELS_PERSISTENT": "要使您的标签在不同的设备上持久可用、请连接到云存储提供商。", "TR_TO_SATOSHIS": "到聪", - "TR_TRADE_BUYS": "购买", - "TR_TRADE_ENTER_COIN": "输入加密货币名称或符号...", - "TR_TRADE_EXCHANGES": "交易", "TR_TRADE_REDIRECTING": "重定向...", - "TR_TRADE_SELLS": "卖出", "TR_TRANSACTIONS_NOT_AVAILABLE": "交易记录不可用", "TR_TRANSACTIONS_SEARCH_TIP_1": "提示:您可以搜索交易ID、地址、标签、数量和日期。", - "TR_TRANSACTIONS_SEARCH_TIP_10": "提示:您可以合并AND(&)和 OR(|)操作符号来进行更复杂的搜索。 例如,>{lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 将显示{lastYear}年所有1月或12月的交易。", + "TR_TRANSACTIONS_SEARCH_TIP_10": "提示:您可以 合并 AND (&) 和 OR (|) 操作符号来进行更复杂的搜索。 例如,>{lastYear}-01-01 & < {lastYear}-01-31 | > {lastYear}-12-01 & < {lastYear}-12-31 将显示{lastYear}年所有1月或12月的交易。", "TR_TRANSACTIONS_SEARCH_TIP_2": "提示:您可以在数量搜索中使用大于 (>) 和小于 (<) 的符号。 例如、 1 将显示所有金额为1或更高的交易。", "TR_TRANSACTIONS_SEARCH_TIP_3": "提示:您可以使用等于(=)符号搜索准确数量。例如 = 0.01 将只显示金额为0.01的交易。", "TR_TRANSACTIONS_SEARCH_TIP_4": "提示:您可以通过同时使用感叹号和等号 (!=) 来排除一个金额。例如 != -0.01 将显示所有的交易、除了金额为 -0.01的交易。", "TR_TRANSACTIONS_SEARCH_TIP_5": "提示:日期可以使用 YYYY-MM-DD 的格式进行搜索。例如 {lastYear}-12-14 将显示{lastYear}年12月14日的所有交易。", "TR_TRANSACTIONS_SEARCH_TIP_6": "提示:在日期搜索中、您可以使用大于 (>) 或小于 (<) 的符号。 例如、 >{lastYear}-12-01 将显示{lastYear}年12月1日及其后的所有交易。", "TR_TRANSACTIONS_SEARCH_TIP_7": "提示:您可以通过使用叹号和等号 (!=) 来排除一个日期。例如 != {lastYear}-12-14 将显示除{lastYear}年12月14日以外的所有交易。", - "TR_TRANSACTIONS_SEARCH_TIP_8": "提示:您可以通过用OR运算符 (|) 分组来显示与多个搜索中至少一个匹配的结果。例如 {lastYear}-11-30 | {lastYear}-12-01 将显示所有发生在{lastYear}年11月30日或12月1日的交易。", + "TR_TRANSACTIONS_SEARCH_TIP_8": "提示:您可以通过用 OR 运算符 (|) 分组来显示与多个搜索中至少一个匹配的结果。例如 {lastYear}-11-30 | {lastYear}-12-01 将显示所有发生在{lastYear}年11月30日或12月1日的交易。", "TR_TRANSACTIONS_SEARCH_TIP_9": "提示:您可以通过使用 AND (&) 操作号分组显示匹配多次搜索的结果。 例如, > {lastYear}-12-01 & < {lastYear}-12-31 & > 0 将显示{lastYear}年12月所有的入账交易 (金额高于0)。", "TR_TRANSACTIONS_SEARCH_TOOLTIP": "按交易ID、标签或金额搜索、或使用操作符、如 < > | & = !=。", "TR_TRANSACTION_DETAILS": "详细内容", - "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor网桥运行版本 {version}", + "TR_TREZOR_BRIDGE_RUNNING_VERSION": "Trezor 网桥运行版本 {version}", + "TR_TREZOR_CONNECT": "Trezor Connect", "TR_TREZOR_DEVICE_TUTORIAL_CANCELED_HEADING": "教程已取消", "TR_TREZOR_DEVICE_TUTORIAL_COMPLETED_HEADING": "教程已完成", "TR_TREZOR_DEVICE_TUTORIAL_DESCRIPTION": "在简短教程的帮助下学习如何使用设备", - "TR_TREZOR_DEVICE_TUTORIAL_HEADING": "了解您的Trezor设备", - "TR_TROUBLESHOOTING_CLOSE_TABS": "关闭可能使用您的Trezor设备的其他标签页和窗口", + "TR_TREZOR_DEVICE_TUTORIAL_HEADING": "了解您的 Trezor 设备", + "TR_TROUBLESHOOTING_CLOSE_TABS": "关闭可能使用您的 Trezor 设备的其他标签页和窗口", "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION": "关闭其他标签页和窗口后、尝试刷新此页面。", - "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "关闭其他浏览器标签和窗口后、尝试退出并重新打开Trezor Suite应用程序。", + "TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP": "关闭其他浏览器标签和窗口后、尝试退出并重新打开 Trezor Suite 应用程序。", "TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED": "实现通讯交流的必要步骤", - "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "访问 Trezor网桥状态页面", - "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "确保Trezor网桥进程正在运行", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "目前只有基于Chromium的浏览器允许与USB设备直接通信", - "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "请使用基于Chromium的浏览器", - "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "数据线必须完全插入接口。如果连接的是USB-C设备、USB数据线插口应卡入到位。", - "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "尝试一个不同的USB数据线", - "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "安装了Trezor网桥后。", + "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_DESCRIPTION": "访问 Trezor 网桥状态页面", + "TR_TROUBLESHOOTING_TIP_BRIDGE_STATUS_TITLE": "确保 Trezor 网桥进程正在运行", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION": "目前只有基于 Chromium 的浏览器允许与 USB 设备直接通信", + "TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_TITLE": "请使用基于 Chromium 的浏览器", + "TR_TROUBLESHOOTING_TIP_CABLE_DESCRIPTION": "数据线必须完全插入接口。如果连接的是 USB-C 设备、USB 数据线插口应卡入到位。", + "TR_TROUBLESHOOTING_TIP_CABLE_TITLE": "尝试一个不同的 USB 数据线", + "TR_TROUBLESHOOTING_TIP_COMPUTER_DESCRIPTION": "安装了 Trezor 网桥后。", "TR_TROUBLESHOOTING_TIP_COMPUTER_TITLE": "如果可以的话,尝试使用另一台电脑", "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_DESCRIPTION": "以防万一", "TR_TROUBLESHOOTING_TIP_RESTART_COMPUTER_TITLE": "尝试重启您的计算机", - "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "运行Trezor Suite电脑应用程序", - "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "使用Trezor Suite电脑应用程序", - "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "试试安装 udev规则。确保在打开之前先把它们保存到桌面上。", - "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "直接连接到电脑上 (不要使用USB集线器)。", - "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "尝试不同的USB接口", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION": "运行 Trezor Suite 电脑应用程序", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE": "使用 Trezor Suite 电脑应用程序", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION": "点击以切换至另一种网桥实施方案。当前版本:({currentVersion})", + "TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE": "使用另一个版本的 Trezor 网桥", + "TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION": "尝试安装 udev 规则。确保在打开之前先把它们保存到桌面上。", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION": "如果您上次更新设备固件是在 2019 年或更早,请按照知识库中的说明进行操作", + "TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE": "看来你可能正在使用较老的 Trezor 型号。", + "TR_TROUBLESHOOTING_TIP_USB_PORT_DESCRIPTION": "直接连接到电脑上 (不要使用 USB 集线器)。", + "TR_TROUBLESHOOTING_TIP_USB_PORT_TITLE": "尝试一个不同的 USB 接口", "TR_TROUBLESHOOTING_UDEV_INSTALL_TITLE": "自动安装规则", - "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "缺少udev规则", + "TR_TROUBLESHOOTING_UNREADABLE_UDEV": "缺少 udev 规则", "TR_TROUBLESHOOTING_UNREADABLE_UNKNOWN": "意外状态:{error}", - "TR_TROUBLESHOOTING_UNREADABLE_WEBUSB": "您的设备已正常连接、但您的互联网浏览器目前无法与之通信。您将需要安装Trezor网桥。", "TR_TRY_AGAIN": "再试一次", "TR_TXID": "交易ID", "TR_TXID_RBF": "待替换的原始交易ID", "TR_TX_CONFIRMATIONS": "{confirmationsCount}x", "TR_TX_CONFIRMED": "交易已确认", "TR_TX_CONFIRMING": "正在确认的交易", + "TR_TX_DATA_FUNCTION": "功能", + "TR_TX_DATA_INPUT_DATA": "输入数据", + "TR_TX_DATA_METHOD": "方法", + "TR_TX_DATA_METHOD_NAME": "方法名称", + "TR_TX_DATA_PARAMS": "参数", "TR_TX_DEPOSIT": "存款", "TR_TX_FEE": "发送费用", "TR_TX_TAB_AMOUNT": "金额", "TR_TX_WITHDRAWAL": "提款", - "TR_UDEV_DOWNLOAD_DESC": "在某些情况下、Linux用户需要安装udev规则才能使用Trezor设备。请安装一下安装包并重新连接您的设备。", - "TR_UDEV_DOWNLOAD_MANUAL": "手动设置 (高级)", - "TR_UDEV_DOWNLOAD_TITLE": "下载udev规则", + "TR_UDEV_DOWNLOAD_DESC": "在某些情况下,Linux 用户需要安装 udev 规则才能使用 Trezor 设备。请安装一下安装包并重新连接您的设备。", + "TR_UDEV_DOWNLOAD_MANUAL": "手动配置(高级)", + "TR_UDEV_DOWNLOAD_TITLE": "下载 udev 规则", "TR_UNACQUIRED": "无法识别的设备", - "TR_UNAVAILABLE_COINJOIN_ACCOUNT_OUT_OF_SYNC": "不可用。账户同步中、请稍候。", - "TR_UNAVAILABLE_COINJOIN_AMOUNTS_TOO_SMALL": "金额太小、无法使用混币功能", + "TR_UNAVAILABLE_COINJOIN_ACCOUNT_OUT_OF_SYNC": "不可用。账户同步中,请稍候。", + "TR_UNAVAILABLE_COINJOIN_AMOUNTS_TOO_SMALL": "金额太小,无法使用 CoinJoin 混币器功能", "TR_UNAVAILABLE_COINJOIN_COORDINATOR": "没有协调员。", - "TR_UNAVAILABLE_COINJOIN_DEVICE_DISCONNECTED": "不可用。关联设备已断开连接。", - "TR_UNAVAILABLE_COINJOIN_NO_ANONYMITY_SET": "在未设置币隐私级别的情况下、无法启动混币。", + "TR_UNAVAILABLE_COINJOIN_DEVICE_DISCONNECTED": "不可用。相关设备已断开连接。", + "TR_UNAVAILABLE_COINJOIN_NO_ANONYMITY_SET": "在未设置币隐私级别的情况下,无法启动 CoinJoin 混币器。", "TR_UNAVAILABLE_COINJOIN_NO_INTERNET": "不可用。没有网络连接。", - "TR_UNAVAILABLE_COINJOIN_TOR_DISABLE_TOOLTIP": "不可用。Tor已被禁用。", + "TR_UNAVAILABLE_COINJOIN_TOR_DISABLE_TOOLTIP": "不可用。Tor 已被禁用。", "TR_UNAVAILABLE_WHILE_LOADING": "加载时不可用", "TR_UNCONFIRMED_TX": "未确认", "TR_UNDISCOVERED_WALLET": "点击以发现钱包", "TR_UNECO_COINJOIN_AGREE": "我明白了", - "TR_UNECO_COINJOIN_EXPLANATION": "如果您的账户余额低于建议的最低限额 ({crypto})、混币可能不划算。请按 取消 返回并添加更多资金、或按 我明白了 继续进行混币。", - "TR_UNECO_COINJOIN_RECEIVE_WARNING": "您可以向该账户接收资金、并像使用其他账户一样使用该账户。请注意、混币连接功能将于2024年6月1日停止使用。", - "TR_UNECO_COINJOIN_TITLE": "不经济的混币", - "TR_UNECO_COINJOIN_WARNING": "使用低于{isAccountWithRate, select, true {(~{fiat})} false {} other {}}的{crypto}来进行混币是不建议的", + "TR_UNECO_COINJOIN_EXPLANATION": "如果您的账户余额低于建议的最低限额 ({crypto}),使用CoinJoin 混币器可能不划算。请按 取消 返回并添加更多资金或按 我明白了 来继续进行 CoinJoin 混币器。", + "TR_UNECO_COINJOIN_RECEIVE_WARNING": "您可以向该账户接收资金,并像使用其他账户一样使用该账户。请注意,CoinJoin 混币器功能将于 2024 年 6 月 1 日停止使用。", + "TR_UNECO_COINJOIN_TITLE": "不划算的 CoinJoin 混币器使用", + "TR_UNECO_COINJOIN_WARNING": "使用低于 {isAccountWithRate, select, true {(~{fiat})} false {} other {}} 的 {crypto} 来使用CoinJoin 混币器是不建议的", "TR_UNHIDE": "取消隐藏", - "TR_UNHIDE_TOKEN": "解除隐藏代币", - "TR_UNHIDE_TOKEN_TEXT": "这个代币似乎很可疑、可能是个骗局。", - "TR_UNHIDE_TOKEN_TITLE": "解除隐藏此代币?", + "TR_UNHIDE_TOKEN": "不再隐藏此代币", + "TR_UNHIDE_TOKEN_TEXT": "这个代币似乎很可疑,可能是个骗局。", + "TR_UNHIDE_TOKEN_TITLE": "不再隐藏此代币?", "TR_UNKNOWN_CONFIRMATION_TIME": "未知", "TR_UNKNOWN_DEVICE": "未知设备", "TR_UNKNOWN_ERROR_SEE_CONSOLE": "未知错误。详情请查看控制台日志。", "TR_UNKNOWN_TRANSACTION": "未知交易", - "TR_UNSTAKE_FROM_EVERSTAKE": "从Everstake解除质押 {symbol}?", + "TR_UNSTAKE_FROM_EVERSTAKE": "从 Everstake 解除质押 {symbol}?", "TR_UNSUPPORTED_ADDRESS_FORMAT": "不支持的地址格式。", - "TR_UNSUPPORTED_COINS": "在较新的Trezor设备所支持", - "TR_UNSUPPORTED_COINS_DESCRIPTION": "Trezor Safe系列和Trezor Model T支持这些加密货币。", + "TR_UNSUPPORTED_COINS": "在较新的 Trezor 设备所支持", + "TR_UNSUPPORTED_COINS_DESCRIPTION": "Trezor Safe 系列和 Trezor Model T 支持这些加密货币。", "TR_UPDATE_AVAILABLE": "有可用更新", "TR_UPDATE_FIRMWARE_HOMESCREEN_LATER_TOOLTIP": "需要固件更新。您可以稍后在设置页面更改您的主屏幕", "TR_UPDATE_FIRMWARE_HOMESCREEN_TOOLTIP": "更新您的固件以更改您的主屏幕", "TR_UPDATE_MODAL_AVAILABLE_HEADING": "有可用更新", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES": "启用自动更新功能", + "TR_UPDATE_MODAL_ENABLE_AUTO_UPDATES_NEW_TAG": "新的", "TR_UPDATE_MODAL_INSTALL_AND_RESTART": "重启并更新", "TR_UPDATE_MODAL_INSTALL_NOW_OR_LATER": "您想现在安装更新吗?", "TR_UPDATE_MODAL_NOT_NOW": "暂不", - "TR_UPDATE_MODAL_RESTART_NEEDED": "这将重启Trezor Suite。", + "TR_UPDATE_MODAL_RESTART_NEEDED": "这将重启 Trezor Suite。", "TR_UPDATE_MODAL_START_DOWNLOAD": "下载", "TR_UPDATE_MODAL_UPDATE_DOWNLOADED": "已下载更新", "TR_UPDATE_MODAL_UPDATE_ON_QUIT": "退出时更新", + "TR_UPDATE_MODAL_WHATS_NEW": "有什么新变化", + "TR_UPDATE_MODAL_YOUR_VERSION": "您的版本:v{version}", + "TR_UPGRADE_FIRMWARE_TO_DISCOVER_ACCOUNT_ERROR": "升级固件以发现此帐户。(见上面的蓝色横幅)。", "TR_UP_TO": "以至", "TR_UP_TO_DATE": "已同步", - "TR_UP_TO_DAYS": "最多{count, plural, other {#天}}", + "TR_UP_TO_DAYS": "最多 {count, plural, other {# 天}}", "TR_URL_IN_TOKEN": "切勿访问代币名称或符号中的网址、它们通常是骗局。", - "TR_USED_TREZOR_BEFORE": "您曾使用过当前这个Trezor设备吗?", + "TR_USED_TREZOR_BEFORE": "您曾使用过当前这个 Trezor 设备吗?", "TR_USE_CHAT": "点击下方并使用下一页的 聊天 选项。", "TR_USE_DEFAULT_BACKENDS": "使用默认的后端", "TR_UTXO_LONG_BANNED_IN_COINJOIN": "被协调员拒绝", - "TR_UTXO_REGISTERED_IN_COINJOIN": "已注册于混币器", - "TR_UTXO_REGISTERED_IN_COINJOIN_RBF_WARNING": "您的硬币正在使用中。请先关闭混币功能。", - "TR_UTXO_SHORT_BANNED_IN_COINJOIN": "暂时禁止加入混币器", + "TR_UTXO_REGISTERED_IN_COINJOIN": "已注册于 CoinJoin 混币器", + "TR_UTXO_REGISTERED_IN_COINJOIN_RBF_WARNING": "您的 ”未消费交易输出” 正在被使用。请先关闭 CoinJoin 混币器功能。", + "TR_UTXO_SHORT_BANNED_IN_COINJOIN": "暂时禁止加入 CoinJoin 混币器", "TR_VALIDATION": "正在验证固件", "TR_VALUES": "余额", "TR_VERIFIED": "已验证", @@ -2045,10 +2053,10 @@ "TR_VERIFYING_PIN": "验证中...", "TR_VERIFYING_SIGNATURE": "验证签名", "TR_VERIFY_MESSAGE": "验证留言", - "TR_VERIFY_TREZOR_OWNERSHIP": "验证您是否拥有这台Trezor设备", + "TR_VERIFY_TREZOR_OWNERSHIP": "验证您是否拥有这台 Trezor 设备", "TR_VERIFY_TREZOR_OWNERSHIP_CARD_1": "获取您当前的钱包备份", "TR_VERIFY_TREZOR_OWNERSHIP_CARD_2": "不要拍照或制作钱包备份的数码备份", - "TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION": "在Trezor设备上输入您当前的钱包助记词备份,确认您拥有该钱包。", + "TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION": "请在 Trezor 设备上输入您当前的钱包助记词备份,确认您拥有该钱包。", "TR_VERSION": "版本 {version}", "TR_VERSION_HAS_BEEN_RELEASED": "版本 {version} 已发布!", "TR_VIEW": "查看", @@ -2056,20 +2064,20 @@ "TR_VIEW_ALL": "查看全部", "TR_VIEW_ALL_TRANSACTION": "查看所有交易", "TR_VIEW_IN_EXPLORER": "在区块链浏览器中查看", - "TR_VIEW_ONLY": "仅供查看", - "TR_VIEW_ONLY_CALL_TO_ACTION": "断开Trezor设备连接后启用仅限查看功能检查余额", - "TR_VIEW_ONLY_DISABLED": "已禁用仅供查看功能", - "TR_VIEW_ONLY_ENABLED": "已启用仅供查看功能", - "TR_VIEW_ONLY_EXPLANATION": "要发送或交易加密货币、只需重新连接设备即可", - "TR_VIEW_ONLY_PROMO_NOT_NOW": "略过", + "TR_VIEW_ONLY": "“仅供查看” 功能", + "TR_VIEW_ONLY_CALL_TO_ACTION": "在断开 Trezor 设备连接之后启用 “仅供查看” 功能检查余额", + "TR_VIEW_ONLY_DISABLED": "已禁用 “仅供查看” 功能", + "TR_VIEW_ONLY_ENABLED": "已启用 “仅供查看” 功能", + "TR_VIEW_ONLY_EXPLANATION": "要发送或交易加密货币,只需重新连接设备即可", + "TR_VIEW_ONLY_PROMO_NOT_NOW": "暂不", "TR_VIEW_ONLY_PROMO_YES": "启用", - "TR_VIEW_ONLY_RADIOS_DISABLED_DESCRIPTION": "Trezor设备断开连接后、应用程序中的余额和交易不可见。", + "TR_VIEW_ONLY_RADIOS_DISABLED_DESCRIPTION": "Trezor 设备断开连接后,应用程序中的余额和交易不可见。", "TR_VIEW_ONLY_RADIOS_DISABLED_TITLE": "已禁用", - "TR_VIEW_ONLY_RADIOS_ENABLED_DESCRIPTION": "Trezor设备断开连接后、应用程序中的余额和交易仍然可见。", + "TR_VIEW_ONLY_RADIOS_ENABLED_DESCRIPTION": "Trezor 设备断开连接后,应用程序中的余额和交易仍然可见。", "TR_VIEW_ONLY_RADIOS_ENABLED_TITLE": "启用", - "TR_VIEW_ONLY_SEND_COINS_INFO": "您始终需要连接您的Trezor设备才能移动您的加密货币。", - "TR_VIEW_ONLY_TOOLTIP_DESCRIPTION": "在此修改仅供查看和访问密码短语。", - "TR_WAIT_FOR_REBOOT": "正在重启Trezor设备", + "TR_VIEW_ONLY_SEND_COINS_INFO": "您始终需要连接您的 Trezor 设备才能移动您的加密货币。", + "TR_VIEW_ONLY_TOOLTIP_DESCRIPTION": "在此修改 “仅供查看” 功能与访问密码短语。", + "TR_WAIT_FOR_REBOOT": "正在重启 Trezor 设备", "TR_WALLET": "账户", "TR_WALLET_DUPLICATE_DESC": "您要添加的隐藏钱包已被发现。", "TR_WALLET_DUPLICATE_RETRY": "尝试不同的密码短语", @@ -2080,6 +2088,7 @@ "TR_WALLET_SELECTION_ACCESS_HIDDEN_WALLET": "访问隐藏钱包", "TR_WALLET_SELECTION_HIDDEN_WALLET": "隐藏钱包", "TR_WELCOME_TO_TREZOR_TEXT_WALLET_CREATION": "创建一个新的钱包或从备份中恢复一个钱包。", + "TR_WERE_CONSTANTLY_WORKING_TO_IMPROVE": "我们一直在努力改善您的 Trezor 用户体验、以下是已发生的变化: ", "TR_WEST": "西", "TR_WHAT_DATA_WE_COLLECT": "我们会收集哪些数据?", "TR_WHAT_IS_PASSPHRASE": "了解更多区别", @@ -2093,35 +2102,35 @@ "TR_WIPE_OR_UPDATE_DESCRIPTION": "前往设备设置", "TR_WIPING_YOUR_DEVICE": "出厂重置会清除设备内存、删除所有信息、包括钱包备份和PIN码。只有在有钱包助记词备份的情况下才能执行出厂重置、因为钱包助记词是恢复资金访问权限所必需的。", "TR_WORDS": "{count} 单词", - "TR_WORD_DOES_NOT_EXIST": "单词 “{word}” 不存在于BIP39词表中。", + "TR_WORD_DOES_NOT_EXIST": "单词 “{word}” 不存在于 BIP39 词表中。", "TR_WRONG_PIN_ENTERED": "输入了错误的PIN码", "TR_XPUB": "公钥 (扩展公钥)", "TR_XPUB_MATCH": "公钥 (扩展公钥) 是否匹配?", "TR_XPUB_MODAL_CLIPBOARD": "复制公钥", "TR_XPUB_MODAL_TITLE": "{networkName} 账户 {accountIndex} 的扩展公钥 (XPUB)。", "TR_XPUB_MODAL_TITLE_METADATA": "{accountLabel} 的扩展公钥 (XPUB)", - "TR_XPUB_PHISHING_WARNING": "为防止网络钓鱼攻击、应验证Trezor设备上的公钥。{claim}", + "TR_XPUB_PHISHING_WARNING": "为防止网络钓鱼攻击、应验证 Trezor 设备上的公钥。{claim}", "TR_XRP_RESERVE_INFO": "瑞波币地址要求有 {minBalance} 的最低余额,才能激活和维护瑞波币账户。", "TR_YES_CONTINUE": "是的、继续", - "TR_YES_SETUP_MY_TREZOR": "是的、设置我的Trezor设备", + "TR_YES_SETUP_MY_TREZOR": "是的、设置我的 Trezor 设备", "TR_YOUR_CURRENT_FIRMWARE_UNKNOWN": "在引导程序模式下使用设备时、无法检测到当前的固件版本", "TR_YOUR_CURRENT_VERSION": "目前正在运行的版本 {version}", "TR_YOUR_DEVICE_IS_SEEDLESS": "您的设备处于无种子模式、不能与此钱包一起使用。", - "TR_YOUR_FIRMWARE_TYPE": "目前的固件类型是 {version}", - "TR_YOUR_FIRMWARE_VERSION": "目前的固件版本是 {version}", - "TR_YOUR_LABELING_IS_SYNCED": "您的标签已与云存储提供商同步。您的数据是安全的、只有您的Trezor设备才能解密它们。", - "TR_YOUR_LABELING_IS_SYNCED_LOCALLY": "标签在您的机器上本地保存。", - "TR_YOUR_NEW_VERSION": "有新的版本可用!", + "TR_YOUR_FIRMWARE_TYPE": "您当前的固件类型是 {version}", + "TR_YOUR_FIRMWARE_VERSION": "您当前的固件版本是 {version}", + "TR_YOUR_LABELING_IS_SYNCED": "您的标签已与云存储提供商同步。您的数据是安全的、只有您的 Trezor 设备才能解密它们。", + "TR_YOUR_LABELING_IS_SYNCED_LOCALLY": "您的标签已在本地保存到您的电脑。", + "TR_YOUR_NEW_VERSION": "版本 {version} 可用。", "TR_YOUR_NEW_VERSION_IS_DOWNLOADING": "版本 {version} 正在下载。", "TR_YOUR_NEW_VERSION_IS_READY": "版本 {version} 已完成下载并准备好安装。", - "TR_YOUR_TREZOR_IS_NOT_BACKED_UP": "您的Trezor钱包尚未进行备份。", + "TR_YOUR_TREZOR_IS_NOT_BACKED_UP": "您的 Trezor 钱包尚未进行备份。", "TR_YOUR_WALLET_IS_ALMOST_READY_DESCRIPTION": "干得好!现在让我们创建一个钱包备份。您的助记词备份是恢复钱包访问权限的唯一途径。", - "TR_YOUR_WALLET_IS_READY_WHAT": "您的钱包已准备就绪、可以开始使用了!", + "TR_YOUR_WALLET_IS_READY_WHAT": "您的钱包已准备就绪!", "TR_YOUR_WALLET_SUCCESSFULLY_CREATED": "已成功创建钱包", "TR_YOU_HAVE_CONNECTED": "您已经连接了", "TR_YOU_SHOULD_ANONYMIZE": "您应该将他们匿名化", "TR_YOU_WERE_DISCONNECTED_DOT": "您已断开连接。", "TR_ZERO_PHISHING_BANNER": "请谨慎操作。这可能是欺诈性交易。 在此阅读更多信息。", "TR_ZERO_PHISHING_TOOLTIP": "地址中毒攻击警报!此交易看起来可疑。 了解更多信息。", - "ZERO_BALANCE_TOKENS": "零余额代币" + "ZERO_BALANCE_TOKENS": "零余额的代币" } diff --git a/packages/suite-data/files/videos/device/trezor_t3w1_hologram.webm b/packages/suite-data/files/videos/device/trezor_t3w1_hologram.webm new file mode 100644 index 00000000000..a04ac74bf2b Binary files /dev/null and b/packages/suite-data/files/videos/device/trezor_t3w1_hologram.webm differ diff --git a/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_1.webm b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_1.webm new file mode 100644 index 00000000000..4027868adaf Binary files /dev/null and b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_1.webm differ diff --git a/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_2.webm b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_2.webm new file mode 100644 index 00000000000..4027868adaf Binary files /dev/null and b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_2.webm differ diff --git a/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_3.webm b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_3.webm new file mode 100644 index 00000000000..4027868adaf Binary files /dev/null and b/packages/suite-data/files/videos/device/trezor_t3w1_rotate_color_3.webm differ diff --git a/packages/suite-data/files/videos/device/trezor_t3w1_success.webm b/packages/suite-data/files/videos/device/trezor_t3w1_success.webm new file mode 100644 index 00000000000..aa1810af85e Binary files /dev/null and b/packages/suite-data/files/videos/device/trezor_t3w1_success.webm differ diff --git a/packages/suite-data/files/videos/lottie/trezor_t3w1_connect.json b/packages/suite-data/files/videos/lottie/trezor_t3w1_connect.json new file mode 100644 index 00000000000..1efd0760b7a --- /dev/null +++ b/packages/suite-data/files/videos/lottie/trezor_t3w1_connect.json @@ -0,0 +1,123 @@ +{ + "nm": "Comp 1", + "ddd": 0, + "h": 800, + "w": 800, + "meta": { "g": "LottieFiles AE 0.1.21" }, + "layers": [ + { + "ty": 3, + "nm": "Null 1", + "sr": 1, + "st": 0, + "op": 250, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, + "s": { "a": 0, "k": [151, 151, 100], "ix": 6 }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [284, 286, 0], "ix": 2 }, + "r": { "a": 0, "k": -45, "ix": 10 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 0, "ix": 11 } + }, + "ef": [], + "ind": 1 + }, + { + "ty": 2, + "nm": "T3W1.png", + "sr": 1, + "st": 0, + "op": 250, + "ip": 0, + "hd": false, + "cl": "png", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [205, 325, 0], "ix": 1 }, + "s": { "a": 0, "k": [72.871, 72.871, 100], "ix": 6 }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [0, -120, 0], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100, "ix": 11 } + }, + "ef": [], + "refId": "t3t1", + "ind": 2, + "parent": 1 + }, + { + "ty": 2, + "nm": "cable.png", + "sr": 1, + "st": 0, + "op": 250, + "ip": 0, + "hd": false, + "cl": "png", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [53, 343.5, 0], "ix": 1 }, + "s": { "a": 0, "k": [72.871, 72.871, 100], "ix": 6 }, + "sk": { "a": 0, "k": 0 }, + "p": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.667, "y": 1 }, + "s": [0, 427, 0], + "t": 13, + "ti": [0, 19.833, 0], + "to": [0, -19.833, 0] + }, + { "s": [0, 308, 0], "t": 25 } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100, "ix": 11 } + }, + "ef": [], + "refId": "cable", + "ind": 3, + "parent": 1 + } + ], + "v": "5.5.7", + "fr": 25, + "op": 50, + "ip": 0, + "assets": [ + { + "id": "t3w1", + "u": "", + "e": 1, + "w": 410, + "h": 650, + "p": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZoAAAKKCAYAAAAEOJ1lAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAABKmNJREFUeJzsvfuWHsdtL1o9FkntdSySyl6WHTvZL8C/EvluWY6zX4QPd55DXuvs3OM4TmLnnviSi21JHFJeK5JIifOdqa5GFQr1Awr9zXw5c2YBtjhf1wVAVXfjB6Cqu5cUFBQUFBR0Qlr+v1YgKCgoKOh2UwBNUFBQUNBJKYAmKCgoKOikFEATFBQUFHRSCqAJCgoKCjopBdAEBQUFBZ2UAmiCgoKCgk5KATRBQUFBQSelAJqgoKCgoJNSAE1QUFBQ0EkpgCYoKCgo6KQUQBMUFBQUdFIKoAkKCgoKOikF0AQFBQUFnZQCaIKCgoKCTkoBNEFBQUFBJ6UAmqCgoKCgk1IATVBQUFDQSSmAJigoKCjopBRAExQUFBR0UgqgCQoKCgo6KQXQBAUFBQWdlAJogoKCgoJOSgE0QUFBQUEnpQCaoKCgoKCTUgBNUFBQUNBJKYAmKCgoKOikFEATFBQUFHRSCqAJCgoKCjopBdAEBQUFBZ2UAmiCgoKCgk5KATRBQUFBQSelAJqgoKCgoJNSAE1QUFBQ0EkpgCYoKCgo6KQUQBMUFBQUdFIKoAkKCgoKOikF0AQFBQUFnZQCaIKCgoKCTkoBNEFBQUFBJ6UAmqCgoKCgk1IATVBQUFDQSSmAJigoKCjopBRAExQUFBR0UgqgCQoKCgo6KQXQBAUFBQWdlAJogoKCgoJOSgE0QUFBQUEnpQCaoKCgoKCTUgBNUFBQUNBJKYAmKCgoKOikFEATFBQUFHRSCqAJCgoKCjopBdAEBQUFBZ2UAmiCgoKCgk5KATRBQUFBQSelAJqgoKCgoJNSAE1QUFBQ0EkpgCYoKCgo6KQUQBMUFBQUdFIKoAkKCgoKOikF0AQFBQUFnZQCaIKCgoKCTkoBNEFBQUFBJ6UAmqCgoKCgk1IATVBQUFDQSSmAJigoKCjopBRAExQUFBR0UgqgCQoKCgo6KQXQBAUFBQWdlAJogoKCgoJOSgE0QUFBQUEnpQCaoKCgoKCTUgBNUFBQUNBJKYAmKCgoKOikFEATFBQUFHRSCqAJCgoKCjopBdAEBQUFBZ2UAmiCgoKCgk5KATRBQUFBQSelAJqgoKCgoJNSAE1QUFBQ0EkpgCYoKCgo6KQUQBMUFBQUdFIKoAkKCgoKOikF0AQFBQUFnZQCaIKCgoKCTkoBNEFBQUFBJ6UAmqCgoKCgk1IATVBQUFDQSSmAJigoKCjopBRAExQUFBR0UgqgCQoKCgo6KQXQBAUFBQWdlAJogoKCgoJOSgE0QUFBQUEnpQCaoKCgoKCTUgBNUFBQUNBJKYAmKCgoKOikFEATFBQUFHRSCqAJCgoKCjopBdAEBQUFBZ2UAmiCgoKCgk5KATRBQUFBQSelAJqgoKCgoJNSAE1QUFBQ0EkpgCYoKCgo6KQUQBMUFBQUdFIKoAkKCgoKOikF0AQFBQUFnZQCaIKCgoKCTkoBNEFBQUFBJ6UAmhtGd+7cOeS/y7Kkw+EwNljy/5e1/uLiorbJx/m/TLks/3d2dlaPEfH2knLRAq4O0ov+0m8PyTHxY00X4m/J4Dq0vzSGsR+Sy+u031J3jXf9vSmi6c511uRyPjM9l3Jx9Dquf8+GcqTDrIz4a+Ne2kFps85X6wPHQe0Fb/r7ox/9KGzULaA4iTeMpkCzEQENP6abFQEA54eMN5JlgUkGsVx+cXHobAdvq4GQHBvXhwMAlz0a38v6hEGrtlCMItIHHXfl+echdfrlOahyNweAj4na8XOD5MkymltumKWR7uoMcOQ6oL5qn62NBDF+nOdftu95bJOGzp8CrgE0t5PiJN4wykCjGfj1xhanTPOyLZDiEQk38NQ3A5g0/MT/QF5q6vmXuouUFGNb/qZhBBJMZtFXPWagOurH+zRdkWFDkQ153wfQtm+3RQuiT+WZSr0ERCsa8oDeHtBw8VMAQQcqBpqbfCt6U0GF6T4AzVYXQHM7KE7iDaM1oqlph0I92KSurpWXsrPNuCIDukZAi4SqRprxpchprD8rknOaLht50kUCJEhtychF/i4efeE5i0w0WagNiuL2tNeMpjy2Un6eCAOBZm/gfRGSZsBVvQBfFM1IPTrQAEAky+U8aAAXQHM7KE7iDSMe0WSSRroeb4so1noJihBkaouv49TyiwO8Mrz8qQ6V6VHSKEvymUUh3nUXPmdam/qbRU41guFBnZgnZDjR2K1owxzDoY88LDDTeA59GE9P5DQDiNZUGSPJGwBs+5cB149//OOwUbeA4iTeMJJAIwkZcI3kTV6aHzpDyw0glksWtQGTBgxW1IHaW+PZE6VYZBlzqw0Bi7xFNJkE2KgNzZuMdjz6qfpuIHdW0E/tpwGN1RY5NhJoVBm83fYbATeXrYFyvmB//Ld/GzbqFlCcxBtGHGg6Y7uuRJffPIIh4jestYh+WNlc1JUSGSVYRlCLjsZUWja8ALSWLVriRQ5A0UgaLU+9DirNOB7IsCKeiuGXIGJFNN4IpCtPqaYReaTA01locR7qkSqjQaYZ1VgAIuVQGyXFpwGh1O1vA2huBcVJvGGkRTQobXSxGm2czuJZHQQY2s4zK82FSLZB6TXJG3CptqX1PWwZlrkOox4Lt1WdgbygbdsATCzDOYtCBqBJBAwgOhBlg55aVJP7ny2JNiCgtlWfmoKSEcQ2PxoAbI1UgLHqtcgG1m/6GW3yBP7t3wXQ3AaKk3jDKAON19jTxgBpuFD/IXJJJe3iWS+xiMu0opMhVbalRiid1xvMZav264LSUp08o49VjviqQDCp40CjlVOffm3IlteBylrYt6U0FIpitAhG9lGjHAEYAw+phxgH5EUwfVkXEc3toDiJN4xWoGGbgPvUlH/Rv+VZRiCxdlvx8ou8XRk89DimzJjRLA3qMXpolAzMy7ybrQPBzha6CK0zIX2lntSOtnJTmbUJAR0jQzkDBy5fEi872yKXcj0kaPQRL2y8RX8AIiqPpU9p0Q5DXob4zKOa1EVHaP0mgOZ2UJzEG0b0wKYkbgTRzqW9kYhnhxYCNVSnGXapdz1OtA07DWPiOqBIzIqa2m8GsMsyXOQcmPaumXRlLIqgNBm6obz8Z5EYMuByTJYMDax4VKJGhrQutHB+AhhAOg4DDGu36QSjn0sd/vbv/i5s1C2gOIk3jGiN5uJwGNZYNKCpRprZ12IozhKPaBIzOHLNhO8m0yKflSfz2qmeohaKEDpFkjAy1ZkegVMSipQ0Y7pnZ5oFXp5+6DcCXeShI4IRirLRARtmkiYNP6VI2ytoVLDhkYXUh8m0xgDTaFDfScTF+EREczsoTuINIy2iyaQtHA+eOW0GUCIRbhgbWCw84wUX1mdRB6em1+B8m/ogklHRHrIiE60cRkaAn7UWZqW3Zuk5maryjGcWGXnKZjy1iEqC19I6sbIROJehzwiUf/f3fx826hZQnMQbRhxotAX1TOh5lmrsUm8e5ToGXoDP5QVUJHjxYy1d1hF4kFGSBA8ENK61KIW3HLelgzeiKRUpMVNqAtksFcbrNKDyRjtToDHSWmoZ6MP1nclH0VOfNuv5s716tfzvA2huBcVJvGF09+7dLUvURwNoMZ0MP4GOXNNAhpR+80VwnpaSXjcRAjWNpGzeVr7OBvVD422N0rrF16OXBEooVwCHNT5PVCTncAAFJk8++0L9LYDUHgyFvw/0BH7q0lcYCGqJ0YZFIOjaknISW7syAEfy5hd+AM3toDiJN4z2ps5kPQIa1F6ux8h+iGd/XMyItc6BQA4BjRbNEIjy/lVealHbOOZUay3PG5E3JWXVochrrUfR0CH1D1rKyIIcfDpeelicRSVqG+LFDDvmVVv3QNMqAW8BIpR+FdHY7Nzko7//h38IG3ULKE7iDaPyHE25h6VnyxfPreiDl0mDLss4P0Q6CHGgKb+lEZH6zmTN9OSkjb3JbWtLOJW3SDvpjmLU1BOQQ1HL+vtsM9B167nOb+AvDTSBqYgypjqXA5xKS6nj17cf69Q+Rr21QQKNNyKa20FxEm8Y5dQZMtCVtoX+fPPyLcKeRXVplOU6DzLeaOfZ2H79t0upyL4W8PFy2RbtTLN0QbK86zBW9LK3XYsY0gAsGg+cjhoNs2mcN+FL/6NGJjISUUET/WV90dx1O8hahS5HrNdQ1FdlXf779/8QQHMbKE7iDaPywOYWI4A1Bgp3alyx2A9harvMLMPb2vC1orG9FqnIiEzqkQFOfrStVySxx2D6dBsCIal7/XAYGK83stIeNJXzZYE7Ghd/ENdqy/laYDCLzGQKDG2BTgK8+Ni648YI8Ld01HnIKKcDsxSps9tCcRJvGNEraCTA9FQsMTLaXSvFeOE1kfVfNSqh/tSPbybo+fTrDd2mAzYuCTSe9Ser3ho32s0ly3B0RPMy8tWikHK8SjDBB/HSNgFYYIDGC59nsaKgJQ0ARO28AFLbDyjG9AfjkFFSrdnK/iGA5lZQnMQbRq+88spBAsHMsFIbK0pgQQIEL+n987UFLe2lfTIgkSznWouVHqNyLTrxpslke1WvGnXgPdoICDRee4GGqTBEDqgvinosOV35JsOKrDoAAeCgRUcdn8P24PAi6qkNksfaBdDcDoqTeMNo3XW27UTKNKazlmIDxUP/RLN1C9mu8V//7YyXtsZipq+Yfe7bpUFhOTbOc8kgBoCT70aTYCTbItIAy9Oeji0gQ8cc5Lv+ckfZDn6DQR/aNBCYgZAGiD0Y9Sm6kSdJHeWupWeKDHHNSJD9x3/8x7BRt4DiJN4w4tubeVTRKBu+tmYhDbA03vIZG49BllGG1Y6iqNz87GyMKrSIh8u3dNLWYaz0GBqLp24WOcp2HWBtkdAsskJG33IIUPQy6MINNYqYVuUO7GhhANdHbn0aLA2ANku1WX81sEIRHNUF0NwOipN4w4jedZYSPcEvaLMN5NVn0rYRZ8rvueJgJTcHNCOgyGOk7WzzRD6Sjyy3Nilo6TEpCwNFmTAPiHTRXBEEU0lWf67fWM916nlSRMDLh8irNaz1dH4LMBhRDOur8bRkyTEOQCa2Wve8eLgiwWwELN4mgOZ2UJzEG0YU0WgL2ERojaQZ4GY7kJds0Wgkyet1rMsAQCAwzB9pW53vSYQyAFUT3/U5hqqM1F/4MwDx1skXk8p2EDy6dm3zgQVwGm/Zpjfi+u40BCq6jsu0H5aPec7GFkBzOyhO4g0jCTTVCFenkcqKUdK3GPfrI9LIzHaetb7j2o4ljwxa/q+AC/Lyi6nX0kQ9P98OMXVLOLfeiiwNTDxRkMVzjCr6VOhMBzrvi3Kbyt1lGr8GCEsF+yFFVtvo49IiEeKvAhYr72TyiGcZ2+Z///Gf/ils1C2gOIk3jO7eLW8G8KxNrGsvqZxEK9KQ1O0u2wwy/9galytfGTMCTUroMkLRDT/mZVxe1acDiYKyFi+pG+fJ64a+i2bGRx6StBebVsO7LAO4dG1Te9eZCjTir7aeZf2Whh7x94Desp2PBbxvrYENAw0x9zClJtpKnSOiuR0UJ/GGUf0ejXg2Br04U9JskbwYqoshPWPxHGSk8aKRGw+0jnwnXQWThI2kFp1JmZbxtQg9a8IqzXSd1Fcbxx4gHH9vWi54/UmCWCePhbASpCwdUUoMHZ9RyOoYM++vPd9TAIwasnNz+b9//KcAmttAcRJvGNHbm+lBx6sYVAlMKFVmsbF2j1lGH+ld/1bj2NI4mqFGO+UsnZFh18BPmxMuW+PddBv3mPN2/Bs/NTgz5KMUk+TNMZBHgPyYxi6jiP5aSKv+6k42OSYBXghUeNSDxoG3Po9j5nz/KVJnt4LiJN4w0t7erO34kvX0mxtyueuHEzKqM/m830wnLsfiq/XjfTU9vaBb2jZwkIba4oc99WWYU09filZQGytNhoAF81dArAnvjL/59miFvwSPMQrq58bLX/4OoLkdFCfxhlH/4bP131o3M8jUxpNemy2wa5sAfFuKR/1GnuMuKE51bUh4wRYIeQAHGTUv0KDUk8VfRhWqHqVgqNOiFVs+gSluIyMJS/Y2Cmrd1bX+AlAYPzRGFMmovwNobg3FSbxhREAzpozs7c71eLMLWmpLtve045QBgK8XfeYzn0kXh4saNWnrKRIQvFFLz48njvp+9Fubs1EnMsr28z4zYGmVqe4KlERbvC1w7uUQw7F+IeNOz61sA6HNHB5AUiOhTbiW1uJ9zrbXymD9Nf7LcG2itBo//ud//uewUbeA4iTeMJKfcs4kjTZ/OzGVlf+aXZBpltlGASSTUnByx5cEBe2448M2A3Cy5PPjGgFtfGaevrZxwhOJNdmDnZ32H4wsA34L1Obggz3+khrtyyB/0MaUM2nrSYG1OcCbE7SxczD953/5l7BRt4DiJN4w0p6j4cZz3IHWP9XPb2a0PZmeJveseaAIhOtT385M6RSDLHkoNScBg8aD9CFbxrdpc6CY7fhC45PjR2ChAg79u7TPLMyiI2/kUWWLXWeyrQRFbd0E9Z9FO50eBtho4+I6oTUi+h0Rze2gOIk3jPLbm+m3B2ioHZF3AV6SFZXM1nx4+6pn2mIhkrVFI7xN5W2sw8jjbl5ySqryWf/V9SyhkAkOnYzU0lESLEgf/gogT+TUgT17FiWlthLCIyH+IsqFtSGDP4AeBxKQQt26Jn7bjwDT6tUFfX6+zHRYZbjp146RbNQngOZ2UJzEG0YZaDyGMJM02LJOI94nr7Fo6xoodSRTUtraEUoToY0Ie6McGeFwHhTZSMCZrW9ZICt1QeNCunuOEeghXTUZ0rhr47AiqVkbsy+VzaIhAkHqxNtOtjz/S6TObgXFSbxhxD98lmlcM8EGCH2IDBmxoT7/J4AG9ZdGn0dWkqe2yUBSxy+1ixGBmASpPWDFeWoA1vjg3XBeUNHKvSmn2QYEq07yrWMtB60+9c/i8L4I6DQ5jd8W1WxyYHvqM4toxO8AmttBcRJvGCGgmXm23i9sEr9Sxx423CyPBTK8bCZTM/ych2cjQVFtWXe1aePhfZHu8lUGs4hmD6BYQOGJKtwgVQpNubAfl8PmYU/EogIHB7QulTbX1QIyma7713/917BRt4DiJN4wotRZBzYpbVtXmyFri+Ltvh68/KVfoJcRCZVZpEUs3PtH4CLLNOCC6bHSSNcvz8OFLs8EDu7OawTmDTfzR5Ay0vDyngFcvTqWPnV2Bt5HhnlzBEo1xcWnqXxnSAeDITorhays6YjGZo0xgOZ2UJzEG0bamwEkeUEDGtttsR6tO3j57E3VIaBRx0U6grUYyUdrIwKZ6Xi4jg38GohraSU091Nw4AadpZ7ovNRyAJ6arqi9N/WF6jRem/owakE6QgBi/XFU0+oDaG4HxUm8YQQf2CyhzPr3sBl4lAKSJKOERWnvSR1p/TwAJesKQPRGHMo4pLr1dbagj6iTmVJdJ5AbGjTdB15MHwtoNH5mykiJCjV+FlBQuQ9gSuziijLo30XXTf7W0mqz6C4imttFcRJvGPFdZ/kvXwtBxqalsZBHjncwaQaatgBL/hpJr7e17RfUJRgdHens2ACQq3PKB8mbgaYNOmKzADkBaTSeGnDvin4AXxJb4qL+ORSLx1DWGPVytnIZZcgNBBJIZnJn8ro+2+9//clPwkbdAoqTeMMIrtFwg7WlV5DBtYywNAD2zquxHRmUqsvm3dNxXhNAoMjTezwlMwOxwqSlmLRxaeOEbQU/U7CyRVq+lHOmh1ZnOQJWHyRDGuqhflnMNuuazrIM9d6oY9Z+2MLMwtmZjJ8E0NwKipN4w4i/GSBTSXvxp93HZ0q0V9JYXq7mvVsg0IzjetS1kUCjEX/IkuiCpQM9INmUTcNrbbxRVsfm0NvYany17+tshrvo1d7KgBwDb8SitfPykpFFV6dEtVIuiv4IKPI8SzCSug3yUuqcIpQ60+aAdPnpT38aNuoWUJzEG0ZyMwBOFzWj2erbzp7O4G9GEfHB3v9okGfGn4BwFh1p8vlrcqxITc7JkBKj3wMAtR17Gk9Nt9lYELh0Brg0SpKk/AZq4/y3tmOkRfUQhNDDkA5dNL5aXxiVlIIObEwdlLWfiGhuB8VJvGEkI5pMmhHkZa3uDIBQTxeTDQXSk5ZyKFW2bkzYPu5lrZ/w7bbW+gwy1CjakWk4zosyM94022yTATK0SD7iLf9agA0N+hZqzcDBMuBU7uUB+6165P+fDRZD8tB4SuC1ZPPxBNDcDoqTeMPIWqOhFI9m1Ln3rG0Q4IQ2DcwAzk6p9e21nV38UwNceRkVzRbxedsmu0wB2n6NdOZkvbvM2qXmWWuZ922RjCZbk2GBSIssluFuN/tVgLE3EQxAw+soSgHtUbSF9IrU2e2gOIk3jF65jGjWdIOyWE801vVvcJbGTxpeuY7DQUIaNk8ExHWyNgDw8vVv6t9ArPGWY+J6ofacT/nJ+1JCZw4MfCceH4cHUKy6FrUkNb1Vx7CBsAo0AESoHTfysu00ytn6zoBMpsbQWPo+S0uTbeNfFB0DaG4HxUm8YURvby6Lr+3pbhR9WNRHC0u3BdeKYDRgo7Z8PWYWAXDivOVYJJjI9pIksM02E0hgmc5hduTP7GeErEgRtbd4SOLRXhsrH0sSdXNw6+ZbAZsBlBR9LbktimEyNr4QxJjMMTpa0k9+Gqmz20BxEm8Y8c8EaJ8DQMYYGT4tfaQZSRkFyFSOd92jo0Pbjj0DDq6TtR7D+810KtHcYRjLnnF4wF1LjZX+ZS5XfRO76cggpwRBF/FCxx6g0dJvJlgMkVOCEc4UkLZ+si2MeoSMiGhuB8VJvGH0mbxGk0ZDRwTTTxYQdJat92w9wMH78CiGyAKzYuQvut9cdy1q8gAbxShWBMb5yDShZ0FfthVTqbZHv/s29rM4pSzrvAwPnU7TXfR7A/gz2k4uQKZFN/0WZMRXS4N1cwojmWW49hrIorWfBnDU8qc/C6C5DRQn8YYRvb1ZIy2K4fVUJkHFihyoTHt2BBm6Mb1j66YtqFu7sdSoazNa8guibh02C+uNXHQvvn0UzexDFh3wkmVNb3qhpd5P8kB6WsBejb8xbisdtmc8SN5aR+s0tCGCgVcAze2gOIk3jDLQoCfsEWh4UkkoCkGkAcnIkwzFuEkA9ZkZfo9eXhCRtNowUe3dPVbLylDFvI92GaWorPUbPg6pg1z7moKYOJbOAmxLUU4pKOXsNz9WQSOxh4kXVM94K3zqv6w/B7YAmttBcRJvGOU1mn59JA0ec12/SMgI6mscXV/FgNOx1bbpQVqk1LnsxBO8O632FXUe0owzr+M0S4tZpM3NVqsFAH1/Sk05NgzMgEpLM1m8JGnrNPRbA5qxba0d2o48lpoO43zU6ErU/+xnPwsbdQsoTuINo8985jMHebNJA6G9U0yCkTTMHBg047cnfUTttTbHGH8Eeh2gsfUGnkbLD49WQ6foIefqmDlA40THqM6KpjRent9anZYulRGujFKG3WLs7wH08eg5K6NjCW4BNLeD4iTeMOK7zqZpncNhWOznfa1dZXSsfY5ZpsXQszRtvaP33FFUJcejAUGRvS7zd+20NOFsV5pGVmoJUh+w1UJy7tG5weNt0dAs0oF1Na2UCsBaUY8yLi/QUJ3nYU8tGkJRkQk0ouznP/952KhbQHESbxjt2QyA6jjJHVyzelmHeM4Mf88kDQ9jSuMrwQLx9ow1k/YA5wxwJb+h/eV/ZwBAZQrNE9VYesl6M4JJ/YOuVDYDBMRbtkVpssKbHBsNKEpbJBuBjHZN8L8BNLeD4iTeMEIRjdw1xeuoTHr1sq9lxPnzOtLT93j72i41ZECsV8NYIEbl1iK6nAfL22+fwp5vNrD01eZLS8+tZext3JwP728BEQIdnu6S25VnkY6V3tLqYRuxS2LGY1nYLFSwLL8DaG4XxUm8YcSBBtFBbHlt5Z5Fb5j/MUFIizqkXGSksf5je00Pbmwl2PKdWdbGBslbGyfooN4cFiDuiWq8ZRZo6GXrvy5QlwBXenaMeh228gpqxrqOlKeWS6BR2qExSHCayZA8+FhnqcN1vvi80CxMosTaVrk8+KaJIoMrgduaWQeUUj+wH9SH7+UR9PLly/yF0/Rf//Vf6bd/+7fTq6++WvusD/tdHC7/f5Hu3bv38sHDh4//6A//8P/GowuguXGkpc6kYaAXU1KZpNnCM+eTieftrSiolrFdY7PFc834z3SelfO6AUjIELK+8pkbi1C0guo8wKKBtOzf8VxSNTaziETTcdanlNHXSM+Gtia/1CInT8Qkt0CjiPGoKCr1wGLxIPBF71cb2jNDrRnubly8vQSrrYGMNPsxjFvFES9EWhsOgr1/2dClS7+yVHemTz/9NP30Jz9J//Xhhz3QpObg5f/u3r338jd+4zce/8Ef/J8Amv+/0Cuv3FlPt+bNc/JEDshIc55tR1e5KNGNqkUVM4M9tNmMpxaFaLukUHrKir62gwo0KELSdEWGD/cp6zOztazSpu/JU5UD380YngFd90Y6xG9pJqd68MOGbRHV8PbjmJZu95+mUz+kZdp/BnBLK6x1ZY5Krewn+67tO72WLoo42+olMKZVRv+JBNW4A0BGIKi259ccCxM90RBR7VudFbH+ingAp5IDzW/91m+n//E/Xu0YZMctA9O9DDT/838+/j//5/85Dmh+77u/9+jp+fn3Pvrwo8+9ZLl1Uv3FixfpV+/+Kr389GUboOL5UfrWaqMZNjQJI/+er3wxodwSbEUN2tuKm1G2b3rvYjZfr0AXIVrPoLSRlrKSRtkCB6hzvTj7cmmE1TEu21ZjIF8bJ2pXproHwD28Wl3x2AdAEB4qNzSCYwJihnmYGRF0bU8NjzgXUhfr2vFuDND4oEhFG9OePpbe/AueHoDtjDiPmLrfCfKE42HAU/vSLzDvULdOsNKepavg81A9Gg7jGMbAo6rhnVP9TwgyW//OVlz+9zIDzU9/uqbOMtDkiIarkO1T3iH66r17Lx++/vDxHx6bOvvud7/76MmTJ9/76KOPPoeM3ieffJJ+9atfrchHBkZ+VpgUWoUZRk7zMnlbCSoz46MZRpnKQJ70DBCRV76OtVi1QZf+uAHXbO0DkQaUW21Cl5MHrLWxSp0QwHfttsFBAFEiLOsa6dJ8S/9cB9JrBoaWk4HAWtMb9W8y1n+HuenmdhuHxsdjtKlM03PGT9YNf4GOUqZHFgQx8j55+SbPmhsPcGn6m3qxttXgC/Dq0lG8H/2i9ga4cX7oGIJV/XecM4v47sSVCIw4QG+sD8yxy39fvvw0f3iurtHcy0BDbA6HFWQO2xrNb7z+G4//4A//4Dig+f1LoHn/Emg+/PDDz6Eb7cWLT9K77xag4Td5f7PX0UFjym8QucBblZyAABzYBHBmxlTaa9u4j3Jt77v/dgwybtY4a0RzOFSjfmB9tfH1x70Osp02DsnTchAkzcalUTc/ZKCUdprOGpDIelVusm9q7dr2Xqey/XBNMmBtbchWH2/wLb28gKW2IyWVPipgKP0Q76GejCU30k4gGvQBfDoQOCQGign2RYCKeCP5WvlMTyKXndw6ok+fZ7veAc29e11fAppX77368vWrrNG8/fbbj549ffq9Dy8jmqZ0y013EQ0xXMa0QLlR2qA0D7a1L0PvFGU3JEqtWZ6oh5C+9NLFmcc/k4uMEOYn0E3h6Rkb1XkW+aWRkym9Yw2zv54bzgMJMRdPuf6S5HXBtzKjB1T3AF+91obX65RxWBGF5IMiMY+x4feh1VY7R5ZuHnCx+nHDjdpK3Xg/eb61trV+k2UZ3mOBD+36arpu/KsDQG0BoPB5UdJ7SD/EYzgWWbKqNVUrmRWdevuT7frPfvazBjR373WAxIHm4cPXH//hHx0Z0bz99luXQPPBO5dA8wbyCk2gyb83I2eDih5heOos0kAC9UdgoKXPVDBNvcEp5e3asWRbUYTmLcsyzss2nuWCQiDJ+87muu+PF8c95w0Z3U4HNomtO7Upsq102KhrIy/IaNcgd3I8ICH7sFbrWNA5sYwvlMe9aWOcXiBBMjWjP7RLqYu2Z2Pp+Gg8FR7lWqGOE/23lNJCQLHo466A0oFGbVRlSjCRMkluB1zt9unbwLImyNwcQPfM1pODg32tt7OV/+VrNP/rt/9Xunvvbnd9Hvj25gcPHv/xH//xkamz3/+9R++/f26kzl6kd999dwCaouhl+4PIDyb9hpc3L/1GfXTP2EcWAGlvJHZyTkNEcpBh6Xjhz4wxMsQubSZtO3nM05IRYx2K4q1b53P2GWhNz5k3RhHgsgDLwop0w47ba6Q5SR3QlIZmf31cLUJBsiyAqSbIAAJUpkYIbBweAJJllsF3gRjT4wDaNINPRjfBeZ8BpCZT8pIANQQ7ClBS+/ENDra+3bEzFYiok516wJ8RB5oP8/bm3/rtCjREL19eJFqjuX///uM/+ZM/ORZofv8SaN7/3iWifQ4pKCMaSk1UZQn5hKCZ90dpCeSRWd51bVs6jHyd3jb3UOkEzSMj5g0APnJO6sWSUznbNkEermvgYs3JsPAH5KFyBAJyrhEfTRc0Zu1Y6yN11eYQzlO+ds7ETkPapKLw5yk1qAtdC2KONF01gzqcMzEPmazPOljGfi9AafXy9ywaomNt3UHbBda3aeXVwKJ2kh8HpPp3A20wpoX9owGtNk4IcNTTAJ+BFw2N8xsUsQFBzmkHIp1JGoEK8trakwOXmeQHNvMazYf0HM29Vyuf3I5vBnjttfuP//RPjwSadddZBprLiKYNXF+jkUBDhG5OBBwej162teRoBkryLOCWEvtHlSvldXK2c1QM2uiFkEHl+qM5sOZEGw/XZ0ayn3QO0HmwAUtfL9BIm1sEHDOwhjyrNzjy5zwsI6pdh5w1mg8ENIMeLHXT894HJFRugYzVX3rZ8v706AHBSgGdVt/Lm8mgskHf7S/Uy0hjmeMBukl+Ekz4/a/dDyoQT5wATJsMkHqjg2UoVzkNditf3xcENB/l52h+C2wGuFj75Odo1tTZnxyZOvvf//v3H7337ntiM0AjmTrLytHrUXojfljzeaZhUMr48egJr6Vdu1x7ccDPmkjPVfKXhk0aN48h94LTXkKG6AI8r4IMRf1N3h+bT95HyksJG2TtnFj9tfFsR6tnpF0foy7lOkNAqI2n6nrWPD4ZQXjPMRqnprc1P8OcTqJaCwQ8xkpezxIgPHxmANCVp97THozzdqzxRscIYLRx1jZbdIQiEcKHQYYiZ+u1FfXOApoP7lAM580RCXW8WWpoiIJSSmORADwe9iX2exkdn1yStzfnV9B89NFHFWj4GMpzNCWief3h64//6I//6MgHNn/vO4+ePClrNHXQbLDaGs3MuMyMsUbo5mgebOoMCLrwZ0YEgRzva7XVZGnGMh/z18h0MljagFRAoqoM6jsZm+wrjaE1Ju+c0riW7Vv1M8CBfOBY+k8mV77UHnRDkdBekOf6k2NjGV3U96p19Zhd49CwHgESVG4ZuQEURVvL0bDkcz5TINvaamCl8ZAL6x2fgVdv+JvsJXUGXrZRIqcF6dmBBatdWF9yoIdxdMv6Fey4sy1psDs8egEAyteTaHszpc5QRLMCTY5oHj44fo3m7be//ejp02ff+2jdDEDzsXkFl4q8eF7eDMCBhk9itS1sclVFdoKPBQIpNaPjNXSaHpoc6W2iiALpCyMNYxyIvAYTgdswDnZxeeRy3jPHod0Irb+WLlvrS6O1j4yMedt+/PTkfzvfs8gEga5G3ZzRPQC8U48cdJ0MfRSj1RZ1mxEbAWA0gpKPDTa9LHKC9kZL3rqF/ePpDw33wE9JeSWKalg/+leAEP0+pBFoKj90zJdDgB6yL4GKjIo819eMdysr/3bXzyJsAop0MtB8kjcD/KSLaDitQLOmzu6uqbOjgea7v/fdR0/On6ybAVA0YUU0yGDz9BXRzOuc3ZhWqqvKLQWdHojQSypRNCLle6On3jseowgU/fB6FD3I8WpkARxqp817P3/l6pR6tLGNaxljumvpAEIbGwRJlScGdn4s5xWN1UueczLz+rUydL3mMmvDAO836tanU1BqTh6j+0vqawGFPOa6mYDUKpqDux1r8iEPVifB6pDw+ZNy+joANExhJAPqZs2RAC+rn6RpPdOJ95ER+6fbrrMKNMObAcoazd1LoHn48OHx25vzu87On5wLoCGvIO86u4xofvVu92aA2eBnbWapNc0IW7KQMbd5t2tsBjScTC8k82ReOkqtaWOxykme9Z0Xki895d7Qtk0exxhcqeeMjzTubTw9OKHzRrz5ehv6vLUlb3YNGCNNdDNqQNLpzYzSel0RC0W2NGaWUzTm1Ud+A4guzTDKa0gDEylXyqi/hT7TDQEToFmd0uKFdPVWKmthoCT58roBjjhWLP1Otia/tsCgOwBQ6+up7/TlKTaPk6Lc35KXn8oaTffAJoto1gA/RzSXzO/dubu+6+xooHn7O28/enr+9HsffvjR54ab67JnDq1o15kcuPW1Qw8hL5MbCeStWZ6tJWe8wMv5R8Zc6uLjZy+YS511I1yAPi+ccx7eSMuSJ+fSM1ZEGshrsnkbZJy5fjwaRuBM9bP3pnlAZwYkfT8clWlzo72dIDPp3tqsGAhkoFVgr8Z51EMCEedrjV0DB56iamWpPxagwduoqR/UXy3fgGSpcAoNdv1N19FMN8f9OxxPgBaVV7Bmcznj0+6bNABXJ+9wGOupz6GlTKmGgCav0XwpRzR3++doeOrs9ddff/xHf3TkZoDvfOc7j87z25u3XWedu3nZNUc0OXWWtzlrobyWJqI6mozugmc5xUFh5Wbmk1xkop1JxAPz4p79Vcg2OOQr1cbtYh8igrNUAX4bgMW7MzZGyI0iD83oqkA2shf9ylg9kYPVxhOxcj7yOqO/Ml0rgQoBpJeaoV+PeM12enujjQy9FQF25dWAzo0WaqM5Z1Y0gyKCNsLt38XQZxJ18Ht3ZtC5fYDAYJRJfbTIwZwzFE0p0YcJnDICgW3FHLFojIDxsKW71LHK+eRyCXfAfZZtz8XFS/b2Zn17893LiOZKmwG+851vP3ry5OkKNJv0Yuy2E50BhoCmnqRs3C96ANFuXuSVckNhGVXcRhjxThb162WP7XrDhKIn3s5rkJBHrfXngAcN8naxdMZxcwQsjxSNRRtHF9UwB0M7h+V37zAROFI/DgQdf0Tshuhl6HpQuRa9eKNpzkeNtDqnqx+f5GOBZT3eeHoi0V6/fs6t9lod0l+NWni9EC7nvo8+yj8cVGoJMNKtGeij6KOt40ga0n8iElPHTv8qoAoB1dADnucD3dwTkNBk1mNqzBznw+i+c8e0+/Bavj9fNqBRd50drmEzQN51dn7OgEYo+MmLT4ZdZ/KCHVMo3f0JPWhRsCUEk7pnnfhYHqpFHkDz9MdRydK18fAhL0Ubq/admrV/2uCWOQTo5pfU9B9lz0C1Akj+LbFeem6CrwSF2YYMS6c+OhvfTq3x4oAiDcshwYyDSjNAVMuARyv1yyQ/w4HH4gOfUl6ka+2qY5DaaZXO4HRswOCq4JX4OaKonvFpSg99O2BjVC9B1n9pDFQ91PGwfgjUpJ4IlAeZ2wwf6LcCZiSzOxcsA4RsoJUharKTmLiSOquvoLG2N2/vOrtCRJNTZ0/WNZrhJsxAo7xUU8uRy3TGHmPG26Ac9zCwHUAj+8E+dLNNvFOUCvGCjOTh1g0Qb0frFpqBlX81g8/Hxc9z5SUeOJzph8bMvz7JeVvja/WHgnRL304DNz4HcmzDecxlsw0XiV93mz5Afy/4zNpoHjOMKlSgsXVDQGP1G8upzDKyqQMBbpStMVtgNZOl9hPZAtRnkA3SaYPePTy09oa+Uid9XBsoN2276GQGNHIMVKIBDWVc8loxAc2Vdp3ld52999578KWa+df6PZr38q6zTwZWKLLQborStn8gb6wHyguvygIwL2lA6eXb98FetaYr4jUYyhLadXxm4IxuyFnkh87LqEuCqTF5Tqx5nO2Ws1KcXuOc3zCb2LVl3LeDDK4vAiJN5jF11QCBNCDiwR2CmQwP2MlyTf6mKTS+KgBtY0PXYwUapDfVMXlaxIEcCBXMwLghOCiAYO58c6S+VngQn4fuODDe8hkYTY9RBm1yQDIEH9AovzTzZz9rX9i8J9/efDhcz5sB8q6z8yfn73xUPxNwlriVy0Dz3nv9czSkgDYAy6OECir1yEv18BnCStGvtdNPkGace5LAuXk03LFBvQ74myk6tYuSp7+WBUdXnrGQDhJwvYZ2np7qryOrLwIvri83EON1pwM9IjkuKUMHY52fBsBkEDznxgIJD6hoelkyazslpafJneoKohVp0DXnqOmUGg/lt+zb/m4t6bxxoKMGh3GdRNu8MOpYrjt5nns9l+ZTWHMFyHNu1TIWUA1twHnOQJMf2MwRDdoMwD8TkL9H88fHA823L4HmKXsFTTMQ+WLIAEMPbKKblJ/kceF3qbvCrJSFdTOXC3f7fHIa328mdSEAkbvRqE0mLWUk5co0ICcVKJhxKXyAwd1uRJl35R/v0i42Pmd0LOcRGWh6FY4EbC3q0Yy71IPPVZdyowiDXdwzQOG85FzLsWrUAJR2iM0j7yFi3gyRem0uLcWqGQ5trD2bTWYeo2F8yYG1xq1GGU6jtT6zw8Y6OB5Mj6SMufu9tbNASdNVylifs+GgUIGsufbWmseyOWoHIN96Zmg9J4LP8IR/Gvki4JL6cEA7iH6Sj+c6G2QIwAcarHLXzQA/+2kFmrt1e3NpUV+qeWWgWTcDnNc1Gjnxn1Dq7JNPh4cR5aCh8AVHE4VPmQd5gc9SVqNsnJKzeLaychFaH26r/LZ/pM5c3ti/5DnRPPB+GnCh+ZpR/QRD7s/WQ6rM7SJE6SXtvCLdUfpsMNppPG+ozAKecR5wxNzuLd+WYkQSQJE+1vVpzYfWRyszPddJ+Qxo5LwOx+Vg4OEFDpSSQtcUnzMNqGqpNODihpiBmwqKHY8lVdW5sebpKgXMVKDzzJmWMqPNA1LHSiyEWWS9bTDy0PL25p//7Ofr25vpOZrK77qBJr9U8+OPP/4c+iAYbW/mD2xa6YXeu23ePDIkCGgk33FyfDd9z2jzRo7pS9GHo70Woc2Mzqycz2k3LAevvUbXijo0j1fqYjkhnogEyUHtpB5eQLHOpbnZgRm9mX58Dq222Oikds2lxMxGv/i7tjmMhkjyReez/Ny5NZfpZepvGHYNiGU9/1tMnq6PlN2BpDDgla/St9bJNJrBs5eN66RMCCzatbC1bdElzUjfstu6LHkKr5KuyWzzK9B8KUc0d7p+tOvs1VdfvdoazbffeuvR+dOnK9Cgm7VGNGKNhi5UrvQwODYofwTQ6tADeMRfM+pSj7VOej4GsHmiqeGmTWP429KIfH2lv7a1CIfqUJlFHhBB7bRzQ3USrLZebFw4spF6mWDAb4TNk3SDh2JsDxSCwjp9zc/yvCVo7wF7L9DI8mH+NmOqzfOMj6cOydWAQ0vtePmj32pdKTDHtP7vbNkugM3wguinB/IxvdbL3eaCpc5MJ2Ew+JsK1IpAbDJHcmyjPBpfStLSd4Ck8M4fPluB5sMP0xe/9MUMKF09B5or7Tr7znfefvTk/Sfr92iqeqwHj2g6puwi5GUodTLeDD0ic4+9n8w8iYcOvCWvvR47oia/ShnGydtJXbgeSJ/mifR+SLGDPQ8ympI/0mc2bk+0YQEo5rfdkBsfmceu5WIL81LvYqyj1FMbVz/naVuL8Z9z7VqbHc94Iv1QvdofpGZ67AXOgYi+psac/gVGTu2DAEQaUcM7H4y1ML5Vq5lMAyTpWLZFW35ncpDMIYU1gJYclw5Eg87G+SgpTKq0+dUasjeOiCkPI29v/vnPf65uBuAv1Xzw4OHxX9hc12jy92gy0JADwBRagSa/VPMlBpqiMDe+KnjWftwAU+TiN3SjfM6X2jU5Z51hlPz4Bcp1QCkkjzHKx3w7L3nVWr8ZcKLx7k1joWPNGdAACwGp1FfKk3PB2/BxDZEq+3AZ6oPmhF9PvMxyDiyHR8rooxryUHE7pJucGzaKyssy/Oh8W/cAate134Y+Ayepg9YGXReobfu5tH+X5oR1L9ikf51GnNfB+l6BUTchi5yoYf619BQ/Bo5DpUNLx2s8Bp1ylzMbuMz+SQDvBph5M8DPLoGG3t58927+8Fm7Z7u3N18CzZ8cCzTfLkDDtjf3RigDzXvvvTdENEXX0VjxE4J3d5VZIyPE+XC+sxSW5Kv36T1w3tcyqtqxDrAtrZEO43oK0hMZD94OzY8GUpwfuqE0QoDKZaIxeqIQDYwQD8RH51eDwM6w6NFdO/8z4ObHUreBbz7VfbCdJHBJOTbgoDkoQuSakWmglrSvvSEfHZcynnBK8FqDIAccWaSf5Km119qtLoT6/Io9H/VY6OqZG8gjMT40bTWjx0BEOgH15zIep8ani2rBeBFYc1pTZx3Q3B0CgQo0Dx8e/2aAb7/91qOnT56uQEOfRybaCzS8H6+XxD1CeSItQJDGQNZZBkIa997ojFuQpQFGoKB52Zy/BoDIA+a/Uepp7zxLGox/fl/dMgf2PurpgVsDmnoTGAAi5Vht2rmg6wdHm7oDsf7L6uqdKvTswYKfT+mpSoLgJTxby7Gw+KE61Qt2GEEtGtfGImVa8nl/Dgbdb6OslDfj6F3QxzKbMe/sMew3GuRhjhS9et0TGbiU1DZNmc6OdfyrEkIHjmHlqKX10jiGjSc65x3QfOm30p27d7prJP+3vuvszp1119nREQ3f3iwnIwtZn6MBqTPksVrhP0fePQZyDlpMjzRkXJQ+/bkjI0SEDIY0xij6k2TVdfovzQedpWhkX6/BRfOI+w/XtWN8cj6xLNnPaj/KJE9X1i+Dvt242I2rzZeUPQN1aKTZzYzGItcLLJDQnKu+HTlIq3bMpuHtvWPEWrTqjtmrfdC548/azPSTdQiYdMAopdLQIpme30iGLKvzNhhpst3j3JhALXS39CD5XZSj1I/lTL/Jdmbp6OT/MtDkzwRkoPniF79U3wxAxN91lj8TcPRmgG9/+63upZoQaMBmgBlZN7CWVtH6zoy6ZUSRXlIuMjyZrHeBeeXxcfdlxUBLoLFIAoIGq5Zn3zwtAxDoghey9bHgeeZ1Vh/rXG2tIZARH+noWM6JbMP5jFHP2G8PeY2wVW61Q3MLjSA75335eJ6lvO4BaXIUtwenNaM56MLaqWAzGXcXTQ3pJMy/41DvMQYYQv+xL5ZjLeB34xGpMijHAJ2hXJtrETWj8jaC/p56uT1HU4Dmi5eA8mqdkvWeojcD3L23fibgT//0T48DmrcugSZ/+Kx7joZ5Z5988ml9BQ26Qfkk8JtYC80l0NQ5OegPLTZeZaJG72zjywcrTy7wKK1Iw0uWp75sN/iBtdU86qEf05zGLOs9hnX03MtOLa19SmP0iWRY4+b90PUw0xcBOzc01gfbtEgOtZ85PBbf3lHp7S2ej74Nv9ZRaopxg7bcY5hn4Gi9Q03OuWwzGMtmv9S21bArhrdGFAcJHj1jBDgI1Gp7zgvU83ILfOp4NBkUlQjAOnR9abcga98hnwAaIxVW65kwlCq0roOLNXX2b5dAsz2w2X1hswHN3e3DZ1f6TEB+YJM+fJZ3NtSBXyr74pMXdY3G8k41gzn9/HBVsp2k4VU2k3w/6eQ1XKjPzAuWdcjDlzcp/z0DNc0wWMCNdWn3gJZGQ/preXAuexiHtJ5JN8jTqGW7gdGWVNg+4TnzOA/wfGxOAQK0Ye6Uhzot0EfzQgbrUELbMgeTa1jzhuUcyEjB4sHnHJ07PicWAGk6Wbu0eqOM2w33RCkEvDYuBpBAfhNg8R5bfDbNKhhIsNH6WP27cfGoJtEjB0wGTKst65sBcursMtBIX/rSl9graLaMy/o9motte/ODx3/2Z392pV1nDWiEMrQZ4GVOnTGjUZk7QEQavJnR9Hhjsg8n7abiBtWKzEpb6PioY59FF61+PYKgpMma8c0k03wzkJkBJHfFsIEqjbRoRSMEwEg/K7rlvCTIanOrOULWtazND8PGvn1qN9vIC19PUh8NMFBb9Be1K3y2L7kKxeU50ICOU92CXFNDbXBuUOHta2GrUw26SBN5wcnUAQDZoRsnG5/SVuXFHIqpPrwdjGQwn1Gn0VnT5oPWaDLQ5M0A917974pohEI5ksnfo8kKkXCp+Mxb9Xh30kAiXqPnauTugZem89aNgBXp7GnvAZSqu7i4Nd7IQGhvY96rBzLQyDuuxlfx8i25s0gL6SVpCpgOXVAUSHz3ODxINzSmKqN6nmspNLAaTzQ/qM5yUjSj29qv/6ptO15bB56ZsPTX+GTiz9JoeiODn0T5FKSFzh0vI9JQwQG15Xoh8DDm0qJeh4WpizMCxFPez9m+511nK9AMqbM+onn9unadaRFN3gxgAQ2nYz1+xEd/0eQ2CYp3ux7XCcc5bjUdlHpDpI2n/KboTr/pNZJyJWh4I0crdTQ8+Ejul+gv9ZkBOupf+vXvthsAdPsEOKrTeHJ9NF2tdBoy8vIYzZ/8hIJ1XimSsuYJG5RiHaRPwceAnDJt3FN5Hm/YY9QnfHgfj/5D3/LD5D+UbX1MoKm6MMO8/WONaQ5Y5bxXS6DovrQCXUeNhwAQupUbWI68Z0RA82//9m/1ORq5RsMf2LwS0Lz11rcePX36rEY0pAARB5pc3q25AMNFCqo3MlPIG730o1lWlC3ie0/E4316QMRLs9TGnpQS4iu9ak80yeXs+ebN3khM6sQvhgFILo+zl3qxheKNn+6UaLrw9qMOPS+e6iJWkDfwNCXAonmYAaYkzWBbEYkEHOs6tQFhsqmg/iEj3Izo8Gp8hxOl6cKNKDcfcuxT4474SaBhdQOfra431L09mYJw7Sv6pXZJoQdHpc4dsYvVPNdCZle+rcd47FsGmv/4j//oPhPQqUOpszvlgc0//bMjd51961vfevTs2TOYOsu/5YfPZhFLuSkuUm9nsGGcpU6gUVhdB75AnaoseTNKQ8RTFuv3UuBktRO4K3rLayQ7jA4TWO+2WdSH5k43dGVuvMZQiwKsSGvmJODzu/47BRZvqk2T7wWrok2f9tsLuhYhQPQaUQSgyPBLOZJXi7pBP5bqsgBPHldjCsZiAY02fo/Mdtw8gwFcGHgM/TYj3o1iYfVwnM1wq2MFRl8CahI6aVuku3OogEnTK43129R096AYaykv6qxA8+//nj68jGjyZoDhw2dbVJOB5sEl0PzZsUDzVn578/m5/vZm8WYA782+ClY8trVseypblvd8SkvUxvK0LZ4WjxnISUPLy20q4GsbwPXfgW81UpsxlBEL6d0b0OJJcYCtFUrKicryf2rKaCkejjVu06g5QE+rl32RMfaWafylDjOgJzC39W1zofG1gBnpx/l7DLqWvoLHSzNiqP9MnhV5NQM6gpTGXxsTU7cDgYEPD2eN/lI/CFSov1ZO/0oAAmBD89H3xyAzRk/bORLZgU7+oT360ea9HH368mX69y11hoCmPrC5PUdz9K6zt76dd509gQ9sZqLnaDLgaBeBvIlnhlj20Yy6rJ95y5YhG/umYflaGiUr4kJ95M1vGd0Z75lcpO/MSFUeh/HClHKGcdFIgNc7AJrClwwQul7QWGaOgGa8ixfXe/6a8yPLzjJAg3zwOP9+AJkRByXZxTqnGjBZRnsd4xkZGrKnmEdvALcIuXWaypM8OqNu8JF6eUDSAomZjl7ehX8z9AikalZD0UEdNwEiS+POQLfqsM3ywWjHe/BTnpdE/j1HNB9+OAGabXvzn//5sW8GyLvOnnzv448uI5rUey75oqSIRgMaTpox9BgS/WbFGSlp2PkJlIZPGibrJvWObREXheVFI4OLfg/8LdlCNw2AOx0UfRHvNk/tvtDG6DHmSG+Np9ZGu2Y8+lgRjXU9eCIhr0OBwJm3kXrMHAfJX9ZLEIS8NySAPMgTFveXZgDRPFK5PY4NZJkMbTxSPgIvmMribYSc1m+UL/U3gZZFEJJ/nUcEFHRvrnTox3BoZVUWu48bnx5sBtuY8G60vG6aIxoVaF5ebG8GoNTZkRFNXqOh1FntsLRc8Ao0714CzaefdIYHGTdvGsND5JXK/DGvJ54WaHjrPEDRgRTzmMF5n45tJstqh+ZD8kFpNQ0AbBq3kGvG9Big4TxQu55HvfMGvhZoa7Lkk/HlnsZj0MCKAw0ankwlkYx+LbC3XR5P1qrzAJQs04x818aQ42mj6stkz9qrYLPx6MsrigKZbf4h+Ex+I4Ab9AY8O7hbWJvDgdkRoZsxJ5Z989jbHK38+7/9+/aFTQto7l1xM0Beo8kRjQU04O3NlmeODBHnzdvN0j7rTWm8yZgbGXnDzow312nm8UrdtXGt5ZveUj/eYPamA43/ADTbRSr1rVtn8/ZEXr6ANy9I/Q3nwBMJljZr7/n5nfC2zmF/7nsnYG3Hnk3h1w8R/DibodcApGydkcjrTKmLuYCHynM7962+GFavsZ7J9egiDTv6Zgoylrv4gjoLnJCBx/oa7Th/8HtYK9HmlIMGHMcYaXSRUdpA0risjgaabAtY6gztOuMv1bzSmwHyu87O33/yvY82oJHK9V/Y7Ld7og+WaUbAmgRuCNDJQIYd6aqBBdrmaxkXb8qHe6nL1o62f1eveVlg7nTQdWPiidSQftp4ZikyqZM03HK8GDS3+1Ck5iwAn0UMHBS1MaH3hXFeWqQj9Zd3yLVEZkvZ0n3VKF+LFGx99q318N/cSUD9uD4oTQZ5Mw/QGo9WJvlwHgjEqvYaOBAfAfa1Xb2W/WA3yGXX1dI6tUxIlTU5/yyr09EGQuX/S7Udy+Z8dPOtpVyW8vbm/6A1mu3tzXxMHGju37//+M+vukazbm/elsf54F68eFGBBhke+g0FGx4iamMZVmSIJD9kWLhHi+RrOs+MgWWIrX5Slgaia73wmng7CS7D3G03i2XI0ZgyWQYctR/5c9TBoCi3ke/RD41HzsPsHHvTfVp/rpMGZOPodT6eNtoceY02LCdD5ey7B2hQmRVRaO0tsNOAZpAFjLylj+SrRTDqeLixZym82dim40jtmppufwb6EkAdNvueXy2WI5pu19mhrc0R0NC7zo4GmrfffvvR+++/X3adEUjWgS3pkww079lAI4+96ZVuAqy2k0GgdNKSdKDRdGnUR26IZpGDDbKlLu/+sTxyyccLkDOPW5sTdE41/jCCSKleoB7A95Dl0Ojg0G5HzXHIN4/2GQgPWSCFo4TZGs8+wGANhnujzvWwrxLztcAAlaO+7lSSlLWNwdLFAqTeCOs6aCBiAsihBwd+fRMPvlZbuS/A6agGJQkb29O4dqSDiRxLV8aCBbo30zamTbPV/ny6AU1+BU35TMC9zuByoMkRzfe///2rAw0yUp/m1Fl+qeb2CppMluHXDJhWR+Wq8ktLn2iRSbmpRp00I9XJO4zbAk2DCzxx+ZvLkK+01/pI/YtI25BZ3vgesPGAmgeA1mPlRuNyLTCc9ZM6WcZbM/IewBvaMOfF4qODDvdFqaSfKzluxBMZWNTeui7RdSP5euV42mpAhse6/quOp5Ztja35WlrBIM+rkyoXAIAEtkOy+e2arzVFDRyZTZ8Zz9KWbA3xOVvf3kyvoMmfCbh75w5rl6+NBjT5FTRHbwbIHz5TX6p5KSnvNrsEom4zADIU2gVteepj6qdXV0tvDHJLJa4DBtLykDUwk+1qHbMfMyNa+yc6ifa2ZDTHUAdhxGbyR7nkvI1zlf9SKs0Com58QrfqzE0MqmXEvWAhx0XX0zFRS884DXkwDeA9kQ5qD9ssWkzSDNDYYHvmBVyPs/tJM4Ra2xkPWYbu1dGw1pYdAKhRwkTXZRmjIq7PQZZpfBUe9li2cyS3KBs8rHKph2b76rlvBaBNAZp1e3OXOmtgxD989vD1h8d/+Oxb3/rm+uEztBkgK5o3A0ig2UUAiTXPlgye/D0V4TDSs3aojr9YsemoG39Z7wIdBZARaQYCzSe/MVFkp93Aso8WRWj9OG80vx5+1jyhOZkZGW2X3d4IaWwD0EfRQz6Ip4115lFP5aTx3JpGa4exbr/Xf5069PexBKeFNeARABlQyxCXujaxUi9eNgXF4mkNYNOnshbJfhh/J+MgwEXIGNorPDmfOsId/bhsPtfZvq2bAdZ3nWWg+aL64bP8HM39q6zRfPOb31hfqvnxx3mNZtyemDcDrEDzyae1TjMM/Gbs6oXX5b3J9nn1o9GSF83aTpw0jTfnZ+mnjUGdC4WGVE9KXapGM4yIRzte/zXbWaAhQRMRPgdYrqWrJUPKq+2YUZpFvceSBtCI7wwUNL5tTH30Jw0SmlfssM09bVSOPGW+ztPVKyAm7znqd6hd7QVw2N8Csk0n6blLY9zrw8BDsRVW+04PKUccW2PMtH4SAaTjLUdF44X01cpJDgGN9gqaXJ/b5ned3X9whTWab33rG4/Oc0Tz0cef6yZ5i0QywLz3/vZmgFLpusGk0Z+lXtAJsrwx2/Pk3hSOppBeM091BnptvKnr3z3cSfJYpCf120OaIeTjs9rWCCb1F0o/1gOLNO2tw1I3LY0k661xkeda/ARsVCWv2fWmtfOSnIdZG60O/x5tlUdHzTh6+HDjZgEIlLWjrsqaLJpLUBu8egAImmxut1pZPz8ITAZeqw6VYzeOQQduSpQxSFmzrEZXtt2wxzoVJaQpu84K0OQPn30x3b0Emmr9Lv95ebEBzRbRfP/YiOZbb32rfmGTlOGDzimz9hyNIkAxwHuMpzQKHp6asbPaaGRHLu36sDx/TRcNmDW9rPmynl26Km/JA7XXxkf3EWqzh2bgRMfyBq1ttwd79uiA+GnjlY6K1k6WI1lozKy0s0mNfxklL7OubY8hsoy2J1oq5UVn2YaMFuKtXauaI4HGsbSDKq87rn/pPtaBaOTNwIcz2X5r/dvzOon1Z4CkgGbrv/27iOOUeodV6NHxI4eby+8FrO0+BUBDlGXw79E8fPDw8Z/9+bGvoHnrm5dA87S+vTkrfsYU5w9sypNkeYmjEqMHYxlD6SloqRFpJOSbh5HR5DftBH+a/su4QwulMoivNAYakJHO0pP1evoeY6fqUbUc2/G2+a/+ETrsIIwR63g+LeNsAen6BunJeeNzQ/pzXtb1i8a3x1ng14rlMO3VAYGw/G31V9sv5fzIp9RtGQ0INTCZyT7bvtWiRTUc1DUeFnBgfWoLmCo0+fdMmLyWseD9qv6iXtosRBxsVBvJ5ENAVGwsP86psf/MazQgddYBzZ1t19mfX2EzQE6dfbw+sDle1Blg6KWa1sQgmqVWJD/NeGppBU/kU8rWf7uTP0utNA+ZTSDIk/IipLdHP0meuUJtNZ52WmvZrkv/molmIFVgcIDmrK0mf3Z9zXSz+urzUUyrG2Sd53oKCDvKUJ3l3HEDrOkyi264sbb4WHp3QAAMqcVDRiGDbpsAJAPpD0GMjLiRepvxhTyJNgPXlS1i5yE5skt9FBSfZ2qq6JgPXr4sEU1+jmYFmrvtzQAyornSA5tb6uydjz766I1+0EVNDjSaoZt9yVF60YS0tcww+MNgRFspW97wyHhZHnPfZq01eXj05OQFmj0GWpZ5ABS1RXpq8r1pMaTL6Dzsn1ueOpD6ekFlDzBoutPN3M5ZSjyadafSgBPTyWLerXXeavlS+ujAMgc1WT7jYxnqY/jxYw/Y9Lr0UYTK19KfHbucANkftmnjqOc0NUCwZEK9eF/NpjBg7OelB5ovbq+gqVia6IHNDDR3Xr7++utXeNfZW2/VV9AgZbvNAIqHR9uArYiBt/cYf62fLFcNJdstIw33Xo9YNUhDqoGncO20FtJHA0DpmSDdhvEjPsox/UVpRzsaGo+1cXO5SH9rLjwRB38nGnI0NKBBL8a0xqrJ90Z4XR0DF+ta5n3kePY4PJaxrL+ZxXMDAUszlD+6EXTpgMaxMR/4sAhgaRbc1teQOwUHAWZeebPxUJnMKmnUr/ls/zhl87o1dfaf/7nuOiOg4cTfDJDf3nx0RPONb+TtzU/b25uXPkzLHz57nwGNZURsA16msRs88OI0mkUCJFt6lFN+AiyoX+M33700AxVvG43n1GClOZhYxnY2NktPb5u9vDz6QH4c7Vkff0pu5WiWr9+B365l+eaHqhtYk6rtumuuX/SXOqPrQMpDfYvMwfaYBla7LmywKmOwdBr6tI4uo0r1vB/Xme5XTWaVAfqr+pHZl3qC1NzsvOwFk2nZodcLOSlL6zjw4vzy9cu3N6O3N6+pszt31wc2j45ovv71rz169uyDdy6BRqTOCtFnAvI2uDTcsNyLSeJmtHdbUf+zM1mGaQZwuA/XzWdoEF8NbJozpwOLdQNz3rPoBXnmWKe+3pMilKT19Z3TOXFv1NsLjqXaAhuU0RhmEYTVbq8jMavTZG9Hmq0Y+GnHe9tRnZxzdH+sZYc0pqmYE2kZzmW7iRb+bSCBkH7Qw8cdSCT+MbceNGbzNgNFJF+bPwI9HSjGDQYjD9xG1U049rmMIpqcOvvN/K4zBjR0/RegubNGNEd/YfNrX/vaow8++KBGNEVpWiDOQPOifo8GGUnqMzOmqJ3VlrfXvNixrwrgbo98pg/i1QxnWmfbY9CteTgGFHm5N2Lg/a2LdQ+IcBmzlJPF19KJ90dppOox7tR7kJFZXPjHwD1m6zqWZAHO0borxxUcmGytHeKB7pXBoJbC1nm7L44BCZdurULo2mo1UNkboahjPEZvc0xALyATpe9WEuk0vEliWV9BwzcD3O2AJv93sX6F895VH9j8xje+Xt4MkHedLZTrbBcPvRmAv1STtOD519FzGz2xmQGceX+orYc/52l5qJwH0qcYn2LANOMu+w39VyFpSNfN9EZ145jHHWQaoNFNeBBjyaR9JkDyIDomFcfrpfFC/WSK5FiQQvogR8ijkzYmqeMMxHG9fv9oBsqqJ2d2Ni+z6GGdC/as0jBWWnQ2jPD4sCRztgVYwP5CL3n9ddf4gfEURplsHd3TrZMhr9PnbBiHxqPWewBViQa94FYjTEMX4pgfyPxPADQULdFmgPIKmisBzTcePT0/7951liewnCMMNHr6ZBslUgLcfJYhGmSgtZTSYdDL651bHiaSLcdpGSkVZAB/rb9GdDFaYDWTpwHumJ5iNyHg7wF3q98MEI+NiuT4Zs5GbSM2B5jnQqQiuOe+NypFemsR+sz730rVvt4oBpXz55EsMOCyyKmhssGAbwPl18RMlz31yDHtQEbjweqHZ1VEXw0QPHquuhFAMzuHeKr8lLFAHdh1Wl5Bk1NnH7XPBFCzzQm9ns0A+V1n59sDmynVhzVpAuoazQY00lunMotmRk9rK9tbN4ll2C1+rWIeZewxINNxph6S9wKNBepWv7WX4xxYukAPXkS4SA46f3vTchY/rpPUs7Wl/GaCXuPMCUCyZPSzR1dkBC3ygIFVrgF91SX1BgP1vwpY8b9y3jw8JWipcjpgGNcyfJFXFQjlccAZeNNzLiowLN1EQz2YfL4VOtF4mPyOz4GQROHfbtT1U87/+Yuy6yyv0bxKQLP5W/wzAffvP3j8F39xZETz1lvfqp8J4AqRN6W9vVnziMsuHGlGFcWUi0vyPmwTV+Zwnn6TvKz6XoaPNOO7v6zME/dqqrmu1+rcaO8x2F6w6foAL2sWaVAbPzjTNQf415sHy5DyeDvt+rJ1wdEPT9/tGZslR+qD7qnhmk6Tm9rgxXnOIhJUpvFU25cCFy9eZulnRSsmcBi614iB6dvqyf4AoAG8PdGFxUsr88y97FvsylbSDyeViOYi/eIX4/Zmfv1nm37nzp31gc2/+Iu/OPJ7NJdA874AmnrhX/7OHz57/7331+/SeDw3TrO0inXDaoYUeUGDLuJO1KKvGdiMcjlA2NHWMNbtRHvAAc2zFVVsDdTdanKMlidZdWMj9YBz/s2/W6OlgCgc1wzqzEGQ49oDGnIOuF7j9ba2VHlKXvzYQ57rUbbbAzQzg2tF/n0ZfufascelrPBlEroBaYbaC3gesBmMODseopNlbGPJJEuxtEqTL9JV9pXnQeNRgaXKTDU64TaIdMkg8otf/KLsOvvN36ypMwk09IXNo4Emv+vsSX4zwIftzQCrElsqid4MwLc3a4aQni+YGVPeRzM6/eylwaOuVUKWB7xmnqg0ApZh9nvsXO/eVByTYkIgS/OZ6YxvFxU0TYsxC2wZJHnBcm/TC2hN7yLyGKNt6aUBzaYBAJo+craA3qOf5ZlbjgRv5ylD9RYg9vVkiRxGFICU6VkvIBLgPEoBNOKj0Rz5uHdnaQAzaVdBo5YLUNwBtPU3T9umVlTHpALd9pt7SFwWS9n1OrSEgNSvAs1HH6cv5pdq3sURTd7evKbOfnAk0HwzfybgydP2CppLA7XQRFz+ffHJi5o6Q0ZAM8q8nedmRcZVyuqHpEc0nhtYEvdiOwPJ01kGWTeyFenI35nyOtnFDiD0ttk7J7LfvC/dlhMdNsdB44uM2Sw65uSJuPc6CHvkS7IiuJl+ppF2Ao52PKvzGlHkXKzlpTKVe2p0fLgRtwCgN+7NSnsjHHy8cZb6UlvZnuk35S2jIGNsXBcOIPB8CL5sJnrwM6KlXofLa+kSRP6zps4y0NxLPHNT12julM8EHL1G841vfG3d3rx+j6Yk7ro5rh8+Y2s0CFyQodC8bm3gsxQNetpa85pnHjjij8Y2IzR2WeZJ7/B2HiO0R0ei/HBs7mJFS9q585xHCzy53oiuYsgt4v1n0fNuAE5pWzD1L+YjvVC5xm/m2Ml2szJU3um2kKfct51FOYj3TH5fz6LN6sn3DsoMaAbgA7+7CKYUwLTaQemvjUv2QYAGwWcDm1lqzdKtybAApzwsT28GKO86K7vOZKaCgOa1+689/sEPfnDsrrOv511nlxHNx2+sUddZj/KfXALNewxo5OeNvYZgz03ebqZ2LrRop6TqsC6zm98T+cg0TG3Lw19gYK9iwDhphgVHebmezcckGpulmFC5F+Q80YrFj9rNjC4HRcQXAQ39nUVDCHTRuHOZ9nLXbnxL8Vrp0kFgjmn/2wFQGTS+7BpBc6Z630658N4BbQdDvCyYd1Nit25SL/kOM+v6scCN66vdQ0ifnger637250cDlkE3th7Dj2X7vJuY1mjyZoBXX73X3Uv8pZoPHjw8PqLJn3I+Pz8vQJPkBJc3A/CIBt10x3id8z7t5tKMkHZDWGCjeenHRjB6OwLK6wGePXoiIJ7pjeqRkZZ1s5sLGV+P/lYUMtPd6jNNO21es2bcpfPjGQ9WOtUMheeamkUDU3HAQDJfSTVcVlQw8BPgiuVzqboRtoyzNp49xxyw1p+TtR4JKnv0ZD2HlDEai3ZdDfoYEZ7at05/aZvfDPDLX/5i/fBZiWheTXRhZj14RLPuOjt2jSZ/j6a8vfnjbnsz/aXnaGiNpr8hdDCYkbw4R6+3O58qeKCToqWCkLxjQFIsRdhNFU/pKvMllZB1NH8JXNSabA8Q8f4aMEk9+LEVSczImjcNaI6R08bWX3+y/lg5MNKZkGlANodm3k6ro/t9bKcZvb3HnjJpwJEuqG5hRhPxR/UIaGY6dUCzlc/AqtSfrTFJEteMJrMrYxchP6bfB9HfBOUe3yvJXWevvvpqrbtWoMlrNOfnz8S7ztpFRrvO0BoNJ69HZqVReNnMI8X997/2hs7APDU19t1jxKARrt4zat8iIqnPLDqSF6Kmb8cHfLVSAnJpWP0dla/szx+Y23udSKPOPwcwI+854sYakXY9SqDh9zLu03so7mtoWcyb2NJNq9f6cwOOAAwbaLD11gE2fDakDtPtv1JnYUjtqKGOpAcQAUoW0HV9nXVoe3GrS928ckCzdKBy1waAjV+9t/IyCKXOnj9Pv/mFL3RvBsi0As1l+7uv3Hn54OEVnqP5et4MgIBmMw7ygU0UPVjRSacIMFyatw+NXPIAByacMulBBvHxppuOioycPOS8cPK0t1Ji/LcF8JIPdwgs/lIfjbeUPz3PB89eQF3ObAycvCmyGdBUQEL8ZmgHxiH5WgZviNA2eS5A6DuOOCPaWHM1jWzqb7zNHP22nnXR5EJwBX152Z6tzV0/AsJagfvP9IHg0w28wucgS5NTI5pLoPniZUSTtzfz+4F/j+bBlbY3f/Prj548edoBDb9oOdDMPCc6tgYm28x47gGyNr9SH+xJYqPGPTrbMKKx7ElLSQ+j8t6sghe40AYNuoikvpyg0VNCbO85praeefH01fSw5nmW/vLqJGXtuW4teSjltneOUF8PaKC6mTFHdSUzq0dNfP41p7Fd6sIgN4uaeORc+4qUUpvPVDtLXoPRHgBFgNXWHoLP0rXq+Nkg3fryV8rIs67xknoiUp0QZTNAXuj/Zd7ezIEmtQ0IBDTrmwHu33/8g7/8y+M/E/Ds2bPvPd/edSZvUA/QYKEtXYLazwzG0Ha7aLzGQdPTA1qa50vEXyzo8Yjtcfk8c8vQWxEB6ueJQmRfS7ZlQImsN0K36218zZAEC2S0jo1GBkBkNxgaQzmkZ0Jk3QhC1nXh0Z217uQNRn8dI7XDcqYSnCDliQxg/Xb/To00Bwc6HrtUAOjAZSvD8g9be8OIz0CC8Z+1pTKk07RuFjl5wAZ4WehcUUTzPKfOwJsBOqB58OD47c35w2dPnz5751LQG9xoahGNbIP+aoM3DRkDErk1D/XxpLssmZrB9Bh0qkPeiEUIEOhYlvUXV7v7VJ3YhWWBjDqWRU/rWJ43927ROL18tHZW3R6A9wDrVcgbXczGZZ0z2R71O5BBFe1MWjaQPYzbtLtIwmIhDHDVpy3qqbxmZcUoNydAkzmUcYcFyEdzDn+LCArpurSD8mdIaZHjvR3VdlsfAahTAOeO/IFQnLFQ55RbrTz+fjPA3Uug4T271NlV3nW27TobtjcT8c0Ae27OPZGDJCvtws+9l+RFugeABk4AAJHRRrxmc+LxgDlfZPR7uaO+xxpYKzLQ9EVyZkDWt4X3y1F6WuM+Rg4CPIuPPDd7z0Npv3Lo+CmtoR5an8Yb6y9BTfKaGUVNBysqMCOGhRlyT3tWtitqYb89sqz5RbxM2QIEJZBpcjTeWlkGkV/+4pfp4+cb0ND3aJYW8dRdZw+v8BzNN/IraN4v25v5iaC/L17kiKb/wiYyXNaNZBl2onn9+m9XtycNp9WjtjpANB26ehGBaWP3jPkqXrYGeFyWBnYzINHq9wKQpveQpkrtwkVe53ANXhzU9+Fdh04YWMSusy0iIJ01vfc6bBzAkGOB+lBbrwGueqVDNeIzj59koPfqWecA6UR+tmW0PQbdKuP969iE3LV+mrpKncGHINI6q3pbQIajGKU9G0N/neaKfixC0PoKml/+8pfDSzWJri2iydubnzx5+s6loDfWAPWshFc0Hv4KmhlgaDfBdaYo0A3vi0bs9hbIXDXdI2XM5FjHFljNAFiVvyw1RNTO8d6oSAPY3dcC3UWGHDdfNs7SZ7xvpc7Hgr8FzJz/nMcZBBkiy7h6gcanTzkRElQ42KJjryzVGM8M+o7j8lOJLpTF8kFP1tcCYGqntR/41ja1tzIGecGmYrNF6k3lv51Hap5B5Fe/+lV9BQ3+wuY1AM1Xv/qVR8+ePnvn4+fP38hNzzagKfqI7c3iRt1jOGektZ8Zd02+xcOKzKy+qJ/Xo9ciCe+4vVGcFsWgfjOjJz1w7zn1zG/zKMu1pp9jT5q05Jrl+gKSK0mL/KqRmPTT+Jcy9r4uo502/3uvLeRIeYFmVkd0xj9ffEh1nWDty8LQnhePT+3rbRZFuaIJAC6yjg1BlzUBZfkKm67N9s/SdkC0Nv10uOR6HIRuLoAXhXhwoBkimksWF5f3VT4feTPAw4evP/7+94/8wuabb7756Ne//vW6GSArdlaVKcinffiMab8OSjOkNEAviKCbxAM0st3eaMei2Q3vkUE3In9wEYHHnshhD5gvzGKjHnsNHr+O90Q41zGXml5SxlXOfeG19lblHbZJWFI/B7P0FpovL6houlplFm8ERPx6RO9Cq7+FMRu97VbvNaQm0GwGGjlOpM/CgdDg3fBnviFgSf09Q+ClRTFI58ZrGfRT5wTxm/XZ5uEg2iytQW2bpzA7aCrQpAJE1wI0X/nKlx998MEH73z88fM36kSwXjl19uTJEx1okEBpQPkOiTpIfQ1harAZWemEXVEDXVBOPZAum0apsSQ9CI+vB/ikfO98WYbeY+g8QIh0yj/Lm6OPSzvyG04CiRXBSON/1chhRuia9s6pHV1KU8fmOdk3ODLKjSs2elKGptcuoznxrrlBHI1kkWMBAvVdlO8wzceSpqDInbUEQAM9i8MjJjX6mOlF8hztrXGNlWWNZgY0tL05A83xmwHySzWfnH/v44/zFzbHEPiTvBngib5G03T2GVAEAkSz9Mw8zWKnuY4hmoo90QPUqXpcI7h28q7J6HH5ljwpW/bhfLwpPpwiGyMp7mFquuVyvi5gtZOeq1aP++P70RsNSLA49jzCdJcSVXiATQOadV6Z9z814pMxeYEE1WllVO4FtO6Yok0gF0YiyZ9G6wGjd6K9ICbtrGdc1vVUZRiexziPix3RbKmzCjQPLoHmB0cCzdfyczTnT9cHNhFqds/RJNtYrLrtSPVYHqnXsFmyZvrM9Fz/JnYh7uBVeKy9tmus7R7ak+7S27aty3sdAO6YHSf7eskVPTJj2/c97ViuM9Xn5SnPqTRypbzdjXStNe9X15UDqgXMPqBpbwafAUlXzgyJx0vXwMbr4fP+e6IwXt5HK0CX0nj4Pa2rv5ulmQGxGaVQW0ozJpn262WvazS//FXd3tx9j+ZQIpqX62aAK0Y022aA7338/Hn3rjNSMQOMuUYzoZmnCRU+AoAQD65DX9cbpz1RlaYfkt23aQvDnnSfR05pQzyVyiPn+yrtij4Tj3676fc6JnRz7CXp6EneKKKherTJQOPj0sVIXyL+ujwyI+WvBk4aL63MW1f8qNGbl/14qrNnklqqfqIjGhuK/DQQKXVXM+ZaJLQ66KD9wBP8rmXVUxh5cTldf5bGkxewukWbnN8tS5C/R5MjGv5mgGYDaY2GUmcPr7Lr7KuPnj3L7zprQMMn5JNPPq3P0agC2EVAHg5ectZJXkhalKTdTMd6rJ4oS2vH5aKbyyNvVj6rs2g0phj0UbSAUzhrxyPBZrwm5BxykEcfEqM+1nxMAe6QujTUcI3levIEgRGVuu4leY1z3to4PLIsfjgawTwQP62eHUDA0VJ59Tf1nehiya4GF7SHYMJkamBTgbG2bdeuBUC1L5dhyNPur3ZM/ca0ntYHjYe3PbDy/B8Hmry9+RJQuvNHHz67c/eVl/fvP3j8w2PfdfaVLaJ5/twPNCMA7AcWiyyjjQDpOmTJm3QvX23DwzFUfVTmCXpfj38d8hFPa36u4zxw4uPWwAb14fog/XXDSX8PFYiQ0eV8rHPh0dMLUmjtZJYm1aidOzr290Xt6u+FNtI0np2eLHLZG2lpkYxnTUkbkxYR8QGobYC82jZN1nkoqiqNWv0QjaT+eKKLVmcC/hbxZLvybl6jWV+q+cV0997d2qYCzeXfO6+88vKzn33t8V//9V8dCTRfuwSa82fdGk0PNPr25mOMMuWXXdc1nQQHT8tbuK5oR5bNDOweA4zaauPyeNXSOG6laY9DUM+veF8Vyf5vJwME+ghgdPJmIDGbfzTv1vzPIt2luql0idsPElt6orqrGiXP+wY9Rljqtn4qPrWxU90sgpJytbbaX3ntmEDE12OELAgwhu1xAVUpMMFzWSpUp24BptM7tQt/lkqrQHNI7767bQb44m+me3fvsevy0O06u3///uMf/vCHx6bOvpxfqnkZ0bwYIposSFuj4RignYz5+oKSRmKpHITKeww9kRcQZn1UmezGGYpUwPQZ/mMjhlk/LVU5668tzB+rF58FzXud0dTz74VAz9NjwAkUuI4efWQd5+uNJjRe/LU3g17gupw5J7zNoF81eDovNK/SKHd8WcSj8toM51SeAImh7TYGS3etnxmtTMDFM09IR62/OjbmILQNELXTwGONaN59t71U8+7drk0Govy557v57c0PHx7/9uavXkY0+cNnNXW2vs6g3ZU5otGeozmFd9su9n3et9SJkxWVHCPnGO/3KkQXhBcIPKmdq+h0LPDx/qSHyhcYSNTfG11owEW/rQX/TlfmNMzmEOk0T0EO9qCCiT96XpiaO7YkTxw82MeYAzTnM12kkfXKQ+1UsFTkWcChOZ08GhvAYuOtg0Lqx6roosmFPPlcG5sj+Ljk9mZ5f+R1HPpMwF8eu0bz9a9/9dH5+dPupZr8BOU1midP9F1no3fq83ZRdELHY1TRgwGSgaIqzZChm38Gmha/WVsrErOMdk1jOMYm+5Fh4mO+KjjsTRkio0n1pOceuTPSQMcLQirfNLmJBL9Me4Dem+7yZw2cqWniw24vfqdNQWEiBNXPNnnIyGEECNJSyCLvnc3T2fbdIBmJSL7WeDTnbqE53hl5dAA3SX9Z+kBbhiJ1sHasAc0XMtCIL2wetu3Nd+7eLamzoz98dgk0+TkavuuME63RZFS7TrIMCLoJZd3M6A+GphTCtMVxBrg9xyLl7zHKnrF5+h0zhgbiGiBQxmIDDmqd/7lIkyvr+vQ8FaE0nayDKdVSMTW0e3Qo5AcIiyyA0O4ni5fmFK5/lLcDI0DwRD8z/eVfec+j6AlGHOvF3VQsIIfH0o1EBUA9Gto1Ri6DS2bNB9lpzMmgiE3W5//mqbNtjeaVOy/vP7zGXWdkXUjRLnVGlmcnIQM6D+uz5zM+L4EuJNV4kYekGP09YDPKsp+LQfpa5VcxwtzLsr6745GBbsg6R6VBLUd9LV5YYBo8UQ/xaEmLCPrzTpfvQTUo1EaeaxlV83o0V3Pd5ym1ri4d6hqGlQa07gfNO7bOEY8SLB1d0dnKqr2J2qMb1GnBz5nMohZqZ0UgHFRlPXdWYV+rzogmYMTRGk/5IUL9Ox23C50DTX7V2Oc//4V0754ONA8eXiF1lh/YfLp+ynmMaPLv6951BhVUPK0ZoJhGnO2UWstKRWc8PN4cN65XIcsI8RQZL7sOouuK0mmItzYX0hDJOUVte8ZpcvVN+gviYEFAX6MtFHlw3StrHWioj9RnZmgRDwQkVllXL1xUSyfe1+M4zYz64EuSYZd9gIG0ZFC5t702VxL8rD4yakFtUR/LebAjmUSIp/JDPBKpOAFBRBqvg9GmXWeXQPPyZXrv3ffS8xfPhw+f5XHzzwTk1NnxQPO1r15GNE+HD5+RsP5Tzn0qhSvkpav03UM2b3kqRjKBiF28Lq9d0W1muPYA+QwsZn15W3RhetOcx8zHTCepg+bVe4HAK1vy0qKHPdGJ1m42777rsVzXM+NY+DW75onK9gKKVm7NiWZY5djRh9Ygj1Lg0rFeT6yPOg7AV+VXgRo7N0guklfa9Tzs8ZUWDaDpMul5ZBDhQHMnr9EwPhcvL9b3ndH3aI7edfaVr3750QfPPihAk9U4609wBhi564wGkVJ7U/E4UB+IIG9xFrnMbgZ0Aw9efOon5spbdtMMuqThUK+RTqe0sPFc/i+fdK8h9Rh9Pj/oo1byt3ZDyWdtDi38UHlaukkv3TM+dN7dxtrQj8/RnrnnPDhPa17VKOeaaI+h7+ddXz+yvXy8ZkJk1TU7szas6TF5Xi0jL2Wg7EHXBy3QT8AKAs9Wpr4Kximb6od+zIBIcEWAginPxyXQvPdefQXNsEazAU15jubB4x/+8Og1mi9fRjQffO/j/MDmBjTsMth2nc0+E+DLkWCj0ZtoD0Dt8fRZp6PXl/47oi40JgRG2s0lz0BpZhsqFFnN5tYyjGhcGplpqUMCb1lo4yn8U6I1vKsaf9SGj8VLGjBYUY+UMQLN2sLU0aundyx7IhUEKlPDDICGyj1AOAUaZW2p51Fawv6T44EXAqcmBOpv8dJ2iRHfAyhTIxvRngPR+vHzy7nRgCa3O2yps1deeeUyonl4BaD5yiXQXEY0eTOA9GplRNNFCJsHq100fIK8qbK+vgEQiky8UY1HbkfCYu8FmmO8X68cK1cvQcmKCGa6aV43l4f00CJRqZt3rHIe0Rgbv1aH9IeAljAvpIvU3epvAQ6UTYNjMnq5Czy3VvSj1XvTfzNQ2VPO+a8vczz0KdY9kY5Lz02OFT17QNNqr53nHsi4RmAMPCoRnSBQkU3UQEgBXEmtXdkM8N577+oRTd7evD1H8/ASaP7yakDzbN3ejE6m9cAmcec7YuSAbI8dPzEsdTgmotCMoGYQZ7L2As6srzV2Xu8FLNkeGjU18OxBnfOcyYPcwHiO4YMiOtSfZO6RK+fCMkrmnAJddD5lPMc4LxZpY/eApqWLZkQRqGn9acfcCK5tJ53GS5Mj9Vj/Sy0vwo30HnDkUYVtoFtEoAMNgUmqvGZpufJTAdT6rx9orHKat7JG8+76rjNze/NVU2f5OZrz989LRJP6icr/WV/YHC4EsM6Bbs7ZxY08UK3djKRnr92Qs3TEXpCRQCJTVIj4/Mm5tbwn7YWb1hx5+FuExueRyb3zw5YOoxtIM/B9YTIAomyfPcYhmIMEjrQtfto1p837LCpChpfOvXWNHXue5cOVxwCN1ka7nq250mSdFfRWdZtFbBIc1r/bdabNlxlJIJAAwOcZGyxj/Dwya3271RKlFktE06fO+Nzz7c2v3X/t8V/91ZEv1VzfdXY+fo+G/uYvbD45vwSay8iGT9YpPP+bRGX8+maHGVnpkz0G/br7H8NblSm8Nc+nBjxyCYCO6euVbUdSukNyVUJRmjdi8vH382hjbDp5ZXuABtWZ0c8Bb0jRZK13aHXy8atgeB80x179SF61gey3h486Bjkepa8LLMXFNehIG7hY3cuXF+vb+XNAse46Y58JyLQCzZY6WyOavzrypZpffvN3Hz374IN3nj9/8UZTnk5CSZ2dn5+viuz1mr0kecyimb38uBc6Hvn4ZZq9qp+f56sAwjSak0ZdTYmNfCtPZx/SwzuWY8atpVeRZyr7yH6e6ADJ08CK0ykcB8RL01fTAZHnvtR5Lfp1vGivv9zhkSO5AjCI1jEsZCRHHh7wIPYoCoDgwaPXpW/vHddo5BuIzCISVF/KkT6HNMyOAoZcDp3XDDT5FWP0PZpXXrnDWXRrNPk5mqMjmi9/Ob+9+emaOpMnOf+mNZr8Vzu5s/z2MbQHwGR6jPe/Kmhpso4hNCY65mkK3k5PcZULTDNOx4IK16nyo5tvJ59dxO5tHtGgCKDXUzgRNBfG9k55vYzGtGNp8kBlmvfJz6ks26O/RxdEFgirHjM45zNZQ70BTqh9/ZRA0p2HPUDDz3eVweZ36EfnxACDxmcbnzInR4HTdtHPAKnTXfRFOpbfCyssQJKfk8xA84Uv5NQZiGjW1NkrL+8/eHD8ZwLefPPNRx988Oyd5x8/f4MmjF94CGimtPSvfpF0yvRPU+FqAKOB56l0RyDpydPv4buXuCG3biSLvw6UsmHqorRZtGHKOjROTT/spWtG/brTWAhoOKnn+aC/FDK/wh0hohYNoc8JWECj6Qb1TW0KrXPn4YWcRqm3qmtp0DkpCGikLI3PoCcy5ArAwbERXwITBEQyBWaAFeQvdEdtDlubQwc0Xxg2A+S5yxHN3Tt3r/YKmrzr7DKieefjS6BBk07fo6GXalrRjOdiVcFnU/QqhlHqoN1wM10HQyQufI326u5dR+DAQ3L2ksdga/xhX7koXxpOxyP5y+uC+Gh6oH7yt2zHgcaThqp80njzIHnWWLVx6obU9w49ycMaU39PsMxQwvM49jEMqGF4tSh76tkrQKORBSJcj17G+q+prybbasMBqsplQuscsGN5M8l5n+nlAUo4hxVonlwCTXl78907d7omWfb6HM1nXnn52v37V/vCZn578/OPnn+OdiJw5bS3N1vAod3s6MawUiMe8gNAlVi9Z8vgefgfE900TzVNzsxcnjdauM4oTJXJxmOdbw9P4pHEZgwPUCGeKP0i9Zzpg+Qfe/45aYAm5dFv9Jp9rOv6r2ncpR7XVVfTXjvehbbXkFY5osrr/cNzCKKM0rZIGyONrVwZi3dMM+e8RiCibAYkvI1sT5Q/03z+5Mnw9mYefed1nDt3Xlk3A/zV0ZsBvvxmfWCTWvOMKv/C5jA5It1xHTSLfE7ZF6WrZsZSeogWDd60YmEs47cnwuIykUfNdUG8vBFXOejHoo2B5HGZpW1Kk0t1a1uEedYwtOhFm7cZSFvRQx0b02uYg0NLg3kjapLDN6LMQNQDfsgZtHRINDrDsGtyBIdqM/YY3msDJxW0UveKm1lfy8HT9PJES117BKSlQfdbf6anckmbYYcOWd7stX6P5gtfWD98RuX0l94MkLc3//Vf/fVxQPO7v/s7j37961+LXWftgpCpMyjAafS8QODl56FjwEfeDJ6oTdbNdqj1fkohiTvSGFMZ/zsbFwKDA3luQ+OmwFXSdMfQntReJnTTygigN8K9Q9eAu5WjMUueHUiwU0gAuGmTmhM89u/lHwcUVT82uCXh68EyirLdLNDea8CtdnuAwitXc6D2gtAYMdTSroxPtwc4NdBRx5MaAJr8DMdr5ixmoMlrNJ///OfTvbsb0LDn+daIhoDmr48EmrJGwz7lzCYiC6HtzTyi6S/mPvc9M8B7AWRP2H8d4CRfZHkUB2d/LWVk9bXSQIjnsWmzvf18gNybMdOIKzpJ3l7dMvAjj1yTgYy/BCuklxp1Hg5D+qMxSOpd2p3vrVmn01ahXXN7z703GtpTr12be6IY7pZJGyXLeP/+3BROCyGEA8x6fqQJpoXAHvAyd7kZMpHcmU6drDTOG3uQJuX3BTagad+joXm7YO86u7+u0RwJNF//xtcfvf/e++V7NMs4CS/yA5tP8GaA66Y9gHHdemRe8j1ve/sPF85STronp742B570Vck9T4oV1Yxrq9+3K4x0MvuzlOxsTmbeGqqXoFb4lluSR0AJ6IDGs2lSjX0Bmebt9nJGvcf6ERBnkddszBpZesmyYf4X3zM1e6IgaX+stKqUoQGaJVMa31kkpelMc8PfUuDVd5eeic2EMcd7nHMCGr7rjO6Ji4uXDWheuwSavzkSaL729a89On/yBH6PJhNtb77uTzlf1zMZewEnG7Czy/+hp9j5Qqu2xqAZyZlBpvaks7fPjB+KBuTU9npzH0fXDfcVtJRdK5YhtqKvGemprHlksodnKW9RgT2etXfXnmhmtLUIFJ+THvhkf0lFLSzPY3T2np+9UY1Wrhlube5Qfw3UtIwBPD6wzxCUCpe+ct7rMbsJrU0L4/gPFBKN42MGA8oVZZ0sYWz49Zfte/nC5ufVd52tazSXQPM3xwLNV7/y5UfnT5+tnwlAJ1iu0XhSHDPifa8zMrF4lbq0hopS/+tIL5HYxgYbdC9f0nneuPf+NYD08JtFALIsA3NJSenG6tgoxJtCk+dwlk7cwxelgzUgRmlAjb+cGxTRyfUdNA4pj0dEKBrT5sGOtlgktXG25kNbmJa8pTOkGXSpJye5A4+36aKM2o7ClqKnqmNhAPlqsrQ6ODbSJOuxyI+4jR64xavTQ1mn6a5JDlZLSSmfn+ftzS/gczTlFTQX6c7dq74Z4CtvljWaj/s3A9Df+vZm9q4zOYCrEo1/D83A4aq6WdGLRz7pMGwKYNeRN2oiXqjd1EBQ+sa4+YfUkXPNwCoj3TxpGe08mSmi7R80pF1ArZB3nGO/qt1wY8M1lgloHuMcXMd96X0zgTdy4SAl22nj1MAHgQmf01KNvXfkTEPZYE2l74NlaHzbpTDKHuxKE5CqlGUxgWQ+nk7x2m4Fmifn6xc2NaB5mT/lfOfO+oXNo98MgIGm5ZvpezTozQDDBX1N6TAPXTWFInl5PfkmG1/wnFC5TJd1HiPyig86SKD2WOY8LaJ5hrNowDISnGfXLo+pu2fGC8eaTxxt9Du9sKc/jk8b4945a/3LeGTkgUga2Zm+x4PfcRH7OM8Y2DsnRekr+Y569fcy4iWNu+RVjomXAIFD/4VOzk+bf2rrBVMXILXCoR/zC1nZVrosYKwY6HoZ2ChzXvQuy5w6o085k4z89on1U86v3FlfQXPF52ja92ik15AVyKFV/tKmZsiuSt4bQUsd7OWFDNFVvF8s5FK3sxap0U2K0g+afm5RYF5SwnOz1VRHAs2nN9VU68/YQFNLq3kimmMjNjlWpDtrmTxAxg2mFmkgeZysBypngCfbW+XW2KXuM7DzkNXOdEA3I+/pjxy3WaTgac9fvWOBlUevvj6ltqSC22j6Dm0qQwROOJKZyVs2BbVIKhMBTd3efO9ed63xNZoHK9AcmTp7883fffRBfmDzxYvxMwGXCn7y6SfpyftP1r/XboyPoA4cnOH9PgHpSjyvAl7SGyYiflr6Tt5k6HgGFpJG44ijR4+X7QIrpxFFbSzDs9sh6VKbxK/vc5XxWQZRjkuOSbYZIw4MMBpAU5SCrjlNtlZvZRi8vG2D307MDGgk0EmuQ/9JSmph/2hRCzfmOn82BiFTj0hapOOPrCoXfe6XVHfEFqDJEc0bNXWGgOZK25tz6uzZ0w+GzQB0sug5Gpk608No3TDeBLqOiGxP1ITkWUbukIQxE2s6NAbJp835xmijfQa+9DWNcsOdrl7Ty5KN+nD2HiPKjy0QkKAh9UBvz9YAnMryf7QZgpM0FAg4pQ5aFCPlq9cvMKiSvxapatcRqYQiDUS7op40polgO4dxJcOKxqHxQPokcZ6lPezllV57QFTqo+kl73tNZ677IbV54GOR16LkVzYDFPv++Tdy6uxOrctUgeYzG9Acu+uMXkHDgYZ7J59+8ml6/8n4ZoCZZ+Ztc+10xYjkKJFXBFQrvTJ6sp6HDtd/IY+9uoxGUTfWFs0igENTvLbvQWJu4KzIzgJDzfvtsz+z88L17qMgS2euS9WRebvHpt66sYsBIcMsCdV5Ips95bAt+0SAl69XLzTnXueIt+35FjPfrteUpAEyAcXQV+1bKozjpVPBkpNB5OnT8/V5yTfeeKPbDED17V1nV4loMtA8LV/YlAPM/6GXalppC6qXN9xNpesGQy8YsA5DWKzx0G4GHDX55t5K6+CIAr9deOYxa3yRPlKmrU9PyPDK+tOcc598TRdPf2uOa/tSYcrkja3diJbOyFvX9PLwt0BtD4DtiY68epLhbhhiRyRj/6RGGJhHk0G/yuYZPw9ZrunKgYY/R0PX3RrRXP5eX0Hz2muP/+Zv/ubY1NmXy9ubO6BpxiQDTAYa63s0MohoJ3BJ5Inq6YF9YHRd6TjNOOzlf5UAymOgriNSmsmxAEDzqHVPGJ9PlNKy+Mh2kiyw4Xy1lBuKPGT05AVKVcZmnbSxe/W22s7AV75SyQJ3rV7TSc6h51o9yztkFskL7zjbE1VNowbg0PX1GHQ90ZQ2t8jQ811ksE8p7G4kuNVa3GhkaT3gwulwcUhPnzWgubN9JoBA7eX6ZoDDGtF89hJofnQs0OTNAHnX2fPn42aAPIGffvpyfQWNfHvzMXQdIHFdQOMTlqYoMvMwZ32JkNHgNwJvu8dId/0XmRryGX/SZ0+6VI5jBhhyDq00ETKMvB8CSi2y8Yxl1tZylixQQtTarf928rcWXbnNU4+stWsP6YKMqRW5W3NK1zQ9X+aJSmZRn6dMGmK0AUDec9ZY+NxsvZNkuGyRCbp+fJFU6i4sTySj3bsaoJWI5ml6cRlIfIG/GWADXr4Z4LOffe3xj3/8oyPf3rwBzQsGNFyp+sBmBzT6iyO1Gx61gcpanqo4l5oHLsss/VBKqLbLeWz5hDXQ4TqBz8NvbxR4nB7rv6Y3i87VnjSZttFhL8hQuQY6Fj8u00rbSRmeMc/0H+vaedUdmGIyuaG3nB3P/YCesNfIc16nXj6LHo7qL8tBNOIx5Np4rMjEc2zqavEphaKsB8RZH3m9azoQCOZo5VlOna2bAd4oz9Gwc9Q+5XznEmg++/hHxwLNV77ylUfn5+fvPH/+8RuEvhJo6O3Ng6aDkVn/HQaJbsTrNM4DKZHIzCtNSd6kY9kenvJFfd6b2fLOM3m8eE5Svz2pG8tpMJ0CIQede89vVIfGNCPPnMzmGh1LkkYS6a71m+nt9e755X+gBz2o7WH8zHX3N9kGw2O0ud61bEl1O63Gx2uwh+ggtWhFAwGP8S3O5VpRnUzUloy0lto7ZmwomipSLKBpZ+s4sCsvzbwMNFY7v24GuHevXTsHBjR37lwtdZaB5jJ0qrvOJNGbAfJaDbqJEO3yap10HeBUeSiRUaaZAeJldVzsoryqnrNIkHTUDI7HuKN+yMJY81LqS8c9oIh0aO1ym7PaNi9CVmdGGCrebxZpIJJGVo5R0xe10eYcydOOrbaecq4TqvP2m1272ji8XrvsM4CGARQzA27xaRWp29Wmnb8ZP2u8ch3G02+P7Bm/Wd865qWkzj7IQJN3nX3h8+OnnC8jnrxOk19B83/liOZHR0Y0edcZ+h5N2rwfimgIaFbh24k54LmsbSwjc10gNItSNudjyoMIeznLwOOwCeDAZYX7KMU0M5JIlzKcltKzZGiEjOsMoJB+jdda4n77tQcwkTzrmrHmqxeQUlKmyZqX/YCmP+Aqde/KxdZmrhuVHeOI9KptXq4iQ8qxzpnmZHDv3AMSVvkqI7H5mbT18Ku/jc0SM6BB9m/hc2vJF53VNzyzdrhfbQx1UA301i4DTY5o8htg8rvO7ihAkyOaK729eX3X2TkAmo040FD9zFvzknVyj+GnyZiBWJE1Xzgdb7jaoqvXLkILmGf6dDc3uChRO3IWpO6cRq+WIgsb/CwHQUtTcBCy9Cht2lxp8y/lacAm5eRyfA2vvabXoBdspBGeOTRaX0+/GYDb/cdzvsdpku20edf6yPvCsgczvuga0drMwHAGUN367TYId8SSNqux9ODpAV8vaM8Ah+Zh3QwAgCbrld9zlm3/K3deefng/oPjn6NZd509fVY+5byMJ+fY79HsiVS0G/eq0U7hQ/bWt5CLeRynB5dNwOEBPkuPYwBA3shDtDnZAqtFX5osC6g08sw/b+uJipDemvct9ee8jwFXBBKjvvo757SxIr175yJtWRvffFqg6r1Oj73HtfIZP2l0+fxo8+WRZetI97B+7ZSfGNxqm9ZQ12O7H+t1JPrsBRptTCWiOVxGNE/h92hyfbb7ZddZjmiukDp7883fya+gmQPNpy+rMTrGUM5SRZ3C7OJXUzCGV2/pQPxn4HEgDyWNBmZPZOIxUr1+lOE10iaTVN3Mi7a8b8mvS+mUH0DnyiVpRlrqpnnrViqHy0GgPZ4nX0SgyUHgZM25HMcMBGU5XddWWnQ2Z7MxjudefwDXQ1dtt7fcauMBEs1wD33BTT6PKmoCYbCj9bdiPFr0odu1AaQ2OwD1ZzqNDEvH/ExTjlgodTb98NlnXzt+15n2HA1dgLQZIL+KRu5+kBetBSJ1ftlEW+2J/3iT9MYMy/IZ4Fl/yUMzJl4ZWmTh8biHSOGQpgZJG4vHuFtAoAPA3PBJfvw8o/ZaP6njbOyoHB1r57QbXykcdJSkAYQ1f2u9ABv6wJzlKAwcFk87rCvX0e0sMSs786Sh7HSYrr9IXS3DX8ez3XTaFm7bSI9g4dFv7L/+q+vaGsF7X7Y9AN00EEUyeL8c0XzwQQMaenszUff25pw6O3aNRgea8ltub6bBey96jyeK+lggMPd2DtUOaMbD4uuNPrzenNZfkmVg94Jird+2t3pAAvHweLkagFIf7DDY5G3LP0cwGxuSYQFBjTCXcR72GHGPbJKnAbUVpVpgz7+CaunC+c4cCe/4rlpOoC5rrMiF2ye6Ls7OFsCl9RsBq3+1/gDCpbCTqwF1cwwPQ5+OH6XLduxgrTLMLdAjyPA2Wcaz7YHNEtHcS9sUDA9s5u/R/M2xazS/87u/kz8T8M4lor2BlMxAkxeL8l/C02NAYI9h1iOSNl+F3//L3ptAa5Jc5YHxv/29eq/2tbu61EK9oK7etFpIA8dIbJKZI4th2A4CoYNZZsYMw6LBMAw2yDBjM8Icj63BGDOWJQQDA8gCBiMsOMaMGSPUElpKS2vrVnerq+rt+/pP3syMzFjuvXEjMvN//yvVlfrVe5kRN25EZt7vfjciI0eUUv4DL4niqWPcccn5EMvB9FT2qPpiSdMhPHvQWmkdrsSwldC11teLj9rl0Td23fS3RjhnKgEGyb1pX6/aGcUwHFMPZSNlr8veKIByy5W1rWcHBAMh7t6VPMOu06OYB6mrdLacTZ4uZThrJNvBOW2ctdTX1gQtyibaPqWqTzUjQFMwkBJgTH0Oow2BTQywuAI2LC8vG6mzSVWPoco31Kw/E3AifdXZCzOgyRhNPkeDGQ2TQdgXNrFIw+yUxHFwDrgpW5AymBDYUFtVeOWLE3i7yGBR0aPU4SAdqm4uLjXkti/SjdjmBh2YA+RscRot7VcGLtpOg3J47hhJmQb2sLsAWZxXlh1YXzXzUQoHf6xtipVQThErLwF+DmhM28mAwLHNrR91jzrnsHuGAwNKFzZeEj0xDtxkSnZBVWQMAqzCPEY9lx549e3kOGd/CGyqv/VzpmobAGiqFzaZOZo2gCZPnRUDqasUIwiLAOYX5r2dATigkUgoJaRvoFA5aTuuXs4ZSuzz2ikqseU5Z4Hd1NHC3JjSPvPOChfvwTHYChZhujq1c1c94OsW2pBthSTEYDF9FFuw7aQBwm67dDwB5439TdlmnLDuNZ7V0notxpmgM3wtQruY++dD7MW2356zxBw9DmJ2kMS1RZ3DgAg7Lu1bKHh069f95lJyfpBs/Vnedzmj2d3Jv0dDffhsdHQ0/0xA8u7NL3jBI1eXl1cMoKkvRr9cQ21tQROJMNxNGYq8Q3UpZ8DZ4ZaTprW49l1xqS8WFaeIxCbLDiZS5hyJ25YHUkUB7zwHSCkA4faB1lfflGSqp6+sLUVSWIdroxR4zHqh8afYDNVWzPUM2YuNHfdsmVF+LEOmzknLS3xEHTTT+uu/7QQchpBYXQk74frgXm+JvmrctdVkn8J2VEBDrDrTL2wC0MALm8mbar7g0UeuLmVAkzXkAE0hWOqsGhzk4XXfbA5F+NjNYJfJf/KOqrQj18REmdTD7LIR6+FS/sXk0hFuu26dFODC9ITaocS8biF7Qm1gDgZ1TkxwggUXITDwH0aVMyHpbgnkcTOFhuTJXedLOWJq7ELXBhMO4ELgnmpDaNxCQRpXR2pnExAS6ezpf3B/oZ97VTp0Shd1D1R+DWEl2HhgczVYf/K2NGEubXNBUTx2vcI+DTTnYdUZkTorgAZ2b/5IauosA5oljNEUooHG21RTCW40B4hEdTA1ZB17dYi0Dcl5ECpqpMGssAllAWU6BWuWciAx0SXWP4mj9seuvm85wK7SQ4hzN+tixzjnJ7lX0PEwQIKzxRV388SigvJAi9PH3SeY7WYd9/e6nrHYwGXEhH2YrWa7kvHA+iUpx+tACUJQRyOw6SlrUYHI55SOV/tBc8yaAKHLMKj7o2rLTLu6Oo2gDQMa7h6syvaNb/MA0KyslF/YPJcBzaSFGOannGfnZhsATbEzQLUFjV7FowXen1lYXKgYDeVgQ9dR4iRDNF0fD6VSMAfk6sD0wn/6fQXM4WP6y8roIGPgUusNO3TtyLmolhoDs6367+o+s/RyekLgIAEW7JgIBAKOTgq+NDDoMZGnpVKcNSXRoNjrWew7lr1WOpC+UnaHyvKMBQ9EqECKsoE+huvX7+SEHD/lt1xgsHQz4GMd69ejrpfgo06/KKwUYq+uE+oHZq8oKIX+ZP5uJQMaczGAWb5iNCOj+8dPHG+wM8CLX+jtdWYaC4sBXKDBouBQeisqakVSLThDMC6+k+pw2wo5DkxvbF+ocqFdDDgAdPuA/W2WCwGyC3x+ezW4xYxTyF7OkVN94c5x1xO3DXNKur9Mmg65r6h7Da3PXNPwtWou+Njn1lQBEgU0GtQErZSgbQdtlF4uqqf6QP5tAKnpu0J63WDP27uMaNs9rsG8er5LJoU9DxIwxXwjNV6V3uKPoG7sPIDIynIGNHsaaApG0yv7ZKbOTsBigCZb0CwuLpObapqps7rDeuD0iPgpMlfoqMdPhWDlqPSRVce56ULpAqrNOLCx52nCrKKdRQxUnzjhIkjMGXNgQwEHxwBD57F2Xd1SW6i/MeHukdR6zcCjuA5uxB3SyTGOJCuE7YWCkvIoGuDwdfDzGNCEwIFjJoaFZUpJZpevs66LsRGa0dDCgqzxd69umNSL1T0wGA21BQ28SwOfcm70wibsDOB/JqC+KcydAerOFFol9FdL6HyorB1xF0ZI9dn14x1eCCDd+kG7jPHjyksiTSoy5sFaVREo57CVbscqozvgi9Sp+ePaN3TLhbr3SHZiRJ+p6TBqkYBZQjo+7lvgwbaZcthYUABPMTyvneImUeYzZ7blthGyPSymU0bqOpkO7vmkjulucUzFTahIWI3EDqmdIR1u+k6koyjsKrZSZxcuXMyAZty6pgXQ7MPx8jMBicubX/ySFz+4uLDw3q0tHmiw3Zutm9dpyEwbUWxHCj7Vw9Ur50KMKIZ2GPhLaF57BgvS56m0kAQQJIyG0h1KR0lYmTnuWDDg2smlbzyb3IucK1Ce444Vrm9WU26Z0lei16Wv/wmzGRnAVBVwWwT6KKDR5yrTEXYoYxY1WHN2mM5WAlwYKHHPnnZ+7r0oEZJtRAKZhImYvYphDrl9jF/jdIjKO8EdB1A0oHJzQrVAORNois8E1KuHDzI2s39QMpom79G85KUvvrowX3xhUxtpGri7u6cyIMpzePiDX1wu90YLOnhDR+gm8oDGrKsUupZcksYqjinLfqwcDSRF/yUpMFcXBbyunpCDwepyZTBgotrCblhsvEwAwtrwO+oHCpydeL9U1aiETbcloesiOS+5zubYcyAs7RvWbmwaji9Xu20MdMy/pfbGHvecK1a2VwIfEjVJwCrFLvf3GHAK6dK/RzE7h0mvrparzi6cVxPjE1UZPUcDJAM+fNZoU82XvORFV+czoLEWAxgRCKTMgNG4W9CA8E4i/6v4XSOkIzEOmpKQo8IeWKtsUQGN8N12KCcdYgI60uzXysg+hxhGaAxCDo50bMxiCifAimKimJMJO2ndXr05KtW/YOTsMFZKD3c8pkwo4MLK6N9j2+NtseL18h6UA64kiMFswZ6z0DPItY2ctJ5Xri4ONIgnKocqBVg4Jx+ypwmQiUFF2+mlfIujcDkqoDl/zt4ZIDunP3wGQNNwC5pHq8UAZvSh/zU31cQeolAkGXKOlqGYfiMFZJbB2ggBl+2Qq6NBvdI+YfaYG39KgFWSQqJsCToE4yGTOFRXL9UWFYBQ+rAyHLC67YcicyoIMF0vJyHnrm3QD6P5hMWyBir1yAUyuL1567SdhG3eGHnt2Uylr6OACPCLYwGhbWv4+l2wkpDeJBaCzPVZZfr+dlKhNgvscOxk0pY1o1ktX9isGY0WOA+LAcbGGr6w+YIMaJYIoIFGivdoFjNm4zMaSjRL0Hq0wXRni1ocq+CBqR7fUMSEgaTUkXHgZZaVAHKMTr/jqvIpUmdE9U9e3n8fiLURKRObwuICCIrFtdUOdl6LV65HLxMOtlsxXTnDiu4jAwxtSm1XFc5Y5yR1Yxw49lzZzrz44eZTdDRP6Q2J6SOpe8cDDsumuLZ1nS7YTg408MImsalmATT75aaaDd6jKYBmydkZoKbakDozGU3l2AorSKftdi4U5WDROfW3f86+dqFoG7tJJQ+0JCqnzkuAhnOYXIRKsY8QQ+DE12lHtqYtXJuxxylgj2VQnITa5epx9risAtMtZyp6JgErU5wRAQ4CMjH3BsaWJOLW8xwdAc4SoKF8DX7vONtE9ZQFOxInzdvG73zt/h3TntdmcSBYLmSzO0buC5ummLs3nzxxMn2O5tEXPArfo7EYjWlc8YXNhZzRYMabwNMzjrkRsCsh5hEjXHSLlZU4s+qcG6D14lKHlB1uexwgmudj0kvUA0mdcyUUDHi6q3SAQbkYnRKHKwGbFNCoyynPVhcMOH2xwQpniypNkb4M2po4zjd0naPVEwFi1Tizeot7pimnjTEj9/mgfIDkb/O49N5wnzv3X86X1GV1UI2wE6XQRVESsIbUmfkejdkf9zMBH/5w4qoz2IJmcWGRBBpYdVa8R+MwGoYRpDhWt4x3I/T71kBiESLXBv2glIBogIirl2qLEzeak4AKNQ50GzyTwCQWaLgxkNR1jxnWZ+f9aJAaK6xNbhybOEbTDu4eL87nf5FtmjZT904cMPK6SDEDpoBuUkVEm9g9o3/n6kj0ph5zbTGPucyh75zD7gV3uy59vKijNflhlwsyRZleuWhKoazFAxCjXG5Lb4Rcbs0diwKaExnQpH8m4NGrS0v+HI0WK3XmdM7tBO4wNRIjN5pz88c4dgvwiPcRKJ3mOS10PW2oHwHLou26vHkuxFRMoW506u/gtdHOiug3Lf5u1SGHEor2KBZJr4BrP9qPBScM8Py6+M7eiDIvhRRzTxy2SJ4tsyyhxHltwfe1IYC2z9Pv0GGBFub4rTrVcmA7C4kxGs5Oj80QE/WxwMkdY8Ebzh1kQLO2KvzwWZM5mhfAh88Wyd2bY4DGFT6i5Z0lpgctazgl7DxnAwUCtu11PEKxplA/sT5w0TKlJ+rhJdrF7A+1YRQ20qO1M0gBGqoMXc+NC9OEu15tOG6OgbHlFf6gFrdoe6BLgWKTsaUCQir7welJPY9F/CEWYdrolSn9iqc3ACwSGyVAYIIRx1SwsZUCja6vGU0NNOX2R9n/YHmz+eGz5FVnjz766NXl5ZLRgOoRu7he3hzaGaA+lv8kO1YX9AEiJoVEMZVQ+iTk3FFbnTaRE16P2X5YEXvxSHAghtlMRdUs69GtefX0WV5CkbxpQxOnLb1GbUhTkAk5nlA6Dfvbj/Djro2tLwwiUkBswrDCgGM/ByFbUo6HnmmuvpsqM8daB+B18GtTsljw8f+uwxC9E7RbRtYPVekx+2UCzfh4ATT6HHz4DMAGPhNw/GSD1FkBNPWqM2tiMOvPbsZoMsaDfo+GkpAztDuvj9G7CnMg4qbNQkCjy3HtYP1AyzgpHpcKc2CIjQf1sGJgwEfmdlm7vO14Yhwt50Sx68DpEUf6HQIN24Z2GPUBhY0pVj+KJcbaJSjPP2dxQgNYfH3T1lCd1PPY8ybRVwAJlPOdt+lXMOfu3ioaeCh73YDM1YvpKI7Z/qXy0z3fN4VA0zxvA824NYbFFzaLLWhOnDiZ/sJmPkcDy5t3dqzdm/W/+ns01qaaDaQAe/uNb9PUHjJodV37JbkQq+GOcyxBClRSZ0mJhB3Uv+c1yPKh46nlkIpoKsfUm1sacHpmmSa6dPDI9SeFIbU1jm04+C7bibUjvt3aEetnW9eTti85Zh7nnC4HnBJm4+qR2CcFTlt//tPqT9UmNbdTV0TaqPW5fVhbW8uB5lwGNOYXNuFcMUfTz1/YPHnyZPpeZ488+ki+vHnHAJqqw/1yC5rFclNNIm/ZhnDOyBxgLN2mWZgdYddu2Y8w65seAxfMwWE2uro5kPLGzGVDAlDiHCZqswHInM1SSXG+Pg+Ii5JDuqR2u/piIv6m7KoNJtCWLW1J6D4P3v+R7UiPU+dTgC2oo3y+SBtdBhLQZ/m3ooClJ/TNLdJOfUw7Pkc00LiLAczUGczRNAKaRx99JF91BkCDUTj3hc2Q1NlE57jQ2XGMw3Q12A1NAUToGGYTZZcrMdF5DIhRYy2OyJE0HmVTE2EddEw6TeEgErpPouyJPEelSwYlVMQskbYYGcX2HS0WWxE0as9BKVXNHUoBgn7W7TtJykKo8roNju2w+ooCXtmQLT3rF/xFT8yniftYR9tqNQMa8PMm0GjdwGb6/RYWA0DqDL5HwwGN/h6NvqFcKbI61e2CDgY2CHU9YwBc3U4azOpYwMljFyOUTuNoNWZLLNCEypgiAkdnVQrLqpQx7r1+dakw2yT9ah5dG4FDoVDMdNqzoZ5gbYv5uRLD2iR12ggQYqVJm6Hnl6tnShQQYemlXv0MhNqEtszJf4qZY/U4G2nAokL0QrB3djD96DmDyVRly2Ng7/r6Wv6+5IXzF9T4xHjVDxBzeTPsdfbRj340dQuaF1xdWoTPBGyfg85wQOOicy30IJlpquoyuwxUELmlRK9mPfJGMFJYUjAoitB6qf6EHEcIzDg9WH9RoHFZjqbkmE6CqmO2S8UONur7hkpjDkKSWVA5nvp+yMvmv8aDdqg96zhF/wQiv8ed68DcJyFdpmABnbRuTBkssmf1lWOKlmfGW8JwzGCQAqHQeGB1ORvs9lV9b6q6O3qM1kpGAy9sFt+j6QBoXvhC+MLmAg40WVt7+/vV92jcTmHOwRSOPXDHvQ4InXZMubzNKsKPcwAh9kA9SHU/i1axsaP+bpL6CkXmbUTIFEhybelgK1aHFnOppyndsrBcizKvX/tiL4oZJHuhmDx2Th8Doepwx3wdfhxb1+MXCqUCFqqrOFGyATsQDemQtEWBLxbIW8EhwkxCH5WjyYGq+re2js/RgJhAA9+j+VDyFjQZo1kwtqBx14rDIgDvU84Kz92GUg8cy8DKu/pdoW5mzrGaN497I2G6MJFGZFIA5voUlnB428RZSRyzNEpuoiemD+aIpDhKVncAtNuUkG0cKHRrl0fa0HGWZCo0gITbNMcd951NwAYbS8dMi/FQfgnzeViZaqxUzS4oO6nXJno9fZYHG1JvT6fO1muggfdojKIV0IyO5p9y/shHU1/Y1MubEaAp3qMpXtjUQMNF6jESotCYc/BAoky9uOexlVaFGNuCKFXzGeSh8e3VtXDh3uD1dfFAG5M+8oEeBx6OFbl2YeclAMGVlerh6ksYJPY3dzwERJh9WmRs0nfKEklhXpLxSxWbieN9im3DZfdMSaPNdhmNPh8EGkInBzSl5TzjYACGKov9LbW3/ruwzgUanTrT5fMvbMJ7NGXqrMHOAPny5vdmQHNeuUCj7FVn2gCOnWDHJIwkNFCYjphUnJR5hGw3AQcbC8pxh8bHFYkDjHV8KYxBakcoxSexL2RjitD2+4AsbTumHEi885UzmFj9qUIBvBY2LSbIGlj3cnlpXEYgySKwfqSn0MUAIbDARKdtOaaQl4MPH9YxMWt3pavftxi5axdqLxFgY4yqOJ7/RIGmVxbIWWn+wmbxPZq52bk3fPRa4hzNww8/dHVlZSUDmp3zplH6X/0eDby46XaYYyOhB8Yta/6twxcJawo9eFwEbF4gTB9qd3VBtZm4E3ZBCLM55CjCEXgdWda68p+0/YE+hgVnTFR7lD0pgBJr/yAd8VBIKn1iVbYLvhIdsdF5tA4HcDB/lspotNggWTwzEoC0y9jOyQIdZIFBNf7GI8rZqcfKS52pOjDQqbNR2IJm7ngGNImM5pH8PZql9+4gQANGmHM0roFYJBty7JjjdIVjSWgHmXIeCKo+vcqKsb38iwSXEMPTY5tHCUT9WAfK26qI43KgoNpm7URucuoece2XtBM7TnxZ4Q7LDYWzw3JIhl8J3e8p9rY7duntSnRLnHIFKAbAStiIlMGEjrNtlZFoqC0KAAoVMuCT2F8SmMKs4mTVBgCN+x6NPlcDzej+iRMn0lNnL3rRi64uzM//yRbxKef9vf1qCxpvIMonQzeApaBMXb3yhsBooXJ0eE6JoN+ss+g5RLlff9NGnuKpPafEXqxcMN3Rc7eK98GAYnehB5hy9iGx9Gv6xpTlmFeKdOEU3XJ5+iN7iPQ9AdIV4IRs5IKxQdnQVd3U9kLncebiB4Mh3TFAwx6r/It+ZHqVPSntxDIsdzwwHVhgLAOasRJoEj8TAB8+W5hfsHZvzg3KPXsvA5rdDGgWUaDRxoQic+ohDg0emlZzO0cMHqXbf5j5CN+9gCHmVrVTIKNIJE6uuCbKSY0EbCfeg6GdmtYnYz1tOx8xwwuYh90zZpBjpzXqxSDYuAQwNkmw+zMWaCQpJlck70WRdVOB2GEbccGDro6DhhmEul6OYtBeXeJvpVUivk60ysso4zKLvl2wWDcWw+AIe7HjOoPjEghTNjY2ir3Ozp0jlzcXjOZkOtAUW9CUuzfnQKMXAxTGhFJnegBEnRZG/xRzoAR7SKl28nLOA0cynOIk0XZKCsrfqkM/UEoRtgrHmmyxRWbQFrB0zR5i7BiUDRww1C9+9q1jbX3SedBspE2RgKmEEUj1Shy+2wZ33A568p9oeQl4UTZwtmN2GLVze6RAA59y/mjq8uZHHnm4/B7NTrW82Yz29/ftnQHM1FMISDhWURkXYAyU1G0p5aa2KNvMNt1jrj2pwjEwrI+6PUkqT8p8sDp06rGshzg7TLftDFUV8aWk5roAzTbBsE3n7N7jlHOKal8Q67RxP6P1y0i8CwDjglE3GyEldZSDxtJNlC1SoClNI5kKlRITgar9w2FLfgBtnqOQQKfOwkBzPH1ngIcBaMxNNeGbDMa9o5c3Uy9suh1yzxedz4+gdNjVYQJIaNypG4BjWJJ0XEikDIoC3oJVKa+DkpQj1yZmZ6iPTcYkjm21N/E+GEZUzptFrbBrf46FZtt4YBC6Z1LaThnv2DbzNphgKOSEJU7aAqbCCVX1YpiMpJyUiUmZVQwrw8pwrC8Hmv19dT4DGv0ejZbWgOYhWN68vIJuqgk3857JaATROnYOW79uRiUmnSTnPBJy0r6N9RRHr/zDmqEgHqikaBPRR0VqtG4zYrNfLG3L2bbhGMO2tLu6q82+u3pigJzXW0wcdAmGYfv4XZUl9V3KxD0j2HFO4p7pMHtJBote7Z3sgFe3a9wTDDOIBxqtrxmoYDaZKTM3fWaNOwDNxkaxGODc+WJTTWPOqz1GkwENpM52dnbR3Zv1HM0+zNF4EXj+E2EjdCorlOrybuCEXDUGeiEKy0WjggyF1z7WriRVF1OXsy7FSbYdjUv1Y8djbJGUjeqb8aAdFfH7R9+13jOF9DclsOuKaXLpK8l57nh50mI3WL1Qis2tg/lEr44DcNSYa8DTP7HgWGKT2Y75+4YGGmNnAC3mC5uNPhNQp85geTO+e/OiseqM6wzpLHr28l2OktcDoQdGPm+A6uNSDFUzzaNPzokq3URhEF+uFGmfY20M6T4MXZT+tnR3DaBttN/MRvn3YGKvW5xdcd+lqdJluY+Q22pG6m45KePg2nKdN5aFkKS2Ys5Jt6oJHXf9HUYezLpQbnNzM7gYYGx0bH92bvYN165dS10MoFedbZ+D7RKq5c2lodYcTaWNTgkVv3vkxytf/21jNAZC4chen5dHxS6L0Xa7tnDtSqM+ycONRkBG5IlFPF2AkURC4E4JdV90ZWsbDj4lssfa0Lqa2MLZddhg6t+bxb8Nh65UoqrH0g4Q8cLUPSZqChlTnm3gwENdHyvw7PnHqba8to367kHs+eT6v7mxmU+RnD17Ft3rTO/ePDvbBGhgefNCBjQloxkZsTvsMhqq49qJBFNVJU0123D1StJdWHlMMKpYnlF+agEHPUk7sUIBKpvC6xX2cWbYjPDwHE+qHLbDNKUNkGlFjGcmVQ6rL5J2TbfAF+U/FVCVMp6BFNBhz/eUNd9MgY801UYBjVmHAzpMfxQ7ypra2NRzNOfUGOx1Zpw3geZ4k92b4cNn8MImLAaAv6tN4ErBgEYCAKFybrRunSMcLlaOatsXnPWwNcROL82pt+NUfXDUEsN4YgBbakuKDBZo0q8bSJXyYVQctemesLM/HKnHnLYvBkhTGA52vGdkeEJ1Y9NrkmPaT4bK9mpjzRKgIWc0sEt/vuoMS53tH6jRsdFyr7PExQDwwuai+ZmAXs8yCABmaWmp2r2Zk1CqSos0heCmkLBBlurAbOmCrdTt5n/hdvbcLWcQm5C0GddepaOP7gWgQgsGYtKMlGDpx5DE3BdGrag2qHb99qR644OW1qQj5IoL2vD63QcI5vWhV6HZ9ysFTLUuGej4W8lI6mGMRVKH+zt0zmqrryzfiQFOsRhgX507R6fORkdG92fn5t7wsY81SZ1lQKMZjYuOsOrM/EwA1ilu7qCmxPEOpRmDCevGHH2sjZRu0yNYujTIOFvUUGm9UJ/d1Btuf6FPzmLCwITV0WmNWJE4qVRGNciUUYyz5dKjnTtsAVixc0AhFtdgzKX9134KynJpKglDi05NRTh+6vkM6ZKADc5SkLpOKs30T2AXLAbQL2yaq86gXP49mv3yMwEZo7mWymhgZ4Al84VNx8gQowkBDVVeqsvVF/cg8k8ECTZlFCCtJ9HbNC3VyAFV/eomrWVGjk2BZrCps/hr1WLLKiXl2Zk1RL9tR009T/hxCeC4YBEKZkrNRTltoLKj+NCzRtpR7gvG1fF8k2EVV56b4Jekv7ixNLMnIPqbYtxcd2V/CTQAKMVigAllBrmtAc0LXgCps+UqdcYBjZemUT4IuMcsQyJuglQdqXa01ba0TtOxsnXkPwXlDiHV06o0T5kltUrcL+QxTeMHbFMXdWLFZRBdtGmOsyjpJWAsZUEWNLjj9d829GA+k/M/HBih9pTNhRgX2fdM70YGNAAm8B5NBihWnQJoYDFAw88ElB8++5Otre1zgIQY0Lhb0JgXrZ1UiZ0qMlNIGqxTIn4OGN2bT0rXQ2WlTqmpSF5kHTQ74KSpLTEpFWk7IeeTkgKStG/pTUhjUee6uN72MxJOQ1EZCTeQw/oT00/sPHXOlJi0l7R8fS35T6BYY+Cwp0qXUtV+khI7TTZU+bSikKgfmtEAmJhzNOb5/fw9mqZb0Dz0YAY0q/mnnPWKM9MoatVZzDb46VIMO5+yaRbVUQAkqdud4JG7TlHpMcl/c0GtemFLH0kfn8MGqqbXl6vf5dyNxCHL9NDzJFgbofPSduyTKgiEnE7SjkS9IHTAK9tkk0ptYedjjxlnyzK43dUy6fJRj9GP+TAJo8G8igRoWvlMwIMPXr26urqWA42HjNm/8AnnxSX/PRqzQ0G2wUTfqSwhfE42Oe3etOa/gxQJE4oBxWGStuwcZGrKaVm58yldXoMm/cSeJ9cJt8n67MLK8zakA6eAJgGAsMxFuLwGAfzTHVxd9wXS0DxNfbD0hX16Toaby+GOpZYB2dzUq87qnQHqgKVvzNHMpb+wefXBB66urqy9d2fH/pSzFkC6paVy1ZkzUFp4kJA9K00BxyurbPQObYMfaj8/X95RbbErSrCxlY53SO9hARPeNs7c0nQNTtq+1q6T6wLEUhhcjPPm6nM6pGk1V7gsh0RPyrlgOiwSJCT95EDJ7av2cZivMD//4gbUm84cjan34AD+2y93BmiwvPnBhzNGs7T63m0HaPS/xfJmWAywg3bQNNz9vYlIIvyY9rp0DnRBFeVH3ZtJp8kOSw7boX+xy6Hcs4coDbJplUhBUQI0GNCKAMqIrim2ZfrZ6rzuPZISNYUCn8puX0XdnnID8IzRbG3mjMZdDABiAk3GaL4zYzTvIPtPnQC5+uDVB1ZXVv8kYyznLYNKegmNLC0tq52dbR8pywGVAo30nCx6L4ZMnL5DUmRcmZCuLh5aLBpr2k60rdadGEbKQ3NgCFUeCsfs2CXRE5fK0imf4ZE2AKIdSf8QGnYuxOgolkEFxDE6MFs4tmT7zPoegXr5cmfixgFwA6ABQgGpMxdoqtTZ6Cgsb/7Oax9LBJqHH37ogfI9GiR1Vnxhc3kZgGbHH8Be/YY7eXH6in2rv9ZZAkdgNVUTZ+JSxlSR2GhKsr2BdN9t6UbaAvmjJG0HDE3Tbk1EmnoDqXyXgZZNWFFsSk5yLL825eIrKWCZ/XPTZq7o92jOn7+gRkdHrHP1HE0ONN+RMZpfI/tHnQB55JFHHoCdAbZ3ttE5GjBAA43ZONVBjtkk3cxI5Joqg4y+Y9pqyrBuNTnMNM8wpZiGyRaQlHmewQvNatygnnPakr5KQMLVI5nD4Y6754JptED7IBzQ6NTZaM5o5r7jYx/7WBrQPPzwww8sLy3BHM0F12CIqKGR5ZUVtb29HTVnIAOaOjUjYRvuuVvREZvLk3uHwGZaSfW0GBzclu7Fd6p0Mqwp+KVO/qeAXAyzoc6lAE4Mq0kBGk6XazNnvz6ugeZCBjQjHtDUX9jMgOb1GdC8k7SHM/ahhx68H17Y3N7eueQhY/b7QUabsvNqa2uL7GSTlVwxNy2mb9jBJoXZyOaoupdhi6jblOGZVzhckThSLCp27wvp3BHWnk6dc/W7AhpdjtKfClYhtuSWQXU4e5RpwbbKMX13aI7InXkF354DzYUL1fY1WhxGkw40Dz549XnwwubOzs6VfIHciN15nTqjgKaNOQ9TzAvuXpQYh611hcoV7ckD8Pi+2t/Q4OpKd2wedhl226k9rVxpyu5I50ihnHMjtpGqStWB3a+UntB8DAYk5LxuhxFAeBzKeeIGLCd13oZjeRyAYG147fVUdb9jtmNAo69Zv98So7l69epzVldXYTHAc+FvE9E4oJFKKCXGg0J94WMdVzRbKv/twj0WtnSl/dYRLMgYNGh1PQ/h9qcpEAxKQu3JwAY5XwJLN6vp7NS8uJYA8MTpsR4f0mDpL3esMAaIAR7GOikdpkDqDARSZybRANGbao6Pj+9lQPOt165d+y2yL3Q38zmayxmQwM4A98LfPnU6IFNnsWkvCdBwtM+Nrlh20CI4pTKrNgRvzyW/XbfXtXTXn1RxJ1mHmaGlisS5DwLQasfZBdgUEpNGc//m0lBuGSmjCabWDBbi1sFYJtWGHlOKncIx7duB0bjMql51NrZ7/Pjxb8yA5t+ihqsA0Dz66COXlpaW/33W2POr1Fm5JFk7AAAajXqFwp6yl93izCP1IU1zdrazasdhduMAY1J7knItWKTa6Kdko89bRVKuTVKa7RDEtFOaZjwMiR2z1PmWUBkvECXmUKg6VLtSQItJ49Xn8p85kYCFXvD7hQvn7TbgfL/6TMD27Ozsaz/+8Y//O1In2VomDz/48PmVtZV/v7W5+SC0XjGaitLaQMM9YINwjMOS/x+MHekfE2vSZvvgOnyM5SjJoNN5KfZgbISL8pssHojVoSU0hlSKCilpteuNH8JGwu33WOZRKUZSgdHzQqp+GqEtHGhKXw6ps4M8dbY1MzPztz7xiU+8l9NLyqOPPHJmeXnljzc2Nx6FRtxPOUOjq6uran19XZxKIAfLyHumO8+w03LpX6weii5HM7MywsfGQ6KrKzAbFrDuQrC+xTjqYUiVuQ6UWqkVAz6HMReEpXwrV4ACU3zqLDQ3VJ60lAs4i8gO189IWAZ3XspMzNVoKWk7U8A/bW1u5QQDtqCxfLUqPhEArCcDmo1jMzNf9/FPfOLPyD5xDT3y8CMnV1ZX/mhjY+MlGkjcAQSgge9Kkw1EOE5TL6eLm5sIOQPKwcfaOzgRgKeOMLpgBvpBvAVkkNc2ta02GErwGXDOJ7fZYBUYtbLMjJgpk0zQEYFJQHBWxW/rIul6aK4lBVBCdtHzMUx75YBSq87g2HmYoykMqMrmiwGy/yYnJtYyRvPVH//4x/8CNV4FxurBqw/OZSDyh9l/L88Lj/QsxITG1tbW8v+qiSd9vt+R44sSw0kzDnPQ4CJ9sGOj7dg+uCtUJDq6GKuUaDWFXUhWR5mTpHghZUWMlA18Wif+nHbATbNkKaDCjQe2ygn73dPhZDCkzt49FpqMl4v2FfGMBdfWKz9SFqejmMdR+aCnsiBzjEgdvbKc0uSh6LsL+gA0wGhgMYCpWy8EgHcpJyYnV+bmjr/q2rWPvo+0jzP+6gNXj21sbv5BBjRfAYa4X9l0gaZCx44mfuOcnJ2z7Iq9fDFNcoMMH+OLk2GaWA8J64CJF/ao/QPTbQiDDPW7dphaeJDnnTIKMD18KUIb6cQQC9DdolRyDMQt46a4QqyFtBnxRe5KYSw44oIeABrYTBM21dS5E70UBIAG/puamlo8fvz4Kz/ykY98gLaNkUcefnh6dW393Rvra6/ql0ZzQDNMgkVZg2ivaZkupZ32w6m820KL5UQC+Ze2WVFK2TbakbShxSvnZEY4W7jzKeJmb2rdddaGb45nR5ytbNrOZTvGSl+3nBhonIwP/Alv/udAMz6uzp09q0zWp4EG0mcZ0NzMGE0GNB/+ENkfsqeZvPCFL5pYWVl519ra6teZ6RXTWFgIAPM0qRdY5vwI58akw/RF1pESxWhAhj5CZ/s5GBkkE2gyv6Hrd9XuYQcLEmn7WjUBI30exGcZ+U/0nK+DZ1aVGCyHYlwxEqzjOWg7VcXZLQGamLkcN7h2dxNx713Tp2O6AERsRmOLBpqZ6elnj83OfWUGNNfI/pA9zeSlL33p2OLC4u+sra99PSiExk2EBJPXMzYjAZo2fGX6Qz6cUfgwOq2uAcXt8zCOgVTMB7tpH8hxF8w6a4fdxXVLmk9SQZODZV1w4u3wnbkkNeSWRx0ulaI0y7D67RR+jA68vE63FX/Z8zF6LNx6hQ0e0CtzjkZZAwnHYekyLG8ezxjN2YzRuOO8t7dXAM3MzNOzBdB8guwf2XMFQPPy0YWF67+ZsZbXaaBxGQ2sOIN3abqPdu19wXT7URpailjbco5BPRg6t8hu2nSUwyahaDaUO29rPDAnGKs/GfxFIIU9TxBQFjbGgJjbV2rc8xZIekLbTM2LYAsCQnMosew3HEib7AUPbFEd1fNcnjNxrUrPldcn+2+kh08JuEwqtg/ablO3ZjSTk5PqzJkzVvlqMUBW5tjMzBOzc3Nf+eEPf/jTpH6yZQW7Nz80srm5+WsZ0HxzxWh0Pk8V47OxsS4GGuwBEzv2skEZyPgXmspNUqtWunK8VpsGaMQAnMRGUl8AqI7SZPltSRRnwUAbKSasrjTl1qhNZTuxLuZqQnMtVkmuYM9fvCAddy8tFsuOAsysqGOPZg00UxnQnPbqaEYzNzf3mWOzx77yQ3/9oc+R+ummYY7mhb0MRN6WsZZvB6WQNhvpjdTbKGT/hzmarhmNCTTF391OWQwqwjfbiW0zVP5WZCm3pRvh5hHcMlKGE6ezvRdG+yU7aKKXk0YMCAEarg4XCEsD++r36v3B4i9JHQ5owC7ABJDZ2dlPHj9x/JUfeOwDnyf1coa+7GUv683Pz//LDEzeWAGN8elPsHpzYyPfwXnQUbC1lK9r5OlAJA9iEzlKQNMEcFPl0JkbpKV69efOJVGzOTZdryxzU6p+fj9iHiag343qY/pG3S9trYjD2gvXo69lba+/VY2k3fIPdMED1g4GXtJ2IDUGczRTU1Pq9GkcaODf2bnZa8fnjr/qgx/84DOkXrbVTO655563Zozm+0ygKVrK/p/VpoAmlN5p6lDMCzaME/2HKU3GtjtHPxzXaVgA+NCBzpA2bDGBBweWbgIrckECMveY1EfHqTuJusT5kHrqwS1GgTsHcBLw8Dc7tsEI0yEFmhMnTnxobm7uqx577LHrpA2shSoHml/c3Nz8gd2dHTUyOuoZBRtqLi0txV/EAfmdGMdym2UcXfHnB4prmTLmXYCAeW+RkXvEDShZDSaZe2kypxHDQLD5Gy2Sulg53TamS2NCDGPExGfbufag7U1SbNCezhzFzMXElnXH1b0+ADQ7md+ngGYXUmfZvydPnnz/3OzcV7//sfcvkO2TlpVyz/Pu+UebW1s/uruzrXolo9EDDnbBpmuLS4uHGp0dhgNv4oxuA04z6YoNtDWRHIpU27CBTGlFjk2oPQk74QCH1e8sSuDaDwGrlrbmekx99HUromUqdSURtlyvZy2A4uoF2+spZc4QmXs+enr6xaTEfgYkO7u7anp6Wp06dUqZ7MBkNBnQ/H/H545/7V+9/6+WmeZ5yYDmzZvbWz+xk1GoERdosvPbGQAtLCwMCGjwrfGpmyHWod8GgC9u6ZLRutF3F/MHdl1ZX1h7tNNpCOwS9kXXLfvRV8G9w0J9NqaWo2ygRTavFjofy1w04+HscHWSQFqc1LVUBQnZP3u7e2o3A5pjx45Beswqor+uCXLixPH/eGx27tUfeOyxNbIPZO9Kueeee394e2vrH29tb/XMORpt+M7ObgY08yE1rThxk0lJwSZef7yONgGqLdD8ohYdeMXMWHdqSFiapbAqTGgElNIUGEjTVJupQ2J3PFOT6HTKNLhfmrzYGWI12NkYYDKPc9fPTaEByMB/x48fh5Vl+TFzMcre/l5uWwZCfzgxMfnaj3zkIzukTaS1pdx3733fk4HMP9/c3Bx1gaagT/tqfv5m8Bq15Sib6unCYQ9oumkg0vU81WHIME28tyneM9cQWMPpqfpOjx1Pd74oVkdKSrAZCyv+5VWYTz6dRjPLYylHbG7RbIJaFB0CLirtGAIpF2hOnDiZsZoZSycwGkidQdFTp07/1sz0zLc89oHH9umeB+S+++7/pq2tzbdnQDMOIDM6OmpMiEGD++rGjRsi+tc10AxD1G8yrlvRuQ2juNe9/jV+kpo6FmWPufReKBzAk/aUvr+tuSXevrSx7LY9fszMVLvpcNsCLX+hgC7nA5D+vUmqLYYRcf3F76ViME1Wo4EGFgLAggCzSzXQ9GD+5pdPHz/9vf/pL/8TedMHR/v+++//2q2trd/eWN+YyRnNaJ0b1J0AoIF8XehitCGSybnBy63EaYYYJK01tIq8e4fW/oB0bTe1Mi9kg6SeWW4wjCX/2SidJm1TAjQyttfkWzf4S6jmYyAFqep35EKaQAMrzsCvwz5nsN+ZyfLgOADNaIYHp06d+bmPfezaj7N94k6C3Hfvfa/Y3t5+1/rG+hnNaFyjbt68mSOfaWT76Sl7J9JW9LeAD8PAor5YROLAUKfgPFBtOHTOFslSYnrVWP6z+rurSXhp3Sar26SLAFKua2yKt+5PmDliIEbZ6DKH8qgo5Wa9/G7oK38xqTnatqdVzIbsNB92nTTQ9PsHGdCczwHF1FMDzag6dfr0D37s2rVfZG1jLc/k3nvvfcHO9s5vZ0Bztws0umFYdQYv9hzFKNKU26BRyzCxgpjIEyTG+WP1KR122fS5LK4d36Gmr9hKEf0MxKaZMICMYjYChuovIjCdpW433BSmW//u6g/1BfMZ4SmEXHsScITAQaqntoW+Rpo8AKNxgWp/fy8HmwwP+hnQvD4DmnewfeFOgtx3333P39ra+vWNjY2HYZ+z0bFRr8zK8ora2NzolNFwchsgbovp6FLvw/zhLSryZQQOWHLetHVYxASa8kiQARS/l0G4MrKbAkdd6WFWbWHX1tZdnOeuS8r1ErEuhHmg5RBpj6EoZS4a4ECR1INETsBo4Fs0+c7NRj+hDrAZ+C873z995szXX/voR/+A7Qt3EuT+++57zubW1r/KgOaV0AA07Mr6Onxlcz2kqnW51QGmSdQsb2OwEXMX0u4kdP4zmRWZerCiIqASrh4LOVXJ/UPr0IYMBxhiztNPWVFAlv9sBPz8ta+glbUDq8cVkwCRZBGWKTpdp+txdfWuAPAOjVtOA8342Pj+qdOnXnbt2rX3sbayPcnkS++//+Lm5uY/X9/YeB00NlpuQ2PKxsamWl1dCam6LbeldUmNYKVlBi3tzB+lzV9Iy4Kkjm21GixX0sweCmBcJiJhMu0xTBt0ZGDhXy+LSfXid30OlfWBxo5uYD4cgGbu2KyanZsr62hi06/maMbHx3dOnT79QMZoPsX2kzsJ8tDVh06trq3+/Nr62huhu5A6MzsCv8NW0vCpAFjy1oVUg5P/6I7BVJ8/aEG6YluHyeIG3bZs4rb95cmHKVx/JIsMurKDuu5N7JEsiAilrmLYm18n/JpEzDyVW9bWj7EXOOD2yz8Wcz+7vpkT/U4kNscExwBoTp48WSxtds4ByOzvH6iJifHlixcv3vHYY49tsHaFDH/owYdmVlZXf3ptbfWHdeoMo1GwsabekuC23Ja2hMoimUHHYYKK6wRSwZiazA7NX3QhnFOvhd72JHVi3rNDhbsdMxGP1UWBsjiZ5OhLK5QPIExpokw+lpY2fDwoRhYzB+SBa0YaYNNM2OMMvrBpntPbz8B/k1NTH5+Znn7+448/zt70wVF48IEHx1bX134sA5qf0akze5+d4uUdABpAwDYudOhc1xLOe8YTK2Ha/bY40k4qSbbC5zCYYuzqLplSpfSLnK6zDM9n4I67Snn1fF3UvElqf2LTWW0sAKCYCM8kZWCKMjYiduDSYNi1q8epgCMMPGLmcPQxDSSwEADeoTHb7x/01f5BkTqbmZl5z+c///mvIRvQOkMFHr7/4d7K9srfWVtb+yX4G1viDAYA0KQtcTbxOlDyENNGt2WIpCFqD1tqDXfYvBNrw5FrkTxXliMuDTQdGTcvEl5VRi/ppsaBWtHGB6+2Lr5dX2fM3I8lPXlCntbpA0mMDi69jIGb3pn57Nkzmb8fs+4PzWjg35ljM7/85BNPfk/QpqDVmdx7zz2vWVpe/n2gU/BNGhdoQODjZ/BtmmF4gIfNkXQpsRO/t+XWkKb3eNvPSBBoAswCBAMXPV8jqR9yxhxrMc/79shWk9H9tHedj0lpSdr126F9AnaNXN1AJnR26mzGaEbybcfqMdBsB44dO3bsp5544omfDlsmkEcefvhLvvDss49nKNfTqTPXuIzx5P8Nk4O/zYBswScpm0kXE9HxNtSrYWLtCDvBwQP5MARKbdkgYRhsGaWqj5fFpvrw59+ZWzLYcQwbCpWnJJzO6lX/oCU0OFjt66xQryrCPZehY/C7fofm9Okz5TkbaGAzZTicMZpveeJzT/wG22klBJoXv/jFx576/FPXd3Z3ZnTqzDUW2Az2SedUuQ0St0UqOuoFGaSD7gKEYqJnqlyMA2wyT0rNx5gAou2zHb+fLudsMMtgbInSwTGU4nh5/fqK3ATVtDnpWtvUzD5ugFyvOiYYF13XY2/yccXGxfwXpkFgEUDxwTNbihVn+znhODY7+xWf/cxn/gxtxLFMJHfcccfHs8bvo4AGDFtcPNwvbQ5SUicgpWWGSYY5PTcsYzkIBsCVM8EWJEZHLNigzr/aixDfCp9mGDhAYEKBGpYu4xYqUH2kQI1Ks5nlsH6ERFqeK5eShtPHsfHQfwOjmZmZyb9F4wpsTQNAkzGeg9m5uS/59Kc+9blgH0IFtNx5551/sLW19WowBFt5Bo3Dnme35ba0JdaDkLAAYBhAqJENTJ/9yXkktQPVjRVoIJIgSIuO+mttfOpHMikfWlEmDdRMiWU4lM2UfkxXUU6fo+3069msQwIUMSvHzFsGm4+praByczWj0R88M8cDfteMZmJiciE7f+Xxxz8Z3BYmAmgu/9LW1ubfgTr6XZoiiikGGhoHoLmd7hpuaSsl2eVkcpc6qTRPVyJlCyEg4FZykfMUqn7AzQlzi3E4ZayUklGoX5W223AVSR29118jbcSmD6s0V+GwU5gYCXRGCo1iMrY+3XHqvGxZfZPzXDn3u0juPAw2Bvq4flkTWI05Dvo7NPDv5NTUY8dmZl/6+OOf2AvaJ+pFJnfdddebNjc3/37/oD+dM5pR+0ubB/sHamFxgd8dwE8hstKF87k999OOiAhG4jLkRjlxVBee8qjLYBO9Om8ua6PJPElTPVUZx1HahWoH3SMAITQZHorEXSAxgTCmX3j5et4Hs5eau+HaqM4jrC/0O34Ov4+K83YdCVMx7TfbkQYnmB5Kp3lOg4l+WdMUOAfZK6g7PT39tjvuuOMNf/mXfxl0qOIn4cqVK387A5q3ZpTp4mi5xNk0EAyAORowkGzstpPvTIYhTdREQimVeH3mPCz/Yh6dBmoB7GoiQdgp6G8AsE2gCT3RmJNrY16RS5tJU1bUObdbtg/Br5HJ4DTzMZ2+JP3GAQv2N9UfTCSMiYrMsXmVGNAKta3JwslTJ9XE+EReVpfXQAOGHTt27EeefPLJt7CNGj0Ryb33PO/qyurab2SNXM0XBMCeZ8pGV707QBM56g6TEzM1MTIyRKCbyDxEqgW5eqnDi7k3zAgYhHJ81gNnLQEK6Zf3yz1v2hPr5DknHnMZKWDA9IX6YvbHOmf4yRB7wRmWtsLXQTEYrFx5VLmptlAqjWJ9XDmur674QGWCIr8EGj9VDzhatxd6cbTQW27/r06eOKnGxuuXNeFfmJspv1OzMzs7+w1PPPHE7we6WVkmkuc//0tPZIzl/97e3nkVAI2555nOBa6urqqNjQ3xQN+KEo5SvvgklCYx0zlYysd8zwGkq3GNuXapAZEMWPKfIju0Tl0Wm3Ph69kOGHP8Wqj0VzIQESJ19uVRlqGYfTTPcf2TgImkT+n3aewnn6UAFbYJzgOQwEaasBggX/RlpBb15wFGR0fnM0bzNZ/73OfeL+uRUK5evTq+sLDwK1tbW68HYwBo3JVnADIANl/MDhbLJQ+rxObJu7Mj/5n/3qRNbTPmSGKYgxQM2h4eafrJZMaUDZL0EmdHyjxAXl5RaS5/2XNlXSSjs6XWy10/HDxqi00d/t+4UMBLtceLmyaTfA7avv6S1HNxmGaGIAAkGYjkK85M0YymYDzjnzs+N/fKT336U5/m+2VaK5AMaHpLS0s/noHJmzXQ6Idai17iPOwO9jClCwcV1344fz6otg9Dv8QBxAAwFR3TUT5+/WMck+3QbH2Vo5fO6xDnTFtCtkn6JE0lcvZyNpng6wYtFNi5+jUjVA6rwxy4N+4Im2nOwHv6//4ZgU6sDNYv1x8AkMDHzqanp626+tw+fIdmYuIDGeN55eOPP74o7IlcLt9553dubG7+KtQbRfY8g4mimzdvHolo/lYT6ZhjkTHlLI+axIB4CuCnROF16qZZgIHZ60bf9knlPd0pQEy1S811hPSR7ZY/nICbbJdKb2FLvr37u9er5p5sqZKOzjE6leY+S+55t16MhFJgYfYSD0Z6w0xqxVn9Ds3EH83MzLz2s5/97FawERULNJcvv2pzc+PdB+USZ++zzlm/b87fzA25DTSFxDm/9AioCbjHAo3VJ+EMdBMmxdfFDQhG031FLwUW2jEMkjKOsdeCKg//6RVK0iCHl/qaek6717NhwHpWinvA7R/l7Kl5KK4uNQZ4n+hv9cjETwd6JQRsk0vtUWX0rs3nzp2r/LtuYx+AplzaPDU5+ZbjJ07/yMc//lGR44h6au66664HNzc3/zgDkgujI6PG1zZr1IcvbQ7LLs63xRYJ6A2jM8Uk1jk2LZdavmsxU0XmxDg1R5GXEoIMF4BI50FCbUvLY2ksrC6mgyoXmu8x27Pql0EKnlKjAUnKMFLYdgiMpKynnOhXZ8+e9cZeLwQYGRnZztjOdz399NPvFNsnLQjyvOd9ycW1tfX3bm9vP1+vPLMXBPTV1ua2Wl5J31yzeFCSqnYi1AM1TM6mTQnn4+v0RBdty8Ah/1n9TTsROu0heQgl9mlb2PEKOJqY+ZKQfVVZxxmGUi2cY2YZgDJ4SOSzwqWbXDu4+RJKbwiQuPOSa0LVQzrhMDBe3PtFBlaSxQNl/fKxcMdSz8HoPc6qnQXgXPn+TLnH2ccmJia+LQOax4KdMVoXy/O/9PlTGYi8Y2Nj4xvAsPGx8WqHAC2352m+iESYNotSWT6nklvHfhjdydn6by5V5J9rNpeCG6qqcZIwgVSbgkBipp8CaSbMueFzGvZ8hssITH2ubSgAGilNs7yE/VD63d+5uvFMyHDgju2VTl2qoU/kQKa6Dj3+XRlqPOB3ABEQABlY3mzqL17U3MnLTE1NvjMDmh988snPXxfbLi0I8uIXv7h3/fr1H1hdXf0n0K+c0YyOeAbPz8/rl3pi1BfSgfM6LEm9sdoC6fpBTnOebaSn2khNSaL6NpwxW083Iqgbm6LknLCkvrSOW1YLZUcMA8LKeWUcJ0g69dIHcExK1h9NJsJjSUlsCjJkB2V/jFBpMn2PhvSbAYSGKPg9X7Y8OqpOnDzpzb8DwMDL+Pn8zNTU35uamviFz33uyW2xzdKCWu6553lfsri49MGs4Vm98mwkp2O9shNKra/D+zRr7UeGQjnqWNVJVJ0obYBe0wcsygbi4vuOPW17Ga6ud90QWyTzC2a52JQPVV/qhCinG5tu88r07JVePkty2RFqBXnNzHQqlo7DUp4Y+/JaRIDOPE6l/lyGIwXMsB34GKSkgl0bAGhgSXOeNnN0wLnyq5v96emZlz/zzNN/IW5EJfjj+++/d3JhYekvskYfrYDG/OJm9s/uzm6+Hc3BQZ9MXxxVaSPK70JulfHNJZBqcotKHKhVx8pLmyl0OxVn/a389AfFRGphVg8ZcyiYfdwcQyybcPtAjQXVNqafaqM4zjtxt6xyUmXYfAxni3bAChnPUD+kbIYrR9Su7DHvHUN5o2CYZDXIvV39rVTFyjE9ACYAMjBHo/sAp/X+ZuVCgeXZ2dl7n3jiiRtR9sYU1nLx0sV/trGx8d/kK8/cDTbBsP16g82j5Pzait6/2Poc3aZSXgqlSboM74OM18ZOusYGGpxOfx6jMJnSnsLKXKcvsS00DlQkXzk3fRwBENd+l8XY4C9jANh9gIEdd5372vA+xrY4wbfAcW2WMlmiCY/scTqk+s0x0MvUT58+rcbHx60xy9Nmuzu5X8/O/eWpU6de9slPfpLZph/vQrTccccd35oBza/B7/p9mqJz9YjcXuY8nHJUgPAogL7LetAy5b8S8HD/DjObuv02QJKrUxMPem7HPG7b3vPq4eDgLw4IAbU1HsoAOWG6VspUZOBDb13j9guzQ7KaM6SHs506rsEE/DgAjTveOm2WWbk3MTHxK9ev3/g+UcNmW7EVQK7cddc9G5ubH8ro1JQGGnffMwAZAJtDd2pDMmFjRlggVFqgKEtHkrpuW7a4doVsMIXrQ5xBipzLwHLP1O+mOhUYqzbnnrT4jtCyyHNWXB/c4xToUHMe5kevJM4JS+9Irm8IJLE+afvMN/hDjhxjQkEQUfij74JXZWtf10tPt+nyHNCYf5sMTLdutUd0wut7gdrmAYUFBZge+A9SY7C/GaTOXNvhHPw30huZn5ic+JFnn332/0SVMZL0pN1///1TS0tLf7K1tfUyABigWtinnZczoNk/YumzYRYKFIZNBsJG+voxoh9MMkWihM6XARIrdVW2ybWHtSOJ0ikd3DwGBciUHaF2OMfpsxfjnBOhpzho117OjuK8nzIL6RGnynrU1jVh4dpOeV4ogDdKKCwt6ekpSwGjATaTMRbrbL+v52d21ejo2F9PTU3B+zMfiTJWNYj1L1269LMbGxs/lhnfA6Cp9j0rHzrI+cFOzhkY3QYaRtpM73SfKgpHrO20o9g9r4pCyptMl84peOeIlEUIJKpzqpzwbTIcRI4NSzFRdnrOH3H0HMCGUnSmYADjCgcQpo7yD/QdH6+c8oHBG5e+z0rsgY0DZqoseq5qG0sX4kyqKdAIa1T2mHXhd/DVcOTM2XP5d7JMgXOQNitTa783PT39bU899dRqZOPpQHPnnXd+eQY0v5cZchxSZ3pBgLkj0WYGMpA+czt3WwYnboRHOZfDvj6Sh01URvUVt54nlNLRwgEWxyylTiPk7DkH5Dm5Ht7j0JyATre4fXJBSpoGcsu47UmAkjpW65elzYpKqk7POaklCVCa7aFnCNChWCTXTmoA5zNPPuBxdcPf+UuYk5P5+zOmgE7ISO1m/2V+vj85OfmTN27c+Idi48x2UiqBXL58+UoGNO/Z29u7TwPNKKTPjI7AJBK8vKk7JJFBRuVN2w7lgFNsaCrujSZqSzKP1XLejkvJYGW583WZCCdU1ct/esd5oAnfQxJ2xTlYzw7TaQpsdXWFUogxDisU+cemD7Fz+rpgDAyzrVaDtNtTVfDBpdZihGdreiVaYY9texh8mgENHzhiQOMuazZt0vMzWbntycmpb7l+/dnfFRtntpNSCeTuu++eXltb+53MiK/V79IUrKZWCwbDdjQ5NQvmQlRZp1V/1qngDGG47E8BNbdOV6xHonfQgUeIBeBpljq9ZQoGOOEgJP+pNaD3kpQ9oG0jk/BseQHLxGyoz9ngj6WNzH5I0mZYMOUKx7pCqTIfKPOfnn5sHNxzcjArmZMBik0Eux7UtQRGc/bsGfiYWT1GsO1MOT+zC/MzI6M3pqamXvWFL3zhQ0n2pFTScvbs2Z/Y3dn5md5Irzc6aqTPjA4tLy8H52msG1oSXXv1Zc49Jq3R1Ll14SCxaCw0rlRuNmRjKPIeNjFTK56ZRs7ejZophxUeVyLd0df/xM2DmOdxRtE3onb73Y1wes1cduuAYhl608yGZj6mSFga5YBDjt8XrSfMBkKMwRxv7Dkh7ehx2+nUz5ypjwJX116KUXJ+TpodMG3XbGYcljWfOZvPz2gdedoMXtQs52fGx8f+36np6dc8/dTTy7hSXhp5jvMXLrw2M+TtsB0NpM/q92l0f3r5MmdYFNB1ZHqUpA2WEd2mKm//CFCmIupBiQjgqpRS8UcKcMakOiW/c/W5NrTgzhL/BID3t9Zh1i8K0rp71QAatU2AMTXTaSD3mMlmKIctceo8+DiBlJFi5Jy6CyxU6okDEMxOCbuijnNML0U4VgP6IQsFQAOfbNafbTbbh3PAaCAjNTEx8fPjk+M/9oWnv7CfZEtSD0q54447vnR7e/vtmTEv0u/TuF/dBGOB1QzLLgFSR5uun8nNC17IMkVfcInTj2Frpu7Ua0L3kx9fDBCoOlUbQpYb4/hN4dIn1N+YYEAWYgaY7bHC9Sd0nTHHZtpZ/56X9tqwbbDTZEpX8SJzKROj2EcI4HDhUnJlCZvtMXq8NpGUF6cnBGrcvWT+zV1b7n6q9GcgcsrYDcDUq7edyY5tzszMvPSZZ575MKkwII1c7pUrV+YyxvLTW1tbP6i/TwMGmwIGA6O5VXcJcHO5R62LnnOucvj5EdWE1UjGI4ZNSM5zjtMsQ52j9IQAwr0PfDCVpTpD4Mg55xDAWGDRc1M/dkot1I5bXuusmJMxF8SBQT3G4XQYxWi0qRKwSRFJkEGVDd1DoXEXp8Miy4C/htQYLOA6c+aMxwZhuxnYdgaAZmJi8s+npia/4umnn47adsayKbUiyCte8Yrepz71+Gs3NzZ/p59pGhut02fmzaHTZ0cJaAYNILgT66DdADsQpauc8odxXS0gMB0cEimG9JSFjfc46offBxFJGgzLr8cBj5T1hIAhlIbxgaB2+BSAY2OBMQUK+GjA0b+bDraHHPMFs804idwffhrMBFBPBwOErg123+s6IcAKsRzzuES4sjptNj09o06cOO71W682g9+npqZ+4saNGz8rbhizpUllkDsvXz67vr72yQwBT+bpM9gloAe7BNQ3FRi8tLSs4C3TYQWbWAdLK1LkqA7CKVePUCJIYePQTYqthQUXhSKFsS46qrcj7lD5prZy/deCOWuqrBvB+4wl/w1lHRSrk6Sj7DK1k6Yduw/Wob5SjEX3yTujr1f+wqxUH9ZHH3RSJIb5hHRgIK3Px+qjBHSeOnVKjY9PWM8PzMlsb2/rlzThs80v+8IXvvCB+N4YdjSpDHLp0qXe7s7On+zs7n5F1ql8lwBgNeaAg+Hw2YBhmafpRPrKe1PdK9IB0DTVmTsj+CUBHGPalkTw3HleuWzsXZEyjLaum45uKXZUORWlnLSWywR8R0/aiUQcfApLNu8RYno+6LnsgGI9SplOnwK9UF9snRhzSUuFYccpthZicUF7ev5CZy4gonRjAj4ZWM25c2ezMvX2YXnaLDsHK4Xh9wxkPjg9Pf3SJ598cifYGGdHk8pa7rjjjjdubm7+fAYkpwBkYL8cc4jACaytramNjY2iUexhbssYgXTJLChn4ZfLfxKOTcZGWmEFjYGq3TkWsgzBQCQgY9YppH6hzo+60xZdtJVy5NJcXpkqJVSnlzA9WheVWuIYDZcyxBwt1x+3T1S6DUu1SViXVNg0G2Ona2s9/mYdcxk6b6cU0Ck7JEJdkzIlpk4iuwFABgq2ncmAaD/z5W+8cePG20SNcXY0VQDynOc8554MSH49o1v56rMMBfMO1p3sZ1QM0meLRaMds5pU50lRVpEjbAAYfkUVvDIpN12K8Iwm/yljEmWZpOEI9JVKEZnjjzteF2iK/lDtaD1sSg5xxqSzQFYhhpyepwPpK66rcIIYM6JYEZa2ocAnBoBpJ48v4fbL1X1vmqriJAQSdeo2vn5KGbecxD9xQDM3N6eOzRyz7kG9txmATUYanpmanPyqLzz77EfJBoTSioe6evVq7+b8zZ/f3Nj8IdA4MT5RLXM2V5/AdjSQ97MGri0jWhTXQcVEFfZDlAg0qE1xE9IxOuNSYPlPL01CVW+UEkN0cVG1X0FV9x4FANS/VNuh6yvR5zmLUlkoTcS1YQoVLGnGZg6Q1FmLwAVhl6FUkslKzTISacJquLY8+5i+hPSSfVY44GLMTcoKXYFT7iaZGmSg/OkzZ/KXNU3RbAbKZIThjyZnjv2XTz3xuUZps9y+pgq0XLp06au2tjbfvbe3P6WXOZudh99XV1bV5tZma47yi1YYdOYZSLsAJTnXpM0YoA45XaoOVq7NcQrbkP9UZsqOAxoONN02w47Mf2+kAL6+UgLfbbJCv182ILtMMY21+Dsi1ODZ985jusy2LRvtH2yfOXt9m3k7Qm31PZvs1G7KvQp1wD+fPn3GCTv6OcjAXPrIyEh/YmLix65fv/6PopQT0pq3v3z58qXt7e0/3traegDYjP5GTZ0egE7s5rs5u6ymbdE3X2wTcZG9LKVWiJ0SkEb5IaeSIqH6IdswjItNp0jtkDloPi0Taj92PKPKM4zK1OdKqKzE2WF6QmyNsycEWhLQwHSadUOROxbtu/U4wPWdtpyZhPRL6/s2CdlY4UCsFXbGSeVmGUK2HJ+bU9PlJpq6P3q1GfybkYXPjYyOftP8zZv/WWZgwPw2lIDc/ZznjO3u7f2D1dXVH4dO6S1p8kaMi6Nf3jSP30pSXcyevxdS0/6m6kiNelCnpAaT6uTSDObfphPBGEEhdNCBReRmH2MBUTsziZOiROq0bX06qs/PIJG/qs77TCPupUcO6FL7zTvd2pGmAjN3XVwdcjvNMefKxUvcmMmDaigPfhmWNQMhMJ+j4pPN2/lYZWzm/8jO/L2F+ZtLyZ0wbW1DiZYrV648uLa2+r7M4EnYZFOnz/JGypG4Ffc+y50V/JLYHWkUPijh875hlkWlU0JtprA3LkoHqZxMSax9AMt/RrEIt+2QDVos56MjVH3M8FmxjIWLrrH0lVuXdGpIsOTaZ+noKyvaxgHTBbWebiop3aTbVZbO8u9+ddGDY2oGK9LUm6krLh0mB/PkMSEEyk5PT+efBTDL60UAZdpMTU5OffPWwf5vrdy8mbwbgGVTG0q0nD17tpcZ+e4MTP4WGK/fqSkaKi46pM30OzX58QE61X5JPaX7ZoHgN2j6vmPStFlIj1SoiMd8qFJST9Lypl/l6seMC6bDlMoeVYOLxDFYbTvOK+k6lI6XZyhuVGz/jY9n3yvJteGCABaV846tbkkKgpRQwMeVDwGpdU2NdCXebqivuG70fJnGCqXwGrG8olCQRZrnuCwAsBgAGfOTzVAG/LJ+dyY79/mJ8YkHnvnCM9Ff0iT70pYiLRcuXPi+za3Nt/YPCopmf4Na5YO2tr5uvVMzqChe2k7lnJXyRijsWJ0H0s01VQ+8/S4HbYecWWDnOWpNOllGqAc16LgZfaakpPhIMSJyyi7KccWkyzibQmkwN+1H1acclqFJUYDh1qtZVM8D4rouPrFePBPxThvrj2tbbIoN65fZh1CaTA6UbjBg6tHvzfgBAgeQ3HH8vA322JhZxx0Q1GXg3RlY1gyspVSQawY2A/9l5eHdmXdO9ia/46kbT6Xn/ty+tKVIy5W7rlxcX1/72M7u7omCgk3WjfWKd2uAzSwuLpYfRCNouCNNIl6JfvNcDPBhwGNebPQlQ+PvvByKAng0zOnCInJ6PGvnZv8uSQf5ixtYWwL2UuPltxuO8rBrQTkkSVoiZAt2zh0TqUMWRexO+1R7MXqpdkL2SvtgntM6JX2VRO6hQAHV0dcumwcCzO5YcT80h9nKHY8BI3crHmwcTp48kYHNtKcT2Ez+bZrx8aez/77/+vXr/za6s4y0DjSPPvpo75lnnvlnm5ub3wf6gdHkq8+ch3IFPoi2vV0YEXBSrUnA+aJVAoyDdDbUy3hOlJ3CVuRl8p/J40m24bK0RMEcnRvh69+5yJSzLQQ4Iafo2itx0BQQYQBh69UdCNtBtY2VC/WVsz0GfEJlap30NaWYFGG1wpiG5HxoPKT2yPXEi6cb6Q4Ogjb7gf8gqIe0GezUDP7YLA9pM1htBscyf/3bWbk3ZkCT9IEzsi9tKtNy5cpdL1ldXfuD3d3dszBPoz8dYN6I0DFY6ixxtiGnioEB9YBIGEvM+XBUlpeyznNthBid2yeqXhMQC5ULsUtunCXn2rAZdbK9nnfDm9dHAho6iKAiR9cery7hbEOgUWpU7iNLRf2SCJqKhqn+1/bX26yE7lW/fd0PG3jqMpgOOfhgQYWMoYVAqx1pF5hwm81rp0EFmjw2M6Nm52a98jptlgHMXuarv+vmzZtvb8lAy9LW5a677prd2Nj47Z2d7a8eGSneqXE/iAYIC+mzJu/UUE4rFMlzLIVui68jAQgtVCROff6X04+30yv18HY2YSxNWRc/Xv5y28pmZTxezjVBwZjoCzcO2N9ap89qtDX+i3RuHdceKZvi2UE9IjigherK013cMUow3TSjq9A3WD5GXJasJUZPatuIJuUGnsk6CWzEgCZfBDB3XI1P2N8LM3dqzvz0p7PyXzc/P//JNIN4U1uXu+++u7e+vv4/7Wxv//2Dfn8E2ykARG+0mR9NBBtOpJGwztmmmiAFAQkDwMqFgbN2erWDpoGmakPghF07TABAy+AqRfqx8xQr4NrXFzQ054O//EY7R7e+W5ZjzxR4YYI6YH0OmffD7DadK3V/SYDG1OUzEQv22X4YR1HWYo1VcdKq44QYjJ30vaOPpTr3dtlIWlv0PcQHG7CkGRYBuPex/u4MyMTExDuy899948aNrXZ70xHQgFy4cOFv7OzuvmNvd/d5gKp6rqYWeEGoWOqMLQpoQ8RAo/xIOqYNEC4dxJ037cSieSqSEkXgoaXcJYuibl7ruMJVmc7MtS9m/FHzCIDVeuE/uHdcW826WF+0Duk5DCxoh4UwGyNKl7AVKN8jyrgBQjhNlZfyAYthCRzI+23ggFuVRRiKpcNYHeUCjQeObr8DumNEArZVu6XlLuhR9wMHjm2J6z/c5xWWNMOKM1OgPLAZWAQwNjq2nbGdr8lA5j90Yl8XSkEuXbp4IuvA/7y5ufVDMAgANEDfXKcG8zTbyKIATqho3dQb4+BC+sh6Ki56D6WtQikcSRvY8eqZLA56EannGBUeOcXYHmOnRFAwrTulzIjOBCiuzSA7ssZKt+NH+Lb0yjK8Y3aPccyIKk+V8SxiGFpK3aDUkVJEFb5fceNTnXVbQRme0EKFAXaXktKWOY76d3h5HlabudMXxU4AO3mwNjEx/h/Hxyde9eyzzzbeQBO1qwulIPBBtEz5q1bX1v4w6+yoXhTgPtCQOoMUmmVUB+wmKEh0L6xGRPr+yrMUoMml509iR9nIMK2yAeudIS7y9XQr/ibCnINlkzHuISDyI3A8WpTqqP5GUmgmwPSccaGcXlFIeQMiZaaUfaF+hhynlKHUfdMdcY/jjj/UnuR86JphdUM24OciGUavftdo0CIFGo4Fwu8z0zN52sy9L/XnAKDyxOTET8zPzzf6XDNrY1eKQZ7znOdMZIzlA1lnnq/TZy6qwiQUbEkDnc4N6ukbId408uFvKNWjzkbHRUkJkMTYyYETxzRCoObqsCJxVY++6ehiWQzvmOtWJOPBpXZMoZgJV8ZlQLQen/laYyC4bblrZNrH9Y1iYhwDCKa4CGZL20SnjqRsiepfUZcGeIwFOppQRkn3hTSIBJlOGaFQgswu+3ck+/f06dMKFmWZwaT+ima57Hk1++/RhYWFT3diqAo+Fs3lzJkz/zQDke9R8Jmacksa+yHuq83NrRxsKqMYZx6DIdhzjzENvx3XgcvblYIJ79B4B4q14QKdq0cKCFIb6/M4CHG2xrTh9qMszKRlsO12fEdVn/MdGm0nDbhmvdC15dml0xshcIZYiO2ci7Lu/IjbZhhsfFtZ9oYsC8f6KmVoWBuUTdQxCRBwwB9bF1tGLhnTWNH14IX5/CuaVvSo1N7+nt6puT82Nv47k5MT33L9+vXd6Iak9nSlWMvFixdfkSHnr2bM5V5gNfrzAZUBPZjQ3Vfz8wsyp5PnWmR7jYUibq6+toNjJl5ZZQ+odk6YCSm2UamPmFRRTPv90gvX7IaPMmk9+U/U1pB9bhRNRap12TAYuHZT17OyA3WQNAOQgi5lC9Y3rC/ccQx4qnNFYdQGLrVk67XHmeo/dc/abSqvDFc+5Lxd3akMSyJhdmWVLv+V2dEEZPJWsrrHj59Q09NT1lgAi8lXm8GWMyMja6OjY69bWlr84+iGYmzqUjnI5cuXJzY3N9+asZo3QifNzwfkBpSDsry8bC0KEDtCRT/YukwK2Ej1S9rTD3aqHaH2tKQCilVe4TcFykzKwi7jsxxFQB9qg1bIOi9/K/z6vG4Zd2ahlIrvmPiPTbl1qIg5llXSTobf1r9NNiAthwNbz7mMNMDqdiTXhzqW6pg5/UgpRQK4kVXoAtTstmj98FXNjKhUOwFY1zz73/7efj5VAdMWGZP5i5HR8dfcvHF9sTOD1QCABuTcuXOv297e+vX+QX9iFP36JuwUsJOnz2KXOlOOJpZFsOmO8gdlVjCa7etbsxnT0m3FAGv1t+Hxpe1TDMbUbR7jmAtmI2qHayfx3oj9sIW/p0IxKW+86uZF7MIu1yvLKOUucjDrolF5wIlSx5PL9PD5Bykb4YQDV4wJV/8WBRBd+QnCFpqBuedD+45hdsrakAsH9FKA4vph6pmbnVMzx2asc/1+xmZgtdl2Pifen5yc/N75+flfju5IpAwEaC5cuHBnxlb+n729vYd0+sxdFAADA0ADrMZ1XBIQcXWB1DevDpC7YRV1m/j8QNVmxQCch6bMl0vb4R4KHkgNR6h4R+JGaLg+ynnWKYKuAJUr4z+AhrMhx4cKThRZ103fYKvNLCucscJtDQsG7IEaius/53jbs7VvHqyjN7swATT8qr04u2jQcNuI0S1lVmKgIQIBiX44B/5Vf9zMFFjSDGmz/N2ZsbHPTk1NveDZZ59t5eNmnAwEaC5fvjy6tbn5lq3t7R8w02fwuxlt66XO9oMUBhZOUqL36DaUHQX3eiPKvZnNvnCpl1SRsDUtVjkD/NpMsaXaapbxbFVmX7ivZtLOgUrBmX/XzFPXwe2ubCQmuV2Xhjlv1OZeqZMBEm2fvp9c/ZL+u+2ngouEVTknSEAJ2cwDjYR50Kkvqg2puCxNUjagUdW22r9TDF7rhZcz4SVNNwDSOwHsZ0AzMTn5rxcXF98g610zGQjQgFy8eP7c5ubWtYOD/hlzUYB5gff3D9TSEr//GetQy3+5TnXt1PX53I6IPvhOLP9ZRTZYWTbdh6QotF4LZwLnuH7QttOOlE9R0NF6qL9YG5JIuGK9daNkG5IUWKyYjiBFv9Sp5foKpeR50waqHbqszZpS2U8IbDzWgICWtifkjJva1KW47dJ2lMzHTMuP9PJ9zdydAPRXNHf3dqEWpM2+fH5+/s+77Ym2ckBy5syZXtbRd2aU7RuzARnV6TPtRnVECOkzWN+dGxcJCFT6KqV+fsFUuZLMiWgpxxoLAG59t0xsX0KRqNQe3446DYmBnqsfB5r8pwigXXDFxp1LU0gctdiBVI4Md6S4Paoqyzk8yh4KKNOdniTSl6uqntkObGmfFbRfd9BthJ5p9z6BJc3wguboyKgVxEAArxcBZL73w+PjEy+fn7/Z2lc02T4MohEt58+ff/X29vYvZYBzl5k+MwUGAXZ1lkbTrmBRW1RKiKnLOQKqjGVYD3HeAifKsYAQa4plEpgOVx8V1WJRGCcmg+LqUX2hH+I6/YXZqctg7A3V5rRN/q47RejA2qGuvVsvbCftwDmwdXV25eClgUI7zjkMZqYNIZul50lyJZTG7Lisr/c1g9/1qyTAZvSWM5nsZkDzvcvLy7+abm2kbYNqCOTOO++c3tjY+BdZh78dBgCARk9W5eyh18/vD1jqXAyI/95EKCJGHaaqO0qWETIAU4cbpWOOufi9LhcCEcwW22HqHsnspMpIgIbSGQQQZYy31m/pwFN0MXZSgCcaT52uMgyNcfAUEHJ2YGCJ3QuVrn75wW8WLG0h2V2/zhhwdkYDTuD9FeOgUsiClxSwCtX1xr0oLNJLBQFcvZgx6pJFad3gV8+cOa2KeeK6bwAye3swP7MHx66NT0x81eLCwtOdGIPZN6iGtGSD8Ld3dnZ/IxsUb6cAPVhbW9tqdXWlOkYJy3ocBkGlqkzHHXZS+KZ8HGiIbFW+4zSPcedi26h6EmBLFHuRAFIYOOyIEwNSyimbQjEoLwBQdqCB1Zc44DC7sJlUdb7Xq3ssZmV4H/lzeGDmBicoS0VAw26j7lvY+dbthVhlKKUXx2Qp3SHbY77qWdVAx+uwBT4HAIzGjOagX3oRQLETwNhbMsbzpmefffZgUHYNHGguXLh4cXt7690Zur4Y/SgaXPCDvlpcWFT7B/v1YWE6hROp43YjbkqP/t200WQ7WNSe4tDcdqoHozTWt4XahsV3hK6T139q50NNVLt1qn/1QWNMMHGdsN/HdlM0ri0cQNnH/L9DqSisDaMQes0ohxrnXMsmQk6wvlBindVFJk/TTtx1+JalCKNsO/Ln7omuJa4vYeDlyoMfnZs7Di9hlv0tyhSLALbzz7Jk8pmM9bxhZWWlk88BcJYOVJ773OeOrq6u/mgGNj8HzXtzNb3iQdEfRcsPBVhACBgkIo3WU8uSwNLDJ74pXa4O7HyoDJd+8s4ZzDDEbkzgS2Fwdj/wN//Na26CApf6CqWJMB2hQALTI9GPAbXrXFLZjtipOUDD15OxDlPMgCemboi56cU5LjuTOnJJsDDswtldLWl2nj9Im21nQHOwfwC+9ld6IyP/49Li4vxA7R5kY1ouXbp0anNz8337+/tf4u5/ZuYUYa4GFgeYx0EkDjVGUEekmg2OyDETzlGTDwyApCmzmOgcsx2r57IP1g4CoFRtFjrAIbZo/m32FWdwPsuUR4y2k5SwlbwNbVCihN76NsuJ5kewuhWNUKL0DwXkIZYgTrPlQ83ZUV+LJizXPabtHkbQibUJ7ocTJ0/kK85M6cOnmnd2cn+aqdudmBj/suzH+9eefXagHT4UoLnvvvt68zdv/uLO7u7fhQHNWQ2kz5yH2VzqjKWDQlLdREoZDhJzSjozwL+jE2qeS8lhKRL9t1nWOOE9fCwzQo5x6ZlQGsNkip7+ykHVqTiXhdAMwR5/LH3n2kWBQ0wqJN4h+oyKt9W3E0vJIZah9bjUXBxgIi2aTlY41xDvjKsnTlDOKdOj06nudSF1iNpy71+iVhMgQp7jLgR8KOzSXLybmDec25wvad7eVvsHB/3R0bG/Hhsb/bKFhYXNzg1y5FCABuTcuXMv2d7e/vP+QX98ZLT4Vo3lpLPfYYDq/c+E5pZOMCZlQ0U1MWzJdcpYfQ5stPPggIjqhyQ1RLdr2lTb4Ovw8+k2W8DnuLA+S+x0+2ra4Ua35Jv5DAPEQJljja4dEpCjHVTAMaLOqaVvrDh1zOdFVl/m1GOCgJS6ocABP4czo/z3gz7hXsyNQdMAYxCMaXZ2Vs3MzHjHYfUu/JfZsD4+PvFzi4sL/7BTQwg5NKC5fPny+MbGxm9mg/Bacv+z7OKvra9FvcDJsZLivM9ovLoMffEdJ/UZgPynZ7euo/RyTyK6D4Fefdp3knSdvj5hOTLO8YccL5XmwnSxNlnt+aBtRrmU7rxYWRYDNbOePI3p9LFXpq0IMOPGj0tdFrdLmlN2U5YhhsI5Po5NpTpL8v5k0n9xdpjg1yvLpYEc1sYwptZMAb8JbKZ6VaS8p/QLmpA2y3zsfx4fG/+2hcWFTx2GjYcGNCDnz59/5dbm5u9kl/A4DFK+KGCkZ623hyV5S0vFnm8hoJGmlbj6PEjRiw9cYJEyJzMd5YKOV0bx512Hx7KInr1pH8eczHaoVCCeWuIYHOOcEQDGxkKmk4rAw9+2wXTGAC82TmYffOCXs7VwvwlB2ZJMQiCAlU1tB6RJG22Ag8nyKf2DBCGqLWAy+aeaDYFy4DsBaLLf+1kg/1MZGL35s5/97KEg5qECzYUL509vbW3/foa8L8tf4Bwfy7dNsB/ivlpYWEQXBXjiIEAIOPzq8Uum3fq6njRlFAIRrIyUtVERNA4IJTMr/V3YodnpMS4KrMG0rmTb0Ld8H8ew3P5Q7XlO35lkd3VY9lsgzKeKJMCDAVDOPHoq6PRlDJA6JktzUe1K2upK2mqL6gdIJ0DhATl9DdoCQ2AzMPWQS8lo9b5mwGayMovZ+dcsLCz8RaPGmth5WA2D3H333b3l5aUf3dvb/9lsMEYrVuM8XOvr62pzc1MMHKHI0y2H1QOR6tBlqXOm2O3V96ULDBQ7wNoyHWldtkaNXs/VEV5YYdrEOX78nB2Za31mmk87c2pOjX4Aa3Di7CDTNcTDzd0H7nmzjGkrxj44PU1TOyF210yKa2jr8hmXVM/gBG/Pvh4KKVPfV4MEVEzE7DSTifFxdfLUKedov9xuJn9BE6Ylfm9iYvJbrl9/dr0Dc0VyqEADcubM2Yf293ffvr9/8LC7LY0WGDRYFAD/gmBOgRL0PJGmkkalZh4cu3F9p+Q7bdsJ4RPtrh2ufu5GpB0s/Wlpagyk5aQO3C8bdkaWo1CGW2DYYqxopuEyOoqJStkWdp4qI09L5Rq4UoHzYaH6EjPWofSWVA+li8oehNpuKvG65dcjnJargwB4b8Zc0gzlddoM/svKbE9PT3/9jRs3Ov1Uc0gOHWguXbo4lVG878/Q9y3wN4BMRQNLgQEFVgP/FQeK6LFeYRufMmuLtZjnXSfu6S3tJMFE+RcETfX07PkVV1DnboBjVcZwqlQ6yu8jdk5bj0vQuXpsDNfBOXmJg6ceXjmbCtvC9rUXXtAgYRBVX3UJjF0FHKEsJadbweuGwU4uqaDQNE3otyvUo1MRbYrRND8eRUHwkwA0+h1ELc7Hzf702LG5r33mmad22jU2Tg4daEAysLlnY2PjwxnNmxzpFUADiwK0wKDDwC0vLauD/kF1jBL7YasxKJQGcvX65fOfHivR5zzihLEhwTmrXKncevCNmzyU4sH6iTulmnVheihGJY3AST0a7JQiwTOU1vLTPH7qjqtPjxm+YAAbP7++qmww69Gszi7DCde+BPQkwKrTgBSbo+phtsYASBMWklI3GWgYPYNIvUEbsAhgZuaYHdxm/4PPNAPIQNos86U/tLS09AudGiOQoQAakDOnT//mzu7uN2QDOEJ96hm2paGWOiel0JDzIQBIdepYGSzFVv4W1MHqKZVhUbDZlgnAFUs0lHgMyGvft9UtzwGU2w+3HZ6dOPZ75926tgPBrjGlJ5SSYVMdEZEvl44LjYUbAMW2IbBOSRywbYM/5qZ93EaeZrloSyV99K5L81RjiqRuzgk+ElaawVSDKQAu2/CC5v4+MJ2nMqB5eH5+fqEte1NlaIDm3Llz37Szs/OL2UBdpL5Vk7Oa5WUGNPqlA8KdiDxNVkfIdBlJKk0GNBTjcetTEWUK4EnSSDGg6jnbHp7ei01TYcI6dsU7O1eHthOzJdROWQplPW1JDDBw6S5J3bwmC2jNAFN6rq224urIgbRrtiKRY8eO5Ts1m2kzsAsABoAm+/0g86E/lx3+ycxnHrrBQwM0d9x117Gt9fV3ZGDyWr0oAHYMcL9hUX+rJuwUqYiVYi6SqNVtw61nSXnvxjhvrkys0+YZDR/1kmUYx4yVC6WdXDsJZZUzt/pERcTGUmY6pcFH23Lb/NbdIIVqE9cvs8s/rzzdfPn0fnab3nLToCk6JeAvaAdho50CjdMe5Z/g79OnT1sgA8eAzRiLAD6fsZlXLSwsfKIbY+NkaIAG5Oy5s9+8u7P79mzAxvKlzuMZ2PTsiS5IncEKNBAqyuYcOMlm+vqfcFQrAQhXB1cm160UG1lzdWlb+LkKT49hBMWeLJsDDxzJmMp2JNErx7pCLIMvYwNVWjtpZVkdyDXQ1pZHLXtFAQ/SjpTJti1S5pTKimLLtXHd2pJQcKsFVpkdP3HcC2L1FzQBcMZGx35tYnLiO27cuLHvKTgEGSqgOX/h/B0Z0LwnQ+QHgM2Mj42Xm8QZD1m/pxYWF/AXOE1vbYgbFfA3lx2R2tFw3VCsA+bSdlaE3lPkG/tUOrDWUdruvJzISu2RrTHoKWU5PHw8BO8hubqRKLE+hZTXY9AnggDlXHKkXKUDsd/TV6bTMBtTomwJgElsC/Unxk4saIp16hRrlIl8PoR6bkIOeRApx8P4+Bn4Q5ibAbAxx8V8QTMrszI+Pv7tGZt590CNY2SogObChQujGYD8t9vbW/8ELr9+p8Zdvgcvb8LCAC0cs+EcdijVhN18aCqOiIxdcLPAANODPEypaT5bj/ad9IoriaOhIi6yTE+WZuO2nOHF33Eary9jONL2MDDMzwojUn1OS4xN3H2QIpLAiAvIjArlmATmxAYksUBjSlMmFVs2RuCbMwA0PT3epe0Wmxkbe9vY+PiPzN+8eaN1AxJlqIAG5NKdd57bWFv90P7+wQUTaMybAQYT9j/TrMZbMVUK5/Ax5yhhPWiqioi2XVto4DOIhaoZje+MaLbFrRLjHIYkrUedM+3Bxs9lG7Iom452KSbgv0CL22qRXyFrwtrH+yIFPV+XW866T5youQ2n6NsWBodYlmTEGuhzZrZtlkkVKQukmIsE+KWBRFcgA3rnZmfV1PS0l2LVczPZ7/2Jycn/anxs7F3Xr18f2KeaQzJ0QANy+vSpN+/s7P4EAAy2LQ0MrN6WRgvl2DHhwMN0+uYxCa3m0mIxZb2+qBp8XIdJtRMCOqwc56Ap0Oba9sFS9wZpxaxjvMPBsa9w1I23YdqWwiaov3VUL3HKdT9sJy8Fkrwsszw4FFxImZdvb926a3eKc41hdqlpvlBZDICoa+toUVK/0AbwwJJmeEET9oM0PbdeaQYBeOYvr01MTLzo5s2bA//mDCdDCTTnzp174c7O9p8dHPRn9CcE9K7O/SLszNEb0mcVq1H0Q0o5GH2DUZEo/hBgN5c8NUOlwlx7tV12mqZKRHn6XNvMh0cCgFKnjbFDrG23jSiHEphjigV0ChTjHIB8XsFvXykMTKTg2HZ0nAI01P0S0yZIU5BoazwGncrjBMUwr0xPzRybUTPTM8axop5mM9kfW2Pj4/90aWnpTR2bHC1DCTT3PPe5o4srK+/c2dn5r2E0x539z2DQAb03NjYsVqPPUSkyt5wWuhy9BxmVfqNEYodpD1WX6pc0VUPVpf+mAYSr5/e3BslQ6kaaBtGpLt4R8qk4swzVn5jr7NlYCg/sSmCjrC2KlcaCZCwYtaUvjV2RhcMePKiCZu0xwNh0/MD/nTp1Kp9GMO9F/c2Z8gXND03PTL/++rPXP5jcUEcylEADcvbs2f8iG8DfzQb1DJY+g9929/aqFzhdkX7TRJ/jnBWlK+RIXP1+OdzBee0T7dQ6C1U9pEzRCs8QTFtsvTJHQLE0CSPxwYD+HLRbP8RUKDA2JQTqWLkmaaI0ZyMBijTGhetRjK5m7bQCJA6ApDCuKMAilajgULTRDuiAlzPhJc1qLrp8RvS+ZlngDd+cefPcqbmffvIzT+4lN9aRDC3QXLx0cXpra+u39vf2XtPrFS9wjo2NVvdXr7zZljKgyWmjop0ECOoIle+c8zLFAbFjws55Tg7Jp4fe4zDL8auyMMDiWIL/hEgeCGkZiQ5Kj4SRihmPCs8LyRxA2KOkRuim/qpsVCSePk8gAVWRBRHj6D5SKQDhs8u2VhWG24yxtS0B36e3mzGDRvhPs5nMlvkMaL5uYWHhfZ0blCBDCzQgZ86c/q69vX3YlmaO+lYNvMAJKTRIpbmCRb6uuFEx9vBJ0k1uu/F0uXY2pm31ufyIkjoW0q5CsWUnWRZhX/qcbR+fGhnUA+kK3i7PIu16vfJvIxBROkhJB9OQnW2l59oU7p2RgV5f4/YPXUMQLtBIavQQBJgMbKDp7nK+D0uaCzYDvvHfTExMfP+NGzcO7ZsznAw10Jw/d+7e3b29X9nb2/3ykZECaNwXOPf3D9Ta6mo+4Fo4ZuMKBkKh6EWSMgs/AHT6CbPFrRdKU1HOiwNPur6fzqIAmeqv2SZVlhPJuPLXggNyWheVfpOkBc12qEjcd2Bx81XF+aolsh+cNAUKsn4UM8srlN1v16l31j+nDIicodEpSrMO+DtYaZYviDKAptpuZg8WAajtzDe+Znl5+b1RHRugDDXQXL58eXRne/t1m9tb/1f2Zw+W9Y2Pw26lPcvhAauBFWghR6gFS6O5x8uK3k0vcfDYOy0hp1zZYebzHJuNwta7NiKH70RDrl1YfUkaS9Qnps1Y4dMYeUuo7ZY95Rhjfcf6kW5PHBMeNjEDuiKjF5dCSg0qUoQdV+N54eqrQJm2RByoZr9OTZovaKrqvtVzM5A2Gx0dfdfU1NTrMjYztDfWUAMNyIULF85nQPKBDMEv6c02YdDNiwWDDUCza7EaGyMwJxoSiePEhabarkM062AmtfkAs6AqKO+eo1JrvA5kfohJyzjaqjFydVX2WBfeYQdFwSCTkTjUVMBoD2jimE+XEmK0MSy0K5GykpiUZart4ZRfAeyazegPQeo68K/+sFm/2KX5OzM28/YkYwYkQw80IKdPn/6lbGDfmF2EfLNN+E/f3PqibW1v5ym08qDXsRgHwdLg4oR9LCEythmGshyolAmI28LqClaixUT5shRPON1E65BOyCvV1AGHWEh69Jv+FjybGlTlS66RabLw2PBjnsoCYyT2+tGBXLieFq+96BRgmrh9zTfPzICmp9svz+t9zeDf3kjvUyOjYy9aXlxc7tzABnIkgOb8+XN/Y3t751ezgX1+9QmBcv8zfYMcHOyrxcWlalGANJfupYmcFV6mUHnoXqAMGSFLV51VtuY/0XNBOwX2iGx2UoOx6RS/TG6xrV/Qh5g2uC2CQswu7EzDiwtKRfQiDGFqh3agUhAuWCAHdoNMIbUhFAC3yV7asClFTpw4UXxt2LgmOm0GQJMd3RmfmPi7i4uL/6JxYx3LkQCaixfPT2/v7P5Cxmq+Fz4boFeggZgXARjNdnYBXFagz2vBmIkpsSkmic6ylLIdQpkKqnKvSmEOI5R+kjiH+IePToVxczzm2HORZWhepy2gCdcNs4zUKNnQQNQNA0RQc1LE340tkraO0hxVqsTu6oyNCfg3AJr8Mym9WidME+j5mSzY/mQGRK9eWFj4VNt9aFuOBNCAnD175lUZir+rf9A/Njo6ln+rppqrKX3v7u6OWoVtafaMTwhUTtx28BQzSI6OispsGUw/VU6XxSbxOd3UPIM0ZcIymbwRRb6Nb9ZvMp4h4eZSsHNs2kkAzoc9/9H1vIEksAodi2+bn8fUtnQhTfQP6l44Dp8CmJryjhubZ6rRsbH//djMzH//zDPPDM3mmZQcGaC5dOniie2t7d/d2d39m8X+Z5A+K7alsVhNBjSwCs08roVzvN5KMTOtVRQKRO/xICMBCKq+m+rTe8CRKUE0BRRatICnCpWg3+FxkeX/MUcWetglabGmDkM6vyFzavratG8n2hoSGEntNB+pW52ZHJYAmzl54mS+vyOIvjbAZvQigMwHPp2V+9bFxcX/cJi2SuXIAA3IpTsuvXpjfeP3Dg4ORqgXOOFCrKysNMi5++WpVAP30KVG1LERuds+7zD8nZilDCulXNtOUpLao9qOs0WS6kpLNbF29OInnRt9fCvQnjRl2GmULxiTgTNO0bjx/gCEslm/oFlIkZKBsnpfM/h9bGz8f8v0/NTS0uJQvqDpypECmitX7pxYXd340wzRv0x/QsDcbBOk3z9Qq6tr+QVxpc1UiCQ1FEpRYfW9ej18ojiGUaUKN9fijqV0TOMco4wVxIChlIXQ1yVvSUmAZjjmI+IAOfa8pI1bV5AUvOD+5sbU3G7GFP2CJrCZrP7OxMTkSxcW5odu80xKjhTQgJw9e/ZNGYj8r/C7/oSAK/BthlW91NkQLpWGlWPnKhTPDCjmFAYI510RI1WFMR2uL5L+hphRcSz/KaiLl6tus15VCNWDjUXQgZVFinRQny3OO80w25MAXpeMLkVCgQB1P6YGZaGU6zBIe9elfVYLbAY20HSfbZ02O4BdmkdHH5uamn3pjRvPDN3mmZQcOaC54447zm5vbX1gZ3f3TrgYADTmp571RVx2NtsECU4gG++WhCITcQSPPMDm32R5ZWTuUQesS9G2SFNjTZiCJMWn8ZLT1RbLlMzdVHYYfsKdt8BAo6prlKN0N+lPGywVu6dTdRwFaXqvx5Tzz9NzbDH6wY/pTwHociA5yOxlbGY3ZzM3M7bzw0tLS28TNTYkcuSA5ty5c9lV6L85Yy1v6ucLL8asFzi16G1pQDBnWJUvPboPLmaEy6dU2pZQjlfb12a6gpuL4tupz0uiYGlqUXJ+kCJjM3kJka5B9qtpe8NwHWKBRG7z8KT99AuaIKaPcrabeU8WXL9xYWHh84dpa6wcOaABuXTp4pdubm79u+wCXNHpM5PVFJNn9QucZlSNzStwaQPzOCXcXIa2JzTXEJ6zCbMCU49bzh0DvA3ZtjTS8cBszc8ZdK1ppJ3KLGPbkZWLc1oxzjOUJo3RyVQOvjjKVz8cQHLHR2SLKJU3OBCCYHl2djYHm/5B33o+YL4ZwCaT/czXff+xY8f+5VNPPTUc6CiUIwk0d91111jGVt6aDf53w9/UCjT4+iZ8QsAFjdDchTSvTR2jGJQEIEJRMWcT1pfyJJOG48VqT+vJp0Li0hTY3FJ89Gnr19J1yk1aBqkkWjGlpY00p+TadCXDwHxyO5qsxLMUDW5eaWpqKgca1zcVuwDs5jufZH7u8bGx8a9ZXFz4zECMalGOJNCAnD175mv29vb/TUYnz7vb0mjHDmwGljqX0UAu7oXU5THBnGOoDlaXOyYRDKwkv6fawZ9HVtoQ8xlsG5QzsB5uLqJseSIWaVfP27QC0i1J1868mZOW7OWWvt8bJbGMZlgAUQvYY26eCaIDTv2CJvyRnf+ZDJB++plnntk/JFOT5cgCzfkL50/t7e795N7u7v8Ad+7oKICNvwJtfX09ZzamSKJr6l9MsIlgU6dTWvGT+IQDFUbHXU+2g1DjUR1jbG3LjkE7irg2u025DJuj5ESD9aCYwVEUSP0fnzuuRkbN9H/xrS29eWbGZj4zOTn1ihs3rj9zaIY2kCMLNFeuXOltbGy8PLsQf5o9dGMjI7ACbcKLbIB2wqIAoJ4gGAiIUl2l8wyt4sGcMRXxa0HnMZDj0vNYv0LnJUwoRkJpxxj9bY0HZ5c0AJCkutpIxSWlSIdQjpq9IIMD8oIvQ8oMUmfudlN6EUAmOxnQ/Kvl5eXvH4BRnciRBRoQ+DBaBiLv2d/f/5vZnz1zUYC+wQ8O+mprq5irMY9rCc7ZaICxbr6eccp3SFW6vK/dU7wDl84DUQ9yE2cnS4XZUTurq6gcZZurozCAnn+Q65SxDRE77DGT59y5luwIi5RZYeXMY7yeYQeTtoCjCz05myk/BWD6If0pAFhplvm0v5qYmHj9/Pz8tcaNH5IcaaABOXv27H+Xof4/yC7MafNbNebNv7+/p5aX8W1pYlJkZnkq1yyf9JfX40BHChaxemN0cSyvqAu/6dmOW8sZSVKqTZhimhNvPs4xzPOozUU1eWbaFnhBc2pqOvcl5nNkbDcDHzb7x5OTkz95/fr13bDG4ZQjDzQXLlw6v7Oz9a8zsPk6d1GAKbAowPwCJyV4asROq9jnUC2Keu8mNM/hlu33wy+PcnVjy7jMzWVkjgbFp5vK/hpMpPPJ7ESgl5Rpe56rOxECjajYIJhX93LYrAarB37q5MmTSBbmoFoEkB27mbGeVy8tLb2vsfGHKEceaEDOnj3zHdlF+aWDg/4UBTZbW9v5wgDYC80Uk6G4W6SEb6rmK2jcD63V0b9tY3FO5hxT8//y/rT7pchQW02i864XSBw9ub1QIU7CacPU/sLGmXrzTBNkqu1mst8zX/aO0dHR715cXNxKamRI5JYAmvPnz13e3d17a3Zxvl5vtoltSgeLAmAfNHdOhhNu8tcFgFgH32S+pomQ+oLzClhKpV3HJZkfajs6HcjEe29w72Tw0v7yYrSVQwKcYZ8v0gI+Cj5sNlp+6iRn/JnpB/sH+VYze/v5djOrGRC9/Pr16x8+ZHMbyy0BNBcvXhzNooBXZyDyruwGG9G7BbiAQm226Qo2bwPSZJWQlmYPQNipN3nAZXWHd54lRmJTi7dKpB7sx9AAYrochWuFvaCpV5rBf+V2M+/Mynz7UfiwWUhuCaABuXLlymwGIp/ILtAluGAANPYnBIp16TBXAxfRFRpMnN2UkfKS9EzTzQ1x2+zzpmB2DWJiN+ycQ0CFj3fbtjYJHtqWw54/aNuOYZeUBRltZxTm5uAFTTsYNj8FkLXXHxsb+8bMX/12aw0fotwyQANy/vz5H9/Z2fnxDEiO6c02Qcx5EGA1a6tr0Tszuw4URB79y1lAyk1NpZe0pDns9lNinC1tPszcIgxTulzV1Jb+W83532r9SRGYl3E/BQCiV5qVL2i+Z2Rk5DXLy8tH5lMAnNxSQHPp0qUHtre33ra3u/eiXjlXUy0KKFMCcJOvLK/kOdBUiZ9g7jjd5KQ7pEtTpamyVnL6raRk0sfxKKXBht2+oybDNG8DtsDcjD2H3MsXKWk2k8lG5rtef6uwGZBbCmjuvvvukdXVlf9ld3fvR+Hv4r0aABq7m/DyprstjZZBpB+SbnzGx4aXKQdUdz6vY9VQdkeES6RLoc4Xi/WkS5bjAeuw0y1dib2oAySuj3mNlH5GBh63SloQfBJ8c8YVYDN6fiYLjj+YlfvqjM3cOAQTO5FbCmhAzp0792AWGfzxwf7+hV651NmjqAf7OasBiooJ9wDFPlz4Kqr6wY5tw9ZHs41ahyLLuDpTnCllZ4xQQNI2WA4WUI+GpF77ruy4lQX6WLygOWUdLxYBAJvJ5473R0ZG37SysvyWQzGyI7nlgOb/b+9MwCWpqjwfW+bL93hVVEFtr6CKWimWKgRKsCiWhhkRlClk0VZBhRJp22Y+h1YctUeke1C71Wn9WlpQu1twaYeW8WvHRgfp7gHns52xlR79+rOZGUSgKF5tuW+xZGTGnBMRN/NmZOR7uURmRGSeX31ZL5fIyJuR997/Pfece+6GDetPwjU1hlG7FafNWLgzX5Hxb6VSFXTdCU33G1yNykHrDQoY1hrp57PjSFgh4O5ZhVGtoQiTuJY77mCAEkaatQcptawZN93MP8Hr78rn878IqZgjYeKEBllY2HB2tVp9BtrSrMT5avjGhT8shjq3NkZbJiPxgIxqVB6WGPWWpaB7B+0NGUf8P3/4hZrdzx083uvo/Y79vn85+j3/IJ/RL4P5BkcfLr98SPfIi2CD1gwGAXjhIs0wA/37JFn5Yi6bjm26GT8mUmhWr14tQeX6Fvx4N+NjlgONB3/UcqUsGLqxrG8gqCmZoXwQPTmye2kx/nPxTQEJxGm/3OePr5MZhsA20BqmDGR9BE4YwQHY/2DyTLsfcmfOsRwsC4BrzWSVRGJ3LpuN5VYASzGRQoOsX7/uak3Tn7AaliRKoq+vxtB1oVKtdvXV9MPQls8Q62yWPbfPSHPw8i7vNB7uWrSLT/8RfkGXp/s5RnXeAc8kjHIxb08lCE0UBxuwjLO83UKaW1sBWHVZUR4p5AvvHkuBxszECs1pp50+q6rVx02zdhVWKW/+M/zBcY+aalUVNC34NEL9Tpkh/XSoNNINh2BCxsdTlmA+RIiy0RkLWLoZb/5F/O1w3YyJ1owoPCsrid8q5PM/CqmYI2VihQZZu3bNWw2j9hdgsczZ02eK7OYjbjVSHFEUCoXAP3upabJxhB0Hef7ux412Iar3fVGYyloSUWxrUDQQGA/91a3xKydaMuif8dK+QFN6SJLk90NfFOvkmd2YaKFZWNiwUteN74CYXIWVsW0Bp41T6VBo3IVSIwmT7dcH5BcSHe1pm1HS25QQMo7vsrQ/rff0OWGaCvH43Xujl9/e75hxXQPsb1auxAWa7T5ilm4GxQbKkoW+CbMA/GTkBQqJCRea9WLNrH+wZtTuh0qV5EOdEVbZcPEm3vqN5lraasF7g6/ZsN+9ZDmG76iC9HEMG1AxLOOKZBp3xBTRSZyEMpWaAWvGkzwT/mGWZiY0iqI8jpkAcrlcPsSijpSJFhpk3bq154C18rBp1i/Gx3571eCPjXvVoBmLFSKYitxfJzTIZ/KdeJCLJ70BA72Vzf/7jmtBYCBh6HgnoDJ2m+ZzgvoGESgStSiyVL1zrJmVgiJjuhk3olNwrBmWBQDQk8nkNSAyPxxboUNg4oVm7dq1SctqHDSM2hfxMVYMvwg0XLxZLlf6O3kvocAjChceVccdp9Fi2AxieUbJRzfJROHazczMCCtWrGh7DsvEb2wGYvR4IpG4AYSmM6X8BDHxQoMsLKw7VVWNf4Efdl3LV4MCILbFs+MWAjjKaE6t9el8Hl3l7i+x5TCOd8R571Ij6GiPrsNYJ9ELvfoTolbu+BLUJm/913e0ZjALAIqN/fluk2KRZvZWJTACTiSTB/L5/PeHLGDkmQqh2bhxo6gb+oNmzTwID2dwXQ3ubOfnq8GEm36MIkig9/dGu2MfJePoeMPu3MP+/KCJ1/cZTdvCfGYYacZnJMG/aMU4kWaWJcvS0yBEB9LpdJ9TKfFjKoQG2bBh/ZW6rv+ZWa/vkETvFgKCXdewEmAEWmsB57g6+OFHXuOKQhv3OhGk3wWbvF+o2/vHudhzWJYvQ/iLNYkWeK3RN8N2+WXXnflmnAWaQhVevw36m/8SamHHxNQIzc6dO6V8Lne/bhgfgYei5GZ29oakLmXVhMVgq+P5LNGjd8J3C8FGlp+KC4Olgxe896cduha9k0wm7R00eTewj2/mSVlWbioU8hNvzSBTIzTIurXrrqjVjL+uNxqn8EEBfCNivpog0tIMxugXQY6TIBZ79na+/o4h4kccflccwGK6mZmZVIfQGDVDMGu2D1iVZfmeVOrkh44ffznaXyggpkpoNm5cmDUM4xuGUbsJH/sl20SW2hjNYZRTFb11wEE4vHvttAf9nKA7hqF8YBHJKhBIGHbb+0e7norSH/UHWjMYBMAvoWj3zdjbND8Jg9zfyuVyL4VY1LEyVUKDbFi//gLN0P+hUW/MehdwMphV467a9T3PsClmgmysYTb8YD+7W6c5mfvCDEyAIfNLDySiNt0ZNdqvD/YnGACAYsP3G/y6GbzOiUTiJrB6/uuRI0fCmjYZO1MnNMiaNac+ZtRqb8JRLhMbHqwMaNX4Jtv0aeQDLbZsG2H30KDdz+1cmS4s/94eSrPc/jHdizWi9TwBWiBTJ0TLMOwgifAHI81w2gyDjfielQUAuL6Z51OJ1J50Lr3UlMnEMZVCs379+tcZhv54o2El2Loar+WClaNcLjvx7iMm+OkUYhIJ6nceWX0b0eLkOMBHmvHgdWIbmwFF6Gs+WSwWPxVKIUNkOoVm3fo506w9bdbrF2EFsbd8FkWB997xOdBGzeSsFncsoyCskWF9UNG+TsRSxPG3w8Cik09e1REAwCfPhH7mJ4lE8h25XPa58EoaDlMpNMiaNWtuMGrGX0KnOGcLjeu8a1k2GI7orKsZ57qRIKY0wm6ocVqjQsQffs1UGLQizWbsx3y9RksGgwCABlgzHwCL5/PZbHZqfDOMqRWaDRs2rNZ17dsgJlcxq0ZGsfGMpLv6aohYuIqHSccTBRGMSjmI7qDAYBCAV+w81sxzIDTXwcB16qwZZGqFZvv27WI+n7vHNOufhIasdEu2iSOSUqkU2LqaIOfZkUlPz8KXAxll9oNeMgoQ0SZwN5F9QrzTvc7Mg8jMpFId9YVfoAmWzOfg9uF0Om0EWLrYMLVCg6xbt/YMqARfqNXM6/Axm0LjxQZ9DZVyRdB1va9zB5K7bIzO1cGyDwR/jkGPnzTi8/2DinwcD0HkI+SbJTr/MUOz39YjLKRZlqTnkzMzF2Uymdyw5Y8rUy00CwsLCctqvEnXjW/UG3UJndh+Vo2hG0KlWmlaNXGzSojgiI8AEMOz/Pot9M1gWLOXmlmzswBgXYE+5bOrVq2659ChQ1NbcaZaaJDNmzetqFbVn4OJuw0rBUu22b7gCiPQevfVkIAET5Q6+CiVZSniUs7RE7w30V14aYc0i4LY1pMy30zD8c1UZSXx6nw+92ygBYgZUy80yNp1a/9D3TQ/bJr1edynRpJkT7jz6CPQqFMgukF1I5pgAMDs7Gzbb8Mnz4QHDSWReMhMpd5XOXZs6iLNeEhoBHsB5yaoGF81TfMqfMxbNXwmXwwKcEMVA2cSraClXEyT0nlOyveYdvoNkcY+4uSTT3Z8M1Zr1VgdrBkYtDqRZqL4MgjN1blc7v+OptTxgYQG2LRpk6iq1YNmzfxiw7IS3XKgochguPM4sgV46a9Di0PgMUHEE943ww8Q8WbWQWTMOt63oA/5c0WW78rmcrWQixw6JDQu69at2w4WzQ9ARLYzX41fDrRKpf8INMYwUVmTkz2ACBOqG176H5Rhv4C+Gcnepdd5jk2ZsRs8n5Zl5WChUHg8+DLHDxIal7PP3ivm8offrmvGI41Gw45VbNuF0wXnXnEKzbJwynXUl48sk6AZV0dLHXon47gmo/6MbpFmtjXjTpnZA1VJ/qSsyJ/I5XLR2kUxJEhoODZv2byqUir/2DTrZ+Njv8zOGFGC62pwEyOCIKYLXP6A+8209QuY08xNnon9gyiIKojMfug/fgFCQ6MNgYSmg/Xr1t1t1GqfY2tmfNfVGIad2bnbFsa9MsymUlHLJ0Yj+KCZRmt2vN8Zq2u/KdIw0qxlzVjNYBe2QBPbAAjME6eccsobXnrppWn7AbtCQuNh06ZNa1S1+s9g1Wxwd8PzzRZQKpWF2hIRaJ37xkSvzvUmDvFa+T3pkKCHB/YDqzBDs9Se5R37CSY0cMwR6DN+p1AofCfEokYOEhofNmxY/x7DqP0pVB4FRUWRlY6tWdFMLqOvZuBPCVN8oil8kwgJw6QggiUzI5w0N9exnQgTGXdg+jX4ze8sFos0t85BQuPDxo0bF0BIvge3C/ysGhbKiNNn9sKsAYh7VmEiXlC9GQ6cQsdpM1lW2qbbWJQZCg1c48PQT9xRKpWeDK+k0YSExoetW7dKqqZ+vGYYH66Zpohbs/LrauzlWRbz1VTsZ3rbjXn4xj6MX4cYjKhc39GWo3crNyrXY5x4180g2A/gmhlmzcBrXwKh+QgIzdQmz+wGCU0XFhYWdtXr5ld1o/YawXHwOVYNt3vksFYNg9bIEER0wanz+RXzGLLc7DGxD7AaLNIMQ5oFDfqHvdAf/Eu4pY0mJDRdOO2001KmWXsX+mpgtGJfJ2+2ALyrabqdLYA6f4KYPLC9oyWDOc3E7r6ZBrz2tCwr1xaLhanPAuAHCc0SnH766at0Tfunmlnb6oQtepNttrIFjCoHGjHtxCdwYxKt7WYWAFFq6y1ZQJCbjuoYWDN3l0qlR8MqZ9QhoVmGjQsL79YN/QGoUCm8XE5QAL7SunRsF06+kQXZ6Lr5ZZBJaNhR6aCiUg4iOqBvBq0Zns5IM+lryeTMXZlMphxSMSMPCc0ygFVzqqHr36+Z5sVYqZivpok74CwWi3bFC5JJ6vgmbeHoJP02wzKya4GnDLGHwnZuZ2gGawZ9Mux7snQzbqRZSVHkGwuF4t+HV9LoQ0KzDOecc46YzWY/BpXqY2AqOznQ0KphYiM6SqOpmqCq6hg7n/hMqcSNXpKfktAEy6iuZ7/p//nypFKzwhzuNyNYbTMIbL8ZN/nu34FF85v5fIEizZaAhKYHTt90+m6zZn5F07SLWMX1y4GGQQHkqyGI+IPte8WKFfZfXgAbVqMZ0gz9gJpIJN6Qz+efDq+k8YCEpge2bNmSMOvmO6uV6p/VG3WMb+66Xw2GOw9GtCyUSRix03cg/Fm+raFfBv0zPJ2+GeXbpVLxzcuejCCh6ZUztm49tVouP2OatTMaDavTVyMEt66GIIjwYFkA8C+D+WZYpBlaM0oicV0hn38qxKLGBhKaPljYsOFBo2bcYZr1pN/0GTK4VbP0KItGtgQxWti0uG8WAN6agb+yovxdIpE4kM1mtRCLHBtIaPpg67btZ1Qr5W/ohn6Z5bVquAgZFJpx+WpIgAgiOPzWzbAMzWxjM2hzR8CaeQNYMz8Pt7TxgYSmD3btOk8uldL36Lr+CbNmynj1gvfVEAQRBtiOZ2ZmbIvGa82YbL8ZJ8nut0Bobs9ls2qIxY0VJDR9smnTaTsNvfakUattwdENv90zH0o5inU1BBEkLWs4WoEoYeG3e6bXNwNtPQ3HvSOfzz8RYlFjBwlNn+zYukOu6tV7waq5zw1xxJXBQquxOpe0ZuJ+NWWa1iKImGBnAUjNNpPmMuqcNZNIJv9YlpU/yGTSpZCKGUtIaAZg27Zt89VK5Zearm+2rO4RaBX01VAEGkFEHrZuBvMZipzUeKwZNZVK7Umn08+HWtgYQkIzIKdt3PhREJr73aR6nds9uxUUE26SVUMQ0YZFmnmnEVmqGaAGYvTYqlWr3nHo0KFGSMWMLSQ0A7J1y5Yzq6r6M8MwVrAcaCg0vNjgPhXVqkrZAggiwjSzAIA1w0+btVsz4ouyrLynUCjQ7pkDQEIzIOeee65YLBb+vappH4cRj2I1BNdXI7SJjWnWhVKpOPJMzmQ1jY64X9+gyx/36+HF8c041ozlY81YVsNSlMT98/Pz9y8uLlKEzwCQ0AzB5s2bztR1429g1HOmO4fbYdUgFIFGENEEE+TOr3SsmSbQfBv1hrt7pj1bcRSsnuvBmvlpeCWNNyQ0Q7B9+3ZJVav3Q4X8SK1mNnfh9IJTZ5hwEyutl0kbHRLDQ3ViPOB1xpxmqVmwZiyxuZ8h2waApZJSFOVBuP1uNpulOfABIaEZks2bN59n1mp/run6RSgkWHl9I9DcXTixMlMfMipoPQjRO7huBn0zbAaC/WXhzHiDYxYTyeTF2UzmlTDLGndIaIZk165diq6pb6mq2tdhBCTae1SgGc6NjrAC4+iIZQsIYrQ6UTtseubGCWLUMGvG9s14LMiWb8YSZmZSf5TLZT8SYlEnAhKaANi2bcsqXav9SDf0c6CCit2sGpw+g2OcjnUCBIIg4koymbSDALztFGclON/MkWRy5opsNvOrkIo5MZDQBMQZZ5xxn6ZpH4RKehILd0baI9BMoVQq2c/5+WsIghg92P5wG4BkItnWA/IZmoG6oiifh8cfKJfLNCocEhKagNi2bdsaXde+rOvGjSzCzC8wAH01uq7bQkRiQxDjJ5FI2L4ZL0xk3ByGv4Djrs3lckdDKOLEQUITEGDRiA2r8baaYfwFiE3K2YGvM7Mz89WwyCKaQhsdFL1FeME6gYkzUWzawcWZjm9GFEQTrJnPJJKJe9PpdD2Ugk4YJDQBsmXLllPNWu27qqbtxwrrlwMNK3S1ogqarpFVM+Xw2b6J8YC+GRQaL8w3g9YMiMw/wiDxXfl8/pchFHEioVoeMNu377iiWi3/LVg1yTpaNZ4caAhWZrRqmEUz2Ki7eygvjeSjB/4cpCnhggM79M2gNeONdESRMetgzcBTiWTyndCGvpnL5ciaCQiq+gGze/d5yWIx93hVVa+um3W7w7d9NdwOnFjBdU0XVFW1Kz9LzDkYtHaEIHoBk2ZiSDM/8GOBObjGzQ3iOQTH7U0DIRZ14iChGQHbt2+/RlWr/03TdJFtjsasDN7awNQ07D5NoRHEcgw+qMJ2h1s0K7LSsWaLZQGAYzRoq5+BdvmxAApLcJDQjIDzzjtPKRYK3wWr5vUsczO/jQATHHwNo9CGt2oIgugGtjWcLvPzzVgWWjOub0ZWfphIJm7NZrOUBSBgSGhGxI4dOy8Eq+YHmqatYYEBLOEmb9nguhpm1ZDYEETw4IwC+mbQmvGumzHNmp1hHdpjTZHldxeKxa+FV9LJhYRmROzceeaMrqsPwWjpdhAb+zp3RqA5TkjMGICNoUa7cRJE4NiJM1OzHcEYOLBjWQDA4vmxoig3gTVzLJxSTjYkNCNk27atbzBr5lerqroGY/QlqXMLAQQj0JiPhqwagggOHNytmJ8XZEVpC/1jGZrtdTOiUE8mZ+6Epx/J5XIUWTMCSGhGyDnnnjOr69rdlXL1k4ah2yY6ZozlYQk30VeDr2HFp8CAyYDCzMPHsWZSHdusNzALANwa9rqZxPclWbohn8/TlMKIIKEZIeeff74IArJVU9Wfabq+Gp3/kmddDeuM1Koq1EynnpNVQxDDg9PRGADQ1uZw3ZqbBQDbGTxvwnG3FovFb4Vb2smGhGbE7NmzR6xWq19SVRV9NQk2wpWw4rPKj7H8UOkLhULTqonzSJhWvC/NUteHrl0wNDc1s7dodtausazpLKcZRpzJsvJUMpm8LpPJqCEXeaKhGj0Gdp119m5VrTwCVsteO14fnhPtwACxzUGJ02f4OoqQSVYNQQwMWjMrV6yEdtY+fYn3WQAAiFE2kUjcnM/nnw6vpNMBCc0YWFhYEGfnZn9XU9VPa5ou44jKbwqtDqOsSrVq/ygoNHG2aiYJsjLiBf5WuNfMzMyM/di2Ziwn5UzLmrGwDX4brJm3Z7NZLeQiTzzUesbEzp07tsFI6juVSnVPDRdxupujtRzGzqpn3EJA0zQKdyaIAcHFmSedNO+uW3Oes3MKNizbD+paM4vQxt5bLBa/G25ppwMSmjGxZ8/ZiqbV7imXy58Aq0ZyR1QdxzUamHCzAkIj2UJDRg1B9I7XmkH4BdEsqlNJJL4MQvN7+VwuE1ZZpwkSmjGya9eulKap/72qqpfUDMdaaRMb17BBiwen0DClOVo4BEEsDwoKS5wpiVJbTjMUFyYy6JuBtnVxLpd7PsTiThUkNGNm27Ztv61p2hdAQCQ+NQ0PDsDK5ZJ9v2E1hEad1tUQRDeYxYLtCHfOTCiJpsiw7MzcFs2mJEt/MpOc+VAmk6GImzFBQjNmduzYMQ+V/n9WKpXdfMLNNnARp5twE8Odw/DV9LrYMAhHeZyc7bSvTHRhWzTzuQTxL9ue2Q3CeV5REq8rFPK/Dru80wQ1mRAAsTkAQvLNSrU6j0n9RNFj1bjZ0DE1jf0QHhsGBQYQRDf4Tc28AyQWzgyNqiFJ8h8mk8l7wZoh7+cYIaEJgR07zpwFg/7rpUr5Zk11IitZZmcG3sWUNY5VIwu6boRVXIKIPKnUjDA7O9f2nL1kABNnYqRZvYGzA8/IsvyOfD7/bEjFnFpIaELirLPO2qOq6tMgJKdgQ8BVy/wUGjP9q9WKk8YcXjMp3JkgOsB2Mw/WjMzlEWTtB60ZO6UTPFASidtAaB7N5XLUkMYMCU2I7Nix45vlcvltuqHDiMtqy+7M/qIfp+pGoLHtZgmCaIGhzBjSjDjNxmk7fDgztKejYNGcWygUsuGVdHohoQmRc845+ywQkZ9UKtWVzOHPT6GxURmGOKPIsEzPBEE44MJmDADwDtBYOLObODMnSfLHSqXin4ZZ1mmGhCZEwKJRoBH8SaVSeQ+Iiew2io4ILGw0GBjAItDIqiGiQBSiBfnEmU5ZnMVobK8Zt4zfkyTpt0ul0uFQCzvFkNCEzO7du89U1eoTlaqK2wnYzzXFhmWmAVR4DRsOzkPrGqVmIqLPqIUIfTMrV67sGJxhVma2DQCggtXzVhAZSjUTIiQ0IbP7/POlmqZ+XNf0DxWLRcmdT+5MuAmNBn01OB+Nf8mqIaYZlmoGfZfeTc0c34yTvgnE6HuKkrw1n88WQizu1ENCEwHOPvvsi81a7UuFUul8XKjp5EGzNxNoHoONCZNtoq8Gp9A0smqIKSYBbcBOnClx1r/gZNJgvhmgoCiJGxVl7uls9gitmwkREpoIsGf3nkSjbt5SqpQfqeJOm7Wa7eRE+BX6zFfDcqCRVUPElWGm1fysGW/iTPe4r0E7OlgoFKihhAwJTUQ4//wLV1QrpZ+WK+VdmqZ3bYgoQuivQScoyxxAENMETh9j/benmPEJdzCGNxbODJRBZC4vFos/D7e0BEJCEyFe9arz7iqXK/dD41ht1kxBkqWOdBr4g5VKJSEBozkUnaDDnacll1cUIqaiTJSvDwYA+Fn8bYkzJelz0E4+JAgCTZlFgGjWpCnlwgsvXFOtVj8HlsrbbYe/1bDTnfNgw2KLOHFkh74a2omTmBZwugxzmnnhtwEAkfmFoig35PP5F8dfQsIPEpqIce7u3Qcq5fKj1Up1zqgZvhFo2JjgGDvUGe+zLNAEMYkwKxutGPTNKFyqGed1q7luRpTEuiIrn0qlUvceP36cfDMRgYQmYrxq70WzRrX8AFg1d2BCTXeE1nydTRU0w51hhIebpBHEpMMvzuSxE2fiFDJGa8ry92VJfm+hWDgUQhGJLpDQRJDdu8/bbZrGU9lsdo13z5rWnLQlgNVjT0Cz5IEEManIuA3A/HzTN8PgE2dC20Br5s1g8fxNNpc1Qyoq4QMJTUS54IIL/hMIzQdK5bJQx4wAPs5PbGBo1czOpgRV1SjcmQiNYYIHennvHFgzMzOpjh6rXjftLACuNfNTsHguTafTNOqKGCQ0EeXyyy8/LZvJ/HMun1+tqlW2ytl+DYM6catalnCTCQ8t4iQmEVycOb9iRcfzfOJMaBtHYTD2vmKx+FgIRSSWgYQmwpx/4YXvKRXyD5RK5QQKCgqNd+SHIuOEOycEQ6dtBIjJY35+3t4500vTmhHsQIEvzc3N3XXs2LH6uMtHLA8JTYTZ++q9azVV/WsQmkthpOamppGaC9VsOwbuqFXVHtXhTpyqSlYNMTlgODNGmvllNEehqdft3IBHQGhugDbyjyEVk1gGEpoIs2/fPlFVq/+uWlU/ncvlEuiT8Vo1LOEmRqjhdraYwoasGmISwLqNa2YwnNkvcSYKjTul/BWweO6CNkKjrIhCQhNxLrlk3zrD0D91Ip25vVKu2HPSXrFBf03NqAm4zcAMiA1aNbSIk4g7s3YAwIx9n6/vjsjUYUCFkWbS/xFF+bJyuZgJq5zE8pDQRJyrr75aKpWK+9PpzN+Wy+UUc/jza2sQexEnWDU4+muuKyCIHvCN+uL2QgoDjLJE34x350w+nxnctyRZ+qgiK3+Yz+dpZBVhSGhiwP7Lr5QLueOPl4rla0BsRDfKpqNzwDU3KEQ4r626m6hNM3wHGuXcXXFllNeULc7kLXOWFQMHUe5n/xoE6ZJisXh8JIUgAoNaXkx49d69V1aqlS/ncvmdKCZ+Wz4jmNEZRYg1SIKIG7Y1s2K+meePiU3LN2MHllXhuHeVSqW/Cq+kRK+Q0MSE/fv3S2Cl3FcqFT9aKBQlDHfGBtkSm9Ze6eirmZ2bs8Oel/LVBDE7QpYCESRsrxk+nJktUnYizeosLdMTcHszWDO0V0YMoB4iRuzbt+8ssGa+k06nd5XLFXtvdG9KDpQP3M+GTa/RIk4iTqDAsHBmfgDDhzMDx6Fu/1uw3mlxZkwgoYkRl+zbl7AE66PZTPbeXD4vok9GkmRBEts33bDDnctlITWbEioVSrhJxAPcvnx2dk5IJpIdPRPLzoyWDQyuHgah+TD5ZuIDCU3MuGLfFalKrfLUiePH95UrFXuEJ8tSx45lhqHDrWaHh9JOnEQcmE2lhBm4ebdn5iPN4LUCCM2FIDK/DrOsRH+Q0MSQS/fvf3c6k/liLpeTWWoaBmuk2Cgx4SZORaDlw/ZRJ4gognUYw5mduizaYyZ+50y8wWMDROYxRVFug7pPqWZiBAlNDNl/ySWrq2r1x5lM9iy0VtyRXsce6hh1xsKdu+3ESc788RLy8pRIgrU2OZO0Q5qd+tv8r23nTKinz8uKcmexUHgqxOISA0B1Pqa85jUXX1kslb6dy+VPUcFy4Z2nvHDYW0LDaBB346TAACJq4ECHz2fmTTXDsjPjXjNg7bwfjn0gk8nQ4syYQUITUy6/7LL5cqXy9UKhgHujN62aJqIzUqybzk6cdnZnQ2dROxMJWWfxoxnOrCQcFyPnn2FWuZtM9jlRkq4rFYvPhVtiYhCoVcaY/fsvuaZSrvzl8RPHT8WQZu/EDOt0NVWz962ZSSXt0GfKgzY8JGrBgNaMPWXmY82gJeP6Fi1Zlu6UJPkrMLCiyhtDqKXEmH991b+a0wz9g0ePHv19XJzpFxjAsjvjVgL2rw033LeGIMJmqXxmXAAAhj3/j9nZuatPnDhBFTemkNDEnMsuvWxNVa0+m86k15RL5bYpNOevY+Vgo0UxSs3MCIa7x7ovnjDpSSBM64MsH3/YlBlaNN5r1AoAcHwzsqxcVywWfxBicYkhoRYQc6688kqx3qj/x2wmezeM+OYxlBlh1gzfiDEYAOe8WdJNmkIjwgDrI5sy8z7PBwAADbDQ/0pRlDvy+TxliY0xJDQTwJW/8Rs7SuXywyA0l5XAqsHUNH5Cg/dZVme8z0SJIMZJIqHYGQDYNC9fTxuuX6bu5DM7Cs/dWC6X/1fIRSaGhIRmQrji8stfD2Lz6NGjR1eyNTNtO3Ha/zlp1lFs+JTrtLqDGBe4gBgtGa8v0U6aCQMkjJJ0d4jVJFm6XRKlx4rF4uSGSk4J1LtMCK997etW183aA4dfOXxLoVAQvYEBCBMeOzjAFRsu7ToxpYzLj2Sv/j9pXpAV2XfaltvQDNMqfQv+HARrhpL1TQAkNBPE9QeuPyNfyP/oyJHF07PZHFvo5qbz4BdyWoIJr1UrToqa5XbkjIZDu3+rKxrlHpxQyx+wkYsRZriRWdP5L7QngmUBAG7SzDRuaJbP538VXAmIMIlvKyR8eeMb33hjLpv9zOFXXtkOlk1zuwBUGtawWeflpKhR7awBjTpOo+GaBQoQIIJFwgizk+YEhS3KFJyBD4PtM+MKKybNvBPqLm0BMEGQ0Ewgb7r5ppuOHjv28OHDh1cWCkVnoyhZEsSW0jSPZfnQ2NbQGCBA0WhEUNYUZhZHx78Cg5lun8MsGfi8miiJXwAb/IPFYpGywE4QJDQTyG3vvD2lq5WDhxdf+dRLh15ewRJvypIsMLtG5BIX4mgSfTr4VwbBMclvQwyIk9hVtOtZAsQF0/7zm/PxU2ZtIgP/JFl8GI74fRCZQ6EUnhgZJDQTyh13vFcpFdMP/vqFF+5cXFwUKpVKR5oPhA99NkBsdLBoUGywM2Db5hLEcmA9YsEnFtSZRDJpLw4WfaLLGFxWZnzvMbB+Ls3l8s+PvfDEyCGhmWAO3nYwVSoXv/Dyy4ffuXhkUSkUC87IkW/89n+tDgDFBafPcErN7gAwDNrdr52m1AgeJi68gKD1gpvtofXM9y5eS4YFn+CgBsToGfj7oWwu9/fj/QbEuCChmXBuveWWLfl8/sHjJ0687tChQ/ZGaQifnsYLyzVV80lVw7LqshsxHfBpjdi0Ky8w6IPBCEYUmqbV7GYQ5+sJ7/iHYy049mdw+z24/8NMJtM99JGINSQ0U8Bbf/MtZxm12md/9avnXp/JZp09ahqW3WH4OXzZHDvC5tH5Pdud90kty8iNaPNaPb0Ikdfp3M0J3fG8Jdhl7OqwdjWUva/5V7CanV/b8+z87nkFtolcWxkELlS8s2zs+3rPx07EfBHeEzefZ4j8hwntx7PysUOX+f4dT3e5vs2N8zz7GjXrgr2gsn2AIYGoJJWELTIYbLIUlmsVsyhItGTgA54Ci/kgnPcQDIZo1DLBkNBMCQdvu+2UcqXy+WPHjx9YXFxciXvY4BSZM/3BqsHS1cG2dBp1OxSadRqYKsTidvj0o+vzwvDB1MudI4jPiAteH0g/tN5n2YMQHhQGdkOLBW9syqyzDO2hy+zcnBWD7zXgvY/CsfdkMtkTAxWYiBUkNFPELbfcIjfq9ZsKhcKnjxw5uuVE+oQdJICpPyTRv+PgaevIOIsB6WbNtKbZmq/wZ2xWQGsZueD3kHdG2ULbczwdCahd66RlebTK4Zesmn+u32TWyx3PPrtpNbgl8TdAWjLJfGnt5+ro0duO4S3TbuVrS1PktWY4Hwxv7TUtNu783USOBZTg6ygyYP0U4cCHQMzuy+ayerfrREwWJDRTxtve9laMcd5ZLlfen8lm3nLs2LGVbC8bZCnLxA9vMEH8GG4JvLv7o3ufnU9oTZFZS4joMqrEVtALPtNtbce0q2pXAV4Ov3P52YN+osI/xwYXLGIRLWYQmLokyS/C83fD7elMJlPuu4BEbCGhmVJuvvGGk6CL/J1sNvOB4ydOrE+n0/ZUGhduGnYRI8VS01J8iHgvz/fDoAsnu5W31zIxoWyJXUtyup3bYn6cpsBItv9GVmQTzvcUPP9ZqGtP9P1liNhDQjPlXH/dv7lY0/X7wLq5Jp8vyOVKGQRHd4IF8ICuFo5n22hhtL6QYfwPbecRli8n38l2KwsyTHn8fBkDn8s9oa+wuNOM/Lf2C77oEEnuHd7r0R4s4pyb+WEQRVYEJaEIyUQCnYA/s+r1P6rV6z+AwQztSzGlkNBMOddee60oWdJqOSHdVqlUXlsuly8ulcunwl9RU1U7SwCbAvGGrUadYcRpOQuml/O2RaIJSwvcuBNoDnxt3KlAvxB3FuIMtxOSKP4YXvkG1J2nFxcX0wEWnYgh0e8tiLGAO3XOpWbWm2bjGqNWe4uua5eWSuWVlWpFwBQ2uKbGERzHkvHu8740YtMVwYcAd8PvGG/oMP/6cp003xl6j+ulPMtitWyGbudphTtbTeuja2fPmTvsPU370eOU9/surdN4oxyE7i2eWSb8+9zP9Q/ssJpTrK6THxdq6vD4/8Exf2w1rCdfPvzykS6fRkwZJDREBwcOHBBLxdIG6EiuNAzjtbqhX69p2inValXCNDUY0uyEN+OWu422iLNBndAMthhwKdqmdZaZglq2820d2H6yHjrxfkWq97J0XyPkL5oW9z6f8zOh6nptLffrexbsOGdpi0BDUcEcZoq9OBNsYVHKi6L0giBa/wDH/mcYkPzvQ4cOaf5fjJhWSGiIrly6/1IZOphVoiS+2jSMS8DSuahRb2xuWI2Tzbp5smmacyA0cr1eF9kWvBgqbXeGrNNjnTEfXGB54rB4i4A9t2znbXnf0Py/zfrxOX/zeU9Zmv1we2/dKuNSFgInUP6+Eu5t7rnYR3mtFlZaPnLN+7GdeM0VfL/UfjT/BVva1HrcDGNmv5nYTFkkybKlyHIR7udkSVqEA9FyeRZuv4T3vgC/+mG4X3rhhRfiGn5IjBASGqIn9u7dC/2OmATlWdkQhNVgxawHy+Z06FZPkyV5E4jNBni8BZ5fC4fPYT/HVthjx4VCY+dNQ+sHDrAaFuvt7F4fOzVuCsxyO2t3tkhk0zaYsoRf+CGwTAUCp20Sd4zFC4nTuTcfOp8hsneKbhnsj8FnuLUrTY96M3S43aRxvmOz8M5xDSyuU2ZmLjS1BJ9GQwLsQefywJHu20XP+hfuPR1ax87VvN/8pp6W3fSptZz6zn8Nq+Vuc31v8OY6HFOBA0rwVAEGDy/C0T+HNz4Dzy/C7bhhmuqLL75IKb6Jnvj/oTLjT6VpdMoAAAAASUVORK5CYII=" + }, + { + "id": "cable", + "u": "", + "e": 1, + "w": 106, + "h": 667, + "p": "data:image/png;base64,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" + } + ] +} diff --git a/packages/suite-data/package.json b/packages/suite-data/package.json index 507656adff6..7a8bdfd98c1 100644 --- a/packages/suite-data/package.json +++ b/packages/suite-data/package.json @@ -14,7 +14,6 @@ "browser-detection": "webpack --config ./browser-detection.webpack.ts", "guide-pull-content": "yarn tsx ./src/guide/index.ts", "update-coinjoin-middleware": "./files/bin/coinjoin/update.sh", - "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", "type-check": "yarn g:tsc --build tsconfig.json" }, "dependencies": { @@ -27,6 +26,7 @@ }, "devDependencies": { "@mobily/ts-belt": "^3.13.1", + "@trezor/eslint": "workspace:*", "@types/fs-extra": "^11.0.4", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", @@ -40,7 +40,7 @@ "simple-git": "^3.26.0", "style-loader": "^3.3.4", "tsx": "^4.16.3", - "webpack": "^5.94.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4" }, "nx": { diff --git a/packages/suite-data/src/browser-detection/index.ts b/packages/suite-data/src/browser-detection/index.ts index 02de2f9d483..429d8aaa1cb 100644 --- a/packages/suite-data/src/browser-detection/index.ts +++ b/packages/suite-data/src/browser-detection/index.ts @@ -196,8 +196,8 @@ window.addEventListener('load', () => { // browser-detection styles are removed because they should not be available and used in app // styles.css gets id="browser-detection-style" on its