Documentation
Desktop Application
Node HID integration

Node HID integration

Introduction

In this section, we will guide you through the creation of a desktop electron application. This application will be connected to your Nano to display the address of your account (eg. bitcoin account, ethereum account).

Implementation for Web USB and Web HID on Nano S/X/S Plus.

For this application we will use the Ledger node hid package which is @ledgerhq/hw-transport-node-hid (opens in a new tab).

One-time setup

Environment

Make sure you have gone through the prerequisites.

App Coding

App setup

First, open a terminal and create a new folder. For this tutorial the folder will be named “examples-node-electron-hid”.

From your Github folder or favourite examples folder, run:

mkdir examples-node-electron-hid
cd examples-node-electron-hid

Then, initialize the project:

npm init

Answer the questions displayed or by default press enter. There is no incidence on the execution.

Run:

touch index.html
touch main.js
touch renderer.js

The folder will contain these files:

Folder of the Application Fig. 1: Folder of the Application

Files

index.html

In index.html copy-paste the following code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Ledger Basic Electron HID</title>
    <style>
      #main {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 100vh;
        font-family: sans-serif;
      }
      h1 {
        font-size: 32px;
      }
      h2 {
        font-size: 20px;
        font-weight: normal;
      }
    </style>
  </head>
  <body>
    <div id="main"></div>
    <script src="./renderer.js"></script>
  </body>
</html>

main.js

In main.js copy-paste the following code:

// Modules to control application life and create native browser window
require("babel-polyfill");
const TransportNodeHid = require("@ledgerhq/hw-transport-node-hid").default;
const AppBtc = require("@ledgerhq/hw-app-btc").default;
const { listen } = require("@ledgerhq/logs");
 
 
const { app, BrowserWindow, ipcMain } = require("electron");
 
// This a very basic example
// Ideally you should not run this code in main thread
// but run it in a dedicated node.js process
function getBitcoinInfo(verify) {
  return TransportNodeHid.open("")
    .then(transport => {
      listen(log => console.log(log))
      const appBtc = new AppBtc({ transport, currency: "bitcoin" });
      return appBtc.getWalletPublicKey("44'/0'/0'/0/0",{verify: verify, format: "legacy"}).then(r =>
        transport
          .close()
          .catch(e => {})
          .then(() => r)
      );
    })
    .catch(e => {
      console.warn(e);
      // try again until success!
      return new Promise(s => setTimeout(s, 1000)).then(() =>
        getBitcoinInfo(verify)
      );
    });
}
 
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
 
function createWindow() {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    webPreferences: {
        nodeIntegration: true,
        contextIsolation: false
    }
})
 
  // and load the index.html of the app.
  mainWindow.loadFile("index.html");
 
  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
 
  // Emitted when the window is closed.
  mainWindow.on("closed", function() {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
 
  // ~~~ BASIC LEDGER EXAMPLE ~~~
 
  ipcMain.on("requestBitcoinInfo", () => {
    getBitcoinInfo(false).then(result => {
      mainWindow.webContents.send("bitcoinInfo", result);
    });
  });
 
  ipcMain.on("verifyBitcoinInfo", () => {
    getBitcoinInfo(true);
  });
 
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
 
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
 
// Quit when all windows are closed.
app.on("window-all-closed", function() {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== "darwin") {
    app.quit();
  }
});
 
app.on("activate", function() {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  }
});
 
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

renderer.js

In renderer.js copy-paste the following code:

const electron = require("electron");
const { ipcRenderer } = electron;
 
document.getElementById("main").innerHTML =
  "<h1>Connect your Nano and open Bitcoin app...</h1>";
 
ipcRenderer.on("bitcoinInfo", (event, arg) => {
  const h1 = document.createElement("h2");
  h1.textContent = arg.bitcoinAddress;
  document.getElementById("main").innerHTML =
    "<h1>Your first Bitcoin address:</h1>";
  document.getElementById("main").appendChild(h1);
  ipcRenderer.send("verifyBitcoinInfo");
});
 
ipcRenderer.send("requestBitcoinInfo");

Dependencies

Installation

Run:

npm install --save-dev electron
npm install --save babel-polyfill
npm install --save @ledgerhq/logs
npm install --save @ledgerhq/hw-app-btc
npm install --save browserify
npm install --save @ledgerhq/hw-transport-node-hid
PackageWhat does it do?
Electron (opens in a new tab)It is a build tool that will help you package your application to run it as a desktop application.
Babel polyfill (opens in a new tab)
@ledgerhq/logs (opens in a new tab)It provides you with the log of all the error from your connexion with your Ledger device that may appear when developing.
@ledgerhq/hw-app-btc (opens in a new tab)It helps you ask your Ledger Nano to access the bitcoin address.
Browserify (opens in a new tab)It helps you use "require" like Node does.
@ledgerhq/hw-transport-node-hid (opens in a new tab)It provides you with all the methods to interact with your Nano with an HID connexion.

Since automatic polyfills have been removed in webpack 5, run the following command to avoid errors when building the app locally:

npm install node-polyfill-webpack-plugin
npm i stream

Package.json

  1. Modify the 5th line: "main": "index.js" => "source": "index.html"
  2. Ensure you have this line in scripts:
  "scripts": {
    "start": "electron"
  },

Your file should know look like this:

{
  "name": "nodehid",
  "version": "1.0.0",
  "description": "",
  "source": "index.html",
  "scripts": {
    "start": "electron"
  },
  "devDependencies": {
    "electron": "^27.1.2"
  },
  "dependencies": {
    "@ledgerhq/hw-app-btc": "^10.1.0",
    "@ledgerhq/hw-transport-node-hid": "^6.28.0",
    "@ledgerhq/logs": "^6.12.0",
    "babel-polyfill": "^6.26.0",
    "browserify": "^17.0.0",
    "node-polyfill-webpack-plugin": "^2.0.1",
    "stream": "^0.0.2"
  }
}

App Launch

1. Launch the App

Now that the Setup is finished, let's start the application.

node_modules/electron/dist/Electron.app/Contents/MacOS/Electron ./main.js

Now the application is up and running. A window must have been launched on your machine, it will display:

Node Desktop Application
Fig. 2: Node Desktop Application

2. Plug Your Nano and open Bitcoin App

Connect your Nano to the USB port, unlock it and open the Bitcoin application. The steps are described below. If you do not have a Bitcoin account, or you want to created one for the purposes of this walkthrough, you can create one here.


Nano Enter Code Pin
Fig. 3: Nano Enter Code Pin

Embedded Application
Fig. 4: Embedded Application

Nano Run Application
Fig. 5: Nano Run Application

5. Check displayed address

Check that the address displayed on the App is the same as the one displayed on the Ledger device.

Address Account Displayed
Fig. 6: Address Account Displayed

Congratulations you have successfully built your first Node Electron HID application connected to your Ledger!

Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Nano S, Ledger Vault, Bolos are registered trademarks of Ledger SAS