@@ -14,30 +14,47 @@ import { networks } from 'bitcoinjs-lib'
1414import Logger from 'utils/Logger'
1515import bs58 from 'bs58'
1616import {
17- LedgerAppType ,
17+ LEDGER_TIMEOUTS ,
18+ getSolanaDerivationPath
19+ } from 'new/features/ledger/consts'
20+ import { assertNotNull } from 'utils/assertions'
21+ import {
22+ AddressInfo ,
1823 ExtendedPublicKey ,
19- LedgerReturnCode ,
2024 PublicKeyInfo ,
21- AddressInfo
25+ LedgerAppType ,
26+ LedgerReturnCode ,
27+ AppInfo
2228} from './types'
2329
24- export interface AppInfo {
25- applicationName : string
26- version : string
27- }
28-
2930export class LedgerService {
30- private transport : TransportBLE | null = null
31+ # transport: TransportBLE | null = null
3132 private currentAppType : LedgerAppType = LedgerAppType . UNKNOWN
3233 private appPollingInterval : number | null = null
3334 private appPollingEnabled = false
3435
36+ // Transport getter/setter with automatic error handling
37+ private get transport ( ) : TransportBLE {
38+ assertNotNull (
39+ this . #transport,
40+ 'Ledger transport is not initialized. Please connect to a device first.'
41+ )
42+ return this . #transport
43+ }
44+
45+ private set transport ( transport : TransportBLE ) {
46+ this . #transport = transport
47+ }
48+
3549 // Connect to Ledger device (transport only, no apps)
3650 async connect ( deviceId : string ) : Promise < void > {
3751 try {
3852 Logger . info ( 'Starting BLE connection attempt with deviceId:' , deviceId )
39- // Use a longer timeout for connection (30 seconds)
40- this . transport = await TransportBLE . open ( deviceId , 30000 )
53+ // Use a longer timeout for connection
54+ this . transport = await TransportBLE . open (
55+ deviceId ,
56+ LEDGER_TIMEOUTS . CONNECTION_TIMEOUT
57+ )
4158 Logger . info ( 'BLE transport connected successfully' )
4259 this . currentAppType = LedgerAppType . UNKNOWN
4360
@@ -62,7 +79,7 @@ export class LedgerService {
6279 this . appPollingEnabled = true
6380 this . appPollingInterval = setInterval ( async ( ) => {
6481 try {
65- if ( ! this . transport || ! this . transport . isConnected ) {
82+ if ( ! this . # transport || ! this . # transport. isConnected ) {
6683 this . stopAppPolling ( )
6784 return
6885 }
@@ -80,7 +97,7 @@ export class LedgerService {
8097 Logger . error ( 'Error polling app info' , error )
8198 // Don't stop polling on error, just log it
8299 }
83- } , 2000 ) // Poll every 2 seconds like the extension
100+ } , LEDGER_TIMEOUTS . APP_POLLING_INTERVAL ) // Poll every 2 seconds like the extension
84101 }
85102
86103 // Stop passive app detection polling
@@ -94,10 +111,6 @@ export class LedgerService {
94111
95112 // Get current app info from device
96113 private async getCurrentAppInfo ( ) : Promise < AppInfo > {
97- if ( ! this . transport ) {
98- throw new Error ( 'Transport not initialized' )
99- }
100-
101114 return await getLedgerAppInfo ( this . transport as Transport )
102115 }
103116
@@ -121,7 +134,10 @@ export class LedgerService {
121134 }
122135
123136 // Wait for specific app to be open (passive approach)
124- async waitForApp ( appType : LedgerAppType , timeoutMs = 30000 ) : Promise < void > {
137+ async waitForApp (
138+ appType : LedgerAppType ,
139+ timeoutMs = LEDGER_TIMEOUTS . APP_WAIT_TIMEOUT
140+ ) : Promise < void > {
125141 const startTime = Date . now ( )
126142 Logger . info ( `Waiting for ${ appType } app (timeout: ${ timeoutMs } ms)...` )
127143
@@ -135,8 +151,10 @@ export class LedgerService {
135151 return
136152 }
137153
138- // Wait 1 second before next check
139- await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
154+ // Wait before next check
155+ await new Promise ( resolve =>
156+ setTimeout ( resolve , LEDGER_TIMEOUTS . APP_CHECK_DELAY )
157+ )
140158 }
141159
142160 Logger . error ( `Timeout waiting for ${ appType } app after ${ timeoutMs } ms` )
@@ -161,7 +179,7 @@ export class LedgerService {
161179 private async reconnectIfNeeded ( deviceId : string ) : Promise < void > {
162180 Logger . info ( 'Checking if reconnection is needed' )
163181
164- if ( ! this . transport || ! this . transport . isConnected ) {
182+ if ( ! this . # transport || ! this . # transport. isConnected ) {
165183 Logger . info ( 'Transport is disconnected, attempting reconnection' )
166184 try {
167185 await this . connect ( deviceId )
@@ -180,10 +198,6 @@ export class LedgerService {
180198 evm : ExtendedPublicKey
181199 avalanche : ExtendedPublicKey
182200 } > {
183- if ( ! this . transport ) {
184- throw new Error ( 'Transport not initialized' )
185- }
186-
187201 Logger . info ( '=== getExtendedPublicKeys STARTED ===' )
188202 Logger . info ( 'Current app type:' , this . currentAppType )
189203
@@ -306,7 +320,7 @@ export class LedgerService {
306320
307321 // Check if Solana app is open
308322 async checkSolanaApp ( ) : Promise < boolean > {
309- if ( ! this . transport ) {
323+ if ( ! this . # transport) {
310324 return false
311325 }
312326
@@ -325,15 +339,19 @@ export class LedgerService {
325339 }
326340 }
327341
342+ // Get Solana address for a specific derivation path
343+ async getSolanaAddress ( derivationPath : string ) : Promise < { address : Buffer } > {
344+ await this . waitForApp ( LedgerAppType . SOLANA )
345+ const transport = await this . getTransport ( )
346+ const solanaApp = new AppSolana ( transport as Transport )
347+ return await solanaApp . getAddress ( derivationPath , false )
348+ }
349+
328350 // Get Solana public keys using SDK function (like extension)
329351 async getSolanaPublicKeys (
330352 startIndex : number ,
331353 count : number
332354 ) : Promise < PublicKeyInfo [ ] > {
333- if ( ! this . transport ) {
334- throw new Error ( 'Transport not initialized' )
335- }
336-
337355 // Create a fresh AppSolana instance for each call (like the SDK does)
338356 const transport = await this . getTransport ( )
339357 const freshSolanaApp = new AppSolana ( transport as Transport )
@@ -342,7 +360,7 @@ export class LedgerService {
342360 try {
343361 for ( let i = startIndex ; i < startIndex + count ; i ++ ) {
344362 // Use correct Solana derivation path format
345- const derivationPath = `44'/501'/0'/0'/ ${ i } `
363+ const derivationPath = getSolanaDerivationPath ( i )
346364
347365 // Simple direct call to get Solana address using fresh instance
348366 const result = await freshSolanaApp . getAddress ( derivationPath , false )
@@ -374,10 +392,6 @@ export class LedgerService {
374392 startIndex : number ,
375393 _count : number
376394 ) : Promise < PublicKeyInfo [ ] > {
377- if ( ! this . transport ) {
378- throw new Error ( 'Transport not initialized' )
379- }
380-
381395 try {
382396 // Use the SDK function directly (like the extension does)
383397 const publicKey = await getSolanaPublicKeyFromLedger (
@@ -388,7 +402,7 @@ export class LedgerService {
388402 const publicKeys : PublicKeyInfo [ ] = [
389403 {
390404 key : publicKey . toString ( 'hex' ) ,
391- derivationPath : `44'/501'/0'/0'/ ${ startIndex } ` ,
405+ derivationPath : getSolanaDerivationPath ( startIndex ) ,
392406 curve : 'ed25519'
393407 }
394408 ]
@@ -441,10 +455,6 @@ export class LedgerService {
441455 startIndex : number ,
442456 count : number
443457 ) : Promise < PublicKeyInfo [ ] > {
444- if ( ! this . transport ) {
445- throw new Error ( 'Transport not initialized' )
446- }
447-
448458 // Connect to Avalanche app
449459 await this . waitForApp ( LedgerAppType . AVALANCHE )
450460
@@ -515,10 +525,6 @@ export class LedgerService {
515525 startIndex : number ,
516526 count : number
517527 ) : Promise < AddressInfo [ ] > {
518- if ( ! this . transport ) {
519- throw new Error ( 'Transport not initialized' )
520- }
521-
522528 // Connect to Avalanche app
523529 await this . waitForApp ( LedgerAppType . AVALANCHE )
524530
@@ -634,31 +640,28 @@ export class LedgerService {
634640
635641 // Disconnect from Ledger device
636642 async disconnect ( ) : Promise < void > {
637- if ( this . transport ) {
638- await this . transport . close ( )
639- this . transport = null
643+ if ( this . # transport) {
644+ await this . # transport. close ( )
645+ this . # transport = null
640646 this . currentAppType = LedgerAppType . UNKNOWN
641647 this . stopAppPolling ( ) // Stop polling on disconnect
642648 }
643649 }
644650
645- // Get current transport (for wallet usage)
646- getTransport ( ) : TransportBLE {
647- if ( ! this . transport ) {
648- throw new Error ( 'Transport not initialized. Call connect() first.' )
649- }
650- return this . transport
651- }
652-
653651 // Check if transport is available and connected
654652 isConnected ( ) : boolean {
655- return this . transport !== null && this . transport . isConnected
653+ return this . # transport !== null && this . # transport. isConnected
656654 }
657655
658656 // Ensure connection is established for a specific device
659657 async ensureConnection ( deviceId : string ) : Promise < TransportBLE > {
660658 await this . reconnectIfNeeded ( deviceId )
661- return this . getTransport ( )
659+ return this . transport
660+ }
661+
662+ // Get the current transport (for compatibility with existing code)
663+ async getTransport ( ) : Promise < TransportBLE > {
664+ return this . transport
662665 }
663666}
664667
0 commit comments