Trezor Suite documentation

This documentation can also be found at docs.trezor.io/trezor-suite where it is available in a HTML-built version compiled using mdBook.

Repository Structure

TODO

Releases

This chapter contains information about the release process.

Adding New Firmwares

In case we are about to release both Suite and firmwares we want to add the signed firmwares during the Freeze so QA has the whole thing to test.

Add firmwares

  1. Complete the firmware release process including firmware signing.
  2. Add firmwares to webwallet-data and modify its releases.json file. See e.g. a1831647 for an example.
  3. Deploy them to data.trezor.io. This is currently done manually and should be automated.
  4. Modify releases.json in Connect. See 5350f7ee for example.

Publish Connect to NPM and bump in Suite

  1. Publish Connect as beta. See Connect: Updating NPM to beta on how to do that.
  2. Bump Connect in Suite. See connect/bump.md.

Freeze & Release

  1. Freeze Suite. At this moment you are all good to Freeze and forward to QA. They should be able to test Suite in its wholeness along with the new firmwares. [1]
  2. If QA gives a go-ahead we release.
  3. Publish Connect to production.
    1. To connect.trezor.io, see Connect: Updating NPM to production.
    2. To NPM, see this.

[1] Note that at this moment you have a -beta version of Connect bundled in Suite. This however does not pose any problem. We simply release Suite with this -beta version and publish Connect production version later.

Desktop Updates

The desktop build of Trezor Suite uses an auto-updating feature to keep the application up to date with our latest published release.

Internals

The package electron-updater (part of electron-builder) is used to manage updates. Information about updates is displayed in our UI and the user can perform actions related to them (trigger update, skip, etc...).

In addition of what electron-updater provides us, we check signatures of downloaded files. For this to work, all files uploaded on Github need to have a signature attached with them. The signature will be checked against the SL signing key which is included in the application at build time. The key is located in packages/suite-desktop/build/app-key.asc and should be updated if the private key is changed.

How to publish an update

  1. Set the GH_TOKEN environment variable to a personal access token with access on the project repo scope. See Github documentation for more information.
  2. Check the version you want to publish is correct.
  3. Build all or a platform specific desktop build using yarn workspace @trezor/suite-desktop run build:desktop (all) or yarn workspace @trezor/suite-desktop run build:linux (platform specific/linux).
  4. Publish all builds or a platform specific build using yarn workspace @trezor/suite-desktop run publish:all (all) or yarn workspace @trezor/suite-desktop run publish:linux (platform specific/linux).
  5. Go to the Github Releases page, you should see a drafted release.
  6. Update the content (which will be displayed in app as a change log) and publish the release.

Versioning

We are using so-called calendar versioning in the format YY.MM.PATCH.

  • YY stands for the current year.
  • MM stands for the current month.
  • PATCH is increased on every release in the given month.

Examples

  • 20.10.1 first release in Oct 2020
  • 20.10.3 third release in Oct 2020
  • 19.12.1 first release in Dec 2019

Beta versions

We version beta in a similar way as production versions but we always set PATCH to 0 and increase the MM.

That means that every release on beta has 0 as the patch version. This has a drawback that you can't distinguish beta deployments by a version number, but beta testers should be able to read and report the commit hash.

Only stable releases have patch version >1 and this increases with each stable release: 1, 2, 3, 4.

Beta also has +1 MM version when compared to stable indicating this is upcoming release which will be deployed on stable next month.

Examples

  • 20.10.1 first release on Oct 15th to stable
  • 20.10.2 second release on Oct 22nd to stable
  • 20.11.0 release on Oct 29th 2020 to beta
  • 20.11.0 another release on Nov 5th to beta
  • 20.11.1 public release on Nov 14th to stable

Develop versioning

We use the same scheme as beta. That is, develop branch has always YY.MM.0 version where MM is the upcoming month's release. When we fork develop to release/20YY-MM branch, we bump the release branch version to YY.MM.1 and increase the develop version to YY.(MM+1).0 indicating we are already brewing next release in the develop.

Version bumping

Versions you need to modify manually for now besides all the package.json files:

  • packages/suite-native/package.json
  • packages/suite-web/package.json
  • packages/suite/package.json

Environments

Staging

Staging is available at staging-suite.trezor.io and is only accessible within SatoshiLabs internal IP range (office + VPN).

Before releasing publicly we deploy to so-called staging environment which should be 1:1 with production. QA tests the release there.

Production (suite.trezor.io)

Stable version is hosted on suite.trezor.io.

routesourceassetPrefix
/@trezor/suite-web-landing-
/web@trezor/suite-web/web

Packages

This directory contains description of various Trezor Suite packages.

Connect

Connect is currently not part of this repository but we plan to merge it into this one.

Most Connect docs are therefore located in its current repository.

Bump Connect in Suite

  1. Change trezor-connect package version in packages/suite/package.json.
  2. Run yarn, it will install the new Connect version.
  3. Run yarn build:connect, it will build Connect files into packages/suite-data/files/connect.
  4. Commit your changes.

Debugging

?trezor-connect-src

We have introduced a feature to significantly help us debugging Connect issues. You can substitute the Connect version easily with the ?trezor-connect-src parameter.

You can simply visit https://suite.trezor.io/web/?trezor-connect-src=YOUR_CONNECT_BUILD and Connect will be replaced by your own build.

This is extremely helpful along with the Connect's build and deploy features in its CI. You can create a new branch in Connect, push it, CI will build it, and if you run the manual deploy job it will also deploy it to https://connect.corp.sldev.cz/[BRANCH_NAME]. And then you can use https://suite.trezor.io/web/?trezor-connect-src=https://connect.corp.sldev.cz/[BRANCH_NAME] and you are testing the production build with your Connect build.

And of course the Suite build does not have to be the production suite.trezor.io one. You can use this feature anywhere.

Is it safe to have this enabled in production?

Only whitelisted domains are allowed so you can't replace the Connect URL with any random one. The list of whitelisted domains is:

  • trezor.io (production)
  • trezoriovpjcahpzkrewelclulmszwbqpzmzgub37gbcjlvluxtruqad.onion (Tor production)
  • sldev.cz (development)
  • localhost

Also Bridge will not talk to any other endpoints than the above mentioned due to its CORS policy.

Suite Desktop

Debugging (VS Code)

Using VS Code configuration files (inside .vscode), Suite Desktop can be built and run with a debugger attached to it. Running the Suite-Desktop: App task (F5) will execute all required scripts (NextJS server + Electron build) and launch the Electron app. VS Code will be set in debugging mode, allowing you, for example, to set breakpoints and inspect variables inside the electron-src folder (as well as other dependencies). For more on Debugging, please refer to the VS Code documentation.

Known issue: The devtools might blank out at launch. If this happens, simply close and re-open the devtools (CTRL + SHIFT + I).

Logging

