import { CELO_DERIVATION_PATH_BASE } from '@celo/base/lib/account' ; import { zeroRange } from '@celo/base/lib/collections' ; import { Address , ReadOnlyWallet } from '@celo/connect' ; import { RemoteWallet } from '@celo/wallet-remote' ; import { TransportError , TransportStatusError } from '@ledgerhq/errors' ; import Ledger from '@ledgerhq/hw-app-eth' ; import debugFactory from 'debug' ; import { LedgerSigner } from './ledger-signer' ; import { transportErrorFriendlyMessage } from './ledger-utils' ; export const CELO_BASE_DERIVATION_PATH = `${ CELO_DERIVATION_PATH_BASE . slice ( 2 ) }/0` ; const ADDRESS_QTY = 5 ; export enum AddressValidation { initializationOnly , everyTransaction , firstTransactionPerAddress , never } export async function newLedgerWalletWithSetup ( transport , derivationPathIndexes ? , baseDerivationPath ? , ledgerAddressValidation ? ) { const wallet = new LedgerWallet ( derivationPathIndexes , baseDerivationPath , transport , ledgerAddressValidation ) ; await wallet . init ( ) ; return wallet ; } const debug = debugFactory ( 'kit:wallet:ledger' ) ; export class LedgerWallet extends RemoteWallet < LedgerSigner > implements ReadOnlyWallet { private ledger : any ; param derivationPathIndexes param baseDerivationPath param transport constructor ( readonly derivationPathIndexes = zeroRange ( ADDRESS_QTY ) , readonly baseDerivationPath = CELO_BASE_DERIVATION_PATH , readonly transport = { } , readonly ledgerAddressValidation = AddressValidation . firstTransactionPerAddress ) { super ( ) ; const invalidDPs = derivationPathIndexes . some ( ( value ) => ! ( Number . isInteger ( value ) && value >= 0 ) ) ; if ( invalidDPs ) { throw new Error ( 'ledger-wallet: Invalid address index' ) ; } } protected async loadAccountSigners ( ) { if ( ! this . ledger ) { this . ledger = this . generateNewLedger ( this . transport ) ; } debug ( 'Fetching addresses from the ledger' ) ; let addressToSigner = new Map < Address , LedgerSigner > ( ) ; try { addressToSigner = await this . retrieveAccounts ( ) ; } catch ( error ) { if ( error instanceof TransportStatusError || error instanceof TransportError ) { transportErrorFriendlyMessage ( error ) ; } throw error ; } return addressToSigner ; } private generateNewLedger ( transport ) { return new Ledger ( transport ) ; } private async retrieveAccounts ( ) { const addressToSigner = new Map < Address , LedgerSigner > ( ) ; const appConfiguration = await this . retrieveAppConfiguration ( ) ; const validationRequired = this . ledgerAddressValidation === AddressValidation . initializationOnly ; for ( const value of this . derivationPathIndexes ) { const derivationPath = `${ this . baseDerivationPath }/${ value }` ; const addressInfo = await this . ledger ! . getAddress ( derivationPath , validationRequired ) ; addressToSigner . set ( addressInfo . address , new LedgerSigner ( this . ledger , derivationPath , this . ledgerAddressValidation , appConfiguration ) ) ; } return addressToSigner ; } private async retrieveAppConfiguration ( ) { const appConfiguration = await this . ledger ! . getAppConfiguration ( ) ; if ( ! appConfiguration . arbitraryDataEnabled ) { console . warn ( 'Beware, your ledger does not allow the use of contract data. Some features may not work correctly, including token transfers. You can enable it from the ledger app settings.' ) ; } return appConfiguration ; } }