speculos
Rust wrapper for the Ledger Speculos device emulator.
Spawns the speculos Python binary as a child process, exposes its REST API for
reading the screen, and drives input (buttons on Nano, touch on Stax/Flex).
Prerequisites
You must build and install Speculos yourself. Follow the instructions in the upstream repo:
After building, the speculos command must be available on your PATH. This crate
does not bundle, download, or otherwise provide the emulator binary.
You also need an app .elf to run inside the emulator (typically built from a
Ledger app project, or downloaded from a Ledger app release).
Usage
Launch with auto-assigned ports
use ;
use Path;
let sim = launch?;
// Send your APDU traffic to sim.apdu_addr() with whatever transport you use.
let apdu = sim.apdu_addr;
// Drive the UI.
sim.press?;
sim.press?;
# Ok::
Specify ports, seed, etc.
use ;
use SocketAddr;
use Path;
let sim = launch_with?;
# Ok::
For a raw hex seed, prefix with hex: (Speculos's own convention):
# use SpawnOptions;
let opts = SpawnOptions ;
Read screen text
# use ;
# use Path;
# use Duration;
# let sim = launch?;
use Regex;
let ev = sim.wait_for?;
println!;
# Ok::
Touchscreen input (Stax / Flex)
# use ;
# use Path;
# let sim = launch?;
sim.tap?;
sim.drag?;
# Ok::
Screenshots
# use ;
# use Path;
# let sim = launch?;
sim.screenshot_to_file?;
# Ok::
Attach to an already-running speculos
use ;
let sim = attach?;
// Drop will NOT kill the external process.
# Ok::
Configure from environment
Speculos::from_env() reads:
| Variable | Required | Notes |
|---|---|---|
SPECULOS_ELF |
yes | Path to the app .elf. |
SPECULOS_MODEL |
yes | nanox/nanosp/stax/flex (case-insensitive). |
SPECULOS_BIN |
no | Use a non default speculos executable |
SPECULOS_APDU |
no | host:port for APDU socket. Free if unset. |
SPECULOS_API |
no | host:port for REST API. Free if unset. |
SPECULOS_SEED |
no | Forwarded to --seed |
SPECULOS_APDU and SPECULOS_API must both be set or both unset.
Override the speculos binary
By default the crate spawns speculos from PATH. To use a different
executable (absolute path, alternate name, wrapper script), set
SPECULOS_BIN. This applies to every launch entry point.
Models
| Variant | Input | Screen |
|---|---|---|
Model::NanoX |
buttons | 128x64 |
Model::NanoSP |
buttons | 128x64 |
Model::Stax |
touchscreen | 400x672 |
Model::Flex |
touchscreen | 480x600 |
press() is buttons-only; tap(), drag(), press_at(), release_at(),
move_to() are touchscreen-only. Calling the wrong family returns
Error::TouchscreenOnly or Error::ButtonsOnly.
Lifetime
Speculos::launch* owns the child process. Dropping the handle kills it.
Speculos::attach does not, so dropping leaves the process alive.
If port selection races (another process grabs the port between pick_free_port and
Speculos's bind), launch_with retries up to three times with fresh ports, as long
as at least one port was auto-picked.