Logging can be enabled by running Suite with the command line flag --log-level=LEVEL (replace LEVEL with error, warn, info or debug based on the logging you wish to display). Additional command line flags can be found below.

More technical information can be found on the Desktop Logger page.

Shortcuts

TODO

Runtime flags

Runtime flags can be used when running the Suite Desktop executable, enabling or disabling certain features. For example: ./Trezor-Suite-20.10.1.AppImage --disable-csp will run with this flag turned on, which will result in the Content Security Policy being disabled.

Available flags:

namedescription
--disable-cspDisables the Content Security Policy. Necessary for using DevTools in a production build.
--pre-releaseTells the auto-updater to fetch pre-release updates.
--bridge-devInstruct Bridge to support emulator (starts Bridge with -e 21324).
--log-level=NAMESet the logging level. Available levels are [name (value)]: error (1), warn (2), info(3), debug (4). All logs with a value equal or lower to the selected log level will be displayed.
--log-writeWrite log to disk
--log-no-printDisable the log priting in the console.
--log-file=FILENAMEName of the output file (defaults to log-%ts.txt)
--log-path=PATHNAMEPath for the output file (defaults to home or current working directory)
--enable-updaterEnables the auto updater (if disabled in feature flags)
--disable-updaterDisables the auto updater (if enabled in feature flags)

Mock

Some libraries are difficult to test in development environments, such as the auto-updater. In order to still allow certain interactions with the feature in developments, libraries can be mocked.

How to use mocks?

  • By default, development builds load mocks.
  • Non-development builds can include mocks if the USE_MOCKS environment variable is defined.

How to make a new mock?

  1. Open the suite-desktop build script located at /packages/suite-desktop/scripts/build.js.
  2. Add a new entry to the mocks object. The key should be the name of the package, exactly as written when imported. The value should be the path to the mock file to point to (located in /packages/suite-desktop/src-electron/mocks).
  3. Create the file in /packages/suite-desktop/src-electron/mocks and export mocked properties that you have imported across the project.

Mocked libraries

Auto-Updater

The auto-updater has been mocked to simulate similar behaviour to the actual library. Unless the commandline parameter --mock-trigger-updater-after=DELAY is passed, checking for updates will always return not-available. This commandline parameter requires a value, representing a delay in seconds before making the update available. Using 0 as a value will make the update available immediately. For example, if you wish to make an update available after 1 minute, you will use the parameter as follows: --mock-trigger-updater-after=60. Note that his parameter is ONLY available with mocks enabled.

Features

This directory contains description of various Trezor Suite features.

Metadata (labeling)

Metadata is a feature which allows user to associate persistent data with their accounts, transactions, addresses or even hidden wallets. Trezor Suite refers to metadata as to "labeling" in user interface.

Data stores

Because Trezor Suite is not a typical application with a backend server, data must be stored elsewhere. Currently supported providers are:

  • Dropbox
  • Google Drive

Planned to be supported in future:

  • Local file system (desktop only)
  • SD card

Data structure in store

version 1.0.0 (current)

device metadata example

{
  "version": "1.0.0",
  "walletLabel": "my hidden wallet label",
},

account metadata example

{
  "version": "1.0.0",
  "accountLabel": "my cool account label",
  "outputLabels": {
    "9f472739fa7034dfb9736fa4d98915f2e8ddf70a86ee5e0a9ac0634f8c1d0007": {
      "0": "transaction 1"
    },
  },
  "addressLabels": {
    "bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk": "my cool address label",
  }
}

version 2.0.0 (future)

Each record will have timestamp which will allow user to resolve potential conflicts

Data encryption

Data is stored in encrypted form using aes-256-gcm cipher. Master key for encryption is generated by users Trezor with constants defined in suite/src/actions/suite/constants/metadataConstants.ts Files encryption-decryption logic is located in suite/src/utils/suite/metadata.ts

Where data lives in App

Settings related data is defined in suite/src/reducers/suite/metadataReducer which contains

{
 enabled: bool,
 initiating: bool,
 provider: {
   type: "dropbox" | "google",
   token: string,
   user: string
 }
}

Metadata itself is divided into 2 groups (device metadata and account metadata). They are stored together with concrete records.

Device has metadata property:

{
  /*
   - disabled is initial state. user has not interacted with metadata before, metadata keys are not available
   - enabled means that metadata is enabled for this device and application has metadata keys. App will try to download and decipher metadata
   - cancelled means that user rejected cipherKeyValue call on device. 
  */
  status: "disabled" |"enabled" | "cancelled",
  key: string,
  fileName: string,
  aesKey: string,
  walletLabel: string
}

Account has metadata property which is an object of following shape:

{
  key: string(xpub),
  fileName: string,
  aesKey: string,
  outputLabels: {
    [string(txid)]: {
      [number(outputIndex)]: string
    },
  },
  addressLabels: {
    [string(address)]: string,
  },
  accountLabel: string
}

User stories

First time user:

  1. User opens App for the first time. Metadata is disabled. "Add label" buttons are present on mouse hover over labelable data.
  2. User clicks "Add label" button.
  3. Device metadata key is generated.
  4. Using device metadata key, account metadata keys are created.
  5. Open modal with metadata providers and connect.
  6. Fetch data from metadata provider and set interval for fetching data.
  7. Activate editable input.

Metadata enabled during discovery process:

Controlled by discoveryActions

  1. If passphrase is not used device metadata key is generated before discovery process start. discoveryActions
  2. If passphrase is used metadata key is generated either:
    • in the middle of the discovery process after successful receiving first bundle of accounts and at least one the account is not empty. discoveryActions
    • after passphrase confirmation process. metadataMiddleware

How to turn metadata off

  • controls for common metadata related actions are located in general settings under the labeling section
  • there is a switch which:
    1. sets metadata.enabled bool value
    2. if setting to false, it triggers removal of all metadata (including keys) from devices and accounts.
    3. if setting to false, disconnects metadata provider (Dropbox, Google Drive)
  • there is a button "disconnect provider" which:
    1. triggers removal of all metadata values (excluding metadata keys) from devices and accounts. This way provider might be reconnected without reconnecting device
    2. disconnects metadata provider

Transactions - Export

You have the possibility to export your data in multiple formats: PDF, CSV and JSON. The export menu is available by clicking the three little dots near to the search button. Export Button

The JSON format contains the most extensive amount of information. This export can be used for data vizualisation for example.

Transactions - Search

Transactions can be searched using an input field located above the transaction list. Clicking the magnifying glass will open the input and focus in it. You can use the keyboard shortcut CTRL/⌘ + F for the same result. Search Button

You can search for the following information:

  • Transaction ID (txid)
  • Input and Output addresses
  • Output labels
  • Address labels
  • Amounts
  • Dates

All searches, apart from the two last ones (Amounts and Dates), are free text searches. No specific format is required, anything (txid, addresses, labels) matching the search will be returned.

