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 GuidesEthereum Signer

Ethereum Signer

This module provides the implementation of the Ledger Ethereum Signer for the Mobile Device Management Kit. It enables interaction with the Ethereum application on a Ledger device:

  • Retrieving the Ethereum address using a given derivation path
  • Signing an Ethereum transaction (Clear Signing )
  • Signing a message displayed on a Ledger device
  • Signing an EIP-712  typed data message

How it works

The Ledger Ethereum Signer uses the interface provided by the Device Management Kit to establish communication with the Ledger device and execute operations. Communication with the device is performed using APDUs (Application Protocol Data Units), encapsulated within Command objects, which are in turn encapsulated within DeviceAction objects to handle real-world scenarios.

For transaction signing, the Signer integrates with the Context Module to provide context data to the device, enabling Clear Signing — the display of human-readable transaction details on the device screen.

Installation

ℹ️

This module is not standalone: it depends on the device-management-kit module, which must be installed first.

The module is published as:

  • Android: io.github.ledgerhq:device-signer-kit-ethereum (Maven/Gradle)
  • iOS: part of the LedgerDeviceManagementKit XCFramework (Swift Package Manager)

Android

Add the Ledger SDK version catalog to your settings.gradle.kts:

dependencyResolutionManagement { versionCatalogs { create("ledger") { from("io.github.ledgerhq:sdk-version-catalog:<version>") } } }

Then in your module’s build.gradle.kts:

dependencies { implementation(platform(ledger.bom)) implementation(ledger.device.management.kit) implementation(ledger.device.signer.kit.ethereum) }

Using the BOM only

dependencies { implementation(platform("io.github.ledgerhq:sdk-bom:<version>")) implementation("io.github.ledgerhq:device-management-kit") implementation("io.github.ledgerhq:device-signer-kit-ethereum") }

Direct dependency

dependencies { implementation("io.github.ledgerhq:device-management-kit:<version>") implementation("io.github.ledgerhq:device-signer-kit-ethereum:<version>") }

iOS (Swift Package Manager)

The Ethereum Signer is part of the LedgerDeviceManagementKit XCFramework, which also contains the Device Management Kit: add that single framework and use import LedgerDeviceManagementKit.

Initialisation

To create an EthereumSigner instance, you need a DeviceManagementKitApi instance. Use the ethereumSigner builder DSL:

