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:
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
Package | What 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
- Modify the 5th line:
"main": "index.js"
=>"source": "index.html"
- 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:
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.
5. Check displayed address
Check that the address displayed on the App is the same as the one displayed on the Ledger device.
Fig. 6: Address Account Displayed
Congratulations you have successfully built your first Node Electron HID application connected to your Ledger!