Any numbers will be treated as amounts. For a given number, partially matching amounts will be shown. Operators, prefixed to the number, can be used for different results:

  • > will return amounts greater than the value (i.e. > 0.001 for all amounts greater than 0.001).
  • < will return amounts lower than the value (i.e. < 0 for all negative amounts).
  • = will return amounts that strictly match the value (i.e. = 0.01 will return all amounts that are exactly 0.01).
  • != will return amounts that do not match the value (i.e. != 0.01 will return all amounts that are not 0.01).

Dates provided in the format yyyy-mm-dd will return transactions matching that date. Just like amount searches, prefixed operators can be used:

  • > will return all transactions after the date, including the given date itself (i.e. > 2020-12-14 will return all transactions after December 14th, 2020).
  • < will return all transactions before the date, including the given date itself (i.e. < 2020-12-14 will return all transactions before December 14th, 2020).
  • != will return all transactions that are not matching the date (i.e. != 2020-12-14 will return all transactions except the ones on December 14th, 2020).

Multiple fields can be searched at the same time using AND (&) and OR (|) operators. A few examples using these operators might be pretty self-explanatory:

  • > 2020-12-01 & < 2020-12-31 will return all transactions from December 2020.
  • > 2020-12-01 & < 2020-12-31 & > 0 will return all incoming (positive) transactions from December 2020.
  • > 0.01 | > 2020-12-01 will return all transactions with an amount higher than 0.01 OR that have happened since December 1st, 2020.
  • > 2019-12-01 & < 2019-12-31 | > 2020-12-01 & < 2020-12-31 will return all transactions from December 2020 and 2019.

As you may see in the last example, and similarly to SQL, the AND (&) operator has precedence over the OR (|) operator.

Tests

This chapter contains information about test.

Suite Web e2e tests

Suite uses Cypress to run e2e tests. It also uses trezor-user-env which is daily built into a docker image providing all the necessary instrumentation required to run tests (bridge and emulators).

Run it locally

Note: All paths below are relative to the root of trezor-suite repository.

On Linux

Prerequisites

Steps

  1. Run xhost + to add yourself to the X access control list.
  2. Run docker/docker-suite-install.sh.
  3. Run docker/docker-suite-test.sh.
    • A Cypress window should open.
    • Wait until the project is built (a warning about "http://localhost:8000/ is not available", should disappear on the retry button click).
  4. Start a test by clicking its name in the Cypress window.
    • It should open a browser window.
    • If the Suite web app is not loading even after two retries. Stop tests, open a new tab, navigate to http://localhost:8000/, refresh the page until the app is loaded. Close the tab and run tests again.

On MacOS

As of now M1 Macs aren't supported. See this issue for detailed information.

Prerequisites

Steps

  1. Run XQuartz. Wait till it is launched. Leave it running in the background.
  2. In XQuartz settings go to Preferences -> Security and enable "Allow connections from network clients".
  3. Open a new terminal window (not in XQuartz) and add yourself to the X access control list:
    • xhost +127.0.0.1
    • You will probably need to logout/login after XQuartz installation to have xhost command available.
  4. Run Docker and go to Preferences -> Resources -> Advanced and increase RAM to at least 4GB. Otherwise, the app during tests does not even load.
  5. In terminal window run docker/docker-suite-install.sh
  6. In the terminal window, set two environment variables:
    • export HOSTNAME=`hostname`
    • export DISPLAY=${HOSTNAME}:0
  7. In terminal window run docker/docker-suite-test.sh
    • A Cypress window should open.
    • Wait until the project is built (a warning about "http://localhost:8000/ is not available", should disappear on the retry button click).
  8. Start a test by clicking its name in the Cypress window.
    • It should open a browser window.
    • If the Suite web app is not loading even after two retries. Stop tests, open a new tab, navigate to http://localhost:8000/, refresh the page until the app is loaded. Close the tab and run tests again.

Troubleshooting

  • [...ERROR:browser_main_loop.cc(1434)] Unable to open X display.
    • Make sure the XQuartz app is launched and you can see its terminal.
    • Check that environment variables are properly set:
      • echo $HOSTNAME # e.g. name.local
      • echo $DISPLAY # e.g. name.local:0
    • Do not mix native terminal window with terminal window in your IDE (e.g. Visual Studio Code).

Notes

Image snapshots

It is possible to run tests with image snapshots to test for visual regressions. To enable snapshots, use env variable:

CYPRESS_SNAPSHOT=1 docker/docker-suite-test.sh

When you need to update image snapshots you have 2 options:

  • use CI job. This will generate new snapshots in artifacts together with a handy script that updates your snapshots locally. Check the log output.
  • use docker/docker-suite-snapshots.sh. This does the same as docker/docker-suite-test.sh, the only difference is it won't fail on non-matching snapshots but generate new snapshots.

run_tests script

The run_tests.js script is the entry point for e2e tests. It:

  • picks tests files to be run (see @tags)
  • retries tests if needed (see @retry)
  • reports tests results

tags

Each test should be assigned a tag at the top of the test file. These allow you to add more fine-grained control in run_tests.js.

At the moment, there are the following tags:

  • @group:[string]
  • @retry=[number]

@group

Assigning a @group allows run_tests.js script to sort the test files into groups and run them in parallel on CI. At the moment these groups exist:

  • @group:metadata
  • @group:device-management
  • @group:suite
  • @group:onboarding
  • @group:settings

@retry

If there is a test that you for any reason need to retry if it fails you may provide @retry=2 tag. In this case, test will be run 3 times in total and count as failed only if all runs fail.

Results

There is a tool to track tests runs and their results, temporarily hosted here https://track-suite.herokuapp.com/ Repo here: https://github.com/mroz22/track-suite

Assorted knowledge

This directory serves as a dumping ground for important knowledge tidbits that do not clearly fit in any particular location. Please add any information that you think should be written down.

At any time, information stored here might be restructured or moved to a different location, so as to ensure that the documentation is well structured overall.

Analytics

Both web and desktop Suite applications collect anonymous data about how a user interacts with them. Analytics is not mandatory and not all users have it enabled, as it can be opt-out during the onboarding process or later in the settings in the general tab. However, by default, it is enabled in the onboarding process and if the user does not opt-out, the application starts to track his interactions immediately after the onboarding process is completed.

Anonymous data:

Collected data have to be anonymous. This means that Suite should never track any data leaking information about a device or a user.

  • device id
  • public keys
  • transaction id
  • ... any other fingerprinting

Tracking process

Data about interactions are transferred in GET HTTP requests encoded in URI.

Data from production builds (codesign branch) are sent to:

  • Desktop build: https://data.trezor.io/suite/log/desktop/stable.log
  • Web build: https://data.trezor.io/suite/log/web/stable.log

