Sign Ethereum Transaction with Ledger

1 Install Necessary Packages

To get started, please install the following essential libraries that enable the script to interact with the Ethereum blockchain and the Ledger Hardware Wallet.

The following package.json includes the specific versions of the dependencies.

touch package.json 
{
  "name": "npm-test",
  "version": "1.0.0",
  "source": "index.js",
  "type": "module",
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@ledgerhq/hw-app-eth": "6.29.0",
    "@ledgerhq/hw-transport-node-hid": "6.28.4",
    "ethers": "5.5.4",
    "web3": "4.5.0"
  },
  "main": "index.js",
  "description": ""
}

By including this package.json in the project, run npm install to install all required dependencies at their specified versions.

npm install

After the required dependencies are installed, run npm list and the result should looks like the following example.

npm list
├── @ledgerhq/[email protected]
├── @ledgerhq/[email protected]
├── [email protected]
└── [email protected]

2 Create JavaScript Script

Create the Javascript script.

touch index.js

The following code is an example of signing Ethereum transaction with Ledger. This is revised from the Ledger official tech doc.

import { ethers } from "ethers";
import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
import Eth from "@ledgerhq/hw-app-eth";

const transport = await TransportNodeHid.default.create();
const eth = new Eth.default(transport);
const result = await eth.getAddress("44'/60'/0'/0/0")
console.log("Connected to Ledger \nWallet address is", result.address);

import Web3 from "web3";
const web3 = new Web3("<ethereum_endpoint>");  // Replace this with your Ethereum Fast API endpoint
const txCount = await web3.eth.getTransactionCount(result.address);
const nonce = await web3.utils.toHex(txCount)
const gasPrice = await web3.eth.getGasPrice();
const chainId = await web3.eth.getChainId();

// The transaction to be signed
let rawTx = {
    nonce: nonce,
    to: '0x3070eb0eac184cb84e825dfff97070521424accd',  // This is InfStones Ethereum Staking Smart Contract Address
    gasPrice: web3.utils.toHex(gasPrice),
    data: "<data>", //retrieved from activity
    value: "<value>", //retrieved from activity
    chainId: Number(chainId), // mainnet: 1, goerli: 5
}

const estimateGas = await web3.eth.estimateGas(rawTx)
rawTx = { ...rawTx, gasLimit: estimateGas}

//Serializing the transaction to pass it to Ledger Nano for signing
let unsignedTx = ethers.utils.serializeTransaction(rawTx).substring(2);

//Sign with the Ledger Nano (Sign what you see)
const signature = await eth.signTransaction("44'/60'/0'/0/0", unsignedTx, null);
signature.r = "0x" + signature.r;
signature.s = "0x" + signature.s;
signature.v = parseInt(signature.v);
signature.from = result.address;

//Serialize the same transaction as before, but adding the signature on it
let signedTx = ethers.utils.serializeTransaction(rawTx, signature);
console.log("Signature is ", signedTx);

3 Connect Ledger Hardware Wallet

Refer to Ledger Developer Portal about how to "Plug Your Ledger Device".

4 Execute Script

node index.js

During the script is running, it retrieves Ledger wallet address and display as the following format:

Connected to Ledger 
Wallet address is <your-etheruem-wallet-address>

Ledger will display the details of the transaction to be signed, press the button to approve all the messages. The signature will be displayed as the following format

Signature is <signature>

5 Send Transaction

Copy the signature from the last step, paste it to the following request.

Complete the stake process by sending the signed transaction anytime.

curl --location 'http://<ethereum_endpoint>' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "method": "eth_sendRawTransaction",
    "params": [
        "<signature_from_above_script>"
    ],
    "id": 1
}'