Trezor Suite documentation
This documentation can also be found at docs.trezor.io/trezor-user-env where it is available in a HTML-built version compiled using mdBook.
Controller
General information
- Always up-to-date info with all the implementation details can be found in src/controller.py.
- Controller runs on port 9001.
- Requests must be in JSON structure, command is specified in the
type
key, e.g.{"type": "ping"}
. - All arguments (if needed) need to be specified in the same root JSON, e.g.
{"type": "emulator-start", "version": "2.4.0"}
. - It is beneficial to always include
id
integer in the request, as it is being sent back in the response, and these two can be matched. - Response is always in JSON format, includes boolean
success
key, indicating if everything went as expected, e.g.{"success": False, "error": "Unknown command - xyz"}
. - Response contains a confirmation of what happened in the
response
key, e.g.{"success": True, "response": "Emulator 2.4.0. started"}
.
Supported commands
-
ping
- response:
{"response": "pong"}
- response:
-
log
- action: log the supplied text to be preserved in debugging.log (e.g. for auditing purposes)
- arguments:
- text:
str
- text:
-
background-check
- action: check current status of bridge and emulator
- response:
{"bridge_status": bool, "emulator_status": bool}
-
emulator-start
- action: start the specified emulator (identified by model + version) (and if one already runs, kills it)
- arguments:
- model:
str
(enum of new model names -["T1B1", "T2T1", "T2B1", "T3T1", "T3W1"]
) - needs to be supplied - version:
str
(1.9.4, 2.4.0., etc.) - default is the latest version from master/main branch (-main
)-latest
can be used to get the latest released version of that model (by release tag, e.g. 2.9.0)
- wipe:
bool
(default=False) whether to delete the emulator profile before starting it - output_to_logfile:
bool
(default=True) whether the debug output should go to a logfile - otherwise it goes to stdout - save_screenshots:
bool
(default=False) whether to save screenshots to enable calling emulator-get-screenshot
- model:
-
emulator-start-from-url
- action: downloads emulator from specified URL and runs it
- arguments:
- url:
str
from where to download the emulator - model:
str
which emulator it is -["T1B1", "T2T1", "T2B1", "T3T1", "T3W1"]
- wipe:
bool
(default=False) whether to delete the emulator profile before starting it - output_to_logfile:
bool
(default=True) whether the debug output should go to a logfile - otherwise it goes to stdout - save_screenshots:
bool
(default=False) whether to save screenshots to enable calling emulator-get-screenshot
- url:
-
emulator-start-from-branch
- action: downloads latest emulators for a specified firmware branch and runs it
- arguments:
- branch:
str
exact name of the firmware branch - model:
str
which emulator it is -["T1B1", "T2T1", "T2B1", "T3T1", "T3W1"]
- btc_only:
bool
(default=False) whether to get btc-only version of the emulator - wipe:
bool
(default=False) whether to delete the emulator profile before starting it - output_to_logfile:
bool
(default=True) whether the debug output should go to a logfile - otherwise it goes to stdout - save_screenshots:
bool
(default=False) whether to save screenshots to enable calling emulator-get-screenshot
- branch:
-
emulator-stop
- action: stop the emulator
-
emulator-setup
- action: perform the emulator setup
- arguments:
- all appropriate to the
load_device()
function intrezorlib
- mnemonic:
str
- pin:
str
- passphrase_protection:
bool
- label:
str
- needs_backup:
bool
(default=False)
- all appropriate to the
-
emulator-press-yes
- action: press yes button on the emulator
-
emulator-press-no
- action: press no button on the emulator
-
emulator-input
- action: enter a string into a field on the emulator (such as passphrase entry)
- arguments:
- value:
str
- value:
-
emulator-click
- action: click on a specified pixel coordination (x, y) on emulator
- arguments:
- x:
int
- y:
int
- x:
-
emulator-read-and-confirm-mnemonic
- action: simulates the Single backup process
-
emulator-read-and-confirm-shamir-mnemonic
- action: simulates the Shamir backup process for chosen amount of shares and threshold
- arguments:
- shares:
int
(default=1) - threshold:
int
(default=1)
- shares:
-
emulator-allow-unsafe-paths
- action: allow unsafe path on emulator
-
emulator-select-num-of-words
- action: set the number of seed words
- arguments:
- num:
int
- num:
-
emulator-swipe
- action: peform swipe on the device
- arguments:
- direction:
str
("up", "down", "right", "left")
- direction:
-
emulator-wipe
- action: wipe the emulator
-
emulator-apply-settings
- action: apply settings on emulator
- arguments:
- all appropriate to the
apply_settings()
function intrezorlib
(all of them are optional) - language:
str
- label:
str
- use_passphrase:
bool
- homescreen:
str
- auto_lock_delay_ms:
int
- display_rotation:
int
- passphrase_always_on_device:
bool
- safety_checks:
int
- all appropriate to the
-
emulator-reset-device
- action: reset the device
- arguments:
- use_shamir:
bool
(optional) - whether to use Shamir backup (SLIP39) - default is False (BIP39 will be used)
- use_shamir:
-
emulator-get-screenshot
- action: get current screen encoded as base64
- response:
{"response": str}
-
emulator-get-debug-state
- action: get current debug state, for example to check what is on the screen
- response:
{"response": dict}
- example responses:
- T1:
{... "recovery_fake_word": "", "recovery_word_pos": 18, ...}
- situation while doing T1 recovery -
recovery_word_pos
says that 18th word is currently requested
- situation while doing T1 recovery -
- TT:
{ ... "layout_lines": ["RecoveryHomescreen", "Select number of words", ""], ...}
layout_lines
showing the current content of the screen
- T1:
-
emulator-get-screen-content
- action: get current screen content
- response:
{"response": {"title": str, "body": str}}
- example response:
{'title': 'Create wallet backup', 'body': 'Your wallet backup contains 12 words in a specific order.'}
-
bridge-start
- action: start the specified version of bridge (only if it is not already running)
- arguments:
- version:
str
(2.0.27, 2.0.31, etc.) - defaults to the latest available one - output_to_logfile:
bool
(default=True) whether the debug output should go to a logfile, otherwise it goes to stdout
- version:
-
bridge-stop
- action: stop the bridge
-
regtest-mine-blocks
- action: mine amount of blocks, optionally to a specified address
- arguments:
- block_amount:
int
- address:
str
(optional) - defaults to the wallet's own address
- block_amount:
-
regtest-send-to-address
- action: send amount of BTC to address (also mines a new block to confirm the transaction)
- arguments:
- btc_amount:
float
- address:
str
- btc_amount:
-
exit
- action: stop the controller
Trezor-user-env tools
tools directory is a collection of small scripts not used in production, but available for developers working with/on tenv
.
delete_red_square.py
- given a picture path as a script argument, deletes the red square from the right top of the picture
- useful for removing the sign of debuglink
ws_client.py
- can connect to the local controller/websocket and send it any message with arguments, while receiving and showing the answer
- useful for testing/developing the controller commands
Development
How to develop
In case you need to modify something in trezor-user-env you have two options.
Natively in NixOS
If you are using NixOS you can do the changes locally and then run the controller yourself. Run it via nix-shell --run 'poetry run python src/main.py'
(or just python src/main.py
in Poetry
shell). Make sure you have run src/binaries/{firmware,trezord-go}/bin/download.sh
beforehand otherwise you'll have old binaries.
This is suitable for smaller changes or things you can check via the HTML dashboard easily. However, if you are adding some functionality to trezor-user-env mainly because of Suite end-to-end tests, it is probably better to go the CI way (below). Otherwise you would need to run the whole Suite test suite locally.
Let CI do it
The simpler but less flexible way is to let the GitHub Actions build it. You can create a branch, commit your changes and then push them. The GH Actions will build it for you and tag the appropriate docker image as test
. You can then modify all scripts/commands and use ghcr.io/trezor/trezor-user-env:test
instead of ghcr.io/trezor/trezor-user-env
which defaults to the latest
tag which equals trezor-user-env's master. Suite's docker-compose files in the docker
subdirectory are the place where you want to change this.
Local development against Suite end-to-end tests
NOTE: The steps below are all dependant on Docker containers. It could be even better to be able to avoid the Docker completely - just running trezor-user-env on localhost and connecting Suite tests to it - but hard to say how to do it, all my experiments failed. Please feel free to improve the solution.
It may be helpful to run Suite e2e tests locally against the local trezor-user-env state, to avoid the need to involve the CI (Gitlab jobs) and the connected pain of merging changes into one branch just to test them.
Suite preparation steps:
- this and this Suite guides should be followed for local Suite setup
- modifying trezor-user-env-unix image in
docker/docker-compose.suite-test.yml
- change it totest-user-env
(or whatever the local image name will be)
trezor-user-env
steps:
- creating two new Dockerfiles, that are not a part of the repository
Dockerfile_base
- exact copy of Dockerfile, but without CMD at the end (serves as a base image for the Dockerfile below, to avoid building it from scratch every time)Dockerfile_final
- starting from the base image and just copying all the files into it and running the server
- creating a base image -
docker build -f docker/Dockerfile_base -t test-user-env-base .
- while not satisfied:
- modify local user-env code
- build a runnable image -
docker build -f docker/Dockerfile_final -t test-user-env .
- run the Suite test container application -
docker/docker-suite-test.sh
(with the appropriate image name in above-mentioned Suite .yml file, instead of the default one) - observing traffic in debugging.log file in the running
test-user-env
container -docker exec $(docker ps | grep 'test-user-env' | awk {'print $1'}) tail -f logs/debugging.log
- choosing some test scenario from Cypress window