Trezor Suite documentation
This documentation provides technical information for Suite developers, third-party wallet developers integrating Trezor, and users interested in implementation details. It is available at docs.trezor.io/trezor-suite in an HTML version compiled using mdBook. More guidance on Suite development can be found in package READMEs of the trezor-suite monorepo.
Data Analytics
Anonymous1 data volunteered by Trezor users directly contributes to improved performance across all the platforms you use Trezor Suite on.
"Anonymous" means that we do not collect any sensitive personal information. AWS and Sentry are able to view IP addresses but they are not tracked or collected by Trezor and Trezor removes such information from the logs automatically. Enable Tor to mask your IP address from third parties when using Trezor Suite.
Participation is easy and completely optional. Enable or disable usage data sharing with one click at any time in Trezor Suite Settings. With full control over what you contribute, you can safely take part in making Bitcoin more secure.
TL;DR
- Data is only collected with explicit permission.
- Your sensitive data is not collected.
- We use AWS logging for data analytics and Sentry for error tracking.
- We store the data concerning errors for the period of 90 days.
- Only limited amount of users is able to access the data.
What data is collected?
When enabled, purely functional data about how the app is used will be collected and analyzed to find defects and inefficiencies. With explicit consent, both web and desktop applications may collect anonymous data such as user interactions with app functions, errors, hardware specifications and app response times.
If usage data is disabled only the decision not to share any data is recorded. This means we do not collect any data, automated Sentry reports (see below) or any other data. An exception is when a user specifically chooses to submit feedback or bug reports through Trezor Suite.
How are the data processed?
Data are logged in the form of HTTPS requests to an AWS S3 bucket. Those data logs are then transformed into sets which can be analyzed to give meaningful information. See AWS for more detailed info about the particular events which are tracked.
Error tracking using Sentry
To catch errors quickly and deliver you the best experience with your Trezor, we use Sentry.io, a tool for error tracking and performance monitoring. Data is only available to Sentry when usage data tracking is enabled. See our page about Sentry for more information on how it works.
Retention period
By principle, the logs collected are destroyed without delay once the purpose of use is met. However, the minimum retention period equals to 90 days when the data is processed to improve Trezor Suite. The 90 days are related to the data concerning any errors occurring in Trezor Suite. Performance related data may be stored for longer periods of time. When the retention period ends, all event data and most metadata is eradicated from the storage and from the servers without additional archiving in order to prevent the threat of intrusion.
Security of data
Access to the data is limited strictly to the members of the development, security and IT team. All users are provided access on the need-to-know basis and the accesses are regularly reviewed. Users accessing the data log in using a strong combination of username and password and use two-factor authentication (where provided by a service provider).
Contents
AWS Analytics: Info
For a deeper technical writeup of analytics processes intended for developers, please see README.md.
Trezor Suite can be set to collect real-world data to improve the performance of both web and desktop apps. This anonymous data is only shared by users who have usage data tracking enabled.
During the first run Trezor Suite prompts the user whether they wish to participate in the data collection and such setting can be changed later on in Settings at any time.
Anonymous data
Collected data are anonymous. This means that Suite does not track personal information and can not be used to view particular users' balances.
Among the data not collected by analytics:
- Device IDs
- Public keys
- Particular amounts
- Transaction IDs
When data tracking is enabled, Trezor Suite collects functional information that can be used to directly improve the app, such as:
- Events triggered by a user during a session
- Hardware, operating system and setup of the connected device
- Errors encountered during a session
Data process
- User with enabled analytics interacts with the application
- Events are sent to specific endpoints
- Collected data are parsed and analysed (can be seen in Keboola)
- Charts and metrics are created (in Tableau)
- We know how to improve the application
Sentry
Trezor Suite uses Sentry.io to track errors and monitor app performance on the machines of users who have enabled anonymous data collection. This allows us to optimize Trezor Suite and fix compatibility issues across many different usage environments.
No data is shared with Sentry if users have disabled usage data tracking 1.
In rare cases where an error would occur before Suite loading its internal storage an error to Sentry might be sent, because Suite is not yet informed whether analytics are enabled or not. As the storage is not yet loaded no private data can be sent.
What is being tracked
General data
Browser (User Agent), System and HW specifications, Suite version, instance id shared with analytics.
Breadcrumbs
timestamps, clicks, navigation, analytics, network requests from Suite.
Extra data:
- Enabled-coins e.g.:
[btc, ltc, eth, xrp, doge]
- Wallet discovery e.g.:
[
{
authConfirm:False,
bundleSize: 0,
deviceState: [redacted],
failed: [],
index: 0,
loaded: 14,
networks: [btc, btc, btc, ltc, ltc, ltc, eth, xrp, doge],
running: [undefined],
status: 4,
total: 14,
},
]
- Device information (slightly redacted):
{
authConfirm: False,
available: False,
buttonRequests: [],
connected: False,
features:
{
auto_lock_delay_ms: 600000,
backup_type: Bip39,
bootloader_hash: None,
bootloader_mode: None,
capabilities:
[
Capability_Bitcoin,
Capability_Bitcoin_like,
Capability_Binance,
Capability_Cardano,
Capability_Crypto,
Capability_EOS,
Capability_Ethereum,
Capability_Monero,
Capability_NEM,
Capability_Ripple,
Capability_Stellar,
Capability_Tezos,
Capability_U2F,
Capability_Shamir,
Capability_ShamirGroups,
[Filtered],
],
device_id: [redacted],
display_rotation: 0,
experimental_features: False,
firmware_present: None,
flags: 0,
fw_major: None,
fw_minor: None,
fw_patch: None,
fw_vendor: None,
fw_vendor_keys: None,
imported: None,
initialized: True,
label: [redacted],
language: en-US,
major_version: 2,
minor_version: 4,
model: T,
backup_availability: 0,
no_backup: False,
passphrase_always_on_device: False,
passphrase_protection: True,
patch_version: 2,
pin_protection: True,
recovery_status: 0,
revision: 9276b1702361f70e094286e2f89e919d8a230d5c,
safety_checks: Strict,
sd_card_present: False,
sd_protection: False,
session_id: [redacted],
unfinished_backup: False,
unlocked: True,
vendor: trezor.io,
wipe_code_protection: False,
},
firmware: valid,
firmwareRelease:
{
changelog: [],
isNewer: False,
isRequired: None,
release: {},
},
id: [redacted],
instance: 1,
label: [redacted],
metadata: { status: disabled },
mode: normal,
passphraseOnDevice: False,
path,
remember: True,
state: [redacted],
status: used,
ts: 1632094494156,
type: acquired,
unavailableCapabilities: {},
useEmptyPassphrase: False,
walletNumber: 1,
}
- Action logs:
[
{ time: 1634644852099, type: @suite/online-status },
{ action: {}, time: 1634644852104, type: @suite/init },
{ time: 1634644852966, type: @message-system/save-valid-messages },
{ time: 1634644852967, type: @suite/tor-status },
{ time: 1634644853131, type: @resize/update-window-size },
{ time: 1634644853306, type: @desktop-update/enable },
{ time: 1634644853361, type: @desktop-update/checking },
{ time: 1634644853449, type: @message-system/save-valid-messages },
{ action: {}, time: 1634644853453, type: @suite/set-language },
{ time: 1634644853455, type: @storage/loaded },
{ time: 1634644853717, type: @message-system/fetch-config-success },
{ time: 1634644853744, type: @analytics/init },
{ time: 1634644854072, type: @desktop-update/not-available },
{ time: 1634644854166, type: iframe-loaded },
{ time: 1634644854168, type: @suite/trezor-connect-initialized },
{ time: 1634644854187, type: @blockchain/update-fee },
{ action: {}, time: 1634644854188, type: @suite/app-changed },
{ time: 1634644854189, type: @router/location-change },
{ time: 1634644854191, type: @suite/ready },
{ time: 1634644854192, type: @wallet-settings/clear-tor-blockbook-urls },
{ time: 1634644854192, type: @blockchain/ready },
]
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 binaries during the Freeze so QA has the whole thing to test.
Binaries and release definitions are stored in packages/connect-common/files/firmware/*
in the same folder structure as they are in https://github.com/trezor/webwallet-data deployed on https://data.trezor.io.
Latest and intermediary firmware binaries are bundled in suite-desktop
and suite-web
so the whole onboarding process in desktop app – including FW installation – can be completed offline. It means that only the latest and intermediary Firmware binaries are currently available in Suite (both web and desktop). There is no fallback on https://data.trezor.io.
Package @trezor/connect-common
is a public NPM package used as dependency of @trezor/connect
.
Add firmwares
-
Complete the firmware release process including firmware signing.
-
Add firmwares to
packages/connect-common/files/firmware/*
and modify itsreleases.json
file. See Firmwarereleases.json
files structure for an explanation and 90bb548 for an example. -
Remove older binaries so they are not bundled in the desktop app any more, but always keep:
- the intermediary FW for T1B1 packages/connect-common/files/firmware/t1b1/trezor-inter-v{1 | 2 | 3}.bin
- and 2.1.1 for T2T1 packages/connect-common/files/firmware/t2t1/trezor-2.1.1.bin
See #4262 for explanation.
-
Test it locally (at least by running
yarn build:libs
to rebuild connect files andyarn suite:dev
to use/copy them). -
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.
Release for 3rd party users of @trezor/connect
After Suite is released, distribute new firmware by releasing new @trezor/connect
with an updated @trezor/connect-common
package.
Follow instructions on how to release a new version of @trezor/connect
.
Firmware releases.json
files structure
Firmware releases.json
files provide data about all available firmware versions and they are used to offer the correct firmware version for the user to update depending on the current version of firmware, bootloader and bridge. See the table below for a description of every param.
Those releases.json
files are bundled inside @trezor/connect
in /static/connect/data
folder. Therefore, suite-web
takes if from https://suite.trezor.io/web/static/connect/data/firmware/{deviceModel}/releases.json?r={timestamp to prevent caching}
and suite-desktop
has it on file:///static/connect/data/firmware/{deviceModel}/releases.json
. Neither the suite-web
nor the suite-desktop
take it from https://data.trezor.io.
key | type | example value | description |
---|---|---|---|
required | boolean | false | If true , user will be forced to update older FW in order to continue using Suite. |
version | [number, number, number] | [1, 11, 1] | Firmware version. Has to be unique. |
bootloader_version | [number, number, number] | [1, 11, 0] | Bootloader version. If you are adding new firmwares, ask & verify if there is new BL included (by running it on the device and checking the version shown) |
min_firmware_version | [number, number, number] | [1, 6, 2] | Minimal supported FW version. See getInfo for the usage. |
min_bootloader_version | [number, number, number] | [1, 5, 0] | Minimal supported bootloader version. See getInfo for the usage. |
url | string | firmware/t1b1/trezor-t1b1-1.11.1".bin" | Where to find the binary. Depends on the filename. While adding new FW, keep the structure, just update the version number. suite-web downloads binaries from https://data.trezor.io, suite-desktop has them bundled. |
url_bitcoinonly | string | firmware/t1b1/trezor-t1b1-1.11.1-bitcoinonly.bin" | Same as url , just for Bitcoin only FW. |
fingerprint | string | "f7c60d0b8c2853afd576867c6562aba5ea52bdc2ce34d0dbb8751f52867c3665" | Fingerprint of FW binary. Run trezorctl firmware-update -f {path-to-the-bin} to retrieve it (you don't have to confirm the update on device unless you want to). Look for Firmware fingerprint: row. |
fingerprint_bitcoinonly | string | "8e17b95b5d302f203de3a8fe27959efd25e3d5140ac9b5e60412f1b3f624995d" | Same as fingerprint , just for Bitcoin only FW. |
notes | string | https://blog.trezor.io/trezor-suite-and-firmware-updates-may-2022-b1af60742291" | Link to blog with info about the changes in this FW version. You could find it on internal Notion page for the release even before it's published. |
changelog | string | "* Remove Lisk.\n* Re-enabled Firo support." | Short description of main changes, displayed to the user on FW update page. Split lines by * sign. You can find it on internal Notion page for the release. |
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-core/build/app-key.asc
and should be updated if the private key is changed.
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.
route | source | assetPrefix |
---|---|---|
/web | @trezor/suite-web | /web |
Publishing @trezor package to npm registry
yarn npm publish
should be done only on gitlab CI in deploy npm
phase.
Purpose
@trezor packages are dependencies of @trezor/connect
public API.
Publish is required to distribute changes to @trezor/connect
and make them available for 3rd party implementations.
Prerequisites
- Update
CHANGELOG.md
and list all changes since the last release of the package. - Bump the version in
packages/<PACKAGE-NAME>/package.json
. Use the semver convention.
Production
- Create new branch with
npm-release/
prefix. - Commit your changes as
release: @trezor/<PACKAGE-NAME> X.X.X
. - Use
<PACKAGE-NAME> deploy npm
job.
Beta
If you want to publish to npm as beta
(from any branch) do the following:
- Change the version in
packages/<PACKAGE-NAME>/package.json
fromX.X.X
toX.X.(X + 1)-beta.1
. The-beta.<n>
suffix is important because NPM registry doesn't allow overriding already published versions. With this suffix we can publish multiple beta versions for a single patch. - Commit your changes as
release: @trezor/<PACKAGE-NAME> X.X.X-beta.X
. - Use
beta <PACKAGE-NAME> deploy npm
job.
Signing binaries win
The desktop build of Trezor Suite uses electron-builder for signing the package and the binaries inside.
In order to be able to sign all the binaries for windows in other operating systems [electron-builder] uses osslsigncode.
Check if binaries are signed for windows in Linux
The installer .exe
can be unpacked with 7za x Trezor-Suite-22.2.1-win-x64.exe
on Linux. The chktrust
is from mono-develop package (Ubuntu LTS, other distros will have it under similar name).
7za x Trezor-Suite-22.2.1-win-x64.exe
After unpacked, test signatures:
for I in **/*.exe **/*.dll; do echo "---Checking $I"---; chktrust "$I"; done
CI signing details for windows
Certificate file is with extension: .pfx
Env variables for signing: WIN_CSC_KEY_PASSWORD
WIN_CSC_LINK
.
Creating Self-signed pfx and cer certificates with OpenSSL
Generate directly the pem:
openssl req -x509 -days 365 -newkey rsa:2048 -keyout cert.pem -out cert.pem
The pem cannot be used with Microsoft products, so we need to convert it to PKCS#12/PFX Format which is what Microsoft uses.
openssl pkcs12 -export -in cert.pem -inkey cert.pem -out cert.pfx
Versioning
This repo contains a mix of packages with 3 different versioning schemes and schedules.
Private Packages
That is, all packages that have private: true
in their package.json
and are not consumed by third parties nor published to NPM. Because they get only consumed by other packages in this repo (eg. @trezor/suite-data
or @trezor/suite
) by the Yarn's workspace resolution or are distributed in other forms like, for example, bundled applications (eg. @trezor/suite-desktop
) we do not version them. That is, their version is kept at 1.0.0
all the time.
Public Packages
That is, packages published to NPM consumed by third parties. At the moment of writing this there is one public package: blockchain-link
. They follow the SemVer scheme on irregular schedule.
Suite App
The version of the Suite App itself is tracked in the suiteVersion
field of the suite package.json
. This version is a way to communicate the steps in evolution of the Suite app between our product, marketing, support teams and the users.
We are using so-called calendar versioning in the format YY.MM.PATCH
where
YY
stands for the current year.MM
stands for the current month.PATCH
is increased on every release in the given month.
For example:
20.10.1
first release in Oct 202020.10.3
third release in Oct 202019.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.
For example:
20.10.1
first release on Oct 15th to stable20.10.2
second release on Oct 22nd to stable20.11.0
release on Oct 29th 2020 to beta20.11.0
another release on Nov 5th to beta20.11.1
public release on Nov 14th to stable
Development versions
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
.
Packages
This directory contains description of various Trezor Suite packages.
@trezor/connect @trezor/suite @trezor/suite-desktop creating new package
Trezor javascript SDK
There are couple of options how to integrate Trezor devices with your project. This page walks you through installation and lets you explore SDK API.
Chose your SDK
Depending on your environment you need to chose the right package.
package | environment |
---|---|
@trezor/connect | node.js |
@trezor/connect-web | web based (DOM required) |
@trezor/connect-webextension | webextension using service worker |
If you are still unsure which package is the right one you may refer to the following table with a collection of examples.
env example | package |
---|---|
node | @trezor/connect |
web app | @trezor/connect-web |
web extension mv2 (foreground or background) | @trezor/connect-web |
web extension mv3 (foreground) | @trezor/connect-web |
web extension mv3 (background) | @trezor/connect-webextension |
electron in main layer | @trezor/connect |
electron in renderer layer with popup | @trezor/connect-web |
Trezor Suite (desktop) electron app | @trezor/connect |
Quick start
Import from your selected package
// in node
import TrezorConnect from '@trezor/connect';
// or in web based
import TrezorConnect from '@trezor/connect-web';
// or in webextension service worker
import TrezorConnect from '@trezor/connect-webextension';
Initialize in project
TrezorConnect.init({
lazyLoad: true, // this param will prevent iframe injection until TrezorConnect.method will be called
manifest: {
email: '[email protected]',
appUrl: 'http://your.application.com',
},
});
Trezor Connect Manifest requires that you, as a Trezor Connect integrator, share your e-mail and application url with us.
This provides us with the ability to reach you in case of any required maintenance.
This subscription is mandatory. Trezor Connect raises an error that reads "Manifest not set" if manifest is not provided. It can be either set via manifest
method or passed as a param in init
method.
TrezorConnect.manifest({
email: '[email protected]',
appUrl: 'http://your.application.com',
});
If you need more customization, refer to init method documentation
API methods
Handling events
How it works under the hood
There is a major difference between node.js based package (@trezor/connect
) and web based packages (@trezor/connect-web
and @trezor/connect-webextension
).
In the former the entire SDK logic is a module of the 3rd party application whereas in the latter, there is strict isolation between 3rd party application code and SDK core logic.
Node.js
In node.js core SDK is loaded as a javascript module without any specificities.
![connect schema when used in node](./schema-connect.jpg =500x338)
Web
@trezor/connect-web
imports only a thin layer with API description into your 3rd party application. When initiated, it injects iframe containing core SDK logic from trezor.io
into your app. User input, if needed, is served by popup.html page opened on trezor.io on behalf of your application. This way users input such as pin or passphrase is isolated from you and persistent connection between your app and core SDK is kept so events such as device connected/disconnected or blockchain subscriptions are available.
![connect schema when used on web](./schema-connect-web.jpg =950x379)
Webextension
In case of @trezor/connect-webextension
, TrezorConnect object is created in a service worker. In this env we can't inject iframe so in order to uphold the same security model as with
@trezor/connect-web
we open popup.html and load core SDK logic into it. This however does not build persistent connection between SDK and 3rd party application meaning that events cannot be used.
![connect schema when used in webextension](./schema-connect-webextension.jpg =950x432)
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.
Dependencies
Webpack:
Since webpack@5
auto polyfills for nodejs
are not provided.
see https://webpack.js.org/blog/2020-10-10-webpack-5-release/#automatic-nodejs-polyfills-removed
List of essential libraries to produce build:
assert
(polyfill)crypto-browserify
(polyfill)html-webpack-plugin
process
(polyfill)stream-browserify
(polyfill)style-loader
terser-webpack-plugin
util
(polyfill)webpack-*
worker-loader
From Protobuf to TypeScript and Flow
This document was moved to @trezor/transport
Supported coins
The pipeline
Do not change @trezor/connect-common/files/coins.json
manually.
The one and only source of truth are *.json
definitions declared and maintained in the firmware repository.
These are exported to a read-only trezor-common repository.
trezor-common
is included as git submodule mounted at submodules/trezor-common
.
Update and maintenance in @trezor/connect
Make sure that desired [coin].json
definition is present in trezor-firmware
repository and corresponding support for connect is enabled.
- Update
trezor-common
submodule:
yarn update-submodules
- Build
src/data/coins.json
file usingtrezor-common/cointool
:
yarn update-coins
Events
Handling events
Once user grants permission for hosting page to communicate with API TrezorConnect will emits events about device state. Events can be distinguished by "type" field of event object (TODO structure) Constants of all types can be imported from package
ES6
import TrezorConnect, { DEVICE_EVENT, DEVICE } from '@trezor/connect';
TrezorConnect.on(DEVICE_EVENT, event => {
if (event.type === DEVICE.CONNECT) {
} else if (event.type === DEVICE.DISCONNECT) {
}
});
List of published events
Full list of events is unfortunately beyond the scope of this documentation but you may refer to the source code for:
Path
path
-string | Array<number>
in BIP44 path scheme orArray
of hardended numbers.
Examples
Bitcoin account 1 using BIP44 derivation path
"m/49'/0/'0'";
Bitcoin account 1 using hardended path
[(49 | 0x80000000) >>> 0, 0 | (0x80000000 >>> 0), (0 | 0x80000000) >>> 0];
Bitcoin first address address of account 1 using BIP44 derivation path
"m/49'/0/'0'/0/0";
Bitcoin first address address of account 1 using hardended path
[(49 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, 0, 0];
Methods
API call return a Promise
. Resolve is guaranteed to get called
with a result
object, even if user closes the window, network connection times
out, etc. In case of failure result.success
is set to false and result.payload.error
is
the error message. It is recommended to log the error message and let user
restart the action.
Every method require an Object
with combination of common
fields and method specific fields.
- TrezorConnect.getPublicKey
- TrezorConnect.requestLogin
- TrezorConnect.cipherKeyValue
- TrezorConnect.wipeDevice
- TrezorConnect.resetDevice
- TrezorConnect.getCoinInfo
- TrezorConnect.getDeviceState
Bitcoin, Bitcoin Cash, Bitcoin Gold, Litecoin, Dash, ZCash, Testnet
- TrezorConnect.getAddress
- TrezorConnect.getAccountInfo
- TrezorConnect.getOwnershipId
- TrezorConnect.getOwnershipProof
- TrezorConnect.composeTransaction
- TrezorConnect.signTransaction
- TrezorConnect.pushTransaction
- TrezorConnect.signMessage
- TrezorConnect.verifyMessage
- TrezorConnect.authorizeCoinjoin
Ethereum
- TrezorConnect.ethereumGetAddress
- TrezorConnect.ethereumSignTransaction
- TrezorConnect.ethereumSignMessage
- TrezorConnect.ethereumSignTypedData
- TrezorConnect.ethereumVerifyMessage
Eos
NEM
Stellar
Cardano
- TrezorConnect.cardanoGetPublicKey
- TrezorConnect.cardanoGetAddress
- TrezorConnect.cardanoSignTransaction
Ripple
Solana
Tezos
Binance
- TrezorConnect.binanceGetAddress
- TrezorConnect.binanceGetPublicKey
- TrezorConnect.binanceSignTransaction
Management
please note that these method are not available from popup mode
- TrezorConnect.firmwareUpdate
- TrezorConnect.getFirmwareHash
- TrezorConnect.changePin
- TrezorConnect.changeWipeCode
Common parameters
Every call requires an Object
with a combination of common and method-specified fields.
All common parameters are optional.
device
- optionalObject
path
- requiredstring
call to a direct device. Useful when working with multiple connected devices. This value is emitted byTrezorConnectEvent
state
- optionalstring
sets expected state. This value is emitted byTrezorConnectEvent
instance
- optionalnumber
sets an instance of device. Useful when working with one device and multiple passphrases. This value is emitted byTrezorConnectEvent
useEmptyPassphrase
— optionalboolean
method will not ask for a passphrase. Default is set tofalse
allowSeedlessDevice
— optionalboolean
allows to use TrezorConnect methods with device with seedless setup. Default is set tofalse
keepSession
—optional boolean
Advanced feature. After method return a response device session will NOT! be released. Session should be released after all calls are performed by calling any method withkeepSession
set to false orundefined
. Useful when you need to do multiple different calls to TrezorConnect API without releasing. Example sequence loop for 10 account should look like:- TrezorConnect.getPublicKey({ device: { path: "web01"}, keepSession: true, ...otherParams }) for first account,
- Trezor.getAddress({ device: { path: "web01"}, ...otherParams }) for the same account,
- looking up for balance in external blockchain
- loop iteration
- after last iteration call TrezorConnect.getFeatures({ device: { path: "web01"}, keepSession: false, ...otherParams })
- useCardanoDerivation - optional
boolean
. default is set totrue
for all cardano related methods, otherwise it is set tofalse
. This parameter determines whether device should derive cardano seed for current session. Derivation of cardano seed takes longer then it does for other coins. A wallet that works with both cardano and other coins might want to set this param totrue
for every call or it must be able to cope with the following scenario:- Connected device is using passhprase
- Wallet calls
getPublicKey
withuseCardanoDerivation=false
, passhprase is entered, seed derived - Wallet calls
cardanoGetPublicKey
. - At this moment user will be prompted to enter passhprase again.
override
- optionalboolean
Interrupt previous call, if any.chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Get public key
Retrieves BIP32 extended public derived by given BIP32 path. User is presented with a description of the requested key and asked to confirm the export.
const result = await TrezorConnect.getPublicKey(params);
Params
Exporting single public key
path
— requiredstring | Array<number>
minimum length is1
. read morecoin
- optionalstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used. Ifcoin
is not set API will try to get network definition frompath
.scriptType
— optionalstring
used to distinguish between various address formats (non-segwit, segwit, etc.).ignoreXpubMagic
— optionalboolean
ignore SLIP-0132 XPUB magic, use xpub/tpub prefix for all account types.ecdsaCurveName
— optionalstring
ECDSA curve name to usecrossChain
— optionalboolean
Advanced feature. Use it only if you are know what you are doing. Allows to generate address between chains. For example Bitcoin path on Litecoin network will display cross chain address in Litecoin format.unlockPath
- optional PROTO.UnlockPath, the result of TrezorConnect.unlockPath method.suppressBackupWarning
- optionalboolean
By default, this method will emit an event to show a warning if the wallet does not have a backup. This option suppresses the message.
Exporting bundle of public keys
bundle
-Array
of Objects withpath
,coin
andcrossChain
fields
Example
Return public key of fifth bitcoin account:
TrezorConnect.getPublicKey({
path: "m/49'/0'/4'",
coin: 'btc',
});
Return a bundle of public keys for multiple bitcoin accounts:
TrezorConnect.getPublicKey({
bundle: [
{ path: "m/49'/0'/0'" }, // account 1
{ path: "m/49'/0'/1'" }, // account 2
{ path: "m/49'/0'/2'" }, // account 3
],
});
Result
Result with only one public key
{
success: true,
payload: {
path: Array<number>, // hardended path
serializedPath: string, // serialized path
xpub: string, // xpub in legacy format
xpubSegwit?: string, // optional for segwit accounts: xpub in segwit format
chainCode: string, // BIP32 serialization format
childNum: number, // BIP32 serialization format
publicKey: string, // BIP32 serialization format
fingerprint: number, // BIP32 serialization format
depth: number, // BIP32 serialization format
}
}
Read more about BIP32 serialization format
Result with bundle of public keys
{
success: true,
payload: [
{ path, serializedPath, xpub, xpubSegwit?, chainCode, childNum, publicKey, fingerprint, depth }, // account 1
{ path, serializedPath, xpub, xpubSegwit?, chainCode, childNum, publicKey, fingerprint, depth }, // account 2
{ path, serializedPath, xpub, xpubSegwit?, chainCode, childNum, publicKey, fingerprint, depth } // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Request login
Challenge-response authentication via Trezor. To protect against replay attacks
you should use a server-side generated and randomized challengeHidden
for every
attempt. You can also provide a visual challenge that will be shown on the
device.
Service backend needs to check whether the signature matches the generated
challengeHidden
, provided challengeVisual
and stored publicKey
fields.
If that is the case, the backend either creates an account (if the publicKey
identity is seen for the first time) or signs in the user (if the publicKey
identity is already a known user).
To understand the full mechanics, please consult the Challenge-Response chapter of SLIP-0013: Authentication using deterministic hierarchy.
const result = await TrezorConnect.requestLogin(params);
Params
Optional common params
Common parameter useEmptyPassphrase
- is always set to true
and it will be ignored by this method
Login using server-side async challenge
callback
— requiredfunction
which will be called from API to fetchchallengeHidden
andchallengeVisual
from server
Login without async challenge
challengeHidden
- requiredstring
hexadecimal valuechallengeVisual
- requiredstring
text displayed on Trezor
Example
Login using server-side async challenge
TrezorConnect.requestLogin({
callback: function () {
// here should be a request to server to fetch "challengeHidden" and "challengeVisual"
return {
challengeHidden: '0123456789abcdef',
challengeVisual: 'Login to',
};
},
});
Login without async challenge
TrezorConnect.requestLogin({
challengeHidden: '0123456789abcdef',
challengeVisual: 'Login to',
});
Result
{
success: true,
payload: {
address: string,
publicKey: string,
signature: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Server side examples
Here is the reference implementation of the server-side signature verification written in various languages:
Symmetric key-value encryption
Cipher key value provides symmetric encryption in the Trezor device where the user might be forced to confirm the encryption/decryption on the display. The key for the encryption is constructed from the private key on the BIP address, the key displayed on the device, and the two informations about whether to ask for confirmation. It is constructed in such a way, that different path, key or the confirm information will get a different encryption key and IV. So, you cannot "skip" the confirmation by using different input. IV can be either manually set, or it is computed together with the key.The value must be divisible into 16-byte blocks. The application has to pad the blocks itself and ensure safety; for example, by using PKCS7.
More information can be found in SLIP-0011.
const result = await TrezorConnect.cipherKeyValue(params);
Params
Common parameter useEmptyPassphrase
- is always set to true
and it will be ignored by this method
Encrypt single value
path
— requiredstring | Array<number>
minimum length is1
. read morekey
— optionalstring
a message shown on devicevalue
— optionalstring
hexadecimal value with length a multiple of 16 bytes (32 letters in hexadecimal). Value is what is actually being encrypted.askOnEncrypt
- optionalboolean
should user confirm encrypt?askOnDecrypt
- optionalboolean
should user confirm decrypt?iv
- optionalstring
initialization vector - keep unset if you don't know what it means, it will be computed automatically.
Encrypt multiple values
bundle
-Array
of Objects withpath
,key
,value
,askOnEncrypt
,askOnDecrypt
fields
Example
Return encrypted value:
TrezorConnect.cipherKeyValue({
path: "m/49'/0'/0'",
key: 'This text is displayed on Trezor during encrypt',
value: '1c0ffeec0ffeec0ffeec0ffeec0ffee1',
encrypt: true,
askOnEncrypt: true,
askOnDecrypt: true,
});
Return a bundle of encrypted values:
TrezorConnect.cipherKeyValue({
bundle: [
{
path: "m/49'/0'/0'",
key: '1 text on Trezor',
value: '1c0ffeec0ffeec0ffeec0ffeec0ffee1',
encrypt: true,
},
{
path: "m/49'/0'/1'",
key: '2 text on Trezor',
value: '1c0ffeec0ffeec0ffeec0ffeec0ffee1',
encrypt: false,
},
{ path: "m/49'/0'/2'", key: '3 text on Trezor', value: '1c0ffeec0ffeec0ffeec0ffeec0ffee1' },
],
});
Result
Result with only one value
{
success: true,
payload: {
value: string
}
}
Result with bundle of values
{
success: true,
payload: [
{ value: string },
{ value: string },
{ value: string }
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Wipe device
Reset device to factory defaults and remove all private data.
const result = await TrezorConnect.wipeDevice(params);
Params
Common parameter useEmptyPassphrase
- is set to true
Common parameter allowSeedlessDevice
- is set to true
Example
TrezorConnect.wipeDevice();
Result
Success type
{
success: true,
payload: {
message: 'Device wiped'
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Reset device
Perform device setup and generate new seed.
const result = await TrezorConnect.resetDevice(params);
Params
strength
— optionalnumber
Accepted values are [128|192|256]. Default is set to256
label
— optionalstring
u2fCounter
— optionalnumber
. Default value is set to current time stamp in seconds.pinProtection
— optionalboolean
passphraseProtection
— optionalboolean
skipBackup
— optionalboolean
noBackup
— optionalboolean
create a seedless device
Example
TrezorConnect.resetDevice({
label: 'My fancy Trezor',
});
Result
Success type
{
success: true,
payload: {
message: 'Device successfully initialized'
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Get Coin Info
Returns information about a specified coin from the coins.json file.
const result = await TrezorConnect.getCoinInfo(params);
Params
Exporting single address
coin
— requiredstring
coin symbol (btc, eth, bch, ...).
Example
Get coin info for Bitcoin.
TrezorConnect.getCoinInfo({
coin: 'btc',
});
Result
Result for Bitcoin
{
success: true,
payload: {
blockchainLink: Object { type: "blockbook", url: (5) […] },
blocks: 10,
blocktime: 10,
cashAddrPrefix: null,
curveName: "secp256k1",
decimals: 8,
defaultFees: Object { Economy: 70, High: 200, Low: 10, … },
dustLimit: 546,
forceBip143: false,
forkid: null,
hashGenesisBlock: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
isBitcoin: true,
label: "Bitcoin",
maxAddressLength: 34,
maxFee: 2000,
maxFeeSatoshiKb: 2000000,
minAddressLength: 27,
minFee: 1,
minFeeSatoshiKb: 1000,
name: "Bitcoin",
network: Object { messagePrefix: "Bitcoin Signed Message:\n", bech32: "bc", pubKeyHash: 0, … },
segwit: true,
shortcut: "BTC",
slip44: 0,
support: Object { connect: true, trezor1: "1.5.2", trezor2: "2.0.5", … },
type: "bitcoin",
xPubMagic: 76067358,
xPubMagicSegwit: 77429938,
xPubMagicSegwitNative: 78792518,
}
}
Get address
Display requested address derived by given BIP32 path on device and returns it to caller. User is asked to confirm the export on Trezor.
const result = await TrezorConnect.getAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
coin
- optionalstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used. Ifcoin
is not set API will try to get network definition frompath
.crossChain
— optionalboolean
Advanced feature. Use it only if you are know what you are doing. Allows to generate address between chains. For example Bitcoin path on Litecoin network will display cross chain address in Litecoin format.multisig
- optional MultisigRedeemScriptType, redeem script information (multisig addresses only)scriptType
- optional InputScriptType, address script typeunlockPath
- optional PROTO.UnlockPath, the result of TrezorConnect.unlockPath method.chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
,showOnTrezor
,coin
andcrossChain
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display third address of first bitcoin account:
TrezorConnect.getAddress({
path: "m/49'/0'/0'/0/2",
coin: 'btc',
});
Return a bundle of addresses from first bitcoin account without displaying them on device:
TrezorConnect.getAddress({
bundle: [
{ path: "m/49'/0'/0'/0/0", showOnTrezor: false }, // address 1
{ path: "m/49'/0'/0'/0/1", showOnTrezor: false }, // address 2
{ path: "m/49'/0'/0'/0/2", showOnTrezor: false }, // address 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.getAddress({
path: "m/49'/0'/0'/0/0",
address: '3L6TyTisPBmrDAj6RoKmDzNnj4eQi54gD2',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string, // displayed address
path: Array<number>, // hardended path
serializedPath: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // address 1
{ address: string, path: Array<number>, serializedPath: string }, // address 2
{ address: string, path: Array<number>, serializedPath: string }, // address 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Get account info
Gets an info of specified account.
const result = await TrezorConnect.getAccountInfo(params);
Params
Using path
path
— requiredstring | Array<number>
minimum length is3
. read morecoin
— requiredstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.
Using public key
descriptor
— requiredstring
public key of accountcoin
— requiredstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.
Using discovery
BIP-0044 account discovery is performed and user is presented with a list of accounts. Result is returned after account selection.
coin
— requiredstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.
Other optional params
params are forwarded to BlockBook backend using @trezor/blockchain-link
package
-
details
— specifies level of details returned by requestbasic
(default) return only account balances, without any derived addresses or transaction historytokens
- response with derived addresses (Bitcoin-like accounts) and ERC20 tokens (Ethereum-like accounts), subject oftokens
paramtokenBalances
- same astokens
with balances, subject oftokens
paramtxs
-tokenBalances
+ complete account transaction history
-
tokens
— specifies which tokens (xpub addresses) are returned by the request (default nonzero)nonzero
- (Default) return only addresses with nonzero balanceused
- return addresses with at least one transactionderived
- return all derived addresses
-
page
—number
transaction history page index, subject ofdetails: txs
-
pageSize
—number
transaction history page size, subject ofdetails: txs
-
from
—number
transaction history from block filter, subject ofdetails: txs
-
to
—number
transaction history to block filter, subject ofdetails: txs
-
gap
—number
address derivation gap size, subject ofdetails: tokens
-
contractFilter
—string
Ethereum-like accounts only: get ERC20 token info and balance -
marker
—{ ledger: number, seq: number }
XRP accounts only, transaction history page marker -
defaultAccountType
—'normal' | 'segwit' | 'legacy'
Bitcoin-like accounts only: specify which account group is displayed as default in popup, subject ofUsing discovery
-
suppressBackupWarning
-boolean
By default, this method will emit an event to show a warning if the wallet does not have a backup. This option suppresses the message.
Example
Get info about first bitcoin account
TrezorConnect.getAccountInfo({
path: "m/49'/0'/0'",
coin: 'btc',
});
Get info about account using public key (device is not used)
TrezorConnect.getAccountInfo({
descriptor:
'xpub6CVKsQYXc9awxgV1tWbG4foDvdcnieK2JkbpPEBKB5WwAPKBZ1mstLbKVB4ov7QzxzjaxNK6EfmNY5Jsk2cG26EVcEkycGW4tchT2dyUhrx',
coin: 'btc',
});
Get info about account using BIP-0044 account discovery
TrezorConnect.getAccountInfo({
coin: 'btc',
});
Result
{
success: true,
payload: {
id: number, // account id
path: string, // serialized path
descriptor: string, // account public key
legacyXpub?: string, // (optional) account public key in legacy format (only for segwit and segwit native accounts)
balance: string, // account balance (confirmed transactions only)
availableBalance: string, // account balance (including unconfirmed transactions)
addresses: {
// subject of details:tokens param
unused: Array<AccountAddress>, // unused addresses
used: Array<AccountAddress>, // used addresses
change: Array<AccountAddress>, // change addresses (internal)
}, // list of derived addresses grouped by purpose (Bitcoin-like accounts)
history: Array<{
total: number,
unconfirmed: number,
transactions?: Array<AccountTransaction>, // subject of details:txs param
}> // account history object
utxo?: Array<AccountUtxo>, // account utxos (Bitcoin-like accounts), subject of details:tokens param
tokens?: Array<TokenInfo>, // account ERC20 tokens (Ethereum-like accounts), subject of details:tokens param
misc?: {
// Ethereum-like accounts only
nonce: string,
contractInfo?: TokenInfo, // subject of contractFilter param
// XRP accounts only
sequence?: number,
reserve?: string,
},
page?: {
// subject of details:txs param
index: number, // current page index
size: number, // current page size
total: number, // total pages count
},
marker?: {
// XRP accounts only
// subject of details:txs param
ledger: number,
seq: number,
}
} //
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Bitcoin: get Ownership identifier
Export SLIP-0019 ownership identifier. Read more
const result = await TrezorConnect.getOwnershipId(params);
:note: Supported only by T2T1 with Firmware 2.4.4 or higher!
Params
Exporting single id
path
— requiredstring | Array<number>
minimum length is5
. read morecoin
- optionalstring
Determines network definition specified in coins.json file. Coin
shortcut
,name
orlabel
can be used.scriptType
— optionalInputScriptType
multisig
— optionalMultisigRedeemScriptType
Exporting bundle of ids
bundle
-Array
of Objects with fields listed above.
Example
Display id of the first bitcoin address:
TrezorConnect.getOwnershipId({
path: "m/86'/0'/0'/0/0",
});
Return a bundle of ids:
TrezorConnect.getOwnershipId({
bundle: [
{ path: "m/86'/0'/0'/0/0" }, // taproot
{ path: "m/84'/0'/0'/0/0" }, // bech32
{ path: "m/49'/0'/0'/0/0" }, // segwit
],
});
Result
Result with single id:
{
success: true,
payload: {
ownership_id: string,
path: number[],
serializedPath: string
}
}
Result with bundle of ids sorted by FIFO
{
success: true,
payload: [
{ ownership_id: string, path: number[], serializedPath: string }, // taproot
{ ownership_id: string, path: number[], serializedPath: string }, // bech32
{ ownership_id: string, path: number[], serializedPath: string } // segwit
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Bitcoin: get Ownership proof
Export SLIP-0019 ownership proof. Read more
const result = await TrezorConnect.getOwnershipProof(params);
:note: Supported only by T2T1 with Firmware 2.4.4 or higher!
Params
Exporting single proof
path
— requiredstring | Array<number>
minimum length is5
. read morecoin
- optionalstring
Determines network definition specified in coins.json file. Coin
shortcut
,name
orlabel
can be used.scriptType
— optionalInputScriptType
userConfirmation
— optionalboolean
ownershipIds
— optionalArray<string>
commitmentData
— optionalstring
multisig
— optionalMultisigRedeemScriptType
preauthorized
— optionalboolean
read more
Exporting bundle of proofs
bundle
-Array
of Objects with fields listed above.
Example
Display ownership proof of the first bitcoin address:
TrezorConnect.getOwnershipProof({
path: "m/86'/0'/0'/0/0",
});
Return a bundle of ownership proofs:
TrezorConnect.getOwnershipProof({
bundle: [
{ path: "m/86'/0'/0'/0/0" }, // taproot
{ path: "m/84'/0'/0'/0/0" }, // bech32
{ path: "m/49'/0'/0'/0/0" }, // segwit
],
});
Result
Result with single proof:
{
success: true,
payload: {
ownership_proof: string,
signature: string,
path: number[],
serializedPath: string
}
}
Result with bundle of proofs sorted by FIFO
{
success: true,
payload: [
{ ownership_proof: string, signature: string, path: number[], serializedPath: string }, // taproot
{ ownership_proof: string, signature: string, path: number[], serializedPath: string }, // bech32
{ ownership_proof: string, signature: string, path: number[], serializedPath: string } // segwit
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Compose transaction
This method works only for Bitcoin
and Bitcoin-like
coins.
Can be used in two different ways and returned result depends on used parameters.
Requests a payment to a set of given outputs.
An automated payment process separated in to following steps:
- Account discovery for requested coin is performed and the user is asked for source account selection. [1]
- User is asked for fee level selection.
- Transaction is calculated, change output is added automatically if needed. [2]
- Signing transaction with Trezor, user is asked for confirmation on device.
Returned response is a signed transaction in hexadecimal format same as in signTransaction method.
Params:
outputs
— requiredArray
of output objects described belowcoin
— requiredstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.push
— optionalboolean
determines if composed transaction will be broadcasted into blockchain network. Default is set to false.sequence
— optionalnumber
transaction input field used in RBF or locktime transactions
Precompose, prepare transaction to be signed by Trezor.
Skip first two steps of payment request
(described above) by providing account
and feeLevels
params and perform only transaction calculation. [2]
The result, internally called PrecomposedTransaction
is a set of params that can be used in signTransaction method afterwards.
This useful for the quick preparation of multiple variants for the same transaction using different fee levels or using incomplete data (like missing output addresses just to calculate fee)
Device and backend connection is not required for this case since all data are provided.
Params:
outputs
— requiredArray
of output objects described belowcoin
— requiredstring
determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.account
— requiredObject
containing essential data, partial result of getAccountInfo methodpath
- requiredstring
utxo
- requiredArray
addresses
- requiredstring
feeLevels
— requiredArray
of objects. set of requested variants, partial result ofblockchainEstimateFee method
feePerUnit
- requiredstring
satoshi per transaction byte.
baseFee
— optionalnumber
base fee of transaction in satoshi. used in replacement transactions calculation (RBF) and DOGEfloorBaseFee
— optionalboolean
decide whenever baseFee should be floored to the nearest baseFee unit, prevents from fee overpricing. used in DOGEsequence
— optionalnumber
transaction input field used in RBF or locktime transactionsskipPermutation
— optionalboolean
do not sort calculated inputs/outputs (usage: RBF transactions)
Accepted output objects:
regular output
amount
- requiredstring
value to send in satoshiaddress
- requiredstring
recipient address
send-max
- spends all available inputs from accounttype
- required withsend-max
valueaddress
- requiredstring
recipient address
opreturn
- read moretype
- required withopreturn
valuedataHex
- requiredhexadecimal string
with arbitrary data
payment-noaddress
- incomplete output, target address is not known yet. used only in precomposetype
- required withpayment-noaddress
valueamount
- requiredstring
value to send in satoshi
send-max-noaddress
- incomplete output, target address is not known yet. used only in precomposetype
- required withsend-max-noaddress
value
Examples
Payment example
Send 0.002 BTC to "18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2"
TrezorConnect.composeTransaction({
outputs: [
{ amount: "200000", address: "18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2" }
]
coin: "btc",
push: true
});
Payment result
{
success: true,
payload: {
signatures: Array<string>, // signer signatures
serializedTx: string, // serialized transaction
txid?: string, // blockchain transaction id
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Precompose example
Prepare multiple variants of the same transaction
TrezorConnect.composeTransaction({
outputs: [{ amount: '200000', address: 'tb1q9l0rk0gkgn73d0gc57qn3t3cwvucaj3h8wtrlu' }],
coin: 'btc',
account: {
path: "m/84'/0'/0'",
addresses: {
used: [
{
address: 'bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk',
path: "m/84'/0'/0'/0/0",
transfers: 1,
},
],
unused: [
{
address: '',
path: "m/84'/0'/0'/0/1",
transfers: 0,
},
],
change: [
{
address: 'bc1qktmhrsmsenepnnfst8x6j27l0uqv7ggrg8x38q',
path: "m/84'/0'/0'/1/0",
transfers: 0,
},
],
},
utxo: [
{
txid: '86a6e02943dcd057cfbe349f2c2274478a3a1be908eb788606a6950e727a0d36',
vout: 0,
amount: '300000',
blockHeight: 590093,
address: 'bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk',
path: "m/84'/0'/0'/0/0",
confirmations: 100,
},
],
},
feeLevels: [{ feePerUnit: '1' }, { feePerUnit: '5' }, { feePerUnit: '30' }],
});
Precompose result
{
success: true,
payload: [
{
type: 'final',
totalSpent: '200167',
fee: '167',
feePerByte: '1',
bytes: 167,
inputs: [
{
address_n: [84 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0],
amount: "300000",
prev_hash: "86a6e02943dcd057cfbe349f2c2274478a3a1be908eb788606a6950e727a0d36",
prev_index: 0,
script_type: "SPENDWITNESS",
}
],
outputs: [
{
address_n: [84 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 0],
amount: "99833",
script_type: "PAYTOWITNESS",
},
{
address: 'tb1q9l0rk0gkgn73d0gc57qn3t3cwvucaj3h8wtrlu',
amount: '200000',
script_type: 'PAYTOADDRESS',
}
],
outputsPermutation: [1, 0],
},
{
type: 'final',
totalSpent: '200835',
fee: '835',
feePerByte: '5',
bytes: 167,
inputs: [{ ... }],
outputs: [{ ... }],
outputsPermutation: [],
},
{
type: 'error',
}
]
}
For more examples see
Additional notes
- [1]
UI.SELECT_ACCOUNT
andUI.SELECT_FEE
events are emitted when usingtrusted mode
- [2] Account utxo selection, fee and change calculation is performed by @trezor/utxo-lib module
Sign transaction
Asks device to sign given inputs and outputs of pre-composed transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.signTransaction(params);
Params
coin
- requiredstring
Determines network definition specified in coins.json file. Coin
shortcut
,name
orlabel
can be used. See supported coinsinputs
- requiredArray
of PROTO.TxInputType,outputs
- requiredArray
of PROTO.TxOutputType,paymentRequests
- optionalArray
of PROTO.TxAckPaymentRequest. See SLIP-24refTxs
- optionalArray
of RefTransaction.If you don't want to use build-in
blockbook
backend you can optionally provide those data from your own backend transformed toTrezor
format. Since Firmware 2.3.0/1.9.0 referenced transactions are required. Zcash and Komodo refTxs should also containsexpiry
,version_group_id
andextra_data
fields.locktime
- optionalnumber
,version
- optionalnumber
transaction version,expiry
- optionalnumber
, only for Decred and Zcash,versionGroupId
- optionalnumber
only for Zcash, nVersionGroupId when overwintered is set,overwintered
- optionalboolean
only for Zcashtimestamp
- optionalnumber
only for Capricoin, transaction timestamp,branchId
- optionalnumber
, only for Zcash, BRANCH_ID when overwintered is setpush
- optionalboolean
Broadcast signed transaction to blockchain. Default is set to falseamountUnit
— optionalPROTO.AmountUnit
show amounts in BTC, mBTC, uBTC, sat
unlockPath
- optional PROTO.UnlockPath, the result of TrezorConnect.unlockPath method.serialize
- optionalboolean
, defaulttrue
serialize the full transaction, as opposed to only outputting the signatureschunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Example
PAYTOADDRESS
TrezorConnect.signTransaction({
inputs: [
{
address_n: [
(44 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
0,
],
prev_index: 0,
prev_hash: 'b035d89d4543ce5713c553d69431698116a822c57c03ddacf3f04b763d1999ac',
amount: 3431747,
},
],
outputs: [
{
address_n: [
(44 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
1,
],
amount: 3181747,
script_type: 'PAYTOADDRESS',
},
{
address: '18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2',
amount: 200000,
script_type: 'PAYTOADDRESS',
},
],
coin: 'btc',
});
SPENDP2SHWITNESS
TrezorConnect.signTransaction({
inputs: [
{
address_n: [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
0,
],
prev_index: 0,
prev_hash: 'b035d89d4543ce5713c553d69431698116a822c57c03ddacf3f04b763d1999ac',
amount: 3431747,
script_type: 'SPENDP2SHWITNESS',
},
],
outputs: [
{
address_n: [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
1,
],
amount: 3181747,
script_type: 'PAYTOP2SHWITNESS',
},
{
address: '18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2',
amount: 200000,
script_type: 'PAYTOADDRESS',
},
],
coin: 'btc',
});
PAYTOADDRESS with refTxs (transaction data provided from custom backend)
TrezorConnect.signTransaction({
inputs: [
{
address_n: [
(44 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
0,
],
prev_index: 0,
prev_hash: 'b035d89d4543ce5713c553d69431698116a822c57c03ddacf3f04b763d1999ac',
amount: 3431747,
},
],
outputs: [
{
address_n: [
(44 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(2 | 0x80000000) >>> 0,
1,
1,
],
amount: 3181747,
script_type: 'PAYTOADDRESS',
},
{
address: '18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2',
amount: 200000,
script_type: 'PAYTOADDRESS',
},
],
refTxs: [
{
hash: 'b035d89d4543ce5713c553d69431698116a822c57c03ddacf3f04b763d1999ac',
inputs: [
{
prev_hash: '448946a44f1ef514601ccf9b22cc3e638c69ea3900b67b87517ea673eb0293dc',
prev_index: 0,
script_sig:
'47304402202872cb8459eed053dcec0f353c7e293611fe77615862bfadb4d35a5d8807a4cf022015057aa0aaf72ab342b5f8939f86f193ad87b539931911a72e77148a1233e022012103f66bbe3c721f119bb4b8a1e6c1832b98f2cf625d9f59242008411dd92aab8d94',
sequence: 4294967295,
},
],
bin_outputs: [
{
amount: 3431747,
script_pubkey: '76a91441352a84436847a7b660d5e76518f6ebb718dedc88ac',
},
{
amount: 10000,
script_pubkey: '76a9141403b451c79d34e6a7f6e36806683308085467ac88ac',
},
],
lock_time: 0,
version: 1,
},
],
coin: 'btc',
});
Result
{
success: true,
payload: {
signatures: Array<string>, // Array of signer signatures
serializedTx: string, // serialized transaction
txid?: string, // broadcasted transaction id
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Push transaction
Broadcast the transaction to the selected network.
const result = await TrezorConnect.pushTransaction(params);
Params
tx
- requiredstring
serialized transaction,coin
- requiredstring
Determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.
Example
TrezorConnect.pushTransaction({
tx: '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000',
coin: 'btc',
});
Result
{
success: true,
payload: {
txid: string, // transaction id
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Sign message
Asks device to sign a message using the private key derived by given BIP32 path.
const result = await TrezorConnect.signMessage(params);
Params
path
— requiredstring | Array<number>
minimum length is3
. read moremessage
- requiredstring
coin
- optionalstring
Determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used. Ifcoin
is not set API will try to get network definition frompath
.hex
- optionalboolean
convert message from hex
Example
TrezorConnect.signMessage({
path: "m/44'/0'/0'",
message: 'example message',
});
Result
{
success: true,
payload: {
address: string, // signer address
signature: string, // signature in base64 format
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Verify message
Asks device to verify a message using the signer address and signature.
const result = await TrezorConnect.verifyMessage(params);
Params
address
- requiredstring
signer address,message
- requiredstring
signed message,signature
- requiredstring
signature in base64 format,coin
- requiredstring
Determines network definition specified in coins.json file. Coinshortcut
,name
orlabel
can be used.hex
- optionalboolean
convert message from hex
Example
TrezorConnect.verifyMessage({
address: '3BD8TL6iShVzizQzvo789SuynEKGpLTms9',
message: 'example message',
signature:
'JO7vL3tOB1qQyfSeIVLvdEw9G1tCvL+lNj78XDAVM4t6UptADs3kXDTO2+2ZeEOLFL4/+wm+BBdSpo3kb3Cnsas=',
coin: 'btc',
});
Result
Success type
{
success: true,
payload: {
message: "Message verified"
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Bitcoin: authorize coinjoin
Allow device to do preauthorized operations in signTransaction
and getOwnershipProof
methods without further user interaction.
Permission persists until physical device disconnection or maxRounds
limit is reached.
const result = await TrezorConnect.authorizeCoinjoin(params);
:warning: This feature is experimental! Do not use it in production!
:note: Supported only by T2T1 with Firmware 2.5.3 or higher!
Params
Exporting single id
path
— requiredstring | Array<number>
prefix of the BIP-32 path leading to the account (m / purpose' / coin_type' / account')read more
coordinator
— requiredstring
coordinator identifier to approve as a prefix in commitment data (max. 36 ASCII characters)
maxRounds
— requirednumber
maximum number of rounds that Trezor is authorized to take part in
maxCoordinatorFeeRate
— requirednumber
maximum coordination fee rate in units of 10**6 percent
maxFeePerKvbyte
— requirednumber
maximum mining fee rate in units of satoshis per 1000 vbytes
coin
- optionalstring
Determines network definition specified in coins.json file. Coin
shortcut
,name
orlabel
can be used.scriptType
— optionalPROTO.InputScriptType
used to distinguish between various address formats (non-segwit, segwit, etc.)
amountUnit
— optionalPROTO.AmountUnit
show amounts in
preauthorized
— optionalCheck if device session is already preauthorized and take no further action if so
coinjoinRequest
— optionalPROTO.CoinJoinRequest
Signing request for a coinjoin transaction
Example:
TrezorConnect.authorizeCoinjoin({
path: "m/10086'/0'/0'",
maxRounds: 3,
maxCoordinatorFeeRate: 500000, // 0.5% => 0.005 * 10**8;
maxFeePerKvbyte: 3500,
scriptType: 'SPENDWITNESS',
});
Result
Success type
{
success: true,
payload: {
message: 'Coinjoin authorized'
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ethereum: get address
Display requested address derived by given BIP32 path on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export on Trezor.
const result = await TrezorConnect.ethereumGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreaddress
— requiredstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display address of first ethereum account:
TrezorConnect.ethereumGetAddress({
path: "m/44'/60'/0'/0/0",
});
Return a bundle of ethereum addresses without displaying them on device:
TrezorConnect.ethereumGetAddress({
bundle: [
{ path: "m/44'/60'/0'/0/0", showOnTrezor: false }, // account 1
{ path: "m/44'/60'/1'/0/0", showOnTrezor: false }, // account 2
{ path: "m/44'/60'/2'/0/0", showOnTrezor: false }, // account 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.ethereumGetAddress({
path: "m/44'/60'/0'/0/0",
address: '0x73d0385F4d8E00C5e6504C6030F47BF6212736A8',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string, // displayed address
path: Array<number>, // hardended path
serializedPath: string,
}
}
Result with bundle of addresses sorted by FIFO
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // account 1
{ address: string, path: Array<number>, serializedPath: string }, // account 2
{ address: string, path: Array<number>, serializedPath: string } // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ethereum: Sign transaction
Asks device to sign given transaction using the private key derived by given BIP32 path. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.ethereumSignTransaction(params);
Params
path
— requiredstring | Array<number>
minimum length is3
. read moretransaction
- requiredObject
type ofEthereumTransactionEIP1559
|
EthereumSignTransaction
"0x" prefix for each field is optionalchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Examples
EIP1559 (after The London Upgrade)
:warning: Supported firmware versions T1B1 v1.10.4 and T2T1 v2.4.2
If both parameters maxFeePerGas
and maxPriorityFeePerGas
are defined, transaction will be signed as the new type introduced in EIP1559.
TrezorConnect.ethereumSignTransaction({
path: "m/44'/60'/0'",
transaction: {
to: '0xd0d6d6c5fe4a677d343cc433536bb717bae167dd',
value: '0xf4240',
data: '0xa',
chainId: 1,
nonce: '0x0',
maxFeePerGas: '0x14',
maxPriorityFeePerGas: '0x0',
gasLimit: '0x14',
},
});
Legacy
For T1B1 and T2T1 with firmware older than 2.4.2 (but supported newer firmware versions too).
TrezorConnect.ethereumSignTransaction({
path: "m/44'/60'/0'",
transaction: {
to: '0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8',
value: '0xf4240',
data: '0x01',
chainId: 1,
nonce: '0x0',
gasLimit: '0x5208',
gasPrice: '0xbebc200',
},
});
Result
{
success: true,
payload: {
v: string, // hexadecimal string with "0x" prefix
r: string, // hexadecimal string with "0x" prefix
s: string, // hexadecimal string with "0x" prefix
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ethereum: sign message
Asks device to sign a message using the private key derived by given BIP32 path.
const result = await TrezorConnect.ethereumSignMessage(params);
Params
Optional common params EthereumSignMessage type
path
— requiredstring | Array<number>
minimum length is3
. read moremessage
- requiredstring
message to sign in plain texthex
- optionalboolean
convert message from hex
Example
TrezorConnect.ethereumSignMessage({
path: "m/44'/60'/0'",
message: 'example message',
});
Result
{
success: true,
payload: {
address: string,
signature: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ethereum: Sign Typed Data
Asks device to sign an EIP-712 typed data message using the private key derived by given BIP32 path.
User is asked to confirm all signing details on T2T1.
const result = await TrezorConnect.ethereumSignTypedData(params);
:warning: Supported only by T2T1 with Firmware 2.4.3 or higher! :warning: Blind signing is supported only on T1B1 with Firmware 1.10.5 or higher!
Params
:warning: Domain-only signing (
data.primaryType
="EIP712Domain"
) is supported only on T2T1 with Firmware 2.4.4 or higher!
path
— requiredstring | Array<number>
minimum length is3
. read moredata
- requiredObject
type ofEthereumSignTypedDataMessage
`. A JSON Schema definition can be found in the EIP-712 spec.metamask_v4_compat
- requiredboolean
set totrue
for compatibility with MetaMask's signTypedData_v4.
Blind signing (optional addition for T1B1 compatibility)
T1B1 firmware currently does not support constructing EIP-712 hashes.
However, it supports signing pre-constructed hashes.
EIP-712 hashes can be constructed with the plugin function at @trezor/connetct-plugin-ethereum.
You may also wish to contruct your own hashes using a different library.
:warning: Domain-only signing (empty
message_hash
) is supported only on T1B1 with Firmware 1.10.6 or higher!
domain_separator_hash
- requiredstring
hex-encoded 32-byte hash of the EIP-712 domain.message_hash
- optionalstring
hex-encoded 32-byte hash of the EIP-712 message. This is optional for the domain-only hashes whereprimaryType
isEIP712Domain
.
Example
const eip712Data = {
types: {
EIP712Domain: [
{
name: 'name',
type: 'string',
},
],
Message: [
{
name: 'Best Wallet',
type: 'string',
},
{
name: 'Number',
type: 'uint64',
},
],
},
primaryType: 'Message',
domain: {
name: 'example.trezor.io',
},
message: {
'Best Wallet': 'Trezor Model T',
// be careful with JavaScript numbers: MAX_SAFE_INTEGER is quite low
Number: `${2n ** 55n}`,
},
};
// This functionality is separate from @trezor/connect, as it requires @metamask/eth-sig-utils,
// which is a large JavaScript dependency
const transformTypedDataPlugin = require('@trezor/connect-plugin-ethereum');
const { domain_separator_hash, message_hash } = transformTypedDataPlugin(eip712Data, true);
TrezorConnect.ethereumSignTypedData({
path: "m/44'/60'/0'",
data: eip712Data,
metamask_v4_compat: true,
// These are optional, but required for T1B1 compatibility
domain_separator_hash,
message_hash,
});
Result
{
success: true,
payload: {
address: string,
signature: string, // hexadecimal string with "0x" prefix
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ethereum: verify message
Asks device to verify a message using the signer address and signature.
const result = await TrezorConnect.ethereumVerifyMessage(params);
Params
address
- requiredstring
signer address. "0x" prefix is optionalmessage
- requiredstring
signed message in plain texthex
- optionalboolean
convert message from hexsignature
- requiredstring
signature in hexadecimal format. "0x" prefix is optional
Example
TrezorConnect.ethereumVerifyMessage({
address: '0xdA0b608bdb1a4A154325C854607c68950b4F1a34',
message: 'Example message',
signature:
'11dc86c631ef5d9388c5e245501d571b864af1a717cbbb3ca1f6dacbf330742957242aa52b36bbe7bb46dce6ff0ead0548cc5a5ce76d0aaed166fd40cb3fc6e51c',
});
Result
Success type
{
success: true,
payload: {
message: "Message verified"
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Eos: get public key
Display requested public key derived by given BIP44 path on device and returns it to caller. User is presented with a description of the requested public key and asked to confirm the export.
const result = await TrezorConnect.eosGetPublicKey(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreshowOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Example
Displays public key derived from BIP44 path:
TrezorConnect.eosGetPublicKey({
path: "m/44'/194'/0'/0/0",
});
Return a bundle of public keys without displaying them on device:
TrezorConnect.eosGetPublicKey({
bundle: [
{ path: "m/44'/194'/0'/0/0", showOnTrezor: false }, // public key 1
{ path: "m/44'/194'/0'/0/1", showOnTrezor: false }, // public key 2
{ path: "m/44'/194'/0'/0/2", showOnTrezor: false }, // public key 3
],
});
Result
Result with only one public key
{
success: true,
payload: {
wifPublicKey: string,
rawPublicKey: string,
path: number[],
serializedPath: string
}
}
Result with bundle of public keys sorted by FIFO
{
success: true,
payload: [
{ wifPublicKey: string, rawPublicKey: string, path: number[], serializedPath: string }, // public key 1
{ wifPublicKey: string, rawPublicKey: string, path: number[], serializedPath: string }, // public key 2
{ wifPublicKey: string, rawPublicKey: string, path: number[], serializedPath: string } // public key 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Eos: sign transaction
Asks device to sign given transaction using the private key derived by given BIP44 path. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.eosSignTransaction(params);
Params
path
— requiredstring | Array<number>
minimum length is3
. read moretransaction
- requiredObject
type of EosSDKTransactionchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Transfer example
TrezorConnect.eosSignTransaction({
path: "m/44'/194'/0'/0/0",
transaction: {
chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f',
header: {
expiration: '2019-12-01T12:00:00',
refBlockNum: 6439,
refBlockPrefix: 2995713264,
maxNetUsageWords: 0,
maxCpuUsageMs: 0,
delaySec: 0,
},
actions: [
{
account: 'eosio.token',
authorization: [
{
actor: 'abcdefg12345',
permission: 'active',
},
],
name: 'transfer',
data: {
from: 'abcdefg12345',
to: '12345abcdefg',
quantity: '1.0000 EOS',
memo: 'memo',
},
},
],
},
});
Result
{
success: true,
payload: {
signature: string, // hexadecimal string
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
NEM: get address
Display requested address on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export.
const result = await TrezorConnect.nemGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is3
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)network
— optionalnumber
0x68
- Mainnet,0x96
- Testnet,0x60
- Mijin. Default is set toMainnet
showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
,network
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display address of third nem account:
TrezorConnect.nemGetAddress({
path: "m/44'/43'/2'",
});
Return a bundle of NEM addresses without displaying them on device:
TrezorConnect.nemGetAddress({
bundle: [
{ path: "m/44'/43'/0'", showOnTrezor: false }, // account 1
{ path: "m/44'/43'/1'", showOnTrezor: false }, // account 2
{ path: "m/44'/43'/2'", showOnTrezor: false }, // account 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.nemGetAddress({
path: "m/44'/43'/0'",
address: 'TDS7OQUHKNYMSC2WPJA6QUTLJIO22S27B4FMU2AJ',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string,
path: Array<number>,
serializedPath: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // account 1
{ address: string, path: Array<number>, serializedPath: string }, // account 2
{ address: string, path: Array<number>, serializedPath: string }, // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
NEM: Sign transaction
Asks device to sign given transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.nemSignTransaction(params);
Params
path
- requiredstring | Array<number>
transaction
- requiredObject
type of NEMTransactionchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Example
Sign simple transaction
// common utility for hexlify message
function hexlify(str) {
var result = '';
var padding = '00';
for (var i=0, l=str.length; i<l; i++) {
var digit = str.charCodeAt(i).toString(16);
var padded = (padding+digit).slice(-2);
result += padded;
}
return result;
}
TrezorConnect.nemSignTransaction(
path: "m/44'/1'/0'/0'/0'",
transaction: {
timeStamp: 74649215,
amount: 2000000,
fee: 2000000,
recipient: "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J",
type: 257,
deadline: 74735615,
version: (0x98 << 24),
message: {
payload: hexlify('test_nem_transaction_transfer'),
type: 1,
},
}
});
Sign mosaic transaction
TrezorConnect.nemSignTransaction(
path: "m/44'/1'/0'/0'/0'",
transaction: {
timeStamp: 76809215,
amount: 1000000,
fee: 1000000,
recipient: "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J",
type: 257,
deadline: 76895615,
version: (0x98 << 24),
message: {},
mosaics: [
{
mosaicId: {
namespaceId: "nem",
name: "xem",
},
quantity: 1000000,
}
]
}
});
Result
{
success: true,
payload: {
data: string,
signature: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Stellar: get address
Display requested address on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export.
const result = await TrezorConnect.stellarGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is3
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display address of first stellar account:
TrezorConnect.stellarGetAddress({
path: "m/44'/148'/0'",
});
Return a bundle of stellar addresses without displaying them on device:
TrezorConnect.stellarGetAddress({
bundle: [
{ path: "m/44'/148'/0'", showOnTrezor: false }, // account 1
{ path: "m/44'/148'/1'", showOnTrezor: false }, // account 2
{ path: "m/44'/148'/2'", showOnTrezor: false }, // account 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.stellarGetAddress({
path: "m/44'/148'/0'/0/0",
address: 'GAXSFOOGF4ELO5HT5PTN23T5XE6D5QWL3YBHSVQ2HWOFEJNYYMRJENBV',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string,
path: Array<number>,
serializedPath: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // account 1
{ address: string, path: Array<number>, serializedPath: string }, // account 2
{ address: string, path: Array<number>, serializedPath: string }, // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Stellar: Sign transaction
Asks device to sign given transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.stellarSignTransaction(params);
Params
path
— requiredstring | Array<number>
minimum length is3
. read morenetworkPassphrase
- requiredstring
network passphrasetransaction
- requiredObject
type of StellarTransaction
Stellar SDK compatibility
@stellar/stellar-sdk
is not a part of trezor-connect
repository.
To transform StellarSDK.Transaction
object into TrezorConnect.StellarTransaction
, install @trezor/connect-plugin-stellar into your project.
import * as StellarSDK from '@stellar/stellar-sdk';
import transformTrezorTransaction from '<path-to-plugin>/index.js';
const tx = new StellarSdk.TransactionBuilder(...);
...
tx.build();
const params = transformTrezorTransaction(tx);
const result = TrezorConnect.stellarSignTransaction(params);
if (result.success) {
tx.addSignature('account-public-key', result.payload.signature);
}
Example
TrezorConnect.stellarSignTransaction(
path: "m/44'/148'/0'",
networkPassphrase: "Test SDF Network ; September 2015",
transaction: {
source: "GAXSFOOGF4ELO5HT5PTN23T5XE6D5QWL3YBHSVQ2HWOFEJNYYMRJENBV",
fee: 100,
sequence: 4294967296,
memo: {
type: 0,
},
operations: [
{
type: "payment",
source: "GAXSFOOGF4ELO5HT5PTN23T5XE6D5QWL3YBHSVQ2HWOFEJNYYMRJENBV",
destination: "GAXSFOOGF4ELO5HT5PTN23T5XE6D5QWL3YBHSVQ2HWOFEJNYYMRJENBV",
amount: "10000"
}
]
}
});
Result
{
success: true,
payload: {
publicKey: string,
signature: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Cardano: get public key
Retrieves BIP32-Ed25519 extended public derived by given BIP32-Ed25519 path. User is presented with a description of the requested key and asked to confirm the export on Trezor.
const result = await TrezorConnect.cardanoGetPublicKey(params);
Params
Exporting single public key
path
— requiredstring | Array<number>
minimum length is3
. read moreshowOnTrezor
— optionalboolean
determines if publick key will be displayed on device. Default is set totrue
derivationType
— optionalCardanoDerivationType
enum. determines used derivation type. Default is set to ICARUS_TREZOR=2suppressBackupWarning
- optionalboolean
By default, this method will emit an event to show a warning if the wallet does not have a backup. This option suppresses the message.
Exporting bundle of publick keys
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Example
Display public key of first cardano account:
TrezorConnect.cardanoGetPublicKey({
path: "m/44'/1815'/0'",
});
Return a bundle of cardano public keys without displaying them on device:
TrezorConnect.cardanoGetPublicKey({
bundle: [
{ path: "m/44'/1815'/0'", showOnTrezor: false }, // account 1
{ path: "m/44'/1815'/1'", showOnTrezor: false }, // account 2
{ path: "m/44'/1815'/2'", showOnTrezor: false }, // account 3
],
});
Result
Result with only one public key
{
success: true,
payload: {
path: Array<number>, // hardended path
serializedPath: string,
publicKey: string,
node: HDPubNode,
}
}
Result with a bundle of public keys
{
success: true,
payload: [
{ path: Array<number>, serializedPath: string, publicKey: string, node: HDPubNode }, // account 1
{ path: Array<number>, serializedPath: string, publicKey: string, node: HDPubNode}, // account 2
{ path: Array<number>, serializedPath: string, publicKey: string, node: HDPubNode } // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Cardano: get address
Display requested address derived by given BIP32-Ed25519 path on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export on Trezor.
const result = await TrezorConnect.cardanoGetAddress(params);
Params
Exporting single address
addressParameters
— required see description belowaddress
— optionalstring
address for validation (readHandle button request
section below)protocolMagic
- requiredInteger
764824073 for Mainnet, 1 for Preprod Testnet, 2 for Preview TestnetnetworkId
- requiredInteger
1 for Mainnet, 0 for TestnetshowOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
derivationType
— optionalCardanoDerivationType
enum. determines used derivation type. Default is set to ICARUS_TREZOR=2chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects with single address fields
Address Parameters
CardanoAddressParameters type
addressType
- requiredCardanoAddressType
/number
- you can use the flowCARDANO.ADDRESS_TYPE
object or typescriptCardanoAddressType
enum. Supports all address types.path
— requiredstring | Array<number>
minimum length is5
. read morestakingPath
— optionalstring | Array<number>
minimum length is5
. read more Used for base and reward address derivationstakingKeyHash
- optionalstring
hex string of staking key hash. Used for base address derivation (as an alternative tostakingPath
)certificatePointer
- optional CardanoCertificatePointer object. Must containnumber
sblockIndex
,txIndex
andcertificateIndex
. Used for pointer address derivation. read more about pointer addresspaymentScriptHash
- optionalstring
hex string of payment script hash.stakingScriptHash
- optionalstring
hex string of staking script hash.
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display byron address of first cardano account:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.BYRON,
path: "m/44'/1815'/0'/0/0",
},
protocolMagic: 764824073,
networkId: 1,
});
Display base address of first cardano account:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.BASE,
path: "m/1852'/1815'/0'/0/0",
stakingPath: "m/1852'/1815'/0'/2/0",
},
protocolMagic: 764824073,
networkId: 1,
});
Display base address with script payment part:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.BASE_SCRIPT_KEY,
paymentScriptHash: '0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe',
stakingPath: "m/1852'/1815'/0'/2/0",
},
protocolMagic: 764824073,
networkId: 1,
});
Display base address with script staking part:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.BASE_KEY_SCRIPT,
path: "m/1852'/1815'/0'/0/0",
stakingScriptHash: '8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9',
},
protocolMagic: 764824073,
networkId: 1,
});
Display base address with both payment and staking part being a script:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.BASE_SCRIPT_SCRIPT,
paymentScriptHash: '0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe',
stakingScriptHash: '8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9',
},
protocolMagic: 764824073,
networkId: 1,
});
Display pointer address of first cardano account:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.POINTER,
path: "m/1852'/1815'/0'/0/0",
certificatePointer: {
blockIndex: 1,
txIndex: 2,
certificateIndex: 3,
},
},
protocolMagic: 764824073,
networkId: 1,
});
Display pointer script address:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.POINTER_SCRIPT,
paymentScriptHash: '0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe',
certificatePointer: {
blockIndex: 1,
txIndex: 2,
certificateIndex: 3,
},
},
protocolMagic: 764824073,
networkId: 1,
});
Display enterprise address of first cardano account:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.ENTERPRISE,
path: "m/1852'/1815'/0'/0/0",
},
protocolMagic: 764824073,
networkId: 1,
});
Display enterprise script address:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.ENTERPRISE_SCRIPT,
paymentScriptHash: '0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe',
},
protocolMagic: 764824073,
networkId: 1,
});
Display reward address of first cardano account:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.REWARD,
stakingPath: "m/1852'/1815'/0'/0/0",
},
protocolMagic: 764824073,
networkId: 1,
});
Display reward script address:
TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: CardanoAddressType.REWARD_SCRIPT,
stakingScriptHash: '8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9',
},
protocolMagic: 764824073,
networkId: 1,
});
Return a bundle of cardano addresses without displaying them on device:
TrezorConnect.cardanoGetAddress({
bundle: [
// byron address, account 1, address 1
{
addressParameters: {
addressType: 8,
path: "m/44'/1815'/0'/0/0",
},
protocolMagic: 764824073,
networkId: 1,
showOnTrezor: false,
},
// base address with staking key hash, account 1, address 1
{
addressParameters: {
addressType: 0,
path: "m/1852'/1815'/0'/0/0",
stakingKeyHash: '1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff',
},
protocolMagic: 764824073,
networkId: 1,
showOnTrezor: false,
},
// byron address, account 2, address 3, testnet
{
addressParameters: {
addressType: 8,
path: "m/44'/1815'/1'/0/2",
},
protocolMagic: 1,
networkId: 0,
showOnTrezor: false,
},
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.cardanoGetAddress({
addressParameters: {
addressType: 8,
path: "m/44'/1815'/0'/0/0",
},
protocolMagic: 764824073,
networkId: 0,
address: 'Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRdsswsZ',
});
// don't forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
addressParameters: {
addressType: number,
path: Array<number>, // hardened path
stakingPath?: Array<number>, // hardened path
stakingKeyHash?: string,
certificatePointer?: {
blockIndex: number,
txIndex: number,
certificatePointer: number,
},
paymentScriptHash?: string,
stakingScriptHash?: string,
}
serializedPath?: string,
serializedStakingPath?: string,
protocolMagic: number,
networkId: number,
address: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{
addressParameters: {
addressType: number,
path: Array<number>, // hardened path
stakingPath?: Array<number>, // hardened path
stakingKeyHash?: string,
certificatePointer?: {
blockIndex: number,
txIndex: number,
certificatePointer: number,
},
paymentScriptHash?: string,
stakingScriptHash?: string,
}
serializedPath?: string,
serializedStakingPath?: string,
protocolMagic: number,
networkId: number,
address: string,
},
{
addressParameters: {
addressType: number,
path: Array<number>, // hardened path
stakingPath?: Array<number>, // hardened path
stakingKeyHash?: string,
certificatePointer?: {
blockIndex: number,
txIndex: number,
certificatePointer: number,
},
paymentScriptHash?: string,
stakingScriptHash?: string,
}
serializedPath?: string,
serializedStakingPath?: string,
protocolMagic: number,
networkId: number,
address: string,
},
{
addressParameters: {
addressType: number,
path: Array<number>, // hardened path
stakingPath?: Array<number>, // hardened path
stakingKeyHash?: string,
certificatePointer?: {
blockIndex: number,
txIndex: number,
certificatePointer: number,
},
paymentScriptHash?: string,
stakingScriptHash?: string,
}
serializedPath?: string,
serializedStakingPath?: string,
protocolMagic: number,
networkId: number,
address: string,
},
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Cardano: Sign transaction
Asks device to sign given transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.cardanoSignTransaction(params);
Params
signingMode
- required CardanoTxSigningModeinputs
- requiredArray
of CardanoInputoutputs
- requiredArray
of CardanoOutputfee
- requiredString
protocolMagic
- requiredInteger
764824073 for Mainnet, 1 for Preprod Testnet, 2 for Preview TestnetnetworkId
- requiredInteger
1 for Mainnet, 0 for Testnetttl
- optionalString
validityIntervalStart
- optionalString
certificates
- optionalArray
of CardanoCertificatewithdrawals
- optionalArray
of CardanoWithdrawalauxiliaryData
- optional CardanoAuxiliaryDatamint
- optional CardanoMintscriptDataHash
- optionalString
collateralInputs
- optionalArray
of CardanoCollateralInputrequiredSigners
- optionalArray
of CardanoRequiredSignercollateralReturn
- optional CardanoOutputtotalCollateral
- optionalString
referenceInputs
- optionalArray
of CardanoReferenceInputadditionalWitnessRequests
- optionalArray
ofstring | Array<number>
(paths). Used for multi-sig and token minting witness requests as those can not be determined from the transaction parameters.metadata
- removed - useauxiliaryData
insteadderivationType
— optionalCardanoDerivationType
enum. Determines used derivation type. Default is set to ICARUS_TREZOR=2.includeNetworkId
— optionalBoolean
. Determines whethernetworkId
should be explicitly serialized into the transaction body. Default isfalse
.chunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
tagCborSets
- optionalboolean
determines if CBOR arrays intended to be sets will be encoded with tag 258. Default is set tofalse
CardanoTxSigningMode
ORDINARY_TRANSACTION
Represents an ordinary user transaction transferring funds, delegating stake or withdrawing rewards. The transaction will be witnessed by keys derived from paths included in the inputs
, certificates
and withdrawals
. Additionally, if token minting is present, transaction will also be witnessed by keys derived from paths included in additionalWitnessRequests
.
The transaction
- should have valid
path
property on allinputs
- must not contain a pool registration certificate
- must not contain
collateralInputs
,collateralReturn
,totalCollateral
andreferenceInputs
- must contain paths as stake credentials in certificates and withdrawals (no key hashes or script hashes)
- may contain only 1852 and 1855 paths
- must not contain 1855 witness requests when transaction is not minting/burning tokens
POOL_REGISTRATION_AS_OWNER
Represents pool registration from the perspective of pool owner.
The transaction
- must have
path
undefined on allinputs
(i.e., we are not witnessing any UTxO) - must have single Pool registration certificate
- must have single owner given by path on that certificate
- must not contain withdrawals
- must not contain token minting
- must not contain
collateralInputs
,requiredSigners
,collateralReturn
,totalCollateral
andreferenceInputs
- must contain only staking witness requests
These restrictions are in place due to a possibility of maliciously signing another part of the transaction with the pool owner path as we are not displaying device-owned paths on the device screen.
MULTISIG_TRANSACTION
Represents a multi-sig transaction using native scripts. The transaction will only be signed by keys derived from paths included in additionalWitnessRequests
.
The transaction
- must have
path
undefined on allinputs
- must not contain output addresses given by parameters
- must not contain a pool registration certificate
- must not contain
collateralInputs
,collateralReturn
,totalCollateral
andreferenceInputs
- must contain script hash stake credentials in certificates and withdrawals (no paths or key hashes)
- may contain only 1854 and 1855 witness requests
- must not contain 1855 witness requests when transaction is not minting/burning tokens
PLUTUS_TRANSACTION
Represents a transactions containing Plutus script evaluation. The transaction will be witnessed by keys derived from paths included in the inputs
, certificates
, withdrawals
, collateralInputs
, requiredSigners
and additionalWitnessRequests
.
The transaction
- should contain
scriptDataHash
andcollateralInputs
- must not contain a pool registration certificate
- may contain only 1852, 1854 and 1855 required signers
- may contain only 1852, 1854 and 1855 witness requests
Note: requiredSigners
are meant for Plutus transactions (from the blockchain point of view), but some applications utilize them for their own purposes, so we allow them in all signing modes (except for pool registration as owner).
Stake pool registration certificate specifics
Trezor supports signing of stake pool registration certificates as a pool owner. The transaction may contain external inputs (e.g. belonging to the pool operator) and Trezor is not able to verify whether they are actually external or not, so if we allowed signing the transaction with a spending key, there is the risk of losing funds from an input that the user did not intend to spend from. Moreover there is the risk of inadvertedly signing a withdrawal in the transaction if there's any. To mitigate those risks, we introduced special validation rules for stake pool registration transactions which are validated on Trezor as well. The validation rules are the following:
- The transaction must not contain any other certificates, not even another stake pool registration
- The transaction must not contain any withdrawals
- The transaction inputs must all be external, i.e. path must be either undefined or null
- Exactly one owner should be passed as a staking path and the rest of owners should be passed as bech32-encoded reward addresses
CIP-36 vote key registration (Catalyst and other)
Trezor supports signing transactions with auxiliary data containing a vote key registration. Vote key registrations used to follow CIP-15, which has been superseded by CIP-36. Currently, Trezor supports both CIP-15 and CIP-36 formats, the intended standard can be specified in the format
field (with CIP-15 being the default). They differ in the following:
- CIP-36 allows delegating the voting power to several vote public keys with different voting power (CardanoCVoteRegistrationDelegation) as an alternative to providing only a single vote public key. Note that Trezor Firmware supports at most 32 delegations in a single registration.
- CIP-36 registrations contain the votingPurpose field. The value 0 is intended for Catalyst voting and the value 1 is intended for other purposes. If no value is provided, Trezor serializes 0 by default (if the CIP-36 format is used).
Trezor does not support the 1694 derivation paths at the moment.
The payment address to receive rewards can be provided either as a paymentAddress
string or as a paymentAddressParameters
object. For the smoothest user experience, we recommend providing paymentAddressParameters
of a BASE address owned by the device.
Transaction examples
Ordinary transaction
TrezorConnect.cardanoSignTransaction({
signingMode: CardanoTxSigningMode.ORDINARY_TRANSACTION,
inputs: [
{
path: "m/44'/1815'/0'/0/1",
prev_hash: '1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
prev_index: 0,
},
],
outputs: [
{
address: 'Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2',
amount: '3003112',
},
{
addressParameters: {
addressType: CardanoAddressType.BASE,
path: "m/1852'/1815'/0'/0/0",
stakingPath: "m/1852'/1815'/0'/2/0",
},
amount: '7120787',
},
{
format: CardanoTxOutputSerializationFormat.ARRAY_LEGACY,
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '2000000',
tokenBundle: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
amount: '7878754',
},
],
},
],
},
{
address: 'addr1w9rhu54nz94k9l5v6d9rzfs47h7dv7xffcwkekuxcx3evnqpvuxu0',
amount: '1',
datumHash: '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
},
{
format: CardanoTxOutputSerializationFormat.MAP_BABBAGE,
address: 'addr1w9rhu54nz94k9l5v6d9rzfs47h7dv7xffcwkekuxcx3evnqpvuxu0',
amount: '1',
inlineDatum:
'3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
referenceScript:
'3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b73b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
},
],
fee: '42',
ttl: '10',
validityIntervalStart: '20',
certificates: [
{
type: CardanoCertificateType.STAKE_REGISTRATION,
path: "m/1852'/1815'/0'/2/0",
},
{
type: CardanoCertificateType.STAKE_DEREGISTRATION,
path: "m/1852'/1815'/0'/2/0",
},
{
type: CardanoCertificateType.STAKE_DELEGATION,
path: "m/1852'/1815'/0'/2/0",
pool: 'f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973',
},
],
withdrawals: [
{
path: "m/1852'/1815'/0'/2/0",
amount: '1000',
},
],
auxiliaryData: {
hash: 'ea4c91860dd5ec5449f8f985d227946ff39086b17f10b5afb93d12ee87050b6a',
},
scriptDataHash: 'd593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02',
protocolMagic: 764824073,
networkId: 1,
includeNetworkId: false,
});
Stake pool registration
TrezorConnect.cardanoSignTransaction({
signingMode: CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
inputs: [
{
// notice no path is provided here
prev_hash: '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
prev_index: 0,
},
],
outputs: {
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '1000000',
},
fee: '300000',
ttl: '500000000',
protocolMagic: 764824073,
networkId: 1,
includeNetworkId: false,
certificates: [
{
type: CardanoCertificateType.STAKE_POOL_REGISTRATION,
poolParameters: {
poolId: 'f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973',
vrfKeyHash: '198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640',
pledge: '500000000', // amount in lovelace
cost: '340000000', // amount in lovelace
margin: {
// numerator/denominator should be <= 1 which is translated then to a percentage
numerator: '1',
denominator: '2',
},
rewardAccount: 'stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el', // bech32-encoded stake pool reward account
owners: [
{
stakingKeyPath: "m/1852'/1815'/0'/2/0", // this is the path to the owner's key that will be signing the tx on Trezor
},
{
stakingKeyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711', // other owner
},
],
relays: [
{
type: CardanoPoolRelayType.SINGLE_HOST_IP,
ipv4Address: '192.168.0.1',
ipv6Address: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', // ipv6 address in full form
port: 1234,
},
{
type: CardanoPoolRelayType.SINGLE_HOST_IP,
ipv6Address: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
port: 1234,
},
{
type: CardanoPoolRelayType.SINGLE_HOST_IP,
ipv4Address: '192.168.0.1',
port: 1234,
},
{
type: CardanoPoolRelayType.SINGLE_HOST_NAME,
hostName: 'www.test.test',
port: 1234,
},
{
type: CardanoPoolRelayType.MULTIPLE_HOST_NAME,
hostName: 'www.test2.test', // max 64 characters long
},
],
metadata: {
url: 'https://www.test.test', // max 64 characters long
hash: '914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6',
},
},
},
],
});
CIP-36 vote key registration
TrezorConnect.cardanoSignTransaction({
signingMode: CardanoTxSigningMode.ORDINARY_TRANSACTION,
inputs: [
{
path: "m/1852'/1815'/0'/0/0",
prev_hash: '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
prev_index: 0,
},
],
outputs: [
{
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '3003112',
},
],
fee: '42',
ttl: '10',
auxiliaryData: {
cVoteRegistrationParameters: {
stakingPath: "m/1852'/1815'/0'/2/0",
paymentAddressParameters: {
addressType: CardanoAddressType.BASE,
path: "m/1852'/1815'/0'/0/0",
stakingPath: "m/1852'/1815'/0'/2/0",
},
nonce: '22634813',
format: CardanoCVoteRegistrationFormat.CIP36,
delegations: [
{
votePublicKey:
'1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
weight: 1,
},
],
},
},
protocolMagic: 764824073,
networkId: 1,
includeNetworkId: false,
});
Multisig transaction
TrezorConnect.cardanoSignTransaction({
signingMode: CardanoTxSigningMode.MULTISIG_TRANSACTION,
inputs: [
{
prev_hash: '1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
prev_index: 0,
},
],
outputs: [
{
address: 'Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2',
amount: '3003112',
},
{
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '2000000',
tokenBundle: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
amount: '7878754',
},
],
},
],
},
],
fee: '42',
ttl: '10',
validityIntervalStart: '20',
certificates: [
{
type: CardanoCertificateType.STAKE_REGISTRATION,
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
},
{
type: CardanoCertificateType.STAKE_DEREGISTRATION,
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
},
{
type: CardanoCertificateType.STAKE_DELEGATION,
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
pool: 'f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973',
},
],
withdrawals: [
{
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
amount: '1000',
},
],
auxiliaryData: {
hash: 'ea4c91860dd5ec5449f8f985d227946ff39086b17f10b5afb93d12ee87050b6a',
},
mint: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
mintAmount: '7878754',
},
],
},
],
additionalWitnessRequests: ["m/1854'/1815'/0'/0/0", "m/1855'/1815'/0'"],
protocolMagic: 764824073,
networkId: 1,
includeNetworkId: false,
});
Plutus transaction
TrezorConnect.cardanoSignTransaction({
signingMode: CardanoTxSigningMode.PLUTUS_TRANSACTION,
inputs: [
{
path: "m/1852'/1815'/0'/0/0",
prev_hash: '1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
prev_index: 0,
},
{
prev_hash: '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7',
prev_index: 0,
},
],
outputs: [
{
address: 'Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2',
amount: '3003112',
},
{
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '2000000',
tokenBundle: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
amount: '7878754',
},
],
},
],
},
],
fee: '42',
ttl: '10',
validityIntervalStart: '20',
certificates: [
{
type: CardanoCertificateType.STAKE_REGISTRATION,
path: "m/1852'/1815'/0'/2/0",
},
{
type: CardanoCertificateType.STAKE_DEREGISTRATION,
keyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711',
},
{
type: CardanoCertificateType.STAKE_DELEGATION,
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
pool: 'f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973',
},
],
withdrawals: [
{
path: "m/1852'/1815'/0'/2/0",
amount: '1000',
},
{
keyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711',
amount: '1000',
},
{
scriptHash: '29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd',
amount: '1000',
},
],
auxiliaryData: {
hash: 'ea4c91860dd5ec5449f8f985d227946ff39086b17f10b5afb93d12ee87050b6a',
},
mint: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
mintAmount: '7878754',
},
],
},
],
scriptDataHash: 'd593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02',
collateralInputs: [
{
path: "m/1852'/1815'/0'/0/0",
prev_hash: '1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
prev_index: 0,
},
],
collateralReturn: {
format: CardanoTxOutputSerializationFormat.ARRAY_LEGACY,
address:
'addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r',
amount: '1000',
tokenBundle: [
{
policyId: '95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39',
tokenAmounts: [
{
assetNameBytes: '74652474436f696e',
amount: '7878754',
},
],
},
],
},
totalCollateral: '1000',
referenceInputs: [
{
path: "m/1852'/1815'/0'/0/0",
prev_hash: '1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc',
prev_index: 0,
},
],
requiredSigners: [
{
keyPath: "m/1852'/1815'/0'/0/1",
},
{
keyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711',
},
],
additionalWitnessRequests: ["m/1852'/1815'/0'/0/2", "m/1854'/1815'/0'/0/0", "m/1855'/1815'/0'"],
protocolMagic: 764824073,
networkId: 1,
includeNetworkId: false,
});
Result
Since transaction streaming has been introduced to the Cardano implementation on Trezor because of memory constraints, Trezor no longer returns the whole serialized transaction as a result of the CardanoSignTransaction
call. Instead the transaction hash, transaction witnesses and auxiliary data supplement are returned and the serialized transaction needs to be assembled by the client.
{
success: true,
payload: {
hash: string,
witnesses: CardanoSignedTxWitness[],
auxiliaryDataSupplement?: CardanoAuxiliaryDataSupplement,
}
}
Example:
{
success: true,
payload: {
hash: "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
witnesses: [
{
type: CardanoTxWitnessType.BYRON_WITNESS,
pubKey: '89053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea',
signature:
'da07ac5246e3f20ebd1276476a4ae34a019dd4b264ffc22eea3c28cb0f1a6bb1c7764adeecf56bcb0bc6196fd1dbe080f3a7ef5b49f56980fe5b2881a4fdfa00',
chainCode:
'26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a635',
},
{
type: CardanoTxWitnessType.SHELLEY_WITNESS,
pubKey: '5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1',
signature:
'622f22d03bc9651ddc5eb2f5dc709ac4240a64d2b78c70355dd62106543c407d56e8134c4df7884ba67c8a1b5c706fc021df5c4d0ff37385c30572e73c727d00',
chainCode: null,
},
],
auxiliaryDataSupplement: {
type: 1,
auxiliaryDataHash:
'a943e9166f1bb6d767b175384d3bd7d23645170df36fc1861fbf344135d8e120',
cVoteRegistrationSignature:
'74f27d877bbb4a5fc4f7c56869905c11f70bad0af3de24b23afaa1d024e750930f434ecc4b73e5d1723c2cb8548e8bf6098ac876487b3a6ed0891cb76994d409',
},
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ripple: get address
Display requested address on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export.
const result = await TrezorConnect.rippleGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display first address of second ripple account:
TrezorConnect.rippleGetAddress({
path: "m/44'/144'/1'/0/0",
});
Return a bundle of ripple addresses without displaying them on device:
TrezorConnect.rippleGetAddress({
bundle: [
{ path: "m/44'/144'/0'/0/0", showOnTrezor: false }, // account 1
{ path: "m/44'/144'/1'/0/1", showOnTrezor: false }, // account 2
{ path: "m/44'/144'/2'/0/2", showOnTrezor: false }, // account 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.rippleGetAddress({
path: "m/44'/144'/0'/0/0",
address: 'rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string,
path: Array<number>,
serializedPath: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // account 1, address 1
{ address: string, path: Array<number>, serializedPath: string }, // account 2, address 2
{ address: string, path: Array<number>, serializedPath: string }, // account 3, address 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Ripple: Sign transaction
Asks device to sign given transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.rippleSignTransaction(params);
Params
path
— requiredstring | Array<number>
minimum length is3
. read moretransaction
- requiredObject
type of RippleTransactionchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Example
TrezorConnect.rippleSignTransaction(
path: "m/44'/144'/0'/0/0",
transaction: {
fee: '100000',
flags: 0x80000000,
sequence: 25,
payment: {
amount: '100000000',
destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws'
}
}
});
Result
{
success: true,
payload: {
serializedTx: string,
signature: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Tezos: get address
Display requested address on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export.
const result = await TrezorConnect.tezosGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is3
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display address of first tezos account:
TrezorConnect.tezosGetAddress({
path: "m/44'/1729'/0'",
});
Return a bundle of tezos addresses without displaying them on device:
TrezorConnect.tezosGetAddress({
bundle: [
{ path: "m/44'/1729'/0'", showOnTrezor: false }, // account 1
{ path: "m/44'/1729'/1'", showOnTrezor: false }, // account 2
{ path: "m/44'/1729'/2'", showOnTrezor: false }, // account 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.tezosGetAddress({
path: "m/44'/1729'/0'",
address: 'tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9',
});
// dont forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
address: string,
path: Array<number>,
serializedPath: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ address: string, path: Array<number>, serializedPath: string }, // account 1
{ address: string, path: Array<number>, serializedPath: string }, // account 2
{ address: string, path: Array<number>, serializedPath: string }, // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Tezos: get public key
Display requested public key on device and returns it to caller. User is presented with a description of the requested public key and asked to confirm the export.
const result = await TrezorConnect.tezosGetPublicKey(params);
Params
Exporting single public key
path
— requiredstring | Array<number>
minimum length is3
. read moreshowOnTrezor
— optionalboolean
determines if public key will be displayed on device.chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of public keys
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Example
Result with only one public key
TrezorConnect.tezosGetPublicKey({
path: "m/49'/1729'/0'",
});
Result with bundle of public keys
TrezorConnect.tezosGetPublicKey({
bundle: [
{ path: "m/49'/1729'/0'" }, // account 1
{ path: "m/49'/1729'/1'" }, // account 2
{ path: "m/49'/1729'/2'" }, // account 3
],
});
Result
Result with only one public key
{
success: true,
payload: {
publicKey: string,
path: Array<number>,
serializedPath: string,
}
}
Result with bundle of public keys
{
success: true,
payload: [
{ path, serializedPath, publicKey }, // account 1
{ path, serializedPath, publicKey }, // account 2
{ path, serializedPath, publicKey }, // account 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Tezos: Sign transaction
Asks device to sign given transaction. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.tezosSignTransaction(params);
Params
path
- requiredstring | Array<number>
branch
- requiredstring
operation
- requiredObject
type of TezosOperationchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Example
Sign transaction operation
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/10'",
branch: 'BLGUkzwvguFu8ei8eLW3KgCbdtrMmv1UCqMvUpHHTGq1UPxypHS',
operation: {
transaction: {
source: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
destination: 'tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9',
counter: 297,
amount: 200000,
fee: 10000,
gas_limit: 11000,
storage_limit: 0,
},
},
});
Sign the first transaction of the account with reveal operation
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/10'",
branch: 'BLGUkzwvguFu8ei8eLW3KgCbdtrMmv1UCqMvUpHHTGq1UPxypHS',
operation: {
reveal: {
source: 'tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1',
counter: 575424,
fee: 10000,
gas_limit: 20000,
storage_limit: 0,
public_key: 'edpkuTPqWjcApwyD3VdJhviKM5C13zGk8c4m87crgFarQboF3Mp56f',
},
transaction: {
source: 'tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1',
destination: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
counter: 575425,
amount: 100000,
fee: 10000,
gas_limit: 20000,
storage_limit: 0,
},
},
});
Sign origination operation
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BLHRTdZ5vUKSDbkp5vcG1m6ZTST4SRiHWUhGodysLTbvACwi77d',
operation: {
origination: {
source: 'tz1ckrgqGGGBt4jGDmwFhtXc1LNpZJUnA9F2',
manager_pubkey: 'tz1ckrgqGGGBt4jGDmwFhtXc1LNpZJUnA9F2',
delegate: 'tz1boot1pK9h2BVGXdyvfQSv8kd1LQM6H889',
balance: 100000000,
fee: 10000,
counter: 20450,
gas_limit: 10100,
storage_limit: 277,
script: '0000001c02000000170500036805010368050202000000080316053d036d03420000000a010000000568656c6c6f',
},
},
});
Sign delegation operation
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BMXAKyvzcH1sGQMqpvqXsZGskYU4GuY9Y14c9g3LcNzMRtfLzFa',
operation: {
delegation: {
source: 'tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9',
delegate: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
fee: 20000,
counter: 564565,
gas_limit: 20000,
storage_limit: 0,
},
},
});
Sign delegation from a KT account (smart contract with manager.tz
script)
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BMdPMLXNyMTDp4vR6g7y8mWPk7KZbjoXH3gyWD1Tze43UE3BaPm',
operation: {
transaction: {
source: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
destination: 'KT1SBj7e8ZhV2VvJtoc73dNRDLRJ9P6VjuVN',
counter: 292,
amount: 0,
fee: 10000,
gas_limit: 36283,
storage_limit: 0,
parameters_manager: {
set_delegate: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
},
},
},
});
Sign cancel delegation from a KT account (smart contract with manager.tz
script)
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BL6oaFJeEjtYxafJqEL8hXvSCZmM5d4quyAqjzkBhXvrX97JbQs',
operation: {
transaction: {
source: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
destination: 'KT1SBj7e8ZhV2VvJtoc73dNRDLRJ9P6VjuVN',
counter: 293,
amount: 0,
fee: 10000,
gas_limit: 36283,
storage_limit: 0,
parameters_manager: {
cancel_delegate: true,
},
},
},
});
Sign transaction operation from a KT account (smart contract with manager.tz
script) to a tz account (implicit account)
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BMCKRpEsFYQTdZy8BSLuFqkHmxwXrnRpKncdoVMbeGoggLG3bND',
operation: {
transaction: {
source: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
destination: 'KT1SBj7e8ZhV2VvJtoc73dNRDLRJ9P6VjuVN',
counter: 294,
amount: 0,
fee: 10000,
gas_limit: 36283,
storage_limit: 0,
parameters_manager: {
transfer: {
amount: 200,
destination: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
},
},
},
},
});
Sign transaction operation from a KT account (smart contract with manager.tz
script) to another KT account (smart contract with manager.tz
script)
TrezorConnect.tezosSignTransaction({
path: "m/44'/1729'/0'",
branch: 'BMCKRpEsFYQTdZy8BSLuFqkHmxwXrnRpKncdoVMbeGoggLG3bND',
operation: {
transaction: {
source: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
destination: 'KT1SBj7e8ZhV2VvJtoc73dNRDLRJ9P6VjuVN',
counter: 294,
amount: 0,
fee: 10000,
gas_limit: 36283,
storage_limit: 0,
parameters_manager: {
transfer: {
amount: 200,
destination: 'tz1UKmZhi8dhUX5a5QTfCrsH9pK4dt1dVfJo',
},
},
},
},
});
Result
{
success: true,
payload: {
signature: string,
sig_op_contents: string,
operation_hash: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Binance: get address
Display requested address derived by given BIP44 path on device and returns it to caller. User is presented with a description of the requested key and asked to confirm the export on Trezor.
const result = await TrezorConnect.binanceGetAddress(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreaddress
— optionalstring
address for validation (readHandle button request
section below)showOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
chunkify
— optionalboolean
determines if address will be displayed in chunks of 4 characters. Default is set tofalse
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Handle button request
Since [email protected] there is a possibility to handle UI.ADDRESS_VALIDATION
event which will be triggered once the address is displayed on the device.
You can handle this event and display custom UI inside of your application.
If certain conditions are fulfilled popup will not be used at all:
- the user gave permissions to communicate with Trezor
- device is authenticated by pin/passphrase
- application has
TrezorConnect.on(UI.ADDRESS_VALIDATION, () => {});
listener registered - parameter
address
is set - parameter
showOnTrezor
is set totrue
(or not set at all) - application is requesting ONLY ONE(!) address
Example
Display address of first Binance account:
TrezorConnect.binanceGetAddress({
path: "m/44'/714'/0'/0/0",
});
Return a bundle of Binance addresses without displaying them on device:
TrezorConnect.binanceGetAddress({
bundle: [
{ path: "m/44'/714'/0'/0/0", showOnTrezor: false }, // account 1, address 1
{ path: "m/44'/714'/1'/0/1", showOnTrezor: false }, // account 2, address 2
{ path: "m/44'/714'/2'/0/2", showOnTrezor: false }, // account 3, address 3
],
});
Validate address using custom UI inside of your application:
import TrezorConnect, { UI } from '@trezor/connect';
TrezorConnect.on(UI.ADDRESS_VALIDATION, data => {
console.log('Handle button request', data.address, data.serializedPath);
// here you can display custom UI inside of your app
});
const result = await TrezorConnect.binanceGetAddress({
path: "m/44'/714'/0'/0/0",
address: 'bnb1afwh46v6nn30nkmugw5swdmsyjmlxslgjfugre',
});
// don't forget to hide your custom UI after you get the result!
Result
Result with only one address
{
success: true,
payload: {
path: Array<number>, // hardended path
serializedPath: string,
address: string,
}
}
Result with bundle of addresses
{
success: true,
payload: [
{ path: Array<number>, serializedPath: string, address: string }, // account 1, address 1
{ path: Array<number>, serializedPath: string, address: string }, // account 2, address 2
{ path: Array<number>, serializedPath: string, address: string } // account 3, address 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Binance: get public key
Display requested public key derived by given BIP44 path on device and returns it to caller. User is presented with a description of the requested public key and asked to confirm the export.
const result = await TrezorConnect.binanceGetPublicKey(params);
Params
Exporting single address
path
— requiredstring | Array<number>
minimum length is5
. read moreshowOnTrezor
— optionalboolean
determines if address will be displayed on device. Default is set totrue
Exporting bundle of addresses
bundle
-Array
of Objects withpath
andshowOnTrezor
fields
Example
Displays public key derived from BIP44 path:
TrezorConnect.binanceGetPublicKey({
path: "m/44'/714'/0'/0/0",
});
Return a bundle of public keys without displaying them on device:
TrezorConnect.binanceGetPublicKey({
bundle: [
{ path: "m/44'/714'/0'/0/0", showOnTrezor: false }, // public key 1
{ path: "m/44'/714'/1'/0/0", showOnTrezor: false }, // public key 2
{ path: "m/44'/714'/2'/0/0", showOnTrezor: false }, // public key 3
],
});
Result
Result with only one public key
{
success: true,
payload: {
path: Array<number>,
serializedPath: string,
publicKey: string,
}
}
Result with bundle of public keys sorted by FIFO
{
success: true,
payload: [
{ path: Array<number>, serializedPath: string, publicKey: string }, // public key 1
{ path: Array<number>, serializedPath: string, publicKey: string }, // public key 2
{ path: Array<number>, serializedPath: string, publicKey: string } // public key 3
]
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Binance: sign transaction
Asks device to sign given transaction using the private key derived by given BIP44 path. User is asked to confirm all transaction details on Trezor.
const result = await TrezorConnect.binanceSignTransaction(params);
Params
path
— requiredstring | Array<number>
minimum length is5
. read moretransaction
- requiredObject
type of BinanceSDKTransactionchunkify
— optionalboolean
determines if recipient address will be displayed in chunks of 4 characters. Default is set tofalse
Transfer example
TrezorConnect.binanceSignTransaction({
path: "m/44'/714'/0'/0/0",
transaction: {
chain_id: 'Binance-Chain-Nile',
account_number: 34,
memo: 'test',
sequence: 31,
source: 1,
transfer: {
inputs: [
{
address: 'tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd',
coins: [{ amount: 1000000000, denom: 'BNB' }],
},
],
outputs: [
{
address: 'tbnb1ss57e8sa7xnwq030k2ctr775uac9gjzglqhvpy',
coins: [{ amount: 1000000000, denom: 'BNB' }],
},
],
},
},
});
Place order example
TrezorConnect.binanceSignTransaction({
path: "m/44'/714'/0'/0/0",
transaction: {
chain_id: 'Binance-Chain-Nile',
account_number: 34,
memo: '',
sequence: 32,
source: 1,
placeOrder: {
id: 'BA36F0FAD74D8F41045463E4774F328F4AF779E5-33',
ordertype: 2,
price: 100000000,
quantity: 100000000,
sender: 'tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd',
side: 1,
symbol: 'ADA.B-B63_BNB',
timeinforce: 1,
},
},
});
Cancel order example
TrezorConnect.binanceSignTransaction({
path: "m/44'/714'/0'/0/0",
transaction: {
chain_id: 'Binance-Chain-Nile',
account_number: 34,
memo: '',
sequence: 33,
source: 1,
cancelOrder: {
refid: 'BA36F0FAD74D8F41045463E4774F328F4AF779E5-29',
sender: 'tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd',
symbol: 'BCHSV.B-10F_BNB',
},
},
});
Result
{
success: true,
payload: {
signature: string,
public_key: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
firmwareUpdate
Installs a new firmware
const result = await TrezorConnect.firmwareUpdate(params);
Params
You either provide binary
binary
— requiredbytes
Or params
version
: requirednumber[]
version of firmware to be installedbtcOnly
:boolean
should install bitcoin only or regular firmwarebaseUrl
:string
url to look for releases.jsonintermediary
:boolean
should install intermediary firmware
Notable firmware ranges
It is not possible to install directly whatever version of a new firmware in all cases. Some specific firmware
versions might be installed only on device which already run a version which is not lower then x.y.z.
These rules are generally expressed by bootloader_version
and min_bootloader_version
in releases.json document
Here is a list of notable firmware ranges. 1.11.1
was the latest firmware at the time of writing this docs.
Firmware versions latest
- 1.7.1
- can be installed only on devices with firmware 1.6.2 and higher
Firmware versions 1.6.3
- 1.0.0
- can not be updated to the latest firmware using single
TrezorConnect.firmwareUpdate
call - if device has one of these firmwares,
TrezorConnect.firmwareUpdate
should be called withintermediary: true
which would install a special intermediary firmware first and automatically switch device into bootloader mode making it ready to accept another firmware update - alternatively, you may call
TrezorConnect.firmwareUpdate
withversion: '1.6.3'
and after succeeding retry this call withversion: '1.11.1'
Bootloader versions latest
- 1.8.0
- the first 256 byte (containing old firmware header) must sliced off the when installing a new firmware on bootloader versions in this range.
- TrezorConnect takes care of this automatically
Firmwares 1.7.2
- 1.6.2
- These can be updated to the latest firmware in one
TrezorConnect.firmwareUpdate
call (this is apparent from bullets above). - Old firmware headers MUST NOT be sliced off when installing new firmwares onto these versions as these versions have lower bootloader than 1.8.0.
- For the purpose of computing firmware hash of a newly installed firmware, we MUST slice off old firmware headers.
Example
TrezorConnect.firmwareUpdate({
version: '2.5.1',
});
Result
{
success: true,
payload: {
// challenge used to compute expected firmware hash. only with firmware 1.11.1 and 2.5.1 or higher
challenge: string,
// expected firmware hash computed from the installed binary. only with firmware 1.11.1 and 2.5.1 or higher
hash: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
getFirmwareHash
:note: Supported only by firmwares 1.11.1 and 2.5.1 or higher!
Params
challenge
— requiredstring
a random 32-byte challenge which is return form successful TrezorConnect.firmwareUpdate call
Example
Get hash of firmware installed in trezor device. It can be used to verify authenticity of firmware binaries intended to be installed.
TrezorConnect.getFirmwareHash({
challenge: '430d1ca5302edb40ac605e0ba61dc50928779336fdd02b688a833564c178307c',
});
Result
{
success: true,
payload: {
hash: string,
}
}
Error
{
success: false,
payload: {
error: string // error message
}
}
Suite docs
Send form active elements description
Outputs (BTC coins only):
- regular (transfer) output is set by default
- add OP_RETURN: if default output has any values then OP_RETURN is added as a second output otherwise will replace the first input
- remove OP_RETURN: if there is only 1 output (OP_RETURN) then switch to regular otherwise just remove it
- add recipient
- remove recipient
- Clear all
Address:
- on address input change
- on QR scan
- on Import (to be done)
Address errors:
- RECIPIENT_IS_NOT_SET (empty field)
- RECIPIENT_IS_NOT_VALID (not valid address)
- RECIPIENT_CANNOT_SEND_TO_MYSELF (XRP only: cannot send to myself)
Amount:
- on amount input change
- on Fiat input change
- on QR scan (optional if defined in QR code)
- on Import (to be done, optional if defined in file)
- IF sendmax is ON
- IF sendmax is set AND has second(or multiple) output(s): on second output Amount change
- IF sendmax is set: on every fee level change
- IF sendmax is set: on custom fee change
- IF sendmax is set: on BTC opreturn data changed
- IF sendmax is set: on ETH data changed
- (ETH only) IF sendmax is set AND switching between ETH (base currency) and TOKEN
Amount errors:
- AMOUNT_IS_NOT_SET (empty field)
- AMOUNT_IS_TOO_LOW (lower/equal than zero + ETH exception: 0 amount is possible ONLY for tx with DATA)
- AMOUNT_IS_BELOW_DUST lower than network dust limit
- AMOUNT_IS_NOT_ENOUGH (not enough funds on account)
- AMOUNT_NOT_ENOUGH_CURRENCY_FEE (ETH only: trying to send TOKEN without enough ETH to cover TX fee)
- AMOUNT_IS_MORE_THAN_RESERVE (XRP only: trying to spend the reserve)
- AMOUNT_IS_LESS_THAN_RESERVE (XRP only: trying to send less XRP than required reserve to the empty account)
- AMOUNT_IS_NOT_IN_RANGE_DECIMALS (amount with invalid decimal places)
- AMOUNT_IS_NOT_INTEGER (ERC20 only: token doesn't accept decimal places)
- REMAINING_BALANCE_LESS_THAN_RENT (solana only: account has to keep a minimal balance equal to rent)
Fiat:
- on fiat input change
- on Amount input change (any reason listed above)
- on Currency select change (recalculation)
- on Import (to be done, optional if defined in file AND amount is not defined in file)
Fiat errors:
- AMOUNT_IS_NOT_SET (empty field)
- AMOUNT_IS_TOO_LOW (lower than 0, 0 is still possible if recalculated amount is lower than 1 cent)
- AMOUNT_IS_NOT_IN_RANGE_DECIMALS (max. 2 decimals allowed)
Fee:
- on fee level click
- on custom fee level input change
- on BTC OP_RETURN data changed
- on ETH data changed
- switching from "regular" fee level to "custom" should set value from last selected fee
- IF fee level wasn't changed yet (normal) and there is not enough coins to satisfy normal level should be automatically switched to first possible (lower) level, either LOW or CUSTOM...
- last used fee level will be remembered globally for this coin
- estimated time is only available for BTC-like coins
Fee errors (custom level):
- CUSTOM_FEE_IS_NOT_SET (empty field)
- CUSTOM_FEE_IS_NOT_INTEGER (BTC and XRP: decimals not allowed)
- AMOUNT_IS_NOT_IN_RANGE_DECIMALS (ETH only: decimals are allowed but with max. 9 decimals - GWEI is not satoshi)
- CUSTOM_FEE_NOT_IN_RANGE (must be between minFee and maxFee specified in coins.json, in @trezor/connect)
(BTC only) OP_RETURN output:
- HEX field, (on the right) should be changed on every ASCII field (on the left) change
- ASCII field should be changed ONLY if HEX is valid, otherwise should be empty
OP_RETURN output errors:
- DATA_NOT_SET (empty fields)
- DATA_NOT_VALID_HEX (not valid hexadecimal)
- DATA_HEX_TOO_BIG (data size limited to 80 bytes)
(BTC only) Locktime:
Additional field in send form, activated by "Add locktime" option.
If the number is greater than 500000000 then it is a timestamp otherwise is block number
- on "add locktime" input change
- on RBF option enable
- should disable RBF option if set
- should disable BROADCAST option if set
Locktime errors:
- LOCKTIME_IS_NOT_SET
- LOCKTIME_IS_NOT_NUMBER
- LOCKTIME_IS_TOO_LOW (lower/equal zero)
- LOCKTIME_IS_NOT_INTEGER (decimals not allowed)
- LOCKTIME_IS_TOO_BIG (locktime larger than max unix timestamp * 2 = 4294967294)
(BTC only) RBF:
Additional checkbox in send form, since this could be only true/false there is no validation for that filed
(ETH only) Data:
Additional field in send form, activated by "Add data" option. Same behavior as BTC OP_RETURN output.
- HEX field, (on the right) should be changed on every ASCII field (on the left) change
- ASCII field should be changed ONLY if HEX is valid, otherwise should be empty
Data errors:
- DATA_NOT_VALID_HEX
- DATA_HEX_TOO_BIG (data size limit: 8192 bytes for protobuf single message encoding)
(XRP only) Destination tag:
Additional field in send form, activated by "Add destination tag" option It doesn't have impact on transaction itself (fee, amount etc)
Destination tag errors:
- DESTINATION_TAG_NOT_SET
- DESTINATION_TAG_IS_NOT_NUMBER
- DESTINATION_TAG_IS_NOT_VALID (decimals not allowed, in range: 0 - 4294967295)
Broadcast:
- toggle "Sign transaction" / "Send transaction" button
- "Review transaction" modal with different options at the last step (copy or download signed tx)
Drafts:
- draft should be saved on change of any field (if this field is valid)
- draft should be loaded after changing url (going back to send form from any other page)
Send RAW:
- Broadcast signed tx to the network regardless of tx OWNER, this tx doesn't have to be signed by currently selected account, only selected NETWORK matters
Precomposed transaction ("Total Sent" field)
- on load draft
- on address change
- on amount change
- on fee change
- on additional option change
Review modal
- can be cancelled at any time during signing
- mirroring data displayed on the device
- if there is BTC OP_RETURN data or ETH DATA present and those data are larger than 10 chars additional "expand button" will appear next to it
- (BTC only) Expandable "Transaction detail" section
- Regarding to BROADCAST option "Send transaction" or "Copy/download transaction" buttons are available on the last step
Send component architecture
Send component is a mix of hooks and redux.
Hooks are used to control and validate form fields using react-hook-form
library
Redux is used for persistent data like drafts, fiatRates, settings etc...
@wallet-views/send
Entry point of send form component.
Implements useSendForm
hook by passing Redux props to it.
@wallet-hooks/useSendForm
Hook and set of sub-hooks The whole logic of send form pre/post validation, working with field (recalculation), async transaction composing and sending
@wallet-actions/sendFormActions
Called from useSendForm
hook. A set of operations with @trezor/connect
and post validation (see: Transaction signing)
@wallet-reducers/sendFormReducer
Storing transaction drafts and temporary data used in TransactionReviewModal
Transaction compose process
Validation of react-hook-form
state occurs in React.useEffect
so potential errors are available after render tick.
In order to work with properly validated state useSendFormCompose.composeRequest
also needs to be handled in React.useEffect
after render tick.
Every networkType
has own sendFormActions.composeTransaction
method
sendFormActionsBitcoin
does calculation using@trezor/connect
sendFormActionsEthereum
does calculation locally, customfeePerUnit
is calculated ifethereumData
is usedsendFormActionsRipple
does calculation locally, additionalaccount.reserve
check on recipient address
PrecomposedLevel
are calculated for all possibleFeeLevel
at once.- if
FeeLevel
wasn't changed by the user and currentPrecomposedLevel
has error then tries to switch to a lower/custom possibleFeeLevel
- if
PrecomposedLevel
has error set this error inreact-hook-form
- if
PrecomposedLevel
hasset-max
set calculated amount inreact-hook-form
Transaction signing process
Every networkType
has own sendFormActions.signTransaction
method. This process is async may be interrupted by the user (ReviewTransaction cancel, disconnect device etc.)
Suite Desktop
Main differences between suite-web and suite-desktop builds
- @trezor/connect API
-
suite-web
@trezor/connect
is hosted at[url]/build/static/connect
and injected as an iframe into DOM.@trezor/connect
imports from@trezor/suite
are replaced to@trezor/connect-web
see webpack configiframe.postMessage/iframe.onmessage
interface is used as communication channel between suite and connect API. -
suite-desktop
@trezor/connect
is installed as regular node_module and works in nodejs context (electron main process).@trezor/connect
files are not hosted on the electron renderer context, there is no iframe or /build/static/connect dir.On the renderer context all
@trezor/connect
methods from are replaced by@trezor/ipc-proxy
methods. see index
- Firmware binaries
-
suite-web
newest firmware binaries are hosted at
[url]/build/static/connect/data/firmware
and they are downloaded using regularfetch
API. -
suite-desktop
firmware binaries are bundled as application resources in
bin
directory, full path depends on OS but it could be found on the as level asapp.asar
file, and they are downloaded usingfs.readFile
API. see @trezor/connect/src/utils/assets
- Trezor Bridge (trezord)
- Tor
App ID and name by environment
Environment | App ID | App name | User data dir name |
---|---|---|---|
production (codesign) | com.trezor.suite | Trezor Suite | @trezor/suite-desktop |
development (sldev) | com.trezor.suite.dev | Trezor Suite Dev | @trezor/suite-desktop-dev |
local | com.github.Electron | Trezor Suite Local | @trezor/suite-desktop-local |
Suite app name and ID are set by the environment so that Suite uses different user data dir and it's not mixed between environments. The main benefit is that you can switch back and forth between Suite dev versions without losing your remembered production wallets. One disadvantage of this solution is checking of other instance running is not so straightforward between environments.
Same concept (user data separated by environment) works on web out of the box (storage per domain name).
Debugging main process (Chrome dev tools)
Open chrome and go to chrome://inspect
In "Devices" tab make sure that "Discover network targets" is enabled and "localhost:5858" is added (use Configure button)
dev mode
modify packages/suite-desktop/package.json
"dev:run": "electron ."
// to
"dev:run": "electron --inspect=5858 ."
prod mode
Run production build with --inspect=5858
runtime flag
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
Available shortcuts:
name | commands |
---|---|
Reload app | F5, Ctrl+R, Cmd+R |
Hard Reload app | Shift+F5, Shift+Ctrl+R, Shift+Cmd+R |
Restart app | Alt+F5, Option+F5, Alt+Shift+R, Option+Shift+R |
Open DevTools | F12, Cmd+Shift+I,Ctrl+Shift+I, Cmd+Alt+I, Ctrl+Alt+I |
Runtime flags
Runtime flags can be used when running the Suite Desktop executable, enabling or disabling certain features. For example: ./Trezor-Suite-22.7.2.AppImage --open-devtools
will run with this flag turned on, which will result in opening DevTools on app launch.
Available flags:
name | description |
---|---|
--open-devtools | Open DevTools on app launch. |
--pre-release | Tells the auto-updater to fetch pre-release updates. |
--bridge-legacy | Use Legacy (trezord-go) Bridge implementation |
--bridge-dev | Instruct Bridge to support emulator on port 21324 |
--log-level=NAME | Set 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-write | Write log to disk |
--log-ui | Enables printing of UI console messages in the console. |
--log-file=FILENAME | Name of the output file (defaults to trezor-suite-log-%tt.txt ) |
--log-path=PATHNAME | Path for the output file (defaults to /logs subfolder of Suite data directory or current working directory) |
--enable-updater | Enables the auto updater (if disabled in feature flags) |
--disable-updater | Disables the auto updater (if enabled in feature flags) |
--updater-url=URL | Set custom URL for auto-updater (default is github) |
Debugging build
Linux
./Trezor-Suite-22.7.2.AppImage --log-level=debug
MacOS
./Trezor\ Suite.app/Contents/MacOS/Trezor\ Suite --log-level=debug
NixOS
appimage-run ./Trezor-Suite.AppImage --log-level=debug
Extract application
MacOS
npx asar extract ./Trezor\ Suite.app/Contents/Resources/app.asar ./decompiled
NixOS
Run application to get mount-id like:
Trezor-Suite.AppImage installed in ~/.cache/appimage-run/e4f67ae8624c4079527c669d8a3c4bbc1dd00b83b2e1d15807a5863b11bd4f38
npx asar extract ~/.cache/appimage-run/[mount-id]/resources/app.asar ./decompiled
How to create new package?
- Use
yarn generate-package @scope/new-package-name
- it will generate package boilerplate inscope/new-package-name
folder.
How to use this new package?
- Place this package to dependency field of package.json in package where you want to use it.
- Run
yarn refs
to generate tsconfig refs. - Run
yarn
to let yarn symlink this package.
Features
This directory contains description of various Trezor Suite features.
- coin-handler
- transactions export
- transactions search
- metadata labeling
- fiat rates
- guide
- localization
- messaging system
- feature flags
- desktop logger
- application log
- onboarding
Coin Protocol Handler
Trezor Suite, both on web and desktop, can handle opening of coin URLs in coin:address
and coin:address?amount=
formats. Currently all bitcoin-like coins schemes are supported (Applies for bitcoin-like coins supported by Trezor Suite). bitcoin:
is supported in both web and desktop app environment. Other coins, such as litecoin:
, dogecoin:
are supported only in desktop app. MDN docs
Behavior
Behavior on web
When opening Suite on web, in Firefox it will prompt the user to associate the opening of bitcoin:
URLs with Suite. On Chrome, you have to click on small icon in address bar and allow bitcoin:
URLs. By accepting, all bitcoin:
URLs in the browser will open Suite.
Behavior on desktop
By installing the desktop application, the bitcoin:
protocol handler will be automatically registered in the system. In Firefox, the user will have the choice to use Suite Desktop to open bitcoin:
URLs or browser if the protocol handler is also registered there. In Chrome, if the desktop application protocol handler is registered but web is not, Chrome will offer the desktop app. If both desktop and web app is registered, Chrome will open the web app without asking.
More apps with same handler (macOS)
If user has more desktop apps with same handler installed, the behavior is not defined. However, it looks like that the last installed application is launched.
Structure
The implementation adheres to the BIP29 specification (with the exception of the label
and message
parameters).
Example
bitcoin:39FpPxnR1gji9LJpNDZsB1bBeZttgTtg4L?amount=0.001 bitcoin:39FpPxnR1gji9LJpNDZsB1bBeZttgTtg4L
UI
When an app is opened using bitcoin:
protocol handler, it shows a notification informing a user about stored address and an amount ready to be filled into bitcoin send form.
Useful links
GitHub #3294 GitHub #5266 GitHub #5508
Handlers manipulation
Chrome
Navigate to chrome://settings/handlers
and remove handlers you want.
Firefox
Navigate to about:preferences#general
and scroll to Applications
. Find handler, click on chevron in Action
column, hit Application details
and remove apps you want.
Desktop app - macOS
Delete desktop app and use terminal open -a Xcode ~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist
. Find and delete bitcoin
and other coin handlers. If it still wants to open a desktop app. Find all occurrences of Trezor Suite.app
and delete them, even the .dmg
files in suite-desktop/build-electron
.
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.
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.
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.
Metadata (labeling)
Metadata is a feature that allows the user to associate persistent data with their wallets, accounts, receive addresses, and outputs. Trezor Suite refers to metadata as to "labeling" in the user interface.
For non-technical introduction, see Trezor Learn.
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
- Local file system (desktop only)
Google Drive specifics
Google Drive authentication has differing implementations for desktop and web version of Suite. For security reasons, Google does not allow the authorization code flow for web apps, thus only allowing the user of web Suite to log in via the implicit flow with an access token lasting for lasts one hour. Authorization code flow used in the desktop app leverages a refresh token to enable the user to stay logged in permanently while using desktop Suite. To implement this flow, we had to establish an authorization backend holding a client_secret
, see @trezor/auth-server. If the authorization via our backend fails for some reason, there is a fallback to the implicit flow in the desktop app. TODO: switch from the fallback flow to the authorization code flow as soon as possible so that the user does not have to re-authenticate every hour.
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. (Implementation not currently planned.)
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 the App
Settings related data is defined in suite/src/reducers/suite/metadataReducer which contains
{
enabled: bool,
initiating: bool,
provider: {
isCloud: bool,
type: "dropbox" | "google" | "fileSystem",
tokens: {
accessToken?: string,
refreshToken?: 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
}
Where metadata is set and displayed
- Wallet label is set in the modal where wallet is selected.
- Account label is set in account header.
- Receiving address label is set in the receive address list.
- Output label is set in send form address field, coin control or transaction history
Note that transaction history displays output label and address next to each other. If the output does not have a label, only the address is shown. If it does, the address is shown when the label is hovered. The address is displayed as follows:
- If a receive address has a label, its label is displayed.
- If a receive address has a label and it belongs to the same account, it is replaced by "Sent to myself".
- If an outgoing address belongs to another discovered wallet or account, it is replaced by the account label (and wallet label, if it is a different wallet).
- If none of the above is true, plain address is displayed.
Wallet and account labels can also be displayed in other places in Suite as read-only, e.g. in send form when sending to an address belonging to another discovered wallet or account.
Device name set in device settings is not part of metadata.
User stories
First time user:
- User opens App for the first time. Metadata is disabled. "Add label" buttons are present on mouse hover over labelable data.
- User clicks "Add label" button.
- Device metadata key is generated.
- Using device metadata key, account metadata keys are created.
- Open modal with metadata providers and connect.
- Fetch data from metadata provider and set interval for fetching data.
- Activate editable input.
Metadata enabled during discovery process:
Controlled by discoveryActions
and metadataMiddleware
- If passphrase is not used, device metadata key is generated before discovery process starts. (
discoveryActions
) - If passphrase is used, metadata key is generated either:
- in the middle of the discovery process after successfully receiving the first bundle of accounts and at least one account is not empty. (
discoveryActions
) - after passphrase confirmation process. (
metadataMiddleware
)
- in the middle of the discovery process after successfully receiving the first bundle of accounts and at least one account is not empty. (
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:
- sets metadata.enabled bool value
- if setting to false, it triggers removal of all metadata (including keys) from devices and accounts.
- if setting to false, disconnects metadata provider (Dropbox, Google Drive)
- there is a button "disconnect provider" which:
- triggers removal of all metadata values (excluding metadata keys) from devices and accounts. This way provider might be reconnected without reconnecting device
- disconnects metadata provider
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. ERC-20 tokens are not yet implemented.
Providers
- Blockbook: For all main networks (BTC, LTC, ETH + ERC-20 tokens, ...) except XRP, SOL + SOL tokens and ADA + ADA tokens
- CoinGecko: Used for XRP, SOL + SOL tokens and ADA + ADA 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 ERC-20 tokens
ERC-20 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 addTransaction
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 3-minute interval. If the rate is older then 10 minutes then it is refetched.
Current fiat rates for ERC-20 tokens
List of tokens is part of the account object (account.tokens
).
Fiat rates for ERC-20 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
. We don't fetch rates for tokens without definitions.
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 redux state then Suite won't fire new fetch.
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 history use useHistoricRate
in combination with historicRate
property:
<FiatValue
amount={targetAmount}
symbol={transaction.symbol}
historicRate={historicRate}
useHistoricRate
/>
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 fields 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.
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 a Suite user might be interested in and then provide it to the user directly in the app.
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 a format usable in the Suite app. This format 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 GITBOOK_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.)
How to update
Change GITBOOK_REVISION
constant in packages/suite-data/src/guide/constants.ts
to the new revision. Example: 40855097. That's it.
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 their CLI.
After strings have been translated we use Crowdin CLI again to download the translated json files to suite-data package. To finish the process these files need to be committed 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 prefixTR_
, or expanded variantTR_<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 adefaultMessage
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.
- Go to Settings in Suite
- Rapidly click on the "Settings" heading 5 times
- Click the three dot context menu on the right
- "Debug Settings" should've appeared. Click it. If "Debug Settings" hasn't appeared, repeat step 2.
- 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
With the automated CI job from GitHub.
Navigate to the Crowdin translations update action and trigger manual job with a base branch develop
Before triggering the job, make sure there is no pull request already opened with the title Crowdin translations update
Action will create a pull request with the title Crowdin translations update
, review it and merge.
Locally
All work could be done with shortcuts defined in package.json scripts section. In order to interact with Crowdin you need to ask the project owner for access token and either store it in your $HOME/.crowdin.yml
file:
'api_token': xxxx
or, alternatively, add it as an option for each called script:
yarn workspace @trezor/suite translations:download --token xxxx
Extract
To extract message definitions from Suite into master.json
file run:
yarn workspace @trezor/suite translations:extract
The newly created master.json
file is generated from messages.ts
and serves only as a base for translations in Crowdin, therefore it is not committed into Git repository.
Upload
To upload extracted master.json
file with updated 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.
Download
To download new translations from Crowdin run:
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
# Extract message definitions from Suite
yarn workspace @trezor/suite translations:extract
# Upload to sync the key set.
yarn workspace @trezor/suite translations:upload
# Download 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.
Message System
Message system was implemented to allow sending emergency messages to Trezor Suite app to a user with specific stack.
Notion for production deployment
Types of in-app messages
There are multiple 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 (e.g. settings page, banner in account page)
- feature
- disabling some feature with an explanation message
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 suite-common/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 minute. 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 30 seconds.
Schema
The configuration structure is specified in JSON file using JSON schema. The file can be found in suite-common/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 @suite-common/message-system msg-system-types
. A messageSystem.ts
file is created in suite-common/suite-types/src
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 @suite-common/message-system validate-config
script.
Signing
- Signing of the configuration file is performed:
- in CI job in
prebuild
phase for distribution and - manually by
yarn message-system-sign-config
(oryarn build:libs
) script for local development.
- in CI job in
- The results are saved into
suite-common/message-system/files
as two files:config.v1.jws
to be uploaded tohttps://data.trezor.io/config/$environment/config.vX.jws
config.v1.ts
to be bundled with application
- Development private key is baked into project structure together with public keys for both development and production.
- Production private key is available only on
codesign
branch in CI (both Gitlab and Github). - Development private key can be found in
suite-common/message-system/scripts/sign-config.ts
file, the public keys can be found inpackages/suite-build/utils/jws.ts
file.
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 and CI jobs has to be adapted. There are also few places where the version is defined statically so search for config.v1
over the whole project.
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.
- All values (except for revision in environment) are version definitions
- 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",
"chromeos": "*"
},
// revision is optional
"environment": {
"desktop": "<21.5",
"mobile": "!",
"web": "<22",
"revision": "7281ac61483e38d974625c2505bfe5efd519aacb"
},
"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" + "T1B1", "T" + "T2T1", "T2B1", "T3B1", "T3T1"
// in case of targeting "T1B1" or "T2T1", for backwards compatibility use old (1, T) and new naming (T1B1, T2T1 together in a new object
// in case of targeting "T2B1" in Suites before device release, please use all three "T2B1", "Safe 3" and empty string ""
"model": "T1B1",
/*
Beware
- firmware version in bootloader mode is unavailable on T1B1
- bootloader version is available only in bootloader mode
*/
"firmware": "2.4.1",
"bootloader": "2.0.4",
// Possible values: "*", "bitcoin-only", and "regular"
"variant": "bitcoin-only",
"firmwareRevision": "*",
"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 a user closes the message, it will never show again until the user clear app storage.
"dismissible": true,
/*
Variants:
- info (blue)
- warning (orange)
- critical (red)
*/
"variant": "warning",
// Options: banner, modal, context, feature
"category": "banner",
/*
- Message in language of Suite app is shown to a user.
- Currently 'en', 'es', 'cs', 'ru', 'ja' are supported.
- 'en-GB' is used for backward compatibility and should match value of 'en'.
*/
"content": {
"en-GB": "New Trezor firmware is available!",
"en": "New Trezor firmware is available!",
"de": "Neue Trezor Firmware ist verfügbar!"
},
// optional headline following the language structure of content
"headline": {
"en-GB": "Update your Trezor",
"en": "Update your Trezor",
"de": "Neue"
},
// 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
- anchor property can be used, see anchors.ts file
- external-link is url address
*/
"action": "internal-link",
// Route name or url address according to action.
"link": "settings-device",
"anchor": "@device-settings/firmware-version",
/*
- Label of call to action button shown to a user.
*/
"label": {
"en-GB": "Update now",
"en": "Update now",
"de": "Jetzt aktualisieren"
}
},
// Used only for modals. (To be implemented)
"modal": {
"title": {
"en-GB": "Update now",
"en": "Update now",
"de": "Jetzt aktualisieren"
},
"image": "https://example.com/example.png"
},
// Used only for context.
"context": {
"domain": [
"coins.receive",
"coins.btc"
]
}
// Used only for feature
"feature": [
{
"domain": "coinjoin",
"flag": false
}
]
}
}
]
}
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.
Updated config is automatically uploaded by CI job to the corresponding 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
- Config is fetched on load of application and is stored in Redux state. To be persisted between sessions, is is mirrored into IndexDB.
- Conditions of config are evaluated on specific Redux actions. See
messageSystemMiddleware.ts
file. - If conditions of a message satisfy the 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 it next time.
Followup
Ideas and non-critical bugs can be added to the followup issue.
Feature Flags
Feature flags allow us to enable and disable certain features at build time, helping us to produce specific builds for specific environments.
Workflow
All feature flags are located in suite-common/suite-config/src/features.ts
. To add a new flag, start by doing the following:
- Add your flag to the
FLAGS
constant and set its default value. When naming your flag, bear in mind the following conventions:- Always explain what the flag is about using a comment next to it.
- The name of the flag should always be in capitals.
- The name of the flag should never contain the world
enable
ordisable
because the name should always towards an enabled state. Its value should reflect whether the feature is enabled or not. - The name of the flag should never contain the word
flag
because it's inferred.
- (optional) You can override the flag for each environment (web, desktop) using their specific constants.
- Use the
isFeatureFlagEnabled
function from@suite-utils/features
to check if the flag is enabled or not. - Wrap the code you wish to control in a condition checking for your flag being enabled or not.
Example
import { isFeatureFlagEnabled } from '@suite-utils/features';
const main = () => {
alwaysRunning();
if (isFeatureFlagEnabled('LABELING')) {
myLabelingFeature();
}
};
Future evolutions
- Control feature flags at runtime.
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.
If you activate Debug menu, logging to file is automatically started. When you deactivate Debug menu, logging stopped if the app is not running with the command line flag. If you run the app with --log-write
flag and then activate the Debug menu, logging just continue with the same file.
API
Exported Types
LogLevel
Any of the following values:
- mute (0)
- error (1)
- warn (2)
- info (3)
- debug (4)
Options (all optional)
name | type | default value | description |
---|---|---|---|
colors | boolean | true | Console output has colors |
writeToConsole | boolean | true | Output is displayed in the console |
writeToDisk | boolean | false | Output is written to a file |
outputFile | string | 'trezor-suite-log-%tt.txt' | file name for the output |
outputPath | string | /logs subfolder in Suite data directory or CWD | path for the output |
logFormat | string | '%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
Expression | Example output | Description |
---|---|---|
%tt | 2021-01-19T11-07-40 | Date and time in filename friendly format |
%ts | 1611054460306 | Timestamp |
%dt | 2021-01-19T11:08:22.244Z | Date and time in ISO format (ISO 8601) |
logFormat
Expression | Example output | Description |
---|---|---|
%lvl | INFO | Level in letters and upper case |
%top | Example | Topic |
%msg | Example message | Message |
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 brackets ([, ]) and remove the comma (,) from the last message.
Onboarding
Incomplete developers guide to Onboarding in Trezor Suite
There are few different ways to trigger the onboarding process:
- Initial run
- Initial run is what we call a state when Suite is launched for the first time. It may also be triggered by clearing the app storage (flag is stored inside a reducer
suite.flags.initialRun
and saved to persistent storage). This is stored per device. If Suite detects initial run it'll automatically launch the onboarding.
- Initial run is what we call a state when Suite is launched for the first time. It may also be triggered by clearing the app storage (flag is stored inside a reducer
- Connecting an uninitialized device (without a seed)
- Suite will automatically launch the onboarding
- Wiping a device from Suite UI and proceeding with device setup
Prerequisites
Button Requests
Each device
object inside has its buttonRequests
array which gets populated through buttonRequestMiddleware
. It it basically a way for a device to request cooperation from the app.
It is used, for example, by firmware update flow for setting correct firmware.status
after device requested confirmation on the device:
case SUITE.ADD_BUTTON_REQUEST:
if (action.payload.code === 'ButtonRequest_FirmwareUpdate') {
draft.status = 'waiting-for-confirmation';
}
Another usage is in "Setup Pin" step, where the device, through button request, will let us know if user should enter pin for the first time or 2nd time (for confirmation). It is also leveraged outside of Onboarding every time user needs to enter a PIN. Thanks to these buttons we can reuse the same component that will adjust just based on these button requests.
In onboarding, we clear this array after each step (handled in buttonRequestMiddleware
).
Steps
- Welcome
- Firmware
- Note about normal and bootloader mode
- Firmware update or installation
- Device with older firmware installed
- Device with no firmware installed
- State of currently shipped devices, intermediary firmware
- Generating seed
- Backup seed
- PIN Setup
- Suite Settings (enabled coins, custom backends, TOR)
- Final step (device label, homescreen)
Welcome
Before a user connects a device
As a first step we prompt the user to connect his device in normal mode. In this step we handle various invalid device modes and problems with transport layer used to facilitate communication with the device (webUSB, Trezor Bridge).
List of valid states
- Waiting for a device
- We provide some troubleshooting tips and link to download Trezor Bridge as it may happen that user launched Suite for the first time without installing Trezor Bridge (only in Web environment)
- Device connected in normal mode
- Part of happy path. After the device is connected we proceed to Data analytics (only in initial run) or to Device security (genuinity) check
List of invalid states
Invalid device states:
- Device connected, but in bootloader mode
- Device connected, but unreadable
- Seedless device setup is not supported in Suite (not to be confused with regular device without seed).
Invalid transport states:
- No transport available (Trezor Bridge is not running)
- We need to provide link to download Trezor Bridge (only in Web environment)
Data analytics (only in initial run)
During the initial run we ask users to give their consent to collect and process anonymous data in order to help us improve the user experience.
Device security (genuinity) check
This sub step is adjusted based on a state of the device. We distinguish between device that was already used before (it has a firmware installed) and most likely unused device (no seed, no firmware).
Device without seed, no firmware installed
This should be the most common case in onboarding.
Scenario:
- New device bought from trusted seller
- User wiped the device (and firmware)
Security questions: Check hologram, verify seller, check package...
Primary action: Setup device (starts onboarding)
Secondary action: Contact support
Device without seed, firmware already installed
Scenario:
- User has wiped the device (without erasing a firmware)
- Device has been tampered with
Security questions: Have the user used the device before?
Primary action: Setup device (starts onboarding)
Secondary action: Contact support
Device with seed (thus also a firmware)
Scenario:
- User cleared app storage so the onboarding was started on initial run.
- Device has been tampered with
Security questions: Have the user used the device before?
Primary action: Setup device (starts onboarding)
Secondary action: Contact support
Firmware
To provide good user and dev experience firmware flow has its own reducer. It is shared between firmware flow used in onboarding and standalone modal for firmware update. Both flows,fw installation via onboarding and firmware update via standalone modal, reuse same components.
Active sub step of firmware step is stored in status
field. Explanation of each field and sub steps can be found in firmwareReducer.ts.
Note about normal and bootloader mode
Device can be connected in “normal” mode or in “bootloader" mode which you access by pressing left button (both buttons on old T1B1 fw) or swiping on touchscreen during connecting usb cable to the device. Before starting the installation process, user needs to disconnect the device and reconnect it in bootloader mode.
From the technical perspective, these two modes are seen as 2 different devices and there is no way we can tell that the device, which was reconnected in bootloader mode is indeed the same device which was before connected in normal mode. This basically means that if you are updating a firmware with a device connected via webUSB you will need to do pairing process twice.
When device is in bootloader mode device.features.major_version
, device.features.minor_version
, device.features.patch_version
is version of a bootloader, not a firmware.
Another interesting fact is that a device without installed firmware acts as it is in bootloader mode regardless of buttons you are or you are not pressing (device has always some bootloader installed).
Bootloader mode has another catch, not all device's features are accessible while a device is in this state. Such inaccessible fields are set to null
. For example, device.features.pin_protection
is set to null
, but that doesn't mean that device has this feature disabled. After reconnecting the same device in normal mode pin_protection
might be set true
(or false
).
This is the reason why, in Welcome step, we force the user to connect a device in normal mode first. Otherwise we wouldn't know what firmware version is installed or what firmware update we can offer.
Firmware installation
Device could be in various states when the user enters this step.
- Firmware not installed
- Fresh device (
device.firmware
set tonone
) - We don't need to prompt the user to switch to bootloader mode in this case.
- Fresh device (
- Firmware already installed and:
- Update available: It can be optional (skip button is present) or mandatory. Be aware that a device, while in bootloader mode, doesn’t report its fw version, only version of bootloader.
- Latest firmware already installed (just wiped seed)
Possible error states:
- Generic firmware installation fail
- User cancelled installation on a device
- Device disconnected during the process
- Some unexpected error
- Device is connected in bootloader mode from the start
- We will prompt the user to connect device in normal mode
- This won't happen in Onboarding so much, but it is handled as this firmware flow is used in standalone firmware update modal which can be triggered from Suite
- Device disconnected before firmware installation starts
- Prompt the user to reconnect the device
- Device disconnected after firmware installation starts
- Installation will fail with generic error
- This could happen when a cable is not connected properly and the device will disconnect during the process
Device with older firmware installed
If device is connected in normal mode and user proceeds by clicking “install firmware” we will ask the user to disconnect the device and reconnect it in bootloader mode (via a small modal popup).
After the device is reconnected we show a button to trigger an update process. Then the device will request a confirmation from the user. Only after the user confirms it the installation begins.
Device with no firmware installed
User proceeds by clicking "Install firmware" CTA. Since the device without firmware is always in bootloader mode we don't need any cooperation from the user. Device doesn't ask for confirming the installation of the fw. User of T1B1 is prompted to disconnect the device and reconnect it in normal mode. Newer device models automatically restart. Then we continue to the next step, (Generating seed)
State of currently shipped devices
Intermediary firmware
T1B1 devices with old bootloader cannot be upgraded to latest firmware directly. First we'll install so called intermediary firmware, which will bump bootloader to newer version. After installation is completed, the user will be asked to reconnect the device. Because intermediary firmware only bumps bootloader and doesn't install any firmware, device will be in bootloader mode regardless of how the user reconnects it (whether they press a button or not). Then it triggers an installation of subsequent firmware, which will be the latest firmware available. It will follow basically the same flow as with the first installation.
WebUSB
Support for the WebUSB came pretty late for T1B1 (bootloader 1.6.0 bundled with FW 1.7.1). Currently shipped devices won't support WebUSB out of-the-box and user won't be able to pair such device. In this case user needs to install Trezor Bridge. After finishing fw upgrade WebUSB support will be available.
Caveats
UI.FIRMWARE_PROGRESS
- Devices won’t dispatch any event after the user confirms the installation on a device. We only detect that the installation has started when we receive
UI.FIRMWARE_PROGRESS
which is triggered about 10 seconds too late on T1B1.
Remembered wallet, multiple devices
The onboarding inherited few bugs from its predecessor. After the installation of a firmware user is asked to reconnect his device (T1B1) or the device is auto restarted. To prevent Suite from selecting another device while the one we use was disconnected, we force remembering the device (and storing it to persistent storage). However this doesn't work in case of freshly unpacked device (or device with wiped fw), which are in bootloader mode from the start and cannot be "remembered".
When this happens Suite will try to select another available device, which will be the other connected device or remembered wallet and there is no way ho to switch the device back unless you restart the app/refresh the page.
To work around this in suiteActions.onHandleDisconnect
, before selecting the next active device, we check if we are in Onboarding (or standalone firmware update flow) and if so we won't allow switching selected device to different one.
Generating seed
User chooses between generating a new seed or seed recovery.
Generating new seed
- Single seed
- Shamir (not available on T1B1)
At first it might seem that both options are doing exactly the same, real difference between these two will present itself in Backup seed step
Recovery from mnemonic
T2T1 or newer
The entire process is done on device. All we need to do in Suite UI is to show generic "Confirm on your Trezor" bubble.
T1B1
For T1B1 there are two things the user needs to decide:
- Does the user want to recover from 12, 28 or 24 words?
- Does the user want to enter the seed by selecting words in Suite UI (Standard recovery) or he/she wants the most secure environment and will enter the words via keyboard matrix shown on the device (advanced recovery)?
Backup seed
User needs to confirm that the seed will be safe, there will be no digital copy of it. Then he can start the process on a device.
T2T1 or newer
The entire process is done on device. All we need to do in Suite UI is to show generic "Confirm on your Trezor" bubble.
T1B1
Process consists of clicking "Next" button too many times and writing down words displayed on a trusty seed card. During this process we will display "Confirm on Trezor" prompt and instructions.
This step is optional and can be skipped and finished later from Suite settings.
PIN Setup
After the user hits CTA button “Create PIN” we need to show confirmation prompt. It is handled by checking device.buttonRequests
to see if there is ButtonRequest_Other
(T1B1) or ButtonRequest_ProtectCall
. Yes, it is hacky. But thanks to clearing buttonRequests
array in each step of the onboarding it should be safe and presence of these requests should indeed indicate that a device is asking for a confirmation.
When the user hits cancel on a device, @SUITE/lock-device
is fired, buttonRequestsMiddleware
will intercept it and fire removeButtonRequests
action which clears the array resulting in cancelling confirmation prompt in the Onboarding UI.
Entering PIN
T1B1
After the user confirms setting new PIN on the device we'll receive UI.REQUEST_PIN
, which will be stored in modal
reducer (as every other request coming from a device). Based on this we display PIN matrix.
The user enters PIN twice, if there is a mismatch, process is stopped and an error shown with a button to try again.
T2T1 or newer
The entire process is done on device (including handling of mismatched pins). All we need to do in Suite UI is to show generic "Confirm on your Trezor" bubble.
Be aware that after the PIN is set, device auto-lock functionality gets activated (starting from some firmware version). Thus if it's taking the user too long to finish the onboarding process, the device will get auto locked. If in the final step, where the user can change device label and/or homescreen, the device is locked and will respond with a request to show a PIN matrix. This is handled globally, in UnexpectedStates component, for the whole onboarding flow.
Suite Settings
User can choose what coins should be enabled right from the start. There is also an option to enable TOR and set custom Blockbook backends. These settings mirror what is already set in Suite. If a user has used Suite before and changed its setting he/she will see these changed settings here as we don't want to reset user's settings just because he/she goes through onboarding process with another (or wiped) device.
Final step
The last step which contains basic device setup such as changing its label and homescreen.
Tests
This chapter contains information about tests.
@trezor/suite-web e2e tests
@trezor/suite-web 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, if not specified otherwise.
On Linux
Prerequisites
Steps
- Run
xhost +
to add yourself to the X access control list. - Run
docker/docker-suite-install.sh
. - 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).
- 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 while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))
- On NixOS: Make sure that docker is enabled in your configuration.nix:
virtualisation.docker.enable = true;
- On NixOS: Make sure that docker is enabled in your configuration.nix:
Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))
- Check the docker.sock permissions:
sudo chmod 666 /var/run/docker.sock
On MacOS (Intel)
Prerequisites
Steps
- Run XQuartz. Wait till it is launched. Leave it running in the background.
- In XQuartz settings go to Preferences -> Security and enable "Allow connections from network clients".
- 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.
- Run Docker and go to Preferences -> Resources -> Advanced and increase RAM to at least 4GB. Otherwise, the app during tests does not even load.
- In terminal window run
docker/docker-suite-install.sh
- In the terminal window, set two environment variables:
export HOSTNAME=`hostname`
export DISPLAY=${HOSTNAME}:0
- 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).
- 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).
On MacOS (ARM)
- currently, it is not possible to run E2E tests only within a Docker container on ARM mac. With
Trezor-user-env
run in Docker, it is possible to runSuite
andCypress
locally.
Prerequisites
- Docker
- XQuartz (to share your screen with Docker)
- Trezor user env
- No other instance of
Suite
ortrezord
service is running
Steps:
- Run XQuartz. Wait till it is launched. Leave it running in the background.
- In XQuartz settings go to Preferences -> Security and enable "Allow connections from network clients".
- 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.
- Run Docker and go to Preferences -> Resources -> Advanced and increase RAM to at least 4GB. Otherwise, the app during tests does not even load.
- In the terminal window, set two environment variables:
export HOSTNAME=`hostname`
export DISPLAY=${HOSTNAME}:0
- In terminal window, navigate to
trezor-user-env
repo root and run./run.sh
. - In another window, run web
Suite
withyarn suite:dev
. - In a third window, run
npx cypress open --project packages/suite-web/e2e --config 'baseUrl=http://localhost:8000'
.
Troubleshooting
Cypress could not verify that this server is running ...
- make sure that the localhost is actually running and is accessible via browser
tests fail at the very beginning on screen with "Use trezor here" button
- make sure that no other instance of
Suite
ortrezord
is running
- make sure that no other instance of
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 asdocker/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_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
@trezor/connect-popup
@trezor/connect-popup is end-to-end tested together with @trezor/connect-explorer using playwright test runner.
Playwright + NixOS
Before the first run or occasionally after the update of npm dependencies you will be asked to run playwright install
command:
Error: browserType.launch: Executable doesn't exist at .cache/ms-playwright/chromium-[revision]/chrome-linux/chrome
Please run the following command to download new browsers:
npx playwright install
Playwright requires specific revisions defined in node_modules/playwright-core/browsers.json
.
Unfortunately downloaded browsers are not compatible with NixOS.
Link your system browser instead of downloading:
FILE=path-from-the-error && mkdir -p "${FILE%/*}" && touch "$FILE" && ./nixos-fix-binaries.sh
Fixtures
Fixtures are located in packages/connect-popup
Test results
Checkout latest screenshots
Regtest
Regtest is a private blockchain which has the same rules and address format as testnet, but there is no global p2p network to connect to.
To use custom backend (electrum server) with bitcoind running in regtest mode you can use the docker container by running the command below:
bash docker/docker-regtest-electrum.sh
The previous command will initialize a new fresh regtest bitcoin blockchain with electrum server running and expose to the localhost at TCP port 50001.
In order to be able to use regtest electrum in suite it is required to configured the REGTEST coin with custom backend with URL below:
localhost:50001:t
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.
Front-End Build
The front-end build of Suite is handled by Webpack configurations inside the suite-build
package.
The folder structure is as follows:
- configs: Contains the Webpack configuration files.
base.webpack.config.ts
serves as a common base for all other configurations. The other files in this folder are project specific such asweb.webpack.config.ts
ordesktop.webpack.config.ts
forsuite-web
andsuite-desktop
respectively. - plugins: Contains custom Webpack plugins.
- utils: Contains various utils for the build scripts.
These Webpack configurations are using TypeScript and use the tsconfig.json
file at the root of the package. This is specified via the TS_NODE_PROJECT
environment variable to avoid any issues regardless of the location where the command is run.
The following commands are available in this package (using yarn run
at the root of the package or yarn workspace @trezor/suite-build run
at the root of the project):
Command | Description |
---|---|
dev:web | Runs a watch build of suite-web with development settings and serves it. |
build:web | Builds a production build of suite-web . |
dev:desktop | Runs a watch build of suite-desktop with development settings, serves it and runs the Electron wrapper. |
build:desktop | Builds a production build of suite-desktop . |
lint | Runs the linter on the package. |
type-check | Runs the TypeScript checker on the package. |
type-check:watch | Same as type-check but in watch mode. |
Aliases
Aliases for imports (for example @suite-utils/features
) are defined in the tsconfig.json
file at the root of the project, in the compilerOptions.paths
property. The values are processed at build time for the webpack configuration in order to properly resolve aliases.
Development on Windows
Thanks to Windows Subsystem for Linux (WSL), you can run Trezor Suite dev environment on a Windows machine.
Prerequisites
On Windows:
- Install an Ubuntu WSL2 (must be v2, you may upgrade existing v1 WSL to v2)
- Install USBIPD
In WSL:
- Run
sudo apt-get install build-essential
- Install these Electron dependencies for Linux
- Install udev rules as per the Trezor docs
Setup
Proceed with the general readme instructions.
Connecting USB device
On Windows, run usbipd list
, find the bus id of the Trezor device, e.g. 2-1
.
Then run:
usbipd bind --busid 2-1
usbipd attach --wsl --busid 2-1
In WSL, run lsusb
to confirm the device is visible.
Note: Without udev rules, the device will be visible by lsusb
, but not in the app.
# Trezor device naming in codebase
Development/Internal name consists of 4 keys
<product_class
> <platform
> <feature_class
> <generation
>
product_class
-'T'
for Trezor hardware walletplatform
-'1'
for STM32F207,'2'
for STM32F42xfeature_class
-'B'
for Buttons,'T'
for Touchgeneration
Official name | Development name |
---|---|
Trezor Model 1 | T1B1 |
Trezor Model T | T2T1 |
Trezor Safe 3 | T2B1, T3B1 |
Trezor Safe 5 | T3T1 |
- Trezor Safe 3 exists in two variants, depending on its chip. It was upgraded post-release.
Review Process
Same as for Trezor Firmware. Please see here.
Videos in Suite
Videos in Suite can be encoded in .mp4
container however we can also use WebM format which have benefits of smaller data footprint, better color accuracy and they also support transparency.
The designers should always provide .mov
file with appropriate quality, pixel dimensions and a alpha channel (if e.g. transparent background is needed).
The following process is tested on MacOS:
Prerequisites
Install encoder
brew install ffmpeg
Encode videos using command line (recommended)
WebM - currently supported by Chrome/Firefox
ffmpeg -i source.mov -pix_fmt yuva420p -an encoded_file.webm
Encode videos using desktop application
Example usage
Encoded video files can be then saved to suite-data
and linked using resolveStaticPath()
function.
<video loop autoPlay muted>
<source src={resolveStaticPath(`videos/onboarding/encoded_file.webm`)} type="video/webm" />
</video>
References
How to make HEVC, H265 and VP9 videos with an alpha channel for the web