Integrating Ledger Nano S Workflow in Your Blockchain Based Web App

Posted By : Amit

Jul 24, 2020

Hardware wallets provide safer alternatives to store your private keys so that there are minimal chances of you losing your private key and hence losing the control of your wallet. With a hardware wallet like Ledger Nano S, you are not allowed to transfer your private key out of the device in plain text.


In this blog, we look into the connection set up with the ledger app and providing users a list of wallets from which he can choose.

 

1) Installing the Ethereum app

  • a) Install Ledger Live application from LEDGER
  • b) Once installed, verify your ledger device on the Ledger Live desktop application
  • c) After verification, go to the manager tab on the Ledger Live and from an app, section install the Ethereum app
  • d) Approve the installation notifications on your Ledger Nano S and the installation will complete


2) Libraries for Ledger Integration

The following repository provided from LedgerHQ provides javascript libraries needed for our application - ledgerjs
a) @ledgerhq/hw-transport-u2f - Allows to communicate with Ledger Hardware Wallets
b) @ledgerhq/hw-app-eth - Ledger Hardware Wallet ETH JavaScript bindings.

Install these libraries in your project and you can use them through

import Transport from '@ledgerhq/hw-transport-u2f'
import Eth from '@ledgerhq/hw-app-eth'

 

3) Start integrating these libraries

We are going to integrate these libraries and will cover the functionality in the following steps
A) Verifying the connection with the user Ledger Wallet

let ledgerPayload = ''
let appEth = ''
const setupLedger = async () => {
    let u2fSupported = false
    try {
        u2fSupported = await Transport.isSupported()
        console.info('u2fSupported', u2fSupported)
        if (!u2fSupported) { 
            let unsError = new Error()
            unsError.message = 'U2F not supported in this browser. Please try using Google Chrome with a secure (SSL / HTTPS) connection!'
            unsError.name = 'U2FNotSupportedError'
            throw unsError
        }
        u2fSupported = true
        if (!appEth) {
            let transport = await Transport.create()
            appEth = await new Eth(transport)
        }
        const path = localStorage.get('hdDerivationPath')
        const result = await appEth.getAddress(
            path,
            false,
            true
        )
        ledgerPayload = result
    } catch (error) {
        console.error('unlock ledger error', error)
        if (!u2fSupported) {
            throw error
        } else {
            let ledgerTransportError = new Error()
            ledgerTransportError.name = 'LedgerTransportError'
            ledgerTransportError.message = 'Unable to connect with your ledger device , make sure your device is connected and keep the app open'
            throw ledgerTransportError   
        }        
    }
}

 

In the above code, we do the following things


a) We check using the Transport library if the browser user is using is u2f compatible or not, if not we display the error message about u2f incompatible browser message.


b) If the browser is u2f compatible, we pass the transport connection details to the Eth constructor which allows us to perform various functions like sending transactions, generating addresses, and their verification based on parameters.


c) Now we fetch the hdPath used by the ethereum application on the ledger, and fetch the payload details using the getAddress function and save the payload details for address generation for the next step.


d) If there is still any error getting the payload details, which can be due to the reasons like the user has not opened the app, or the ledger device is not unlocked while interacting with our web application, we show him a generic message for the same.

The real error scenarios in the d) case result in TransportError or TransportStatusError, but we have updated the name and message of the errors to show the user a general message. 


B) Generating user wallet addresses

In our app, we can generate multiple ethereum addresses and then let the user choose which address he wants to use, to comply with some regulations we can also add a verification step, which is processed using the getAddress call.

const loadMultipleLedgerWallets = async function (offset, limit) {
    try {
        const payload = ledgerPayload
        let balance = 0
        let convertedAddress
        let wallets = {}
    
        for (let i = offset; i < (offset + limit); i++) {
            convertedAddress = createHDWallet(payload, i)
            balance = await web3.eth.getBalance(convertedAddress)
            wallets[i] = {
                address: convertedAddress,
                balance: parseFloat(web3.utils.fromWei(balance, 'ether')).toFixed(2)
            }
        }
        ledgerPayload = ''
        return wallets
    } catch (error) {
        let loadWalletInfoError = new Error()
        loadWalletInfoError.message = 'Unable to connect with Wethio network, please try again'
        loadWalletInfoError.name = 'LoadWalletInfoError'
        throw loadWalletInfoError
    }
}

 

a) In the above code, we are using a limit of 5 max addresses whose details can be shown to the user to choose from
b) We fetch the payload details from the last step using the ledgerPayload variable , we call the create wallet function 5 times

const createHDWallet = (payload, index) => {
    let derivedKey
    const pubKey = payload.publicKey
    const chainCode = payload.chainCode
    const hdkey = new HDKey()
    hdkey.publicKey = Buffer.from(pubKey, 'hex')
    hdkey.chainCode = Buffer.from(chainCode, 'hex')
    derivedKey = hdkey.derive('m/' + index)
    let pubKey = ethUtils.bufferToHex(derivedKey.publicKey)
    const buff = ethUtils.publicToAddress(pubKey, true)
    return ethUtils.bufferToHex(buff)
}

 

c) In each iteration of the HDWalletCreate function, we generate a child public key, and through it the address using the index value provided.
For example - the Ethereum app uses the path m/44'/60'/0', and we are generating the public key for the children of this node in HD wallet tree, from child number 0 to 4 in case of the limit being set to 5.


d) Then we fetch the balance of all these addresses using the web3 getBalance api, and return an array of objects with fields, address, and balance and let the user choose one of the addresses.


e) Once the user chooses an address we store the correct hdPath details, initial HD Path along with the wallet index through which the user wants to interact, into the local storage, to retrieve it later for sending transactions.


f) We also update the payload variable to empty string, as we don't need to derive anything from the base public key and base chain code, once we have generated and passed the keys to the user.

 

 

 

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

April 4, 2024 at 07:46 am

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text