@@ -121,6 +121,21 @@ export type IndexedAddress = {
121121export type DeviceDetails = CommonDeviceDetails &
122122 ( HDModeDeviceDetails | AccountModeDeviceDetails ) ;
123123
124+ export type DeviceOptions = {
125+ /**
126+ * The requestScan function to scan the QR code
127+ */
128+ requestScan : QrKeyringBridge [ 'requestScan' ] ;
129+ /**
130+ * The source of the device, which can be of type `DeviceDetails`,
131+ * `string`, or `SerializedUR`.
132+ *
133+ * When a `string` or `SerializedUR` is provided, the Device will
134+ * initialize itself from the UR.
135+ */
136+ source : DeviceDetails | string | SerializedUR ;
137+ } ;
138+
124139/**
125140 * Get the fingerprint of the source CryptoAccount or CryptoHDKey
126141 *
@@ -189,32 +204,23 @@ function readCryptoAccountOutputDescriptors(source: CryptoAccount): {
189204export class Device {
190205 readonly #requestScan: QrKeyringBridge [ 'requestScan' ] ;
191206
192- #pairedDevice?: DeviceDetails | undefined ;
193-
194- constructor ( requestScan : QrKeyringBridge [ 'requestScan' ] ) {
195- this . #requestScan = requestScan ;
196- }
207+ readonly #pairedDevice: DeviceDetails ;
197208
198209 /**
199- * Initialize the Device with a source .
210+ * Create a new Device instance .
200211 *
201- * @param source - The account source, which can be a CryptoAccount, CryptoHDKey, or a UR string.
212+ * @param options - The options for the Device, including the requestScan function
213+ * and the source of the device details.
214+ * @param options.requestScan - The function to request a scan of the QR code.
215+ * @param options.source - The source of the device details, which can be a
216+ * UR string, a `SerializedUR`, or a `DeviceDetails` object.
202217 */
203- init ( source : DeviceDetails | string | SerializedUR ) : void {
204- if ( typeof source === 'string' || 'cbor' in source ) {
205- this . #initFromUR( source ) ;
206- } else {
207- this . #pairedDevice = source ;
208- }
209- }
210-
211- /**
212- * Check if this class instance is paired with a device
213- *
214- * @returns True if the Device is initialized, false otherwise
215- */
216- isInitialized ( ) : boolean {
217- return this . #pairedDevice !== undefined ;
218+ constructor ( { requestScan, source } : DeviceOptions ) {
219+ this . #requestScan = requestScan ;
220+ this . #pairedDevice =
221+ typeof source === 'string' || 'cbor' in source
222+ ? this . #deviceDetailsFromUR( source )
223+ : source ;
218224 }
219225
220226 /**
@@ -225,10 +231,6 @@ export class Device {
225231 * @throws Will throw an error if the source is not initialized
226232 */
227233 addressFromIndex ( index : number ) : Hex {
228- if ( ! this . #pairedDevice) {
229- throw new Error ( 'Device not paired' ) ;
230- }
231-
232234 if ( this . #pairedDevice. keyringMode === DeviceMode . ACCOUNT ) {
233235 const address = Object . keys ( this . #pairedDevice. paths ) [ index ] ;
234236 if ( ! address ) {
@@ -262,10 +264,6 @@ export class Device {
262264 * @returns The path of the address
263265 */
264266 pathFromAddress ( address : Hex ) : string {
265- if ( ! this . #pairedDevice) {
266- throw new Error ( 'UR not initialized' ) ;
267- }
268-
269267 const normalizedAddress = getChecksumAddress ( add0x ( address ) ) ;
270268
271269 if ( this . #pairedDevice. keyringMode === DeviceMode . ACCOUNT ) {
@@ -289,10 +287,6 @@ export class Device {
289287 * @returns The index of the address
290288 */
291289 indexFromAddress ( address : Hex ) : number {
292- if ( ! this . #pairedDevice) {
293- throw new Error ( 'UR not initialized' ) ;
294- }
295-
296290 const cachedIndex = this . #pairedDevice. indexes [ address ] ;
297291 if ( cachedIndex !== undefined ) {
298292 return Number ( cachedIndex ) ;
@@ -344,21 +338,14 @@ export class Device {
344338 }
345339
346340 /**
347- * Retrieve the details of the paired device, if any .
341+ * Retrieve the details of the paired device.
348342 *
349- * @returns Thea paired device details, or undefined if not initialized
343+ * @returns Thea paired device details
350344 */
351- getDeviceDetails ( ) : DeviceDetails | undefined {
345+ getDeviceDetails ( ) : DeviceDetails {
352346 return this . #pairedDevice;
353347 }
354348
355- /**
356- * Clear the paired device details.
357- */
358- clear ( ) : void {
359- this . #pairedDevice = undefined ;
360- }
361-
362349 /**
363350 * Sign a transaction. This is equivalent to the `eth_signTransaction`
364351 * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
@@ -372,10 +359,6 @@ export class Device {
372359 address : Hex ,
373360 transaction : TypedTransaction ,
374361 ) : Promise < TypedTxData > {
375- if ( ! this . #pairedDevice?. xfp ) {
376- throw new Error ( 'No device paired.' ) ;
377- }
378-
379362 const dataType =
380363 transaction . type === TransactionType . Legacy
381364 ? DataType . transaction
@@ -433,10 +416,6 @@ export class Device {
433416 address : Hex ,
434417 data : TypedMessage < Types > ,
435418 ) : Promise < string > {
436- if ( ! this . #pairedDevice?. xfp ) {
437- throw new Error ( 'No device paired.' ) ;
438- }
439-
440419 const requestId = uuidv4 ( ) ;
441420 const ethSignRequestUR = EthSignRequest . constructETHRequest (
442421 Buffer . from ( JSON . stringify ( data ) , 'utf8' ) ,
@@ -478,10 +457,6 @@ export class Device {
478457 * @returns The signed message.
479458 */
480459 async signPersonalMessage ( address : Hex , message : Hex ) : Promise < string > {
481- if ( ! this . #pairedDevice?. xfp ) {
482- throw new Error ( 'No device paired.' ) ;
483- }
484-
485460 const requestId = uuidv4 ( ) ;
486461 const ethSignRequestUR = EthSignRequest . constructETHRequest (
487462 Buffer . from ( remove0x ( message ) , 'hex' ) ,
@@ -511,38 +486,39 @@ export class Device {
511486 }
512487
513488 /**
514- * Set the paired device from a UR string
489+ * Derive the device details from a UR string
515490 *
516491 * @param ur - The UR string to set the root account from
492+ * @returns The device details derived from the UR
517493 */
518- #initFromUR ( ur : string | SerializedUR ) : void {
494+ #deviceDetailsFromUR ( ur : string | SerializedUR ) : DeviceDetails {
519495 const source = this . #decodeUR( ur ) ;
520496 const fingerprint = getFingerprintFromSource ( source ) ;
521497
522498 if ( source instanceof CryptoAccount ) {
523499 const { name, xfp, paths, keyringAccount } =
524500 readCryptoAccountOutputDescriptors ( source ) ;
525- this . #pairedDevice = {
501+ return {
526502 keyringMode : DeviceMode . ACCOUNT ,
527503 keyringAccount,
528504 name,
529505 xfp,
530506 paths,
531507 indexes : { } ,
532508 } ;
533- } else {
534- const { getBip32Key, getOrigin, getChildren, getName, getNote } = source ;
535- this . #pairedDevice = {
536- keyringMode : DeviceMode . HD ,
537- keyringAccount : getNote ( ) ,
538- name : getName ( ) ,
539- xfp : fingerprint ,
540- hdPath : `m/${ getOrigin ( ) . getPath ( ) } ` ,
541- childrenPath : getChildren ( ) ?. getPath ( ) || DEFAULT_CHILDREN_PATH ,
542- xpub : getBip32Key ( ) ,
543- indexes : { } ,
544- } ;
545509 }
510+
511+ const { getBip32Key, getOrigin, getChildren, getName, getNote } = source ;
512+ return {
513+ keyringMode : DeviceMode . HD ,
514+ keyringAccount : getNote ( ) ,
515+ name : getName ( ) ,
516+ xfp : fingerprint ,
517+ hdPath : `m/${ getOrigin ( ) . getPath ( ) } ` ,
518+ childrenPath : getChildren ( ) ?. getPath ( ) || DEFAULT_CHILDREN_PATH ,
519+ xpub : getBip32Key ( ) ,
520+ indexes : { } ,
521+ } ;
546522 }
547523
548524 /**
0 commit comments