import com.ledger.signer.eth.ethereumSigner val signerEth = ethereumSigner( deviceManagementKit = dmk, originToken = "your-origin-token", // identifies your application to the context module ) { // optional configuration }

You can also customize the context module endpoints:

val signerEth = ethereumSigner( deviceManagementKit = dmk, originToken = "your-origin-token", ) { calUrl = "https://your-custom-cal-url" metadataServiceUrl = "https://your-custom-metadata-url" transactionCheckUrl = "https://your-custom-tx-check-url" enableLog = true }

On iOS (Swift):

import LedgerDeviceManagementKit let signerEth = ethereumSigner( deviceManagementKit: dmk, originToken: "your-origin-token" ) { builder in builder.enableLog = true }

On Android, pass the application context:

val signerEth = ethereumSigner( deviceManagementKit = dmk, originToken = "your-origin-token", ) { context = applicationContext // required on Android }

Use Cases

The EthereumSigner instance exposes 4 methods. Each returns a Flow<DeviceActionResult<T>> — a stream of state updates tracking the operation’s progress.

Get Address

Retrieve the Ethereum address for a given derivation path.

val flow: Flow<DeviceActionResult<EthereumAddress>> = signerEth.getAddress( deviceUid = connectedDevice.uid, request = GetEthereumAddressRequest( derivationPath = "44'/60'/0'/0/0", checkOnDevice = false, // set to true to require user confirmation on device withChainCode = false, ), )

Parameters:

NameTypeDescription
deviceUidStringUID of the connected device, from ConnectedDevice.uid
request.derivationPathStringe.g. "44'/60'/0'/0/0"
request.checkOnDeviceBooleanWhether user confirmation is required on the device
request.withChainCodeBooleanWhether to return the chain code

Returns: Flow<DeviceActionResult<EthereumAddress>>

data class EthereumAddress( val publicKey: String, // Public key derived from the given path val address: String, // Ethereum address (hex format) val chainCode: String?, // Chain code (if requested) )

Sign Transaction

Sign an Ethereum transaction using Clear Signing on the Ledger device. The Context Module automatically enriches the transaction with token, NFT, and trusted name information so the device can display human-readable details.

val flow: Flow<DeviceActionResult<Signature>> = signerEth.signTransaction( deviceUid = connectedDevice.uid, request = SignEthereumRequest( derivationPath = "44'/60'/0'/0/0", value = rawTransactionHex, // RLP-encoded transaction as hex string ), )

A transaction that already carries a signature is accepted: the signature is dropped and the transaction is signed again.

Parameters:

NameTypeDescription
deviceUidStringUID of the connected device
request.derivationPathStringe.g. "44'/60'/0'/0/0"
request.valueStringRLP-encoded transaction as hex string
request.isHexadecimalValueBooleanIgnored for transaction signing (default false)

Returns: Flow<DeviceActionResult<Signature>>

data class Signature( val r: String, // R component of the ECDSA signature (hex) val s: String, // S component of the ECDSA signature (hex) val v: Int, // Recovery parameter )

Sign Personal Message

Sign a text message (EIP-191 personal_sign) displayed on the Ledger device.

val flow: Flow<DeviceActionResult<Signature>> = signerEth.signPersonalMessage( deviceUid = connectedDevice.uid, request = SignEthereumRequest( derivationPath = "44'/60'/0'/0/0", value = "Hello from Ledger", isHexadecimalValue = false, ), )

Parameters:

NameTypeDescription
deviceUidStringUID of the connected device
request.derivationPathStringe.g. "44'/60'/0'/0/0"
request.valueStringThe message to sign (UTF-8 string or hex)
request.isHexadecimalValueBooleanSet to true if value is in hexadecimal format

Returns: Flow<DeviceActionResult<Signature>>


Sign Typed Data

Sign an EIP-712  typed data message. The Context Module provides the data needed to display the fields in a readable way on the device.

val flow: Flow<DeviceActionResult<Signature>> = signerEth.signEip712Message( deviceUid = connectedDevice.uid, request = SignEthereumRequest( derivationPath = "44'/60'/0'/0/0", value = typedDataJson, // EIP-712 typed data as a JSON string ), )

Parameters:

NameTypeDescription
deviceUidStringUID of the connected device
request.derivationPathStringe.g. "44'/60'/0'/0/0"
request.valueStringEIP-712 typed data (domain, types, message) as a JSON string

Returns: Flow<DeviceActionResult<Signature>>


Observable Behavior

Each method returns a Flow emitting DeviceActionResult<T> updates:

  • DeviceActionResult.IntermediateValue: Operation in progress. May include a userRequirement indicating what action is needed from the user.
  • DeviceActionResult.Success: Operation completed; the result is available.
  • DeviceActionResult.Failure: An error occurred; the reason is available (see EthereumErrors). When the user rejects the operation on the device, the reason is EthereumErrors.CancelByUser.
signerEth.signTransaction(deviceUid, request).collect { result -> when (result) { is DeviceActionResult.IntermediateValue -> { when (result.userRequirement) { is UserInteractionRequirement.UnlockDevice -> { println("Ask the user to unlock the device.") } null -> println("Processing: ${result.value}") } } is DeviceActionResult.Success -> { val signature: Signature = result.value println("Signed: r=${signature.r}, s=${signature.s}, v=${signature.v}") } is DeviceActionResult.Failure -> { println("Signing failed: ${result.reason}") } } }

To cancel an in-progress signing operation:

deviceAction.cancel()

Lifecycle

The EthereumSigner is initialized once per process and must be initialized before use. Building a second instance throws EthereumSigner already initialized, and there is no public destroy or reset API: treat the signer as a process-wide singleton.

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.