import * as ethUtil from 'ethereumjs-util' ; import { CommonWalletOptions , Helpers , WalletModule } from '../../../../interfaces' ; import { ModalType , renderModal } from './entryModal' ; import keepKeyIcon from '../../wallet-icons/icon-keepkey.png' ; const ACCOUNTS_TO_GET = 5 ; const WALLET_NAME = 'KeepKey' ; const ERROR_BUSY = 'busy' ; const ERROR_PAIRING = 'pairing' ; const errorMessages = { [ ERROR_BUSY ] : `Your KeepKey is currently connected to another application.
  Please close any other browser tabs or applications that may be connected to your device and try again.` , [ ERROR_PAIRING ] : 'There was an error pairing the device. Please disconnect and reconnect the device and try again.' } ; type ErrorCode = 'busy' | 'pairing' ; type Balance = { address : string ; balance : string ; } ; interface KeepProviderOptions { BigNumber : Helpers [ 'BigNumber' ] ; rpcUrl : string ; resetWalletState : Helpers [ 'resetWalletState' ] ; networkId : number ; } function keepkey ( options ) { const { label , iconSrc , rpcUrl , networkId , preferred } = options ; let installMessage ; return { name : label || WALLET_NAME , iconSrc : iconSrc || keepKeyIcon , wallet : async ( { BigNumber , resetWalletState } ) => { const { provider , error } = await createKeepKeyProvider ( { resetWalletState , BigNumber , rpcUrl , networkId } ) ; installMessage = errorMessages [ error as ErrorCode ] || '' ; return { provider , interface : ! error ? { name : WALLET_NAME , connect : provider . enable , disconnect : provider . disconnect , address : { get : async ( ) => provider . getPrimaryAddress ( ) } , network : { get : async ( ) => networkId } , balance : { get : async ( ) => { const address = provider . getPrimaryAddress ( ) ; return address && provider . getBalance ( address ) ; } } } : null } ; } , installMessage : ( ) => installMessage ? `<style>footer a > button { display: none !important }</style>
        <p style="font-size: 0.889rem; font-family: inherit; margin: 0.889rem 0;">
          ${ installMessage }
        </p>` : '' , type : 'hardware' , mobile : false , desktop : true , preferred } ; } async function createKeepKeyProvider ( { BigNumber , rpcUrl , resetWalletState , networkId } ) { const { WebUSBKeepKeyAdapter } = await import ( '@shapeshiftoss/hdwallet-keepkey-webusb' ) ; const { Keyring , Events , bip32ToAddressNList , HDWalletErrorType } = await import ( '@shapeshiftoss/hdwallet-core' ) ; const { default : createProvider } = await import ( '../providerEngine' ) ; const { isValidPath } = await import ( '../hd-wallet' ) ; const keyring = new Keyring ( ) ; const keepKeyAdapter = WebUSBKeepKeyAdapter . useKeyring ( keyring ) ; let keepKeyWallet ; try { keepKeyWallet = ( await keepKeyAdapter . pairDevice ( ) ) as import ( '@shapeshiftoss/hdwallet-keepkey' ) . KeepKeyHDWallet ; } catch ( error ) { if ( error . name === HDWalletErrorType . ConflictingApp ) { return { provider : undefined , error : ERROR_BUSY } ; } else if ( error . name === HDWalletErrorType . WebUSBCouldNotPair ) { return { provider : undefined , error : ERROR_PAIRING } ; } } const DEFAULT_DERIVATION_PATH = "m/44'/60'/0'/0/0" ; let dPath = '' ; let addressToPath = new Map ( ) ; let enabled = false ; let customPath = false ; const provider = createProvider ( { getAccounts : ( callback ) => { getAccounts ( ) . then ( ( res ) => callback ( null , res ) ) . catch ( ( err ) => callback ( err , null ) ) ; } , signTransaction : ( transactionData , callback ) => { signTransaction ( transactionData ) . then ( ( res ) => callback ( null , res ) ) . catch ( err => callback ( err , null ) ) ; } , processMessage : ( messageData , callback ) => { signMessage ( messageData ) . then ( ( res ) => callback ( null , res ) ) . catch ( err => callback ( err , null ) ) ; } , processPersonalMessage : ( messageData , callback ) => { signMessage ( messageData ) . then ( ( res ) => callback ( null , res ) ) . catch ( err => callback ( err , null ) ) ; } , signMessage : ( messageData , callback ) => { signMessage ( messageData ) . then ( ( res ) => callback ( null , res ) ) . catch ( err => callback ( err , null ) ) ; } , signPersonalMessage : ( messageData , callback ) => { signMessage ( messageData ) . then ( ( res ) => callback ( null , res ) ) . catch ( err => callback ( err , null ) ) ; } , rpcUrl } ) ; keyring . on ( [ '*' , '*' , Events . DISCONNECT ] , async ( ) => { resetWalletState ( { disconnected : true , walletName : WALLET_NAME } ) ; } ) ; keyring . on ( [ '*' , '*' , Events . PIN_REQUEST ] , ( ) => { renderModal ( keepKeyWallet , ModalType . Pin ) ; } ) ; keyring . on ( [ '*' , '*' , Events . PASSPHRASE_REQUEST ] , ( ) => { renderModal ( keepKeyWallet , ModalType . Passphrase ) ; } ) ; provider . setPath = setPath ; provider . dPath = dPath ; provider . enable = enable ; provider . setPrimaryAccount = setPrimaryAccount ; provider . getPrimaryAddress = getPrimaryAddress ; provider . getAccounts = getAccounts ; provider . getMoreAccounts = getMoreAccounts ; provider . getBalance = getBalance ; provider . getBalances = getBalances ; provider . send = provider . sendAsync ; provider . disconnect = disconnect ; provider . isCustomPath = isCustomPath ; async function enable ( ) { keepKeyWallet . cancel ( ) . catch ( err => err ) ; enabled = true ; return getAccounts ( ) . catch ( ( error ) => { if ( error ?. message ?. message ) { throw new Error ( error ?. message ?. message ) ; } return [ ] ; } ) ; } function disconnect ( ) { dPath = '' ; addressToPath = new Map ( ) ; enabled = false ; keepKeyWallet . clearSession ( ) ; provider . stop ( ) ; } async function setPath ( path , custom ? ) { if ( ! isValidPath ( path ) ) { return false ; } if ( path !== dPath ) { addressToPath = new Map ( ) ; } if ( custom ) { try { const addressNList = bip32ToAddressNList ( path ) ; const address = await getAddress ( addressNList , path ) ; addressToPath . set ( address , addressNList ) ; dPath = path ; customPath = true ; return true ; } catch ( error ) { throw new Error ( `There was a problem deriving an address from path ${ path }` ) ; } } customPath = false ; dPath = path ; return true ; } function isCustomPath ( ) { return customPath ; } async function getAddress ( addressNList , path ) { try { return keepKeyWallet . ethGetAddress ( { addressNList } ) ; } catch ( error ) { throw new Error ( `Unable to derive address from path ${ path }` ) ; } } function addresses ( ) { return Array . from ( addressToPath . keys ( ) ) ; } function setPrimaryAccount ( address ) { const accounts = [ ... addressToPath . entries ( ) ] ; const accountIndex = accounts . findIndex ( ( [ accountAddress ] ) => accountAddress === address ) ; accounts . unshift ( accounts . splice ( accountIndex , 1 ) [ 0 ] ) ; addressToPath = new Map ( accounts ) ; } function getPrimaryAddress ( ) { return enabled ? addresses ( ) [ 0 ] : undefined ; } async function getMoreAccounts ( ) { const accounts = await getAccounts ( true ) ; return accounts && getBalances ( accounts ) ; } async function getAccounts ( getMore ? ) { if ( ! enabled ) return [ ] ; if ( addressToPath . size > 0 && ! getMore ) return addresses ( ) ; dPath = dPath || DEFAULT_DERIVATION_PATH ; const { accountIdx } = keepKeyWallet . describePath ( { path : bip32ToAddressNList ( dPath ) , coin : 'Ethereum' } ) ; if ( accountIdx === undefined ) throw new Error ( `Could not derive account from path: ${ dPath }` ) ; const startingIndex = accountIdx + addressToPath . size ; for ( let i = startingIndex ; i < ACCOUNTS_TO_GET + startingIndex ; i ++ ) { const { addressNList } = keepKeyWallet . ethGetAccountPaths ( { coin : 'Ethereum' , accountIdx : i } ) [ 0 ] ; const address = await keepKeyWallet . ethGetAddress ( { addressNList , showDisplay : false } ) ; addressToPath . set ( address , addressNList ) ; } return addresses ( ) ; } function getBalances ( addresses ) { return Promise . all ( addresses . map ( address => new Promise ( async ( resolve ) => { const balance = await getBalance ( address ) ; resolve ( { address , balance } ) ; } ) ) ) ; } function getBalance ( address ) { return new Promise ( ( resolve , reject ) => { provider . sendAsync ( { jsonrpc : '2.0' , method : 'eth_getBalance' , params : [ address , 'latest' ] , id : 42 } , ( e , res ) => { e && reject ( e ) ; const result = res && res . result ; if ( result != null ) { resolve ( new BigNumber ( result ) . toString ( 10 ) ) ; } else { resolve ( null ) ; } } ) ; } ) ; } async function signTransaction ( transactionData ) { if ( addressToPath . size === 0 ) { await enable ( ) ; } const addressNList = [ ... addressToPath . values ( ) ] [ 0 ] ; const { nonce , gasPrice , gas , to , value , data } = transactionData ; const { serialized } = await keepKeyWallet . ethSignTx ( { addressNList , nonce , gasPrice , gasLimit : gas , to , value : value || '0x0' , data : data || '' , chainId : networkId } ) ; return serialized ; } async function signMessage ( { data : message } ) { if ( addressToPath . size === 0 ) { await enable ( ) ; } const addressNList = [ ... addressToPath . values ( ) ] [ 0 ] ; const { signature } = await keepKeyWallet . ethSignMessage ( { addressNList , message : ethUtil . toBuffer ( message ) . toString ( 'utf8' ) } ) ; return signature ; } return { provider } ; } export default keepkey ;