Data from development builds are sent to:

  • Desktop build: https://data.trezor.io/suite/log/desktop/develop.log
  • Web build: https://data.trezor.io/suite/log/web/develop.log

Data from localhost are not currently tracked anywhere.

List of available configured endpoints:

https://data.trezor.io/suite/log    /desktop   /staging     .log
https://data.trezor.io/suite/log    /desktop   /beta        .log
https://data.trezor.io/suite/log    /desktop   /develop     .log
https://data.trezor.io/suite/log    /desktop   /stable      .log
https://data.trezor.io/suite/log    /web       /staging     .log
https://data.trezor.io/suite/log    /web       /beta        .log
https://data.trezor.io/suite/log    /web       /develop     .log
https://data.trezor.io/suite/log    /web       /stable      .log

Example URI:

https://data.trezor.io/suite/log/web/stable.log?c_v=1.8&c_type=transport-type&c_commit=4d09d88476dab2e6b2fbfb833b749e9ac62251c2&c_instance_id=qlT0xL2XKV&c_session_id=FZjilOYQic&c_timestamp=1624893047903&type=bridge&version=2.0.30

Which tracks:

{
  c_v: '1.11',
  c_type: 'transport-type',
  c_commit: '4d09d88476dab2e6b2fbfb833b749e9ac62251c2',
  c_instance_id: 'qlT0xL2XKV',
  c_session_id: 'FZjilOYQic',
  c_timestamp: 1624893047903,
  type: 'bridge',
  version: '2.0.30'
}

Attributes which are always tracked:

  • c_v: version of analytics
  • c_type: type of tracked event
  • c_commit: current revision of app
  • c_instance_id: until user does not wipe storage, the id is still same
  • c_session_id: id changed on every launch of app
  • c_timestamp: time in ms when event is created

Other attributes are connected to a specific type of events.

Specific events can be found in analyticsActions.ts file and also in company Notion where implemented events with expected attributes and other notes related to analytics can be found.

Add/Modify event

In case a new event has to be added or an old one has to be modified, please follow the following subsections.

What to track

Navigation between pages is not required to be tracked as it is tracked automatically by router/location-change event. However, a case when it is good to track it is when a user can get to the same location using different methods (e.g. two different buttons on the same page). All other user actions without sensitive info can be tracked. If you are in doubt, please contact our analyst.

Type declaration

All events and their properties should be declared in AnalyticsEvent type in analyticsActions.ts file.

Reporting in code

To report an event, use useAnalytics hook in your component and use its report method.

analytics.report({
    type: 'event',
    payload: {
        attribute: attributeValue,
    },
});

Versioning

version variable in analyticsActions.ts file should be bumped (applies only if it has not yet been bumped in the current version of the app). Please follow simple semver versioning in format <breaking-change>.<analytics-extended>. Breaking change should bump major version. Any other change bumps minor version.

Changelog

Add a record of change to Changelog section in this file. Please use a format of previous records.

Notion

Add event to the analytics overview in the company Notion

How does analytics work?

  1. User with enabled analytics interacts with the application
  2. Events are sent to specific endpoints
  3. Collected data are parsed and analysed (can be seen in Keboola)
  4. Charts and metrics are created (in Tableau)
  5. We know how to improve the application

How to check that events are tracked?

  1. Option: Open DevTools, navigate to Network tab, filter traffic by .log and check the Query String Parameters section
  2. Option: Get access to Keboola
  3. Option: Create a modified build of app with an analytics server URL pointing to your server
  4. Option: Edit NAT to resolve requests to https://data.trezor.io/suite/log/web/stable.log to your local server

Changelog

1.11

Added:

  • c_timestamp: number (time of created in ms sent with every event)
  • menu/settings/dropdown
    • option: 'guide' (+ old ones)
  • menu/guide
  • guide/feedback/navigation
    • type: 'overview' | 'bug' | 'suggestion'
  • guide/feedback/submit
    • type: 'bug' | 'suggestion'
  • guide/header/navigation
    • type: 'back' | 'close' | 'category'
    • id?: string
  • guide/report
    • type: 'overview' | 'bug' | 'suggestion'
  • guide/node/navigation
    • type: 'category' | 'page'
    • id: string

1.10

Removed:

  • initial-run-completed
    • newDevice
    • usedDevice

1.9

Changed:

  • use stable.log for codesign builds and develop.log otherwise
  • suite-ready is now also tracked on initial run

Added:

  • suite-ready
    • platformLanguages: string
  • device-connect
    • language: string
    • model: string
  • settings/device/goto/background
    • custom: boolean
  • settings/device/background
    • image: string | undefined (gallery image)
    • format: string | undefined (custom image)
    • size: number | undefined (custom image)
    • resolutionWidth: number | undefined (custom image)
    • resolutionHeight: number | undefined (custom image)
  • add-token
    • token: string
  • transaction-created
    • action: 'sent' | 'copied' | 'downloaded' | 'replace'
    • symbol: string
    • tokens: string
    • outputsCount: number
    • broadcast: boolean
    • bitcoinRbf: boolean
    • bitcoinLockTime: boolean
    • ethereumData: boolean
    • rippleDestinationTag: boolean
    • ethereumNonce: boolean
    • selectedFee: string
  • menu/notifications/toggle
    • value: boolean
  • menu/settings/toggle
    • value: boolean
  • menu/settings/dropdown
    • option: 'all' | 'general' | 'device' | 'coins'
  • menu/goto/tor
  • accounts/empty-account/receive

Fixed:

  • device-update-firmware
    • toBtcOnly
  • accounts/empty-account/buy
    • symbol (lowercase instead of uppercase)

1.8

Added:

  • settings/device/update-auto-lock
    • value: string
  • suite-ready
    • browserName: string
    • browserVersion: string
    • osName: string
    • osVersion: string
    • windowWidth: number
    • windowHeight: number

Fixed:

  • suite-ready
    • suiteVersion
    • c_instance_id
    • c_session_id
  • device-update-firmware
    • fromFwVersion (changed separator to dots from commas)
    • fromBlVersion (changed separator to dots from commas)
  • analytics/dispose

Removed:

  • menu/goto/exchange-index

Changed:

  • desktop build is now tracked to stable.log instead of beta.log

1.7

Added:

  • send-raw-transaction
    • networkSymbol: string
  • device-connect
    • totalDevices: number

1.6

Added:

  • suite-ready
    • suiteVersion: string | ""
  • device-connect
    • isBitcoinOnly: boolean
  • desktop-init
    • desktopOSVersion: string | "" (in format: {platform}_{release})
  • accounts/empty-account/buy
    • symbol: string
  • account-create
    • tokensCount: number
  • transaction-created
    • action: 'sent' | 'copied' | 'downloaded'
    • symbol: string
    • broadcast: boolean
    • outputsCount: number
    • bitcoinRbf: boolean
    • bitcoinLockTime: boolean
    • ethereumData: boolean
    • tokenSent: boolean
  • add-token
    • networkSymbol: string
    • addedNth: number

