Skip to Content
📢 Breaking change: Applications using LedgerJS for transport implementation should migrate to the Device Management Kit (DMK). Learn more.
DocumentationDevice interactionMobile Native DMKBeginner's GuidesExchange data with the device

Exchange data with the device

In this guide, you’ll learn how to send commands to a connected Ledger device and process the responses using the Device Management Kit (DMK). By the end, you’ll understand multiple ways to communicate with your device.

What you’ll learn

  • How to send low-level APDU commands to a device
  • How to use pre-defined commands for common operations
  • How to work with Device Actions for complex flows

Prerequisites

Before starting, make sure you have completed:

You should have a working DMK instance and a connected device with a valid deviceUid.

Step 1: Sending an APDU

APDU (Application Protocol Data Unit) commands are the low-level protocol used to communicate with Ledger devices. While it is generally recommended to use higher-level abstractions, you can send raw APDUs when needed.

import com.ledger.devicemanagement.api.apdu.ApduPayload val apduPayload: ApduPayload = /* build your APDU payload */ val result: DeviceOperationResult<ByteArray> = dmk.sendApdu(deviceUid, apduPayload) when (result) { is DeviceOperationResult.Success -> { val responseBytes: ByteArray = result.value } is DeviceOperationResult.Failure -> { when (result.reason) { DeviceOperationFailureReason.DeviceLocked -> { /* Device is locked */ } DeviceOperationFailureReason.DeviceBusy -> { /* Device is busy */ } DeviceOperationFailureReason.NoResponse -> { /* No response from device */ } else -> { /* Handle other failures */ } } } }

Step 2: Using pre-defined commands

For most common operations, the DMK provides pre-defined commands. executeCommand handles building the APDU, sending it to the device, and parsing the response.

⚠️

Most commands return DeviceOperationResult.Failure if the device is locked or busy. Check the device state with observeDeviceState before sending commands.

Opening an app

import com.ledger.devicemanagement.api.command.openapp.OpenApplicationCommand val command = OpenApplicationCommand(name = "Ethereum") val result: DeviceOperationResult<Unit> = dmk.executeCommand(deviceUid, command) when (result) { is DeviceOperationResult.Success -> println("App opened successfully") is DeviceOperationResult.Failure -> println("Failed: ${result.reason}") }

Closing an app

import com.ledger.devicemanagement.api.command.closeapp.CloseRunningApplicationCommand val command = CloseRunningApplicationCommand() val result: DeviceOperationResult<Unit> = dmk.executeCommand(deviceUid, command) when (result) { is DeviceOperationResult.Success -> println("App closed successfully") is DeviceOperationResult.Failure -> println("Failed: ${result.reason}") }

Getting OS information

import com.ledger.devicemanagement.api.command.getosversion.GetOsVersionCommand import com.ledger.devicemanagement.api.command.getosversion.OsVersion val command = GetOsVersionCommand() val result: DeviceOperationResult<OsVersion> = dmk.executeCommand(deviceUid, command) when (result) { is DeviceOperationResult.Success -> { val osVersion: OsVersion = result.value println("SE version: ${osVersion.secureElementVersion}") println("MCU version: ${osVersion.microcontrollerSephVersion}") println("Hardware version: ${osVersion.hardwareVersion}") } is DeviceOperationResult.Failure -> println("Failed: ${result.reason}") }

Getting app information

import com.ledger.devicemanagement.api.command.getappandversion.GetAppAndVersionCommand import com.ledger.devicemanagement.api.command.getappandversion.AppAndVersion val command = GetAppAndVersionCommand() val result: DeviceOperationResult<AppAndVersion> = dmk.executeCommand(deviceUid, command) when (result) { is DeviceOperationResult.Success -> { val appInfo: AppAndVersion = result.value println("Running app: ${appInfo.appName} v${appInfo.appVersion}") } is DeviceOperationResult.Failure -> println("Failed: ${result.reason}") }

Step 3: Working with Device Actions

Device Actions define a sequence of commands sent to the device. They are useful for operations that require user interaction, such as opening an app while handling device lock or confirmation screens automatically.

Device Actions return a Flow<DeviceActionResult<T>> that emits state updates during execution.

Opening an app with a Device Action

import com.ledger.devicemanagement.api.deviceaction.openapp.OpenApplicationDeviceAction import com.ledger.devicemanagement.api.deviceaction.DeviceActionResult import com.ledger.devicemanagement.api.deviceaction.UserInteractionRequirement val deviceAction = OpenApplicationDeviceAction(appName = "Ethereum") dmk.executeDeviceAction(deviceUid, deviceAction).collect { result -> when (result) { is DeviceActionResult.IntermediateValue -> { when (result.userRequirement) { is UserInteractionRequirement.UnlockDevice -> { println("Ask the user to unlock the device") } null -> println("Processing...") } } is DeviceActionResult.Success -> { println("App opened successfully") } is DeviceActionResult.Failure -> { println("Action failed: ${result.reason}") } } }

The OpenApplicationDeviceAction automatically handles the full flow:

  1. Checks if the device is onboarded
  2. Checks if the requested app is already open
  3. Closes the current app if a different one is running
  4. Opens the requested app and waits for user confirmation on the device

To cancel an in-progress action:

deviceAction.cancel()

What you’ve learned

  • How to send raw APDU commands with sendApdu()
  • How to use pre-defined commands (OpenApplicationCommand, CloseRunningApplicationCommand, GetOsVersionCommand, GetAppAndVersionCommand) with executeCommand()
  • How to use OpenApplicationDeviceAction to handle complex multi-step flows with user interaction
Last updated on
Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Stax, Ledger Flex, Ledger Nano, Ledger Nano S, Ledger OS, Ledger Wallet, [LEDGER] (logo), [L] (logo) are trademarks owned by Ledger SAS.