Python libraries and CLI tools for Ledger embedded applications (Nano S+, Nano X, Stax, Flex, ...).
Originally meant to regroup LedgerComm, Ledgerblue and Ledgerctl, it currently provides:
| Module | CLI | Purpose |
|---|---|---|
ledgered.manifest |
ledger-manifest |
Parse and check an app ledger_app.toml manifest |
ledgered.binary |
ledger-binary |
Extract metadata from a compiled app ELF file |
ledgered.github |
List and filter LedgerHQ app repositories, read their content | |
ledgered.devices |
Ledger devices reference (names, resolution, ...) |
pip install ledgeredEvery Ledger embedded application provides a ledger_app.toml manifest at the root of its repository.
It describes how to build and test the application, and is used by the
reusable workflows and the VSCode extension.
[app]
build_directory = "./"
sdk = "C"
devices = ["nanox", "nanos+", "stax", "flex"]
[use_cases]
debug = "DEBUG=1"
test_with_feature_activated = "TEST_FLAG_TO_SET=1"
[unit_tests]
directory = "./unit-tests/"
[metadata]
author = "Ledger"
contact = "support@ledger.com"
compatible_wallets = ["Ledger Wallet"]
[pytest.standalone]
directory = "tests/"
[pytest.swap]
directory = "tests_swap/"
self_use_case = "test_with_feature_activated"
[pytest.swap.dependencies]
testing_with_latest = [
{url = "https://github.com/LedgerHQ/app-exchange", ref = "develop", use_case = "dbg_use_test_keys"},
]
testing_with_prod = [
{url = "https://github.com/LedgerHQ/app-exchange", ref = "master"},
]| Section | Required | Fields |
|---|---|---|
[app] |
yes | sdk (C or Rust), build_directory (where the Makefile / Cargo.toml is), devices |
[use_cases] |
no | <use_case> = "<options>": make variables for C apps (DEBUG=1), Cargo options for Rust apps. default is implicit (no option) |
[unit_tests] |
no | directory |
[metadata] |
no | author, contact (email address), required; publisher, support_url, compatible_wallets (list), optional. Unknown fields are rejected |
[pytest.<name>] |
no | directory (contains a conftest.py), self_use_case (use case to build the app with for these tests) |
[pytest.<name>.dependencies] |
no | <scenario> = [{url, ref, use_case}, ...]: apps to sideload for the tests |
Dependencies notes:
use_caserefers to the dependency's own manifest, and defaults todefault.- Dependencies are checked out in
<directory>/.dependencies/<repo_name>-<ref>-<use_case>.
Warning
Deprecated formats
- v1 used a single
[tests]section (unit_directory,pytest_directory) with[tests.dependencies]instead of[unit_tests]and[pytest.*]. It is still parsed.pytest_directoryis mandatory as soon as dependencies are declared. - The legacy Rust
[rust-app] manifest-path = "..."format is no longer supported. Use[app] sdk = "Rust"withbuild_directoryanddevicesinstead.
Manifest values take precedence over workflow inputs, except devices, which is overridden by the
run_for_devices input (tests may only run on a subset of devices). In
reusable_ragger_tests.yml,
pytest_directory takes precedence over the test_dir input.
ledger-manifest ledger_app.toml -os -od # outputs the SDK and the devices
ledger-manifest ledger_app.toml -c . # checks the manifest against the given directory
ledger-manifest app-boilerplate -u -od -j # fetches the manifest from GitHub, outputs as JSONSee ledger-manifest --help for all the outputs (build directory, use cases, tests directories and dependencies, ...).
Ledger app ELF files embed metadata (target, SDK, API level, ...) used by tools like
Speculos. ledger-binary extracts them:
$ ledger-binary build/stax/bin/app.elf
api_level 15
app_name Boilerplate
app_version 2.1.0
sdk_graphics bagl
sdk_hash a23bad84cbf39a5071644d2191b177191c089b23
sdk_name ledger-secure-sdk
sdk_version v15.1.0
target stax
target_id 0x33200004
target_name TARGET_STAXUse -j for a JSON-like output.
from ledgered.github import Condition, GitHubLedgerHQ
gh = GitHubLedgerHQ(auth=...) # same arguments as `github.Github`
apps = gh.apps.filter(archived=Condition.WITHOUT, plugin=Condition.WITHOUT, sdk=["rust"])
app = gh.get_app("app-boilerplate")
app.current_branch = "develop"
print(app.manifest.app.devices, app.variant_param, app.variants)