1.5

Added:

  • suite-ready
    • theme (dark mode)
  • wallet/created
    • type: standard | hidden
  • device-disconnect

1.4

Added:

  • suite-ready
    • rememberedStandardWallets
    • rememberedHiddenWallets
  • analytics/enable
  • analytics/dispose
  • check-seed/error
  • check-seed/success

1.3

Added:

  • device-connect
    • backup_type
  • router/location-change
    • prevRouterUrl
    • nextRouterUrl

1.2

Added

  • suite-ready
    • tor

1.1

Added:

  • device-update-firmware:
    • toFwVersion
  • suite-ready
    • platformLanguage
    • platform
  • device-connect:
    • totalInstances

1.0

  • initial version

Desktop Logger

The desktop application includes a logging library to display various types of log in the console or in a file.

Four (or five if we count 'mute') log levels are currently implemented:

  • error (1)
  • warn (2)
  • info (3)
  • debug (4)

All messages with an inferior level to the selected one will be displayed. For example, if the selected log level is info, then it will also display warn and error messages.

How to enable logging

Logging can be enabled by running Suite with the command line flag --log-level=LEVEL (replace LEVEL with error, warn, info or debug based on the logging you wish to display). Additional command line flags can be found on the Suite-Desktop page.

API

Exported Types

LogLevel

Any of the following values:

  • mute (0)
  • error (1)
  • warn (2)
  • info (3)
  • debug (4)

Options (all optional)

nametypedefault valuedescription
colorsbooleantrueConsole output has colors
writeToConsolebooleantrueOutput is displayed in the console
writeToDiskbooleanfalseOutput is written to a file
outputFilestring'log-%ts.txt'file name for the output
outputPathstringHome or CWDpath for the output
logFormatstring'%dt - %lvl(%top): %msg'Output format of the log

String formatters

The options outputFile and logFormat can be used with some expressions, prefixed with the percent (%) symbol, to apply certain dynamic elements. It is for example possible to display a timestamp or the current date & time in the listed options above. While some expressions can be used in any strings, some strings have their own expressions.

Global

ExpressionExample outputDescription
%ts1611054460306Timestamp
%dt2021-01-19T11:08:22.244ZDate and time in ISO format (ISO 8601)

logFormat

ExpressionExample outputDescription
%lvlINFOLevel in letters and upper case
%topExampleTopic
%msgExample messageMessage

Constructor

The constructor has the following parameters:

  • level (LogLevel): Selected log level (see LogLevels in Exported Types above)
  • options (Options): Optional parameter containing settings for the logger (see Options in Exported Types above)

Log methods

All log methods have the same signature as they are just wrappers around a private logging method.

The following methods are available:

  • error(topic: string, messages: string | string[]); // level: 1
  • warn(topic: string, messages: string | string[]); // level: 2
  • info(topic: string, messages: string | string[]); // level: 3
  • debug(topic: string, messages: string | string[]); // level: 4

Parameters:

  • topic (string): Message topic
  • messages (string | string[]): Single message or array of messages which will be displayed one by line.

Exit method

The exit() method is used to close the write stream of the log file. If you are not planning to write logs to disk, you won't need to use this method. Otherwise it is highly advised to place this inside exit/crash callbacks.

Example

const logger = new Logger('warn', {
    colors: false, // Turning off colors
    logFormat: '%lvl: %msg', // Level and message only
    writeToDisk: true, // Write to disk
    outputFile: 'log-desktop.txt', // Static file name, will be overwritten if it exists
});

// These messages will be printed with the level provided above
logger.error('example', 'This is an example error message with the topic "example"');
logger.warn('example', 'This is an example warn message with the topic "example"');

// And these won't
logger.info('example', 'This is an example info message with the topic "example"');
logger.debug('example', 'This is an example debug message with the topic "example"');

// Closes the write stream (only needed of writing the log file)
logger.exit();

Q&A

How to format a string?

The library does not have any formatting capabilities as JavaScript already has templating features built-in. Simply use string literals (for example: logger.info('Topic', `My string ${myvar}.`)).

How to output an object?

The library does not include any helper for this as there is already a language feature that does this. Simply use JSON.stringify(myObject).

How can I write the log in JSON format?

You can change the logOutput option to be formatted like JSON. For example: logOutput: '{ "ts": "%ts", "lvl": "%lvl", "topic": "%top", "message": "%msg" },'. In order to display/use it properly, you will have to edit the output a little bit. Wrap all the messages in square brakets ([, ]) and remove the comma (,) from the last message.

Fiat rates

Suite provides several types of fiat rates:

  • Current fiat rate: Used on Dashboard, next to the account balance, for converting a crypto amount in send form to a fiat currency, etc...
  • Weekly rates: In addition to current rate we also fetch 7 days old rate and based on the difference we either show green or red arrow next to an exchange rate (can be seen in assets table on Dashboard).
  • Historical fiat rate: Exchange rate at the time of facilitating a transaction. Used in list of transactions to calculate daily deltas and in transaction detail modal. Not supported for ERC20 tokens.

Providers

  • Blockbook: For all main networks (BTC, LTC, ETH) except XRP and ERC20 tokens
  • Blockbook "live" updates: Apart from using Blockbook to manually fetch fiat rates, it also provides us with "live" fiat rate updates (basically push notifications via websocket) for all blockbook-supported coins (see above).
  • CoinGecko: Used for XRP, ERC20 tokens and as a fallback for failed requests to blockbook.

First fetch

Current fiat rates

For main networks: On app launch for enabled networks and then immediately after enabling new coin/network

Current fiat rates for ERC20 tokens

ERC20 tokens: On ACCOUNT.CREATE which is triggered during account discovery (if account were not remembered), on ACCOUNT.UPDATE when account.tokens has some new items.

Weekly fiat rates

Weekly rates are downloaded on app launch and on enabling new network.

Historical fiat rates (for transactions)

Historical rates for transactions: On TRANSACTION.ADD action, which means after a new transaction is added, stored within the tx object,

Update intervals

Current fiat rates

Every rate stored in wallet.fiat reducer is checked in 2-minute interval. If the rate is older then 10 minutes then it is refetched. Although this shouldn't really be necessary thanks to live updates from Blockbook, the same logic is also used for ERC20 tokens and XRP where we don't have a comfort of receiving these updates and we are relying on manual checks.

Current fiat rates for ERC20 tokens

List of tokens is part of the account object (account.tokens). Fiat rates for ERC20 tokens are fetched on ACCOUNT.CREATE (fired during account discovery) and ACCOUNT.UPDATE (new token can appear after receiving a token transaction). These actions are intercepted in fiatRatesMiddleware.

Weekly fiat rates

