UNPKG

@metamask/eth-ledger-bridge-keyring

Version:
1 lines 26.5 kB
{"version":3,"file":"ledger-dmk-bridge.mjs","sourceRoot":"","sources":["../../src/dmk/ledger-dmk-bridge.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACZ,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACf,wCAAwC;AAQzC,OAAO,EACL,MAAM,EACN,KAAK,EACL,cAAc,EACd,EAAE,EACF,OAAO,EACP,YAAY,EACZ,KAAK,EACN,aAAa;AACd,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,GAAG,EACH,SAAS,EACV,uBAAuB;AAiBxB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EAClB,mCAA+B;AAChC,OAAO,EAAE,6BAA6B,EAAE,gDAA4C;AACpF,OAAO,EACL,UAAU,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACZ,6BAAyB;AAC1B,OAAO,EAAE,4BAA4B,EAAE,8CAA0C;AAWjF;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IAW1B,IAAI,oBAAoB;QACtB,OAAO,uBAAA,IAAI,sCAAe,CAAC,YAAY,EAAE,CAAC;IAC5C,CAAC;IAID,IAAI,iBAAiB;QACnB,OAAO,uBAAA,IAAI,oCAAa,CAAC;IAC3B,CAAC;IAED,YAAY,IAA4B;;QApB/B,uDAAmD;QAEnD,uCAA0B;QAEnC,wCAA8B;QAE9B,uCAAe,KAAK,EAAC;QAErB,yCAAiB,IAAI,OAAO,EAA0B,EAAC;QAMvD,+CAA4C,IAAI,EAAC;QAO/C,uBAAA,IAAI,wBAAQ,IAAI,0BAA0B,EAAE;aACzC,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC;aACnC,KAAK,EAAE,MAAA,CAAC;QACX,uBAAA,IAAI,yBAAS,IAAI,MAAA,CAAC;QAClB,uBAAA,IAAI,wCAAwB,IAAI,4BAA4B,CAAC,uBAAA,IAAI,4BAAK,CAAC,MAAA,CAAC;IAC1E,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI;QACR,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,OAAO;QACX,uBAAA,IAAI,4CAAqB,EAAE,WAAW,EAAE,CAAC;QACzC,uBAAA,IAAI,wCAAwB,IAAI,MAAA,CAAC;QAEjC,IAAI,CAAC;YACH,MAAM,uBAAA,IAAI,4CAAqB,CAAC,OAAO,EAAE,CAAC;QAC5C,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,gCAAgB,KAAK,MAAA,CAAC;YAC1B,uBAAA,IAAI,sCAAe,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/C,uBAAA,IAAI,sCAAe,CAAC,QAAQ,EAAE,CAAC;YAC/B,uBAAA,IAAI,kCAAkB,IAAI,OAAO,EAA0B,MAAA,CAAC;QAC9D,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU;QACd,OAAO,uBAAA,IAAI,6BAAM,CAAC;IACpB,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,UAAU,CAAC,IAA4B;QAC3C,uBAAA,IAAI,yBAAS,IAAI,MAAA,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,eAAe,CAAC,SAAiB;QACrC,MAAM,uBAAA,IAAI,4CAAqB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACxD,uBAAA,IAAI,gCAAgB,IAAI,MAAA,CAAC;QACzB,uBAAA,IAAI,2EAAwB,MAA5B,IAAI,EAAyB,SAAS,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CACd,GAAG,IAAkE;QAErE,OAAO,uBAAA,IAAI,4CAAqB,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,GAAG,IAAyD;QAE5D,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,4CAAqB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACnE,uBAAA,IAAI,gCAAgB,IAAI,MAAA,CAAC;QACzB,uBAAA,IAAI,2EAAwB,MAA5B,IAAI,EAAyB,SAAS,CAAC,CAAC;QAExC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,qBAAqB,CACzB,cAAkC;QAElC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,SAAS,GAAG,uBAAA,IAAI,4CAAqB,CAAC,YAAY,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,uBAAA,IAAI,4BAAK,CAAC,WAAW,CAAC;YACzC,SAAS;YACT,OAAO,EAAE,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;SACrD,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,uBAAA,IAAI,4DAAS,MAAb,IAAI,EAAU,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,SAAS,GAAG,uBAAA,IAAI,4CAAqB,CAAC,YAAY,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,uBAAA,IAAI,4BAAK,CAAC,WAAW,CAAC;YACzC,SAAS;YACT,OAAO,EAAE,IAAI,eAAe,EAAE;SAC/B,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,uBAAA,IAAI,4DAAS,MAAb,IAAI,EAAU,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,EACjB,MAAM,GACa;QACnB,MAAM,EAAE,UAAU,EAAE,GAAG,uBAAA,IAAI,4CAAqB;aAC7C,YAAY,EAAE;aACd,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE;YACnC,aAAa,EAAE,KAAK;YACpB,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QACL,MAAM,QAAQ,GACZ,MAAM,uBAAA,IAAI,wEAAqB,MAAzB,IAAI,EAAuC,UAAU,CAAC,CAAC;QAE/D,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,SAAS,EAAE,QAAQ,CAAC,SAAS;SAC9B,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,qBAAqB,CAAC,EAC1B,EAAE,EACF,MAAM,GACsB;QAC5B,MAAM,EAAE,UAAU,EAAE,GAAG,uBAAA,IAAI,4CAAqB;aAC7C,YAAY,EAAE;aACd,eAAe,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wEAAqB,MAAzB,IAAI,EAAiC,UAAU,CAAC,CAAC;QAEzE,OAAO;YACL,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;YAC3B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;SAC/B,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,iBAAiB,CAAC,EACtB,MAAM,EACN,OAAO,GACiB;QACxB,MAAM,EAAE,UAAU,EAAE,GAAG,uBAAA,IAAI,4CAAqB;aAC7C,YAAY,EAAE;aACd,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wEAAqB,MAAzB,IAAI,EAAiC,UAAU,CAAC,CAAC;QAEzE,OAAO;YACL,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;SAC/B,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,mBAAmB,CAAC,EACxB,MAAM,EACN,OAAO,GACmB;QAC1B,MAAM,EAAE,UAAU,EAAE,GAAG,uBAAA,IAAI,4CAAqB;aAC7C,YAAY,EAAE;aACd,aAAa,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wEAAqB,MAAzB,IAAI,EAAiC,UAAU,CAAC,CAAC;QAEzE,OAAO;YACL,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;SAC/B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iCAAiC,CAAC,EACtC,MAAM,EACN,OAAO,EACP,eAAe,EACf,KAAK,GACmC;QACxC,MAAM,EAAE,UAAU,EAAE,GAAG,uBAAA,IAAI,4CAAqB;aAC7C,YAAY,EAAE;aACd,2BAA2B,CAC1B,eAAe,CAAC,MAAM,CAAC,EACvB,OAAO,EACP,eAAe,EACf,KAAK,CACN,CAAC;QACJ,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wEAAqB,MAAzB,IAAI,EAAiC,UAAU,CAAC,CAAC;QAEzE,OAAO;YACL,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;YAC3B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9B,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;SAC/B,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,oBAAoB;QACxB,MAAM,SAAS,GAAG,uBAAA,IAAI,4CAAqB,CAAC,YAAY,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,uBAAA,IAAI,4BAAK,CAAC,WAAW,CAAC;YACzC,SAAS;YACT,OAAO;SACR,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,uBAAA,IAAI,4DAAS,MAAb,IAAI,EAAU,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI;YACzB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;SAC7B,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,SAAS,GAAG,uBAAA,IAAI,4CAAqB,CAAC,YAAY,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,6BAA6B,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,uBAAA,IAAI,4BAAK,CAAC,WAAW,CAAC;YACzC,SAAS;YACT,OAAO;SACR,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,uBAAA,IAAI,4DAAS,MAAb,IAAI,EAAU,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;QAExB,mDAAmD;QACnD,gGAAgG;QAChG,0EAA0E;QAC1E,wEAAwE;QACxE,wEAAwE;QACxE,oEAAoE;QACpE,oEAAoE;QACpE,qBAAqB;QACrB,EAAE;QACF,sEAAsE;QACtE,sEAAsE;QACtE,sEAAsE;QACtE,0BAA0B;QAC1B,OAAO;YACL,oBAAoB,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACtD,0BAA0B,EAAE,CAAC;YAC7B,YAAY,EAAE,CAAC;YACf,gBAAgB,EAAE,CAAC;YACnB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC;IACJ,CAAC;CA8GF;0WA5GC,KAAK,+CACH,UAAoE;IAEpE,MAAM,KAAK,GAAG,MAAM,cAAc,CAChC,UAAU,CAAC,IAAI,CACb,UAAU,CAAC,CAAC,KAAc,EAAE,EAAE;QAC5B,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,EACF,MAAM,CACJ,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CACb,MAAM,KAAK,kBAAkB,CAAC,SAAS;QACvC,MAAM,KAAK,kBAAkB,CAAC,KAAK,CACtC,CACF,CACF,CAAC;IAEF,IAAI,KAAK,CAAC,MAAM,KAAK,kBAAkB,CAAC,SAAS,EAAE,CAAC;QAClD,OAAO,KAAK,CAAC,MAAM,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAC9C,MAAM,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,oEAAoE;IACpE,qDAAqD;IACrD,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;AACpE,CAAC,+DAEQ,KAAc;IACrB,IAAI,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;AAC7C,CAAC,6FAuBuB,SAAiB;IACvC,uBAAA,IAAI,4CAAqB,EAAE,WAAW,EAAE,CAAC;IAEzC,MAAM,cAAc,GAAG,KAAM,CAAC;IAC9B,MAAM,aAAa,GAAG,IAAK,CAAC;IAC5B,MAAM,WAAW,GAAG,EAAE,CAAC;IAEvB,MAAM,sBAAsB,GAAG,CAC7B,UAAU,GAAG,CAAC,EACsB,EAAE,CACtC,uBAAA,IAAI,4BAAK,CAAC,qBAAqB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,CACjD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACd,SAAS,EAAE,KAAK,CAAC,YAAY,KAAK,YAAY,CAAC,aAAa;KAC7D,CAAC,CAAC,EACH,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,aAAa,GAAG,CAAC,IAAI,UAAU,EAC/B,cAAc,CACf,CAAC;QACF,OAAO,MAAM,CACX,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EACxB,KAAK,CAAC,GAAG,EAAE,CACT,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CACf,SAAS,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CACxD,CACF,CACF,CAAC;IACJ,CAAC,CAAC,EACF,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAA4B,CAAC,CACxD,CAAC;IAEJ,uBAAA,IAAI,wCAAwB,sBAAsB,EAAE;SACjD,IAAI,CACH,oBAAoB,CAClB,CAAC,CAAyB,EAAE,CAAyB,EAAE,EAAE,CACvD,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,CAC9B,CACF;SACA,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACnB,uBAAA,IAAI,gCAAgB,KAAK,CAAC,SAAS,MAAA,CAAC;QACpC,uBAAA,IAAI,sCAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,MAAA,CAAC;AACP,CAAC","sourcesContent":["import {\n CloseAppCommand,\n DeviceActionStatus,\n DeviceManagementKitBuilder,\n DeviceStatus,\n GetAppAndVersionCommand,\n isSuccessCommandResult,\n OpenAppCommand,\n} from '@ledgerhq/device-management-kit';\nimport type {\n DeviceActionState,\n DeviceManagementKit,\n} from '@ledgerhq/device-management-kit';\nimport type { Signature } from '@ledgerhq/device-signer-kit-ethereum';\nimport type Transport from '@ledgerhq/hw-transport';\nimport type { Observable } from 'rxjs';\nimport {\n concat,\n defer,\n firstValueFrom,\n of,\n Subject,\n Subscription,\n timer,\n} from 'rxjs';\nimport {\n catchError,\n distinctUntilChanged,\n endWith,\n filter,\n map,\n switchMap,\n} from 'rxjs/operators';\n\nimport {\n AppConfigurationResponse,\n GetAppNameAndVersionResponse,\n GetPublicKeyParams,\n GetPublicKeyResponse,\n LedgerBridge,\n LedgerSignDelegationAuthorizationParams,\n LedgerSignDelegationAuthorizationResponse,\n LedgerSignMessageParams,\n LedgerSignMessageResponse,\n LedgerSignTransactionParams,\n LedgerSignTransactionResponse,\n LedgerSignTypedDataParams,\n LedgerSignTypedDataResponse,\n} from '../ledger-bridge';\nimport {\n isDeviceExchangeError,\n translateDmkError,\n} from './dmk-error-translator';\nimport { EthGetAppConfigurationCommand } from './eth-get-app-configuration-command';\nimport {\n hexToBytes,\n stripHexPrefix,\n stripPathPrefix,\n toHexString,\n} from './internal-utils';\nimport { LedgerDmkTransportMiddleware } from './ledger-dmk-transport-middleware';\n\nexport type LedgerDmkBridgeOptions = {\n transportFactory: Parameters<DeviceManagementKitBuilder['addTransport']>[0];\n};\n\ntype PublicKeyOutput = Pick<\n GetPublicKeyResponse,\n 'address' | 'chainCode' | 'publicKey'\n>;\n\n/**\n * LedgerDmkBridge is a bridge between the LedgerKeyring and the\n * LedgerDmkTransportMiddleware.\n * It initializes and manages the DeviceManagementKit internally.\n * The transport factory is injected via constructor, making it platform-agnostic.\n */\nexport class LedgerDmkBridge implements LedgerBridge<LedgerDmkBridgeOptions> {\n readonly #transportMiddleware: LedgerDmkTransportMiddleware;\n\n readonly #sdk: DeviceManagementKit;\n\n #opts: LedgerDmkBridgeOptions;\n\n #isConnected = false;\n\n #sessionState$ = new Subject<{ connected: boolean }>();\n\n get onSessionStateChange(): Observable<{ connected: boolean }> {\n return this.#sessionState$.asObservable();\n }\n\n #sessionSubscription: Subscription | null = null;\n\n get isDeviceConnected(): boolean {\n return this.#isConnected;\n }\n\n constructor(opts: LedgerDmkBridgeOptions) {\n this.#sdk = new DeviceManagementKitBuilder()\n .addTransport(opts.transportFactory)\n .build();\n this.#opts = opts;\n this.#transportMiddleware = new LedgerDmkTransportMiddleware(this.#sdk);\n }\n\n /**\n * Compatibility shim for the shared {@link LedgerBridge} interface.\n *\n * DMK session setup happens externally via `updateSessionId` or `connect`,\n * so this method is a no-op.\n *\n * @returns A promise that resolves immediately.\n */\n async init(): Promise<void> {\n return undefined;\n }\n\n /**\n * Destroys the bridge and cleans up resources.\n *\n * Unsubscribes session monitoring, disposes the transport middleware, and\n * completes the session-state subject so all subscribers are released. A\n * fresh subject is installed afterward so the bridge can be reconnected\n * after destroy. State cleanup happens in a `finally` block so the bridge\n * is marked disconnected even when middleware `dispose()` rejects.\n *\n * @returns A promise that resolves when cleanup is complete.\n */\n async destroy(): Promise<void> {\n this.#sessionSubscription?.unsubscribe();\n this.#sessionSubscription = null;\n\n try {\n await this.#transportMiddleware.dispose();\n } finally {\n this.#isConnected = false;\n this.#sessionState$.next({ connected: false });\n this.#sessionState$.complete();\n this.#sessionState$ = new Subject<{ connected: boolean }>();\n }\n }\n\n /**\n * Returns the bridge options captured at construction.\n *\n * Compatibility shim for the shared {@link LedgerBridge} interface. The DMK\n * bridge does not use runtime-reconfigurable options; this method exists\n * only to satisfy the interface contract.\n *\n * @returns A promise that resolves with the current bridge options.\n */\n async getOptions(): Promise<LedgerDmkBridgeOptions> {\n return this.#opts;\n }\n\n /**\n * Replaces the stored bridge options.\n *\n * Compatibility shim for the shared {@link LedgerBridge} interface. Stored\n * options are not re-applied to the underlying DMK instance or transport\n * middleware; the values used at construction time remain in effect.\n *\n * @param opts - The options to set for the bridge.\n * @returns A promise that resolves when options are set.\n */\n async setOptions(opts: LedgerDmkBridgeOptions): Promise<void> {\n this.#opts = opts;\n }\n\n /**\n * Updates the session ID used for communication.\n *\n * @param sessionId - The session ID from DMK.\n * @returns A promise that resolves with true if the session was updated successfully.\n */\n async updateSessionId(sessionId: string): Promise<boolean> {\n await this.#transportMiddleware.setSessionId(sessionId);\n this.#isConnected = true;\n this.#startSessionMonitoring(sessionId);\n return true;\n }\n\n /**\n * Starts device discovery for the configured DMK transport.\n *\n * @param args - Optional DMK discovery options.\n * @returns An observable that emits discovered devices.\n */\n startDiscovering(\n ...args: Parameters<LedgerDmkTransportMiddleware['startDiscovering']>\n ): ReturnType<LedgerDmkTransportMiddleware['startDiscovering']> {\n return this.#transportMiddleware.startDiscovering(...args);\n }\n\n /**\n * Connects to a discovered device using the configured DMK transport.\n *\n * @param args - The DMK connection arguments.\n * @returns The created session ID.\n */\n async connect(\n ...args: Parameters<LedgerDmkTransportMiddleware['connect']>\n ): ReturnType<LedgerDmkTransportMiddleware['connect']> {\n const sessionId = await this.#transportMiddleware.connect(...args);\n this.#isConnected = true;\n this.#startSessionMonitoring(sessionId);\n\n return sessionId;\n }\n\n /**\n * Compatibility shim for the shared {@link LedgerBridge} interface.\n *\n * DMK transport selection happens through the injected transport factory\n * at construction time, so this method is a no-op that always succeeds.\n *\n * @param _transportType - The requested transport type (ignored).\n * @returns A promise that resolves with `true`.\n */\n async updateTransportMethod(\n _transportType: string | Transport,\n ): Promise<boolean> {\n return true;\n }\n\n async openEthApp(): Promise<void> {\n const sessionId = this.#transportMiddleware.getSessionId();\n const result = await this.#sdk.sendCommand({\n sessionId,\n command: new OpenAppCommand({ appName: 'Ethereum' }),\n });\n\n if (!isSuccessCommandResult(result)) {\n throw this.#toError(result.error);\n }\n }\n\n async closeApps(): Promise<void> {\n const sessionId = this.#transportMiddleware.getSessionId();\n const result = await this.#sdk.sendCommand({\n sessionId,\n command: new CloseAppCommand(),\n });\n\n if (!isSuccessCommandResult(result)) {\n throw this.#toError(result.error);\n }\n }\n\n /**\n * Compatibility shim for the shared {@link LedgerBridge} interface.\n *\n * The Ethereum signer kit automatically opens the Ethereum app before each\n * signing operation via DMK's `CallTaskInAppDeviceAction`, so this method\n * is a no-op that always succeeds.\n *\n * @returns A promise that resolves with `true`.\n */\n async attemptMakeApp(): Promise<boolean> {\n return true;\n }\n\n /**\n * Retrieves public key/address for a given HD path.\n *\n * @param options0 - The parameters object.\n * @param options0.hdPath - The HD path to derive the public key from.\n * @returns A promise that resolves with the public key response.\n */\n async getPublicKey({\n hdPath,\n }: GetPublicKeyParams): Promise<GetPublicKeyResponse> {\n const { observable } = this.#transportMiddleware\n .getEthSigner()\n .getAddress(stripPathPrefix(hdPath), {\n checkOnDevice: false,\n returnChainCode: true,\n });\n const response =\n await this.#waitForDeviceAction<PublicKeyOutput>(observable);\n\n return {\n publicKey: response.publicKey,\n address: response.address,\n chainCode: response.chainCode,\n };\n }\n\n /**\n * Signs an Ethereum transaction.\n *\n * @param options0 - The parameters object.\n * @param options0.tx - The transaction hex string to sign.\n * @param options0.hdPath - The HD path for signing.\n * @returns A promise that resolves with the transaction signature.\n */\n async deviceSignTransaction({\n tx,\n hdPath,\n }: LedgerSignTransactionParams): Promise<LedgerSignTransactionResponse> {\n const { observable } = this.#transportMiddleware\n .getEthSigner()\n .signTransaction(stripPathPrefix(hdPath), hexToBytes(tx));\n const signature = await this.#waitForDeviceAction<Signature>(observable);\n\n return {\n v: toHexString(signature.v),\n r: stripHexPrefix(signature.r),\n s: stripHexPrefix(signature.s),\n };\n }\n\n /**\n * Signs a personal message.\n *\n * @param options0 - The parameters object.\n * @param options0.hdPath - The HD path for signing.\n * @param options0.message - The message to sign.\n * @returns A promise that resolves with the message signature.\n */\n async deviceSignMessage({\n hdPath,\n message,\n }: LedgerSignMessageParams): Promise<LedgerSignMessageResponse> {\n const { observable } = this.#transportMiddleware\n .getEthSigner()\n .signMessage(stripPathPrefix(hdPath), hexToBytes(message));\n const signature = await this.#waitForDeviceAction<Signature>(observable);\n\n return {\n v: signature.v,\n r: stripHexPrefix(signature.r),\n s: stripHexPrefix(signature.s),\n };\n }\n\n /**\n * Signs EIP-712 typed data.\n *\n * @param options0 - The parameters object.\n * @param options0.hdPath - The HD path for signing.\n * @param options0.message - The typed data message to sign.\n * @returns A promise that resolves with the typed data signature.\n */\n async deviceSignTypedData({\n hdPath,\n message,\n }: LedgerSignTypedDataParams): Promise<LedgerSignTypedDataResponse> {\n const { observable } = this.#transportMiddleware\n .getEthSigner()\n .signTypedData(stripPathPrefix(hdPath), message);\n const signature = await this.#waitForDeviceAction<Signature>(observable);\n\n return {\n v: signature.v,\n r: stripHexPrefix(signature.r),\n s: stripHexPrefix(signature.s),\n };\n }\n\n async deviceSignDelegationAuthorization({\n hdPath,\n chainId,\n contractAddress,\n nonce,\n }: LedgerSignDelegationAuthorizationParams): Promise<LedgerSignDelegationAuthorizationResponse> {\n const { observable } = this.#transportMiddleware\n .getEthSigner()\n .signDelegationAuthorization(\n stripPathPrefix(hdPath),\n chainId,\n contractAddress,\n nonce,\n );\n const signature = await this.#waitForDeviceAction<Signature>(observable);\n\n return {\n v: toHexString(signature.v),\n r: stripHexPrefix(signature.r),\n s: stripHexPrefix(signature.s),\n };\n }\n\n /**\n * Retrieves the current app name and version.\n *\n * @returns A promise that resolves with the app name and version.\n */\n async getAppNameAndVersion(): Promise<GetAppNameAndVersionResponse> {\n const sessionId = this.#transportMiddleware.getSessionId();\n const command = new GetAppAndVersionCommand();\n const result = await this.#sdk.sendCommand({\n sessionId,\n command,\n });\n\n if (!isSuccessCommandResult(result)) {\n throw this.#toError(result.error);\n }\n\n return {\n appName: result.data.name,\n version: result.data.version,\n };\n }\n\n /**\n * Retrieves the Ethereum app configuration using the eth-specific\n * GetAppConfiguration APDU command (CLA 0xE0, INS 0x06).\n *\n * @returns A promise that resolves with the app configuration.\n */\n async getAppConfiguration(): Promise<AppConfigurationResponse> {\n const sessionId = this.#transportMiddleware.getSessionId();\n const command = new EthGetAppConfigurationCommand();\n const result = await this.#sdk.sendCommand({\n sessionId,\n command,\n });\n\n if (!isSuccessCommandResult(result)) {\n throw this.#toError(result.error);\n }\n\n const { data } = result;\n\n // The `ethapp.adoc` spec for `getAppConfiguration`\n // (https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.adoc#get-app-configuration)\n // defines four flag bits. This bridge only consumes three (blind signing,\n // web3 checks enabled, web3 checks opt-in). The fourth — `0x02` *ERC 20\n // Token information needs to be provided externally* — is intentionally\n // mapped to `erc20ProvisioningNecessary: 0` because the rest of the\n // keyring does not implement the `PROVIDE ERC 20 TOKEN INFORMATION`\n // provisioning flow.\n //\n // TODO: The shared `AppConfigurationResponse` interface also requires\n // `starkEnabled` and `starkv2Supported`, which are not exposed by the\n // `getAppConfiguration` APDU at all. They are stubbed to 0 to satisfy\n // the interface contract.\n return {\n arbitraryDataEnabled: data.blindSigningEnabled ? 1 : 0,\n erc20ProvisioningNecessary: 0,\n starkEnabled: 0,\n starkv2Supported: 0,\n version: data.version,\n };\n }\n\n async #waitForDeviceAction<TOutput>(\n observable: Observable<DeviceActionState<TOutput, unknown, unknown>>,\n ): Promise<TOutput> {\n const state = await firstValueFrom(\n observable.pipe(\n catchError((error: unknown) => {\n throw translateDmkError(error);\n }),\n filter(\n ({ status }) =>\n status === DeviceActionStatus.Completed ||\n status === DeviceActionStatus.Error,\n ),\n ),\n );\n\n if (state.status === DeviceActionStatus.Completed) {\n return state.output;\n }\n\n if (state.status === DeviceActionStatus.Error) {\n throw translateDmkError(state.error);\n }\n\n // Unreachable: the filter above only lets Completed or Error states\n // through. Defensive guard retained for type safety.\n throw new Error('Ledger device action ended without completion.');\n }\n\n #toError(error: unknown): Error {\n if (isDeviceExchangeError(error)) {\n return translateDmkError(error);\n }\n\n if (error instanceof Error) {\n return error;\n }\n\n return new Error('Ledger command failed.');\n }\n\n /**\n * Monitors the DMK session connection state with exponential backoff retries.\n *\n * This is necessary because the DMK does not emit disconnection events on its\n * own — the bridge must actively poll the session state to detect when a\n * Ledger device is unplugged, the Bluetooth/WUSB link drops, or the session\n * is invalidated. Without this monitoring the keyring would retain a stale\n * `isDeviceConnected = true` and subsequent signing requests would hang or\n * fail with cryptic transport errors.\n *\n * Subscribes to the device session state and emits connection changes through\n * {@link #sessionState$}. On error, retries up to 10 times with exponential\n * backoff (capped at 30 s) and emits a `connected: false` state in between\n * attempts. Once the retry limit is exhausted the stream ends with a final\n * `connected: false`. Duplicate consecutive states are suppressed via\n * `distinctUntilChanged`.\n *\n * Any previous monitoring subscription is cancelled before a new one is created.\n *\n * @param sessionId - The DMK session ID to monitor.\n */\n #startSessionMonitoring(sessionId: string): void {\n this.#sessionSubscription?.unsubscribe();\n\n const MAX_BACKOFF_MS = 30_000;\n const BASE_DELAY_MS = 1_000;\n const MAX_RETRIES = 10;\n\n const createMonitoringStream = (\n retryCount = 0,\n ): Observable<{ connected: boolean }> =>\n this.#sdk.getDeviceSessionState({ sessionId }).pipe(\n map((state) => ({\n connected: state.deviceStatus !== DeviceStatus.NOT_CONNECTED,\n })),\n catchError(() => {\n if (retryCount >= MAX_RETRIES) {\n return of({ connected: false });\n }\n const delay = Math.min(\n BASE_DELAY_MS * 2 ** retryCount,\n MAX_BACKOFF_MS,\n );\n return concat(\n of({ connected: false }),\n defer(() =>\n timer(delay).pipe(\n switchMap(() => createMonitoringStream(retryCount + 1)),\n ),\n ),\n );\n }),\n endWith({ connected: false } as { connected: boolean }),\n );\n\n this.#sessionSubscription = createMonitoringStream()\n .pipe(\n distinctUntilChanged(\n (a: { connected: boolean }, b: { connected: boolean }) =>\n a.connected === b.connected,\n ),\n )\n .subscribe((state) => {\n this.#isConnected = state.connected;\n this.#sessionState$.next(state);\n });\n }\n}\n"]}