import transport from './ledgerTransport' ; const algosdk = require ( 'algosdk' ) ; const Algorand = require ( '@ledgerhq/hw-app-algorand' ) ; import LedgerActionResponse from './ledgerActionsResponse' ; import { WalletTransaction } from '@algosigner/common/types' ; import { EncodedSignedTransaction } from 'algosdk' ; let ledgerTransport ; const _PATH = { pathIndex : 0 , get primary ( ) { { return `44'/60'/0'/0/0` ; } } , get current ( ) { { return `44'/60'/${ this . pathIndex }'/0/0` ; } } } ; const getDevice = async ( ) => { if ( ledgerTransport ) { return ledgerTransport ; } try { const newTransport = await transport . hid . create ( ) ; ledgerTransport = new Algorand . default ( newTransport ) ; } catch ( e ) { if ( e && 'message' in e ) { throw e ; } else { return { message : 'Error creating the ledger transport. Please ensure device is connected and the Algorand app is open.' , } ; } } return ledgerTransport ; } ; const isAvailable = async ( ) : Promise < boolean > => { const ledgerDevice = await getDevice ( ) . catch ( ( ) => { return false ; } ) ; if ( ledgerDevice !== undefined ) { return true ; } return false ; } ; const findAccountIndex = async ( fromAccount ) : Promise < number > => { let foundIndex = 0 ; let foundAccount = false ; let hasError = false ; const maxAccounts = 8 ; _PATH . pathIndex = 0 ; const fromPubKey = Buffer . from ( algosdk . decodeAddress ( fromAccount ) . publicKey ) . toString ( 'hex' ) ; while ( ! foundAccount && ! hasError && _PATH . pathIndex < maxAccounts ) { await ledgerTransport . getAddress ( _PATH . current ) . then ( ( o ) => { if ( o . publicKey === fromPubKey ) { foundIndex = _PATH . pathIndex ; foundAccount = true ; } } ) . catch ( ( e ) => { console . log ( `Error when trying to find Ledger account. ${ JSON . stringify ( e ) }` ) ; hasError = true ; } ) . finally ( ( ) => { if ( ! foundAccount ) { _PATH . pathIndex += 1 ; } } ) ; } return foundIndex ; } ; const getAllAddresses = async ( ) : Promise < LedgerActionResponse > => { const accounts = Array < object > ( ) ; let errorOnIndex = false ; const maxAccounts = 8 ; let lar = { } ; _PATH . pathIndex = 0 ; if ( ! ledgerTransport ) { ledgerTransport = await getDevice ( ) . catch ( ( e ) => { lar = e && 'message' in e ? { error : e . message } : { error : 'An unknown error has occured in connecting the Ledger device.' } ; } ) ; } if ( lar . error ) { return lar ; } while ( ! errorOnIndex && _PATH . pathIndex < maxAccounts ) { const currentIndex = `${ _PATH . pathIndex }` ; await ledgerTransport . getAddress ( _PATH . current ) . then ( ( o ) => { const publicAddress = algosdk . encodeAddress ( Buffer . from ( o . publicKey , 'hex' ) ) ; const retrievedAccount = { 'ledgerIndex' : currentIndex , 'hex' : o . publicKey , 'publicAddress' : publicAddress } ; accounts . push ( retrievedAccount ) ; } ) . catch ( ( e ) => { console . log ( e ) ; errorOnIndex = true ; lar . error = e ; } ) . finally ( _PATH . pathIndex += 1 ) ; } lar . message = accounts ; return lar ; } ; function cleanseBuildEncodeUnsignedTransaction ( transaction ) { if ( transaction . encodedTxn ) { const byteTxn = new Uint8Array ( Buffer . from ( transaction . encodedTxn , 'base64' ) . toString ( 'binary' ) . split ( '' ) . map ( ( x ) => x . charCodeAt ( 0 ) ) ) ; return { transaction : byteTxn , error : '' } ; } const { groupsToSign , currentGroup , ledgerGroup } = transaction ; const txPositionInGroup = Math . min ( ( ledgerGroup || currentGroup ) , groupsToSign . length - 1 ) ; const walletTransactions = groupsToSign [ txPositionInGroup ] ; const transactionObjs = walletTransactions . map ( ( walletTx ) => { const byteWalletTxn = new Uint8Array ( Buffer . from ( walletTx . txn , 'base64' ) . toString ( 'binary' ) . split ( '' ) . map ( ( x ) => x . charCodeAt ( 0 ) ) ) ; return byteWalletTxn ; } ) ; if ( transactionObjs . length === 0 ) { return { transaction : undefined , error : 'No signable transaction found in cached Ledger transactions.' } ; } const encodedTxn = transactionObjs [ 0 ] ; return { transaction : encodedTxn , error : '' } ; } const getAddress = async ( ) : Promise < LedgerActionResponse > => { let lar = { } ; if ( ! ledgerTransport ) { ledgerTransport = await getDevice ( ) . catch ( ( e ) => { lar = e && 'message' in e ? { error : e . message } : { error : 'An unknown error has occured in connecting the Ledger device.' } ; } ) ; } if ( lar . error ) { return lar ; } await ledgerTransport . getAddress ( _PATH . primary ) . then ( ( o ) => { lar = { message : o . publicKey } ; } ) . catch ( ( e ) => { lar = e && 'message' in e ? { error : e . message } : { error : 'An unknown error has occured in connecting the Ledger device.' } ; } ) ; return lar ; } ; const signTransaction = async ( txn ) : Promise < LedgerActionResponse > => { let lar = { } ; if ( ! ledgerTransport ) { ledgerTransport = await getDevice ( ) . catch ( ( e ) => { lar = e && 'message' in e ? { error : e . message } : { error : 'An unknown error has occured in connecting the Ledger device.' } ; } ) ; } if ( lar . error ) { return lar ; } const txnResponse = cleanseBuildEncodeUnsignedTransaction ( txn ) ; const decodedTxn = algosdk . decodeUnsignedTransaction ( txnResponse . transaction ) ; const message = Buffer . from ( txnResponse . transaction ) . toString ( 'hex' ) ; const fromAccount = algosdk . encodeAddress ( decodedTxn . from . publicKey ) ; const foundIndex = await findAccountIndex ( fromAccount ) ; if ( foundIndex === - 1 ) { lar . error = 'Transaction "from" field does not match any ledger account.' ; return lar ; } else { _PATH . pathIndex = foundIndex ; await ledgerTransport . sign ( _PATH . current , message ) . then ( ( o ) => { const sTxn = { sig : o . signature , txn : decodedTxn . get_obj_for_encoding ( ) , } ; const encTxn = algosdk . encodeObj ( sTxn ) ; lar = { message : Buffer . from ( encTxn , 'base64' ) . toString ( 'base64' ) } ; } ) . catch ( ( e ) => { lar = e && 'message' in e ? { error : e . message } : { error : 'An unknown error has occured in connecting the Ledger device.' } ; } ) ; return lar ; } } ; export const ledgerActions = { isAvailable , getAddress , getAllAddresses , signTransaction , } ; export default ledgerActions ;