Check for deciding if a weekly rate for a coin needs to be updated runs every hour. Fetched rates are cached also for 1 hour. Eg. If user opens app and there are already weekly fiat rates, no older than 1 hour, stored in persistent storage then Suite won't fire new fetch. If, after one hour, fetch fails then next one will be fired after another hour.

Historical fiat rates (for transactions)

They are stored as part of the transaction. They don't need to be periodically updated as the exchange rate in the past cannot change anymore. If fetch fails for some reason we will retry on next BLOCKCHAIN.CONNECTED.

Usage

To make your life easier use FiatValue component.

Most straightforward usage is to just pass amount and symbol, if you need to work with tokens also add tokenAddress property:

 <FiatValue
    amount={amount}
    symbol={assetSymbol}
    tokenAddress={tokenTransfer?.address}
/>

For converting to fiat amount using rates from custom source use useCustomSource in combination with source property:

<FiatValue
    amount={targetAmount}
    symbol={transaction.symbol}
    source={transaction.rates}
    useCustomSource
/>

To support more complex use-cases we are leveraging render props. When passing function as a children it will get called with one parameter, object with value, rate and timestamp. This allows us to handle cases where fiat rates are missing (all fiels in the object are set null) or show not only fiat amount, but also used exchange rate.

<FiatValue amount="1" symbol={symbol}>
    {({ _value, rate, timestamp }) =>
        rate && timestamp ? (
            // we got rates!
            // show the exchange rate and provide information about last update in tooltip
            <Tooltip
                content={
                    <LastUpdate>
                        <Translation
                            id="TR_LAST_UPDATE"
                            values={{
                                value: (
                                    <FormattedRelativeTime
                                        value={rateAge(timestamp) * 60}
                                        numeric="auto"
                                        updateIntervalInSeconds={10}
                                    />
                                ),
                            }}
                        />
                    </LastUpdate>
                }
            >
                <FiatRateWrapper>
                    {rate}
                </FiatRateWrapper>
            </Tooltip>
        ) : (
            // no rates available!
            <NoRatesTooltip />
        )
    }
</FiatValue>

There are other handy props like showApproximationIndicator (self explanatory) and disableHiddenPlaceholder which disables blurred overlay used when discreet-mode is activated.

Feature Flags

Feature flags allow us to enable and disable certain features at build time, helping us to produce specific builds for specific environments.

Work flow

All feature flags are located in packages/suite/config/features.ts. To add a new flag, start by doing the following:

  1. Add your flag to the FLAGS constant and set its defautl value. When naming your flag, bear in mind the following conventions:
    1. Always explain what the flag is about using a comment next to it.
    2. The name of the flag should always be in capitals.
    3. The name of the flag should never contain the world enable or disable because the name should always towards an enabled state. Its value should reflect whether the feature is enabled or not.
    4. The name of the flag should never contain the word flag because it's inferred.
  2. (optional) You can override the flag for each environment (web, desktop, landing) using their specific constants.
  3. Use the isEnabled function from @suite-utils/features to check if the flag is enabled or not.
  4. Wrap the code you wish to control in a condition checking for your flag being enabled or not.

Example

import { isEnabled } from '@suite-utils/features';

const main = () => {
    alwaysRunning();

    if (isEnabled('LABELING')) {
        myLabelingFeature();
    }
};

Future evolutions

  • Control feature flags at runtime.

Localization

Suite uses react-intl package for all in-app localization needs. Definitions of all messages are stored in messages.ts.

To allow non-developers to edit these messages through user-friendly interface, we upload them to Crowdin via trezor-translations-manager (TTM) which takes care of conversion to CSV file format before the upload. Be aware that TTM is rather legacy project and most likely could be replaced with formatjs cli.

After strings have been translated we use TTM again to download the messages and generate language json files. They are automatically copied to suite-data package. To finish the process these files need to be commited to the repository.

Message definitions

messages.ts is the place where you add new messages to be used in Suite. It's basically just a huge object where a key is an ID of the message and a value is the message definition.

Do not manually edit language json files in suite-data/files/translations/ directory. These are auto-generated, changing them directly is plausible only for development purposes.

Structure

  • id: We don't have strict conventions for generating these IDs, although using a prefix TR_, or expanded variant TR_<SCOPE>, 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.

Example:

{
  ...
  TR_ADDRESS: {
      id: 'TR_ADDRESS',
      defaultMessage: 'Address',
      description: 'Used as label for receive/send address input',
  },
  ...
}

Usage in Suite

To render a message use our wrapper for react-intl's FormattedMessage, Translation. It will always return JSX.Element. If, for some reason, you need to render the message as a string (for example for passing it as a placeholder prop to an input) use useTranslation hook.

Translation accepts the same parameters as FormattedMessage and adds a little bit of magic.

Most straightforward usage is to just pass message's ID to id prop:

<Translation id="TR_CONTINUE" />

or for string variant using hook:

const { translationString } = useTranslation();
translationString('TR_CONTINUE');

There are cases where you need to pass a variable which needs to be part of the translated message:

<Translation id="TR_ENTERED_PIN_NOT_CORRECT" values={{ deviceLabel: device.label }} />

or for string variant using hook:

const { translationString } = useTranslation();
translationString('TR_ENTERED_PIN_NOT_CORRECT', {deviceLabel: device.label});

Definition for TR_ENTERED_PIN_NOT_CORRECT:

TR_ENTERED_PIN_NOT_CORRECT: {
    defaultMessage: 'Entered PIN for "{deviceLabel}" is not correct',
    id: 'TR_ENTERED_PIN_NOT_CORRECT',
}

Sometimes you need to provide a translator the ability to emphasize some words in a sentence AKA rich text formatting. In this example a text enclosed in <strong> will be wrapped in StrongStyling component.

 <Translation
    id="TR_TRANSACTIONS_SEARCH_TIP_2"
    values={{
        strong: chunks => <StrongStyling>{chunks}</StrongStyling>, // search string is wrapped in strong tag for additional styling
    }}
/>

Definition for TR_TRANSACTIONS_SEARCH_TIP_2:

TR_TRANSACTIONS_SEARCH_TIP_2: {
  id: 'TR_TRANSACTIONS_SEARCH_TIP_2',
  defaultMessage:
      'Tip: You can use the greater than (>) and lesser than (<) symbols on amount searches. For example <strong>> 1<strong> will show all transactions that have an amount of 1 or higher.',
},

For even more shenanigans (like handling plural form) check this great overview on ICU Message syntax.

Translation mode

Section shamelessly stolen from Crowdin contributions.

There's a hidden feature in Suite, intended for translators, called Translation mode that redirects you into Crowdin upon clicking any particular string. This is immensely handy in comparison to blindly translating strings within Crowdin as it allows you to understand the context of a certain string before being taken to Crowdin to translate it.

  1. Go to Settings in Suite
  2. Rapidly click on the "Settings" heading 5 times
  3. Click the three dot context menu on the right
  4. "Debug Settings" should've appeared. Click it. If "Debug Settings" hasn't appeared, repeat step 2.
  5. Enable "Translation mode"

