diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 80ce8cb8..fc74738f 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -18,6 +18,12 @@ export interface LedgerAppErrorResponse { } /* eslint-enable */ +interface ConnectedApp { + /** The transport used by the app */ + readonly transport: Transport; + readonly app: CosmosApp; +} + const defaultInteractionTimeout = 120; // seconds to wait for user action on Ledger, currently is always limited to 60 const requiredCosmosAppVersion = "1.5.3"; @@ -53,7 +59,7 @@ export class LaunchpadLedger { private readonly testModeAllowed: boolean; private readonly hdPaths: readonly HdPath[]; private readonly prefix: string; - private cosmosApp: CosmosApp | null; + private connectedApp: ConnectedApp | null; public readonly platform: string; public readonly userAgent: string | null; @@ -70,7 +76,7 @@ export class LaunchpadLedger { this.testModeAllowed = testModeAllowed; this.hdPaths = hdPaths; this.prefix = prefix; - this.cosmosApp = null; + this.connectedApp = null; try { this.platform = navigator.platform; @@ -83,9 +89,9 @@ export class LaunchpadLedger { public async getCosmosAppVersion(): Promise { await this.ensureConnected(); - assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + assert(this.connectedApp, "Cosmos Ledger App is not connected"); - const response = await this.cosmosApp.getVersion(); + const response = await this.connectedApp.app.getVersion(); this.handleLedgerErrors(response); // eslint-disable-next-line @typescript-eslint/naming-convention const { major, minor, patch, test_mode: testMode } = response as VersionResponse; @@ -95,11 +101,11 @@ export class LaunchpadLedger { public async getPubkey(hdPath?: HdPath): Promise { await this.ensureConnected(); - assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + assert(this.connectedApp, "Cosmos Ledger App is not connected"); const hdPathToUse = hdPath || this.hdPaths[0]; // ledger-cosmos-js hardens the first three indices - const response = await this.cosmosApp.publicKey(unharden(hdPathToUse)); + const response = await this.connectedApp.app.publicKey(unharden(hdPathToUse)); this.handleLedgerErrors(response); return Uint8Array.from((response as PublicKeyResponse).compressed_pk); } @@ -119,18 +125,25 @@ export class LaunchpadLedger { public async sign(message: Uint8Array, hdPath?: HdPath): Promise { await this.ensureConnected(); - assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + assert(this.connectedApp, "Cosmos Ledger App is not connected"); const hdPathToUse = hdPath || this.hdPaths[0]; // ledger-cosmos-js hardens the first three indices - const response = await this.cosmosApp.sign(unharden(hdPathToUse), fromUtf8(message)); + const response = await this.connectedApp.app.sign(unharden(hdPathToUse), fromUtf8(message)); this.handleLedgerErrors(response, "Transaction signing request was rejected by the user"); return Secp256k1Signature.fromDer((response as SignResponse).signature).toFixedLength(); } + public async disconnect(): Promise { + if (this.connectedApp) { + await this.connectedApp.transport.close(); + this.connectedApp = null; + } + } + private async ensureConnected(timeout = defaultInteractionTimeout): Promise { // assume good connection if connected once - if (this.cosmosApp) { + if (this.connectedApp) { return; } @@ -139,7 +152,10 @@ export class LaunchpadLedger { } const transport = await this.createTransport(timeout * 1000); - this.cosmosApp = new CosmosApp(transport); + this.connectedApp = { + transport: transport, + app: new CosmosApp(transport), + }; await this.verifyDeviceIsReady(); } @@ -189,9 +205,9 @@ export class LaunchpadLedger { private async getOpenAppName(): Promise { await this.ensureConnected(); - assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + assert(this.connectedApp, "Cosmos Ledger App is not connected"); - const response = await this.cosmosApp.appInfo(); + const response = await this.connectedApp.app.appInfo(); this.handleLedgerErrors(response); return (response as AppInfoResponse).appName; } diff --git a/packages/launchpad-ledger/src/ledgersigner.spec.ts b/packages/launchpad-ledger/src/ledgersigner.spec.ts index 4b0941ca..8fe88e9c 100644 --- a/packages/launchpad-ledger/src/ledgersigner.spec.ts +++ b/packages/launchpad-ledger/src/ledgersigner.spec.ts @@ -46,6 +46,8 @@ describe("LedgerSigner", () => { pubkey: fromBase64("A2ZnLEcbpyjS30H5UF1vezq29aBcT9oo5EARATIW9Cpj"), }, ]); + + await signer.disconnect(); }); }); @@ -87,6 +89,8 @@ describe("LedgerSigner", () => { fistAccount.pubkey, ); expect(valid).toEqual(true); + + await signer.disconnect(); }, interactiveTimeout, ); diff --git a/packages/launchpad-ledger/src/ledgersigner.ts b/packages/launchpad-ledger/src/ledgersigner.ts index 224a55e7..8c863165 100644 --- a/packages/launchpad-ledger/src/ledgersigner.ts +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -52,4 +52,8 @@ export class LedgerSigner implements OfflineSigner { signature: encodeSecp256k1Signature(accountForAddress.pubkey, signature), }; } + + public async disconnect(): Promise { + return this.ledger.disconnect(); + } } diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index 459d82fe..6e729254 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -12,7 +12,7 @@ export declare class LaunchpadLedger { private readonly testModeAllowed; private readonly hdPaths; private readonly prefix; - private cosmosApp; + private connectedApp; readonly platform: string; readonly userAgent: string | null; constructor(options?: LaunchpadLedgerOptions); @@ -21,6 +21,7 @@ export declare class LaunchpadLedger { getPubkeys(): Promise; getCosmosAddress(pubkey?: Uint8Array): Promise; sign(message: Uint8Array, hdPath?: HdPath): Promise; + disconnect(): Promise; private ensureConnected; private createTransport; private verifyAppMode; diff --git a/packages/launchpad-ledger/types/ledgersigner.d.ts b/packages/launchpad-ledger/types/ledgersigner.d.ts index 6ddfee8b..8a81c649 100644 --- a/packages/launchpad-ledger/types/ledgersigner.d.ts +++ b/packages/launchpad-ledger/types/ledgersigner.d.ts @@ -8,4 +8,5 @@ export declare class LedgerSigner implements OfflineSigner { constructor(options?: LaunchpadLedgerOptions); getAccounts(): Promise; sign(signerAddress: string, signDoc: StdSignDoc): Promise; + disconnect(): Promise; }