Explain the design rationale and domain concepts behind the Ledger Device Management Kit (DMK) API, including Clear Signing, Secure Channel, Ledger OS (BOLOS), Device Actions vs Commands, sessions, transports, derivation paths, and Genuine Check. Use when a developer asks 'what is X?' about a DMK concept, 'why does X work this way?' about an API design decision, or 'what's the difference between X and Y?' for DMK components.
93
—
Does it follow best practices?
Impact
100%
1.12xAverage score across 3 eval scenarios
Advisory
Suggest reviewing before use
This skill explains the why behind DMK API design decisions — domain model, security constraints, and trade-offs — so developers understand the reasoning, not just the calls. It is also loaded as a connector by ledger-dmk-implementation/SKILL.md when a term or concept needs clarification during implementation.
Blind signing means the user sees raw hex bytes on the Ledger screen — a long, unreadable string they cannot verify. They are asked to approve something they cannot meaningfully review.
Clear Signing means the user sees human-readable transaction details on the device screen — recipient address, amount, token name, contract name, fee. They can verify what they are actually approving before pressing the button.
The device screen is the only trusted display. A compromised host (malware, phishing site) can show the user anything on the browser or desktop UI. The device screen cannot be spoofed. Clear Signing makes the device screen meaningful — Blind Signing makes it useless.
The DMK works fully without Clear Signing — it is an enhancement, not a prerequisite. To enable it for Ethereum:
@ledgerhq/context-module must be installed regardless — it is a mandatory peer dependency of the ETH signer kit and handles fetching metadata (ABIs, token info, NFT details) the device needs to display human-readable fields.originToken is an optional partner token passed to SignerEthBuilder. Without it, the signer works but the device shows raw hex — the experience silently degrades to blind signing with no runtime error. To obtain a token, enroll in Ledger's partner program: https://developers.ledger.com/docs/clear-signing/for-wallets.// Clear Signing enabled (requires partner token):
new SignerEthBuilder({ dmk, sessionId, originToken: "your-partner-token" }).build();
// Blind signing — works but users see raw hex on device:
new SignerEthBuilder({ dmk, sessionId }).build();For Bitcoin and Solana, Clear Signing is handled at the app level — no originToken is required, but the Ledger app on device must support the transaction type.
originToken is optional — integrate and ship without it, then enroll in Ledger's partner program when ready to enable Clear Signing in production@ledgerhq/context-module when using the ETH signer kit, even during developmentcheckOnDevice: true for getAddress() flows — this is the address verification equivalent of Clear Signing for receive flowsA Secure Channel is an encrypted, authenticated connection between the Ledger device and Ledger's HSM (Hardware Security Module) backend. It allows the backend to perform privileged operations on the device that cannot be done locally.
| Operation | Requires secure channel |
|---|---|
GenuineCheckDeviceAction | Yes |
ListInstalledAppsDeviceAction | Yes |
InstallAppDeviceAction | Yes |
UninstallAppDeviceAction | Yes |
| All signer operations (sign, get address) | No |
OpenAppDeviceAction | No |
wss://manager.live.ledger.comAllowSecureConnection on the device screen — they must physically approve the connectionThe DMK exposes two levels of device interaction:
Commands (dmk.sendCommand()) are low-level, single APDU round-trips. They send one instruction to the device and return a result synchronously. No user interaction is handled — the caller is responsible for everything else.
Device Actions (dmk.executeDeviceAction(), or signer kit methods) are high-level orchestrated flows. They handle the full sequence: unlock detection, app opening, user confirmation prompts, retries, and terminal state emission — all through a single observable.
| Use case | Use |
|---|---|
| Signing a transaction, getting an address | Signer kit device action (via signer builder) |
| Genuine check, app install/uninstall | dmk.executeDeviceAction() with the appropriate DeviceAction class |
| Opening an app with user confirmation UI | OpenAppDeviceAction via dmk.executeDeviceAction() |
| Checking firmware version, getting current app name | sendCommand() — simple, no user interaction needed |
| Custom/unsupported chain, raw APDU | sendCommand() with ApduBuilder — only when no signer kit exists |
Without device actions, every developer would have to re-implement:
ConfirmOpenApp promptDevice actions encapsulate all of that. The observable they return emits states that map directly to UI prompts — UserInteractionRequired.SignTransaction means "show the user a prompt", DeviceActionStatus.Completed means "done, extract output".
A session represents an active USB/BLE connection to a specific Ledger device. It is created by dmk.connect() and destroyed by dmk.disconnect(). The sessionId is an opaque string that identifies the session within the DMK instance.
sessionId works with ETH, BTC, SOL, and any other signer without reconnecting.A session is valid until:
dmk.disconnect({ sessionId }) is calledDisconnected)Do not try to reconnect using an existing sessionId — once Disconnected, start a new flow from startDiscovering().
The DMK polls the device periodically to keep session state up to date (current app, device status). This is the session refresher. In multi-tab scenarios where two instances share the same physical device, disable it to avoid conflicting polls:
dmk.connect({ device, sessionRefresherOptions: { isRefresherDisabled: true } });The transport layer handles the physical communication protocol between the host and the Ledger device. The DMK's API is identical regardless of transport — only the initialization differs.
| Transport | Protocol | Environment |
|---|---|---|
| WebHID | USB HID, 64-byte frames | Browser (Chromium only) |
| WebBLE | Bluetooth GATT | Browser |
| Node-HID | USB HID via native addon | Node.js, Electron |
| React Native HID | USB HID | React Native |
| React Native BLE | Bluetooth | React Native |
| Speculos | TCP socket to emulator | Development/CI only |
WebHID requires:
startDiscovering() and connect() must be called from a button click or equivalent. Silent failure if called on page load.Each DeviceManagementKitBuilder takes one or more transports. In practice, use one. The DMK will attempt transports in registration order.
A derivation path is a sequence of indices that specifies which key to derive from the root seed stored on the device. Given the same seed phrase, the same path always produces the same key — and a different path produces a completely different key with no runtime error.
There is no "wrong path" from the device's perspective — every path produces a valid key. If a user enters a path that differs by even one segment, they get a different address. Funds sent to that address are inaccessible unless the correct path is known. This is a silent, unrecoverable mistake.
Paths must be hardcoded as constants in application code, matching the standard your users' wallets use.
m/ prefix. DerivationPathUtils.splitPath parses segments separated by /. The leading m is not a valid segment — parseInt("m") returns NaN and throws "invalid number provided". Use "44'/60'/0'/0/0" not "m/44'/60'/0'/0/0".' suffix (e.g. 44'). This is shorthand for adding 0x80000000 to the index.account selects the wallet (most users use 0). index selects the address within that wallet.| Chain | Ledger Live path | Notes |
|---|---|---|
| Ethereum | 44'/60'/0'/0/0 | BIP44, account 0, index 0 |
| Bitcoin Native SegWit | 84'/0'/0' | BIP84, passed to wallet policy |
| Solana | 44'/501'/0'/0' | Community standard |
| Cosmos | 44'/118'/0'/0/0 | BIP44 |
For unlisted chains: SLIP-0044 defines coin types (github.com/satoshilabs/slips/blob/master/slip-0044.md).
BOLOS (Blockchain Open Ledger Operating System), now known as Ledger OS, is Ledger device's operating system. When no app is open and the device is at the home screen (dashboard), state.currentApp.name returns "BOLOS".
This is how you detect that no app is open:
const isOnDashboard =
state.currentApp.name === "BOLOS" || state.currentApp.name === "Dashboard";You do not need to detect this explicitly when using signer kit device actions — they handle app switching automatically. It is useful for pre-operation UX feedback ("no app open, the correct app will open when you proceed").
A genuine check verifies that a physical Ledger device is authentic hardware manufactured by Ledger, not a counterfeit. It works by having the device prove possession of a private key whose corresponding certificate was signed by Ledger's root CA, verified through the Secure Channel.
It returns output.isGenuine: boolean.
When to use it: In high-security flows where device authenticity must be established before proceeding — for example, an enterprise key management setup or a first-time device onboarding flow. It is not required for routine signing operations.
What it does not prove: It does not prove the device hasn't been tampered with after manufacture, or that the seed phrase is correctly backed up.
Ledger Live is the previous name for Ledger Wallet — they refer to the same desktop and mobile application. Documentation, skill files, and SDK references may use either name; treat them as identical.
App names used in device actions (OpenAppDeviceAction, InstallAppDeviceAction, UninstallAppDeviceAction) must match exactly what the Ledger app catalog uses — the same names shown in Ledger Wallet. A typo or casing difference causes a silent failure or a cryptic error with no indication of what went wrong.
c9739bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.