After enabling it each string, which is rendered via Translation component, is now underlined with red and shows a popup with the message's ID when you hover the mouse over it.

To join the ranks of translators follow Crowdin contributions guide.

Synchronization with Crowdin

All work is done via trezor-translations-manager with time-saving shortcuts defined in package.json scripts section. In order to work with Suite project in Crowdin you need to set environment variables:

As of now TTM works with Crowdin API v1. For more insights on TTM I recommend checking its superb documentation.

To upload message definitions from Suite to Crowdin run:

yarn workspace @trezor/suite translations:upload

You can even do that from your branch with messages that are not yet merged in develop branch, just be sure you have rebased your branch on latest develop before doing so. This process replaces all definitions in Crowdin, meaning if your branch is missing some definitions, that are already in develop branch and uploaded in Crowdin, they will be removed.

To download new translations from Crowdin:

yarn workspace @trezor/suite translations:download

and then open a PR with updated language files.

Workflow for regular Crowdin Synchronization

BRANCH_NAME=feat/crowdin-sync

git checkout develop
git pull
git checkout -b $BRANCH_NAME

# Upload first to sync the key set.
yarn workspace @trezor/suite translations:upload
# Download second to fetch values for all keys.
yarn workspace @trezor/suite translations:download

git add packages/suite-data/files/translations
git commit -m 'feat(translations): Sync with Crowdin'
git push origin $BRANCH_NAME

Plus creating, reviewing and merging the PR.

Review Process

Same as for Trezor Firmware. Please see here.

Weird stuff, notes and issues

Things to do in future

Bridge in electron

  • package.json > "build": { "asar": false } required
  • How to get bridge version?
  • Test windows

Debugging Electron build

  • macOS: Run /path/to/app/TrezorSuite.app/Contents/MacOS/TrezorSuite --debug
  • Decompile: npx asar extract packages/suite-desktop/build-electron/mac/TrezorSuite.app/Contents/Resources/app.asar ./decompiled

Remove IndexedDB from desktop

To remove a database remove following folder:

Windows

C:\Users\<user>\AppData\Roaming\@trezor\suite-desktop\IndexedDB

Linux

/home/<user>/.config/@trezor/suite-desktop/IndexedDB

macOS

/Users/<user>/Library/Application Support/@trezor/suite-desktop/IndexedDB

Clearing Electron cache

To clear electron cache delete following folder:

Windows

C:\Users\<user>\AppData\Roaming\@trezor\suite-desktop\Cache

Linux

/home/<user>/.config/@trezor/suite-desktop/Cache

macOS

/Users/<user>/Library/ApplicationSupport/@trezor/suite-desktop/Cache

Tests

React-native tsconfig regex:

copy block from top level tsconfig.json find: ./packages/suite/src/(.*)" replace: ./src/$1", "../../packages/suite/src/$1"

Debugging suite-web on android

Server needs to be running on https in order to have access to navigator.usb functionality

  • Generate localhost certificate: yarn workspace @trezor/suite-web cert

  • Run https server: yarn workspace @trezor/suite-web dev:https

  • Find your ip: ifconfig | grep "inet "

  • Connect phone (dev mode) to computer

  • Access suite using IP (it needs to be in the same network as your computer)

  • Open debugger: chrome://inspect/#devices

How to release - staging

git checkout releases

yarn

Suite web release

  1. assetPrefix=/wallet yarn workspace @trezor/suite-web build
  2. cd packages/suite-web
  3. cd build/static
  4. mkdir desktop
  5. copy desktop apps into the folder (Trezor Suite.(zip, AppImage, exe)).

Image of Yaktocat

  1. ./scripts/s3sync.sh stage beta (from the packages/suite-web folder )

Upload source maps to Sentry

  1. sentry-cli releases -o satoshilabs -p trezor-suite files <COMMIT> upload-sourcemaps ./build

Message System

Message system was implemented to allow sending emergency messages to Trezor Suite app to a user with specific stack.

Example messages

Issue on Github

Types of in-app messages

There are four ways of displaying message to a user.

  • banner
    • looks like a cookie bar above the page
  • modal
    • TODO: missing implementation
  • context
    • messages on specific places in app - high level (e.g. settings page)
    • TODO: missing implementation
  • super-context
    • messages on specific places in the app - low level (e.g. category in settings page)
    • TODO: missing implementation

Implementation

Config

The system of messages is based on a configuration file in which messages with specific conditions ​are described. If specific conditions are satisfied, the message is shown to a user.

Current configuration file is located in packages/suite-data/src/message-system/config folder. Its name is config.vX.json. The X express current version messaging system.

The config is fetched at launch of the application and then every 6 hours. It remembers the previously fetched config to inform the user even if he is offline. For this reason, the latest available config during build time is bundled with the application.

If fetching of a new config fails, the fetching process is repeated every 1 hour.

Schema

The configuration structure is specified in JSON file using JSON schema. The file can be found in packages/suite-data/src/message-system/schema folder. Its name is config.schema.vX.json.

We use JSON schema for 2 reasons:

  • generating TypeScript types
  • validating configuration file

Types

Types are generated from JSON-schema during the build:libs process or can be generated manually by yarn workspace @trezor/suite-data msg-system-types. A messageSystem.ts file is created in packages/suite/src/types/suite folder.

  • This file should never be changed manually.
  • This file is committed into the repository.

Signing

To ensure the authenticity of a configuration file, JSON Web Signatures are used. The configuration file is signed by a private key using elliptic curves (ES256) and the data specified in the config are used. The authenticity is verified on client side using corresponding public key.

CI job

Validation

  • Validation of configuration file is performed in CI job in validation phase. It is used to detect possible structure and semantic errors.
  • It can be run locally by yarn workspace @trezor/suite-data msg-system-validate-config script in suite-data.

Signing

  • Signing of the configuration file is performed in CI job in prebuild phase.
  • The result is saved into suite-data/files/message-system to be bundled with application and manually uploaded to https://data.trezor.io/config/$environment/config.vX.json (TODO Finish automatic upload of configs to S3). For example, on localhost, the config is available at http://localhost:8000/static/message-system/config.vX.jws.
  • It can be run manually by yarn workspace @trezor/suite-data msg-system-sign-config script in suite-data. The resulting JWS is stored in packages/suite-data/files/message-system/ folder in config.vX.jws file.
  • Development public and private keys are baked into project structure. For production environment, these keys are replaced by CI job by production keys. This production CI job is activated on codesign branches.
  • Development private key can be found in suite-data/src/message-system/scripts/sign-config.ts file, the public key can be found in suite-web and suite-desktop in next.config.js files (web, desktop). Change when feat/tschuss-next PR is merged

