import TransportHID from '@ledgerhq/hw-transport-node-hid' ; import Eth from '@ledgerhq/hw-app-eth' ; import { ethers } from 'ethers' ; import Web3 from 'web3' ; export class LedgerEthSigner { public app : any ; public transport : TransportHID | null ; constructor ( ) { this . transport = null ; } async createTransport ( ) { if ( this . app === null || this . app === undefined ) { const transport = await TransportHID . open ( '' ) ; this . app = new Eth ( transport ) ; this . transport = transport ; } } async closeTransport ( ) { if ( this . transport != null ) { this . transport . close ( ) ; this . transport = null ; this . app = null ; } } async getAddress ( index = 0 , display ) { try { const path = `44'/60'/0'/0/${ index }` ; await this . createTransport ( ) ; const retAddress = await this . app . getAddress ( path , display , false ) ; return retAddress . address ; } finally { await this . closeTransport ( ) ; } } public static padZero ( original_array , wanted_length ) { const new_array = new Uint8Array ( wanted_length ) ; for ( let i = wanted_length - 1 ; i >= 0 ; i -- ) { const j = wanted_length - 1 - i ; const new_i = original_array . length - 1 - j ; if ( new_i >= 0 && new_i < original_array . length ) { new_array [ i ] = original_array [ new_i ] ; } else { new_array [ i ] = 0 ; } } return new_array ; } public static padZeroString ( src , wanted_length ) { const srcBinary = Buffer . from ( src , 'hex' ) ; const paddedSrcBinary = LedgerEthSigner . padZero ( srcBinary , wanted_length ) ; const hexSrcBinary = Buffer . from ( paddedSrcBinary ) . toString ( 'hex' ) ; return hexSrcBinary ; } async doSignTx ( path = "44'/60'/0'/0/0" , chainId = 9000 , nonce = 0 , gasLimit = '0x5000' , gasPrice = '0x0400000000' , to , value = '0x00' , data = '0x' ) { const baseTx = { chainId , data , gasLimit , gasPrice , nonce , to , value , } ; const unsignedTx = ethers . utils . serializeTransaction ( baseTx ) . substring ( 2 ) ; const sig = await this . app . signTransaction ( path , unsignedTx ) ; const sigR = LedgerEthSigner . padZeroString ( sig . r , 32 ) ; const sigS = LedgerEthSigner . padZeroString ( sig . s , 32 ) ; const ret = ethers . utils . serializeTransaction ( baseTx , { v : ethers . BigNumber . from ( '0x' + sig . v ) . toNumber ( ) , r : '0x' + sigR , s : '0x' + sigS , } ) ; return ret ; } async signTx ( index = 0 , chainId = 9000 , nonce = 0 , gasLimit = '0x5208' , gasPrice = '0x04e3b29200' , to , value = '0x00' , data = '0x' ) { try { await this . createTransport ( ) ; const path = `44'/60'/0'/0/${ index }` ; const signedTx = await this . doSignTx ( path , chainId , nonce , gasLimit , gasPrice , to , value , data ) ; return signedTx ; } finally { await this . closeTransport ( ) ; } } async signAndSendTx ( url = 'http://127.0.0.1:8545' , index = 0 , chainId = 9000 , gasLimit = '0x5000' , gasPrice = '0x0400000000' , to , value = '0x00' , data = '0x' ) { try { await this . createTransport ( ) ; const path = `44'/60'/0'/0/${ index }` ; const web3 = new Web3 ( url ) ; const from_addr = ( await this . app . getAddress ( path ) ) . address ; const nonce = await web3 . eth . getTransactionCount ( from_addr ) ; const signedTx = await this . doSignTx ( path , chainId , nonce , gasLimit , gasPrice , to , value , data ) ; const txHash = ( await web3 . eth . sendSignedTransaction ( signedTx ) ) . transactionHash ; return txHash ; } finally { await this . closeTransport ( ) ; } } }