Versioning of implementation

If changes made to the message system are incompatible with the previous version, the version number should be bumped in messageSystemConstants.ts file in suite package as well as in suite-data package in message-system/constants file. Also in ci/packages/suite-data.yml file.

Config Structure

Structure of config, types and optionality of specific keys can be found in the schema or in generated types. Example config is commented below.

{
    // Version of message system implementation. Bump if new version is not backward compatible.
    "version": 1, 
    // Datetime in ISO8601 when was config created.
    "timestamp": "2021-03-03T03:48:16+00:00",
    // Version of config. New config is accepted only if sequence number is higher.
    "sequence": 1, 
    "actions": [
        {
            /*
            - User's stack has to match one of the condition objects to show this message.
            - The bitwise operation is OR.
            */
            "conditions": [ 
                /* 
                - All keys are optional (duration, os, environment, browser, settings, transport, 
                  devices, architecture (To be implemented))
                - If a value is specified then all its subkeys have to be specified 
                - The bitwise operation is AND.
                */
                {
                    /* 
                    - Datetime in ISO8601 from / to which date this message is valid.
                    - If duration category is used, then both times have to be set.
                    */
                    "duration": {
                        "from": "2021-03-01T12:10:00.000Z",
                        "to": "2022-01-31T12:10:00.000Z"
                    },
                    /* 
                    For os, environment, browser and transport.
                    - Values can be array, string or null.
                    - Semver npm library is used for working with versions https://www.npmjs.com/package/semver.
                    - "*" = all versions; "!" = not for this type of browser/os/...
                    - Options: gte, lt, ranges, tildes, carets,... are supported, see semver lib for more info.
                    */
                    "os": {
                        "macos": [
                            "10.14",
                            "10.18",
                            "11"
                        ],
                        "linux": "*",
                        "windows": "!",
                        "android": "*",
                        "ios": "13"
                    },
                    "environment": {
                        "desktop": "<21.5",
                        "mobile": "!",
                        "web": "<22"
                    },
                    "browser": {
                        "firefox": [
                            "82",
                            "83"
                        ],
                        "chrome": "*",
                        "chromium": "!"
                    },
                    "transport": {
                        "bridge": [
                            "2.0.30",
                            "2.0.27"
                        ],
                        "webusbplugin": "*"
                    },
                    /*
                    - If key is not available (undefined), then it can be whatever.
                    - Currently supported keys are "tor" and coin symbols from "enabledNetworks".
                    - The bitwise operation is OR.
                    */
                    "settings": [
                        { 
                            "tor": true,
                            "btc": true
                        },
                        {
                            "tor": false,
                            "ltc": true
                        }
                    ],
                    // Empty device array is targeting users without a connected device.
                    "devices": [ 
                        {
                            // Possible values: "1" or "T"
                            "model": "1", 
                            "firmware": "1.9.4",
                            "vendor": "trezor.io"
                        }
                    ]
                }
            ],
            "message": {
                // Used for remembering dismissed messages.
                "id": "0f3ec64d-c3e4-4787-8106-162f3ac14c34",
                /*
                - Existing banners have defined priorities. 
                - The range is 0 to 100.
                */
                "priority": 100,
                // When user closes the message, it will never show again.
                "dismissible": true,
                /* 
                Variants:
                - info (blue)
                - warning (orange)
                - critical (red)
                */
                "variant": "warning",
                // Options: banner, modal, context, super-context
                "category": "banner",
                /*
                - Message shown to a user. 
                - Current implementation uses only "en-GB".
                */
                "content": {
                    "en-GB": "New Trezor firmware is available!",
                    "de-DE": "Neue Trezor Firmware ist verfügbar!"
                },
                // Call to action. Used only for banner and context.
                "cta": {
                    /*
                    Options: "internal-link" or "external-link"
                    - internal-link is route name, see routes.ts file
                    - external-link is url address
                    */
                    "action": "internal-link",
                    // Route name or url address according to action.
                    "link": "firmware-index",
                    /*
                    - Label of call to action button shown to a user. 
                    - Current implementation uses only "en-GB".
                    */
                    "label": {
                        "en-GB": "Update now",
                        "de-DE": "Jetzt aktualisieren"
                    }
                },
                // Used only for modals. (To be implemented)
                "modal": {
                    "title": {
                        "en-GB": "Update now",
                        "de-DE": "Jetzt aktualisieren"
                    },
                    "image": "https://example.com/example.png"
                },
                // Used only for context and super-context. (To be implemented)
                "context": {
                    "domain": [
                        "coins.*.receive",
                        "coins.btc"
                  ]
                }
            }
        }
    ]
}

How to update

When updating message system config, sequence number must always be higher than the previous one. Once released config cannot be rolled back to the previous one with lower sequence number. A new one with higher sequence number has to be created.

Information about updated config has to be sent to @tsusanka, who will manually upload it to S3 bucket. TODO Updated config will be automatically uploaded by CI job to the correspondent S3 bucket based on the current branch.

Priorities of messages

Based on the priority of the message, the message is displayed to the user. 0 is the lowest priority, 100 is the highest priority. Current priorities of existing banners can be found here.

Targeting Linux version

Unfortunately, it is not possible to target specific distributions and versions of Linux. It is possible to only target all Linux users using * or exclude all Linux users using !.

Application steps

  1. Config is fetched on load of application and is stored in Redux state. To be persisted between sessions, is is mirrored into IndexDB.
  2. Conditions of config are evaluated on specific Redux actions. See messageSystemMiddleware.ts file.
  3. If conditions of message satisfies user's stack, the message is accordingly propagated. If it is dismissible, its id is saved to Redux state (IndexDB) on close, to avoid displaying next time.

Followup

Ideas and non-critical bugs can be added to the followup issue.

Suite Guide

Guide is a feature that allows us to write content on various topics like basics of deterministic wallets, cryptocurrencies, Suite specifics or generally anything Suite user might be interested in and then provide it to the user directly in the app.

See the tech spec for details and rationale behind the implementation.

Content

The content is maintained in a GitBook project. (You'll need an account with appropriate privileges to access it.) GitBook mirrors the content to a GitHub repository from where it's fetched when Suite builds. The fetch happens in the suite-data package on build:lib yarn task. Once fetched the content is indexed and transformed to form usable in the Suite app. This form is then copied into the static directories of web, desktop and native builds.

Indexing, transforming and copying of the content is handled by scripts in packages/suite-data/src/guide. These are configured to fetch a particular commit of the GitBook mirror by the GB_REVISION constant in packages/suite-data/src/guide/constants.ts. This constant must/can be changed accordingly to propagate changes from GitBook to suite builds. (One could set it to the master branch to always use the latest content version, however that is discouraged as we rather want to precisely control which version of the content will get included in each release.)