Merge pull request #446 from CosmWasm/445-ledger-transport-agnostic
Pass transport to Ledger signer
This commit is contained in:
@@ -41,6 +41,9 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: sudo apt-get install libusb-1.0-0-dev
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
@@ -111,6 +114,11 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: |
|
||||
wget -O "$HOME/libusb.deb" http://de.archive.ubuntu.com/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.20-1_amd64.deb
|
||||
sudo dpkg -i "$HOME/libusb.deb"
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
@@ -214,6 +222,11 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: |
|
||||
wget -O "$HOME/libusb.deb" http://de.archive.ubuntu.com/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.20-1_amd64.deb
|
||||
sudo dpkg -i "$HOME/libusb.deb"
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
@@ -298,6 +311,11 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: |
|
||||
wget -O "$HOME/libusb.deb" http://de.archive.ubuntu.com/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.20-1_amd64.deb
|
||||
sudo dpkg -i "$HOME/libusb.deb"
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
@@ -344,6 +362,9 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: sudo apt-get install libusb-1.0-0-dev
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
@@ -391,6 +412,9 @@ jobs:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install libusb
|
||||
command: sudo apt-get install libusb-1.0-0-dev
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
@@ -4,25 +4,16 @@
|
||||
|
||||
## Supported platforms
|
||||
|
||||
This library works with Node.js as well as certain browsers. We use the
|
||||
This library must be provided with a Ledger transport, and has been tested with
|
||||
appropriate transports for Node.js as well as certain browsers. In our demo code
|
||||
we use
|
||||
[@ledgerhq/hw-transport-node-hid](https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-transport-node-hid)
|
||||
to connect to Ledger devices from Node.js. We use the
|
||||
[@ledgerhq/hw-transport-webusb](https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-transport-webusb)
|
||||
library to connect to Ledger devices from the browser via USB. You can check the
|
||||
support status of this library
|
||||
[here](https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-transport-webusb#support-status).
|
||||
|
||||
Note the optional dependencies:
|
||||
|
||||
```json
|
||||
"optionalDependencies": {
|
||||
"@ledgerhq/hw-transport-node-hid": "^5.23.2",
|
||||
"@ledgerhq/hw-transport-webusb": "^5.23.0"
|
||||
}
|
||||
```
|
||||
|
||||
If you are using this library with Node.js you must install
|
||||
`@ledgerhq/hw-transport-node-hid`. You’ll need `@ledgerhq/hw-transport-webusb`
|
||||
for the browser.
|
||||
|
||||
## Running the demo
|
||||
|
||||
### Node.js
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="getAccounts()">
|
||||
<button onclick="getAccounts(window.signer)">
|
||||
Get Accounts
|
||||
</button>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="sign()">
|
||||
<button onclick="sign(window.signer)">
|
||||
Sign Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const demo = require("../build/demo/node");
|
||||
|
||||
async function run() {
|
||||
const signer = await demo.createSigner();
|
||||
|
||||
const accountNumbers = [0, 1, 2, 10];
|
||||
const accounts = await demo.getAccounts();
|
||||
const accounts = await demo.getAccounts(signer);
|
||||
console.info("Accounts from Ledger device:");
|
||||
console.table(accounts.map((account, i) => ({ ...account, accountNumber: accountNumbers[i] })));
|
||||
|
||||
const accountNumber0 = 0;
|
||||
const address0 = accounts[accountNumber0].address;
|
||||
const signature0 = await demo.sign(accountNumber0, address0, address0);
|
||||
const signature0 = await demo.sign(signer, accountNumber0, address0, address0);
|
||||
console.info(`Signature from Ledger device for account number 0 (${address0}):`);
|
||||
console.info(signature0);
|
||||
|
||||
@@ -17,7 +19,7 @@ async function run() {
|
||||
|
||||
const accountNumber10 = 10;
|
||||
const address10 = accounts[accountNumbers.findIndex((n) => n === accountNumber10)].address;
|
||||
const signature1 = await demo.sign(accountNumber10, address10, address10);
|
||||
const signature1 = await demo.sign(signer, accountNumber10, address10, address10);
|
||||
console.info(`Signature from Ledger device for account number 10 (${address10}):`);
|
||||
console.info(signature1);
|
||||
}
|
||||
|
||||
@@ -47,11 +47,8 @@
|
||||
"ledger-cosmos-js": "^2.1.7",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@ledgerhq/hw-transport-node-hid": "^5.23.2",
|
||||
"@ledgerhq/hw-transport-webusb": "^5.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ledgerhq/hw-transport": "^5.25.0",
|
||||
"@ledgerhq/hw-transport-node-hid": "^5.23.2",
|
||||
"@ledgerhq/hw-transport-webusb": "^5.23.0",
|
||||
"@types/ledgerhq__hw-transport-node-hid": "^4.22.1",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { toBase64 } from "@cosmjs/encoding";
|
||||
import { makeCosmoshubPath, makeSignDoc, StdFee, StdSignature } from "@cosmjs/launchpad";
|
||||
import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
|
||||
|
||||
import { LedgerSigner } from "../ledgersigner";
|
||||
|
||||
@@ -12,12 +13,18 @@ const defaultFee: StdFee = {
|
||||
const defaultMemo = "Some memo";
|
||||
const defaultSequence = "0";
|
||||
|
||||
const signer = new LedgerSigner({
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(2), makeCosmoshubPath(10)],
|
||||
});
|
||||
export async function createSigner(): Promise<LedgerSigner> {
|
||||
const interactiveTimeout = 120_000;
|
||||
const ledgerTransport = await TransportNodeHid.create(interactiveTimeout, interactiveTimeout);
|
||||
return new LedgerSigner(ledgerTransport, {
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(2), makeCosmoshubPath(10)],
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAccounts(): Promise<
|
||||
export async function getAccounts(
|
||||
signer: LedgerSigner,
|
||||
): Promise<
|
||||
ReadonlyArray<{
|
||||
readonly algo: string;
|
||||
readonly address: string;
|
||||
@@ -29,6 +36,7 @@ export async function getAccounts(): Promise<
|
||||
}
|
||||
|
||||
export async function sign(
|
||||
signer: LedgerSigner,
|
||||
accountNumber: number,
|
||||
fromAddress: string,
|
||||
toAddress: string,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { toBase64 } from "@cosmjs/encoding";
|
||||
import { AccountData, makeCosmoshubPath, StdSignDoc } from "@cosmjs/launchpad";
|
||||
import { Uint53 } from "@cosmjs/math";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
|
||||
|
||||
import { LedgerSigner } from "../ledgersigner";
|
||||
|
||||
@@ -43,11 +44,6 @@ function createSignDoc(accountNumber: number, address: string): string {
|
||||
return JSON.stringify(signDoc, null, 2);
|
||||
}
|
||||
|
||||
const signer = new LedgerSigner({
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(2)],
|
||||
});
|
||||
|
||||
window.updateMessage = (accountNumberInput: unknown) => {
|
||||
assert(typeof accountNumberInput === "string");
|
||||
const accountNumber = Uint53.fromString(accountNumberInput).toNumber();
|
||||
@@ -63,7 +59,20 @@ window.updateMessage = (accountNumberInput: unknown) => {
|
||||
signDocTextArea.textContent = createSignDoc(accountNumber, address);
|
||||
};
|
||||
|
||||
window.getAccounts = async function getAccounts(): Promise<void> {
|
||||
window.createSigner = async function createSigner(): Promise<LedgerSigner> {
|
||||
const interactiveTimeout = 120_000;
|
||||
const ledgerTransport = await TransportWebUSB.create(interactiveTimeout, interactiveTimeout);
|
||||
return new LedgerSigner(ledgerTransport, {
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(2)],
|
||||
});
|
||||
};
|
||||
|
||||
window.getAccounts = async function getAccounts(signer: LedgerSigner | undefined): Promise<void> {
|
||||
if (signer === undefined) {
|
||||
console.error("Please wait for transport to connect");
|
||||
return;
|
||||
}
|
||||
const accountNumberInput = document.getElementById("account-number");
|
||||
const addressInput = document.getElementById("address");
|
||||
const accountsDiv = document.getElementById("accounts");
|
||||
@@ -88,7 +97,11 @@ window.getAccounts = async function getAccounts(): Promise<void> {
|
||||
}
|
||||
};
|
||||
|
||||
window.sign = async function sign(): Promise<void> {
|
||||
window.sign = async function sign(signer: LedgerSigner | undefined): Promise<void> {
|
||||
if (signer === undefined) {
|
||||
console.error("Please wait for transport to connect");
|
||||
return;
|
||||
}
|
||||
const signatureDiv = document.getElementById("signature");
|
||||
signatureDiv.textContent = "Loading...";
|
||||
|
||||
@@ -102,3 +115,7 @@ window.sign = async function sign(): Promise<void> {
|
||||
signatureDiv.textContent = error;
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = async function onLoad(): Promise<void> {
|
||||
window.signer = await window.createSigner();
|
||||
};
|
||||
|
||||
@@ -18,37 +18,13 @@ export interface LedgerAppErrorResponse {
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
interface ConnectedApp {
|
||||
/** The transport used by the app */
|
||||
readonly transport: Transport;
|
||||
readonly app: CosmosApp;
|
||||
}
|
||||
|
||||
/** Time to establish a connection in milliseconds */
|
||||
const defaultOpenTimeout = 120_000;
|
||||
const requiredCosmosAppVersion = "1.5.3";
|
||||
|
||||
function isWindows(platform: string): boolean {
|
||||
return platform.indexOf("Win") > -1;
|
||||
}
|
||||
|
||||
function verifyBrowserIsSupported(platform: string, userAgent: string | null): void {
|
||||
if (isWindows(platform)) {
|
||||
throw new Error("Windows is not currently supported.");
|
||||
}
|
||||
|
||||
const isChromeOrBrave = userAgent && /chrome|crios/i.test(userAgent) && !/edge|opr\//i.test(userAgent);
|
||||
if (!isChromeOrBrave) {
|
||||
throw new Error("Your browser does not support Ledger devices.");
|
||||
}
|
||||
}
|
||||
|
||||
function unharden(hdPath: HdPath): number[] {
|
||||
return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber()));
|
||||
}
|
||||
|
||||
const cosmosHdPath = makeCosmoshubPath(0);
|
||||
const cosmosBech32Prefix = "cosmos";
|
||||
const requiredCosmosAppVersion = "1.5.3";
|
||||
|
||||
export interface LaunchpadLedgerOptions {
|
||||
readonly hdPaths?: readonly HdPath[];
|
||||
@@ -60,11 +36,9 @@ export class LaunchpadLedger {
|
||||
private readonly testModeAllowed: boolean;
|
||||
private readonly hdPaths: readonly HdPath[];
|
||||
private readonly prefix: string;
|
||||
private connectedApp: ConnectedApp | null;
|
||||
public readonly platform: string;
|
||||
public readonly userAgent: string | null;
|
||||
private readonly app: CosmosApp;
|
||||
|
||||
public constructor(options: LaunchpadLedgerOptions = {}) {
|
||||
public constructor(transport: Transport, options: LaunchpadLedgerOptions = {}) {
|
||||
const defaultOptions = {
|
||||
hdPaths: [cosmosHdPath],
|
||||
prefix: cosmosBech32Prefix,
|
||||
@@ -77,22 +51,14 @@ export class LaunchpadLedger {
|
||||
this.testModeAllowed = testModeAllowed;
|
||||
this.hdPaths = hdPaths;
|
||||
this.prefix = prefix;
|
||||
this.connectedApp = null;
|
||||
|
||||
try {
|
||||
this.platform = navigator.platform;
|
||||
this.userAgent = navigator.userAgent;
|
||||
} catch (error) {
|
||||
this.platform = "node";
|
||||
this.userAgent = null;
|
||||
}
|
||||
this.app = new CosmosApp(transport);
|
||||
}
|
||||
|
||||
public async getCosmosAppVersion(): Promise<string> {
|
||||
await this.ensureConnected();
|
||||
assert(this.connectedApp, "Cosmos Ledger App is not connected");
|
||||
await this.verifyCosmosAppIsOpen();
|
||||
assert(this.app, "Cosmos Ledger App is not connected");
|
||||
|
||||
const response = await this.connectedApp.app.getVersion();
|
||||
const response = await this.app.getVersion();
|
||||
this.handleLedgerErrors(response);
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { major, minor, patch, test_mode: testMode } = response as VersionResponse;
|
||||
@@ -101,12 +67,12 @@ export class LaunchpadLedger {
|
||||
}
|
||||
|
||||
public async getPubkey(hdPath?: HdPath): Promise<Uint8Array> {
|
||||
await this.ensureConnected();
|
||||
assert(this.connectedApp, "Cosmos Ledger App is not connected");
|
||||
await this.verifyDeviceIsReady();
|
||||
assert(this.app, "Cosmos Ledger App is not connected");
|
||||
|
||||
const hdPathToUse = hdPath || this.hdPaths[0];
|
||||
// ledger-cosmos-js hardens the first three indices
|
||||
const response = await this.connectedApp.app.publicKey(unharden(hdPathToUse));
|
||||
const response = await this.app.publicKey(unharden(hdPathToUse));
|
||||
this.handleLedgerErrors(response);
|
||||
return Uint8Array.from((response as PublicKeyResponse).compressed_pk);
|
||||
}
|
||||
@@ -125,85 +91,16 @@ export class LaunchpadLedger {
|
||||
}
|
||||
|
||||
public async sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array> {
|
||||
await this.ensureConnected();
|
||||
assert(this.connectedApp, "Cosmos Ledger App is not connected");
|
||||
await this.verifyDeviceIsReady();
|
||||
assert(this.app, "Cosmos Ledger App is not connected");
|
||||
|
||||
const hdPathToUse = hdPath || this.hdPaths[0];
|
||||
// ledger-cosmos-js hardens the first three indices
|
||||
const response = await this.connectedApp.app.sign(unharden(hdPathToUse), fromUtf8(message));
|
||||
const response = await this.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<void> {
|
||||
if (this.connectedApp) {
|
||||
await this.connectedApp.transport.close();
|
||||
this.connectedApp = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureConnected(): Promise<void> {
|
||||
// assume good connection if connected once
|
||||
if (this.connectedApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.platform !== "node") {
|
||||
verifyBrowserIsSupported(this.platform, this.userAgent);
|
||||
}
|
||||
|
||||
const transport = await this.createTransport(defaultOpenTimeout);
|
||||
this.connectedApp = {
|
||||
transport: transport,
|
||||
app: new CosmosApp(transport),
|
||||
};
|
||||
|
||||
await this.verifyDeviceIsReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param openTimeout The time to establish a connection in milliseconds. This is
|
||||
* [passed into as the second argument into Transport.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport/src/Transport.js#L235),
|
||||
* which is ignored by both [TransportWebUSB.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport-webusb/src/TransportWebUSB.js#L116)
|
||||
* and [TransportNodeHid.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport-node-hid/src/TransportNodeHid.js#L115).
|
||||
*/
|
||||
private async createTransport(openTimeout: number): Promise<Transport> {
|
||||
// HACK: Use a variable to get webpack to ignore this
|
||||
const nodeJsTransportPackageName = "@ledgerhq/hw-transport-node-hid";
|
||||
/* eslint-disable-next-line @typescript-eslint/naming-convention */
|
||||
const { default: TransportClass } =
|
||||
this.platform === "node"
|
||||
? await import(nodeJsTransportPackageName)
|
||||
: await import("@ledgerhq/hw-transport-webusb");
|
||||
|
||||
try {
|
||||
const transport = await TransportClass.create(openTimeout);
|
||||
return transport;
|
||||
} catch (error) {
|
||||
const trimmedErrorMessage = error.message.trim();
|
||||
if (trimmedErrorMessage.startsWith("No WebUSB interface found for your Ledger device")) {
|
||||
throw new Error(
|
||||
"Could not connect to a Ledger device. Please use Ledger Live to upgrade the Ledger firmware to version 1.5.5 or later.",
|
||||
);
|
||||
}
|
||||
if (trimmedErrorMessage.startsWith("Unable to claim interface")) {
|
||||
throw new Error("Could not access Ledger device. Is it being used in another tab?");
|
||||
}
|
||||
if (trimmedErrorMessage.startsWith("Not supported")) {
|
||||
throw new Error(
|
||||
"Your browser does not seem to support WebUSB yet. Try updating it to the latest version.",
|
||||
);
|
||||
}
|
||||
if (trimmedErrorMessage.startsWith("No device selected")) {
|
||||
throw new Error(
|
||||
"You did not select a Ledger device. If you did not see your Ledger, check if the Ledger is plugged in and unlocked.",
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private verifyAppMode(testMode: boolean): void {
|
||||
if (testMode && !this.testModeAllowed) {
|
||||
throw new Error(`DANGER: The Cosmos Ledger app is in test mode and should not be used on mainnet!`);
|
||||
@@ -211,10 +108,9 @@ export class LaunchpadLedger {
|
||||
}
|
||||
|
||||
private async getOpenAppName(): Promise<string> {
|
||||
await this.ensureConnected();
|
||||
assert(this.connectedApp, "Cosmos Ledger App is not connected");
|
||||
assert(this.app, "Cosmos Ledger App is not connected");
|
||||
|
||||
const response = await this.connectedApp.app.appInfo();
|
||||
const response = await this.app.appInfo();
|
||||
this.handleLedgerErrors(response);
|
||||
return (response as AppInfoResponse).appName;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,29 @@ import {
|
||||
StdFee,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
|
||||
import { LedgerSigner } from "./ledgersigner";
|
||||
import { pendingWithoutLedger, pendingWithoutWasmd, wasmd } from "./testutils.spec";
|
||||
|
||||
const interactiveTimeout = 120_000;
|
||||
|
||||
async function createTransport(): Promise<Transport> {
|
||||
let platform: string;
|
||||
try {
|
||||
platform = navigator.platform;
|
||||
} catch (error) {
|
||||
platform = "node";
|
||||
}
|
||||
// HACK: Use a variable to get webpack to ignore this
|
||||
const nodeJsTransportPackageName = "@ledgerhq/hw-transport-node-hid";
|
||||
const { default: TransportClass } =
|
||||
platform === "node"
|
||||
? await import(nodeJsTransportPackageName)
|
||||
: await import("@ledgerhq/hw-transport-webusb");
|
||||
return TransportClass.create(interactiveTimeout, interactiveTimeout);
|
||||
}
|
||||
|
||||
describe("LedgerSigner", () => {
|
||||
const defaultChainId = "testing";
|
||||
const defaultFee: StdFee = {
|
||||
@@ -28,11 +45,20 @@ describe("LedgerSigner", () => {
|
||||
const defaultSequence = "0";
|
||||
const defaultAccountNumber = "42";
|
||||
const defaultRecipient = "cosmos1p6xs63q4g7np99ttv5nd3yzkt8n4qxa47w8aea";
|
||||
let transport: Transport;
|
||||
|
||||
beforeEach(async () => {
|
||||
transport = await createTransport();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await transport.close();
|
||||
});
|
||||
|
||||
describe("getAccount", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLedger();
|
||||
const signer = new LedgerSigner({
|
||||
const signer = new LedgerSigner(transport, {
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(10)],
|
||||
});
|
||||
@@ -56,8 +82,6 @@ describe("LedgerSigner", () => {
|
||||
pubkey: fromBase64("A2ZnLEcbpyjS30H5UF1vezq29aBcT9oo5EARATIW9Cpj"),
|
||||
},
|
||||
]);
|
||||
|
||||
await signer.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +95,7 @@ describe("LedgerSigner", () => {
|
||||
"returns valid signature",
|
||||
async () => {
|
||||
pendingWithoutLedger();
|
||||
const signer = new LedgerSigner({
|
||||
const signer = new LedgerSigner(transport, {
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(10)],
|
||||
});
|
||||
@@ -104,8 +128,6 @@ describe("LedgerSigner", () => {
|
||||
fistAccount.pubkey,
|
||||
);
|
||||
expect(valid).toEqual(true);
|
||||
|
||||
await signer.disconnect();
|
||||
},
|
||||
interactiveTimeout,
|
||||
);
|
||||
@@ -115,7 +137,7 @@ describe("LedgerSigner", () => {
|
||||
async () => {
|
||||
pendingWithoutLedger();
|
||||
pendingWithoutWasmd();
|
||||
const signer = new LedgerSigner({
|
||||
const signer = new LedgerSigner(transport, {
|
||||
testModeAllowed: true,
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(10)],
|
||||
});
|
||||
@@ -124,8 +146,6 @@ describe("LedgerSigner", () => {
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, fistAccount.address, signer);
|
||||
const result = await client.sendTokens(defaultRecipient, coins(1234567, "ucosm"));
|
||||
assert(isBroadcastTxSuccess(result));
|
||||
|
||||
await signer.disconnect();
|
||||
},
|
||||
interactiveTimeout,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
StdSignDoc,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { serializeSignDoc, SignResponse } from "@cosmjs/launchpad";
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
|
||||
import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger";
|
||||
|
||||
@@ -15,9 +16,9 @@ export class LedgerSigner implements OfflineSigner {
|
||||
private readonly hdPaths: readonly HdPath[];
|
||||
private accounts?: readonly AccountData[];
|
||||
|
||||
public constructor(options: LaunchpadLedgerOptions = {}) {
|
||||
public constructor(transport: Transport, options: LaunchpadLedgerOptions = {}) {
|
||||
this.hdPaths = options.hdPaths || [makeCosmoshubPath(0)];
|
||||
this.ledger = new LaunchpadLedger(options);
|
||||
this.ledger = new LaunchpadLedger(transport, options);
|
||||
}
|
||||
|
||||
public async getAccounts(): Promise<readonly AccountData[]> {
|
||||
@@ -52,8 +53,4 @@ export class LedgerSigner implements OfflineSigner {
|
||||
signature: encodeSecp256k1Signature(accountForAddress.pubkey, signature),
|
||||
};
|
||||
}
|
||||
|
||||
public async disconnect(): Promise<void> {
|
||||
return this.ledger.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-13
@@ -1,4 +1,6 @@
|
||||
/// <reference types="ledgerhq__hw-transport" />
|
||||
import { HdPath } from "@cosmjs/crypto";
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
export interface LedgerAppErrorResponse {
|
||||
readonly error_message?: string;
|
||||
readonly device_locked?: boolean;
|
||||
@@ -12,24 +14,13 @@ export declare class LaunchpadLedger {
|
||||
private readonly testModeAllowed;
|
||||
private readonly hdPaths;
|
||||
private readonly prefix;
|
||||
private connectedApp;
|
||||
readonly platform: string;
|
||||
readonly userAgent: string | null;
|
||||
constructor(options?: LaunchpadLedgerOptions);
|
||||
private readonly app;
|
||||
constructor(transport: Transport, options?: LaunchpadLedgerOptions);
|
||||
getCosmosAppVersion(): Promise<string>;
|
||||
getPubkey(hdPath?: HdPath): Promise<Uint8Array>;
|
||||
getPubkeys(): Promise<readonly Uint8Array[]>;
|
||||
getCosmosAddress(pubkey?: Uint8Array): Promise<string>;
|
||||
sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array>;
|
||||
disconnect(): Promise<void>;
|
||||
private ensureConnected;
|
||||
/**
|
||||
* @param openTimeout The time to establish a connection in milliseconds. This is
|
||||
* [passed into as the second argument into Transport.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport/src/Transport.js#L235),
|
||||
* which is ignored by both [TransportWebUSB.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport-webusb/src/TransportWebUSB.js#L116)
|
||||
* and [TransportNodeHid.open](https://github.com/LedgerHQ/ledgerjs/blob/v5.25.2/packages/hw-transport-node-hid/src/TransportNodeHid.js#L115).
|
||||
*/
|
||||
private createTransport;
|
||||
private verifyAppMode;
|
||||
private getOpenAppName;
|
||||
private verifyAppVersion;
|
||||
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
/// <reference types="ledgerhq__hw-transport" />
|
||||
import { AccountData, OfflineSigner, StdSignDoc } from "@cosmjs/launchpad";
|
||||
import { SignResponse } from "@cosmjs/launchpad";
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
import { LaunchpadLedgerOptions } from "./launchpadledger";
|
||||
export declare class LedgerSigner implements OfflineSigner {
|
||||
private readonly ledger;
|
||||
private readonly hdPaths;
|
||||
private accounts?;
|
||||
constructor(options?: LaunchpadLedgerOptions);
|
||||
constructor(transport: Transport, options?: LaunchpadLedgerOptions);
|
||||
getAccounts(): Promise<readonly AccountData[]>;
|
||||
sign(signerAddress: string, signDoc: StdSignDoc): Promise<SignResponse>;
|
||||
disconnect(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -315,6 +315,15 @@
|
||||
"@ledgerhq/logs" "^5.23.0"
|
||||
rxjs "^6.6.3"
|
||||
|
||||
"@ledgerhq/devices@^5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.25.0.tgz#c9522d7495e7be41fad59eb7d5e4e3e1030fd205"
|
||||
integrity sha512-3wsxVDkUYd5ERA5KVy99wEGbHAkfsuWbQ8b2ruzr8JX9wIxUS8mu1XkdoDt5M+H4jXP9C8ZGj9YT3VsiiIzfxg==
|
||||
dependencies:
|
||||
"@ledgerhq/errors" "^5.25.0"
|
||||
"@ledgerhq/logs" "^5.25.0"
|
||||
rxjs "^6.6.3"
|
||||
|
||||
"@ledgerhq/errors@^4.78.0":
|
||||
version "4.78.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.78.0.tgz#23daf3af54d03b1bda3e616002b555da1bdb705a"
|
||||
@@ -325,6 +334,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.23.0.tgz#30a0338dafba8264556011604abed08bf24979f3"
|
||||
integrity sha512-qtpX8aFrUUlYfOMu7BxTvxqUa8CniE+tEBpVEjYUhVbFdVJjM4ouwJD++RtQkMAU2c5jE7xb12WnUnf5BlAgLQ==
|
||||
|
||||
"@ledgerhq/errors@^5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.25.0.tgz#2a24b7ad6a88719046de4fcdf0b4c27b9ba79523"
|
||||
integrity sha512-+FyyweA9odaWRwRkS35Wf0n2gC4tGsBlgW6VMhT7m1kcwVzUqNFyQzqMKqYvYEAga0P0X59wE+IAlqokkG7Usg==
|
||||
|
||||
"@ledgerhq/hw-transport-node-hid-noevents@^5.23.2":
|
||||
version "5.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.23.2.tgz#0ee4b54c62d23b0f0cce258fe2c612f71b8d454a"
|
||||
@@ -378,6 +392,15 @@
|
||||
"@ledgerhq/errors" "^5.23.0"
|
||||
events "^3.2.0"
|
||||
|
||||
"@ledgerhq/hw-transport@^5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.25.0.tgz#e04e2130fedcb433ee68f25125e6ac49efa0aede"
|
||||
integrity sha512-q6hhWWwbSZPmzdTaQMowY+uQYwCC4xRqRQFQFgF6BjZFn2n+AwGOEU9mDmHnjL6XxkQfj8CjWuPJSRRv5OLmlg==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^5.25.0"
|
||||
"@ledgerhq/errors" "^5.25.0"
|
||||
events "^3.2.0"
|
||||
|
||||
"@ledgerhq/logs@^4.72.0":
|
||||
version "4.72.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5"
|
||||
@@ -388,6 +411,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.23.0.tgz#7a86b1e6479c8aa8e8b9affe00eb8e369efdbc3b"
|
||||
integrity sha512-88M8RkVHl44k6MAhfrYhx25opnJV24/2XpuTUVklID11f9rBdE+6RZ9OMs39dyX2sDv7TuzIPi5nTRoCqZMDYw==
|
||||
|
||||
"@ledgerhq/logs@^5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.25.0.tgz#94dd930a33fd05e3897532f554e31397a45247b6"
|
||||
integrity sha512-OPlkvufjwJWQR+Jp7C6GaEeub+ZHzh3x0xyQMVLL62O7VGrhpzjwq2il/aLNt687k7mKJ+Vg6lFL1B6gO1ZiIg==
|
||||
|
||||
"@lerna/add@3.21.0":
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b"
|
||||
|
||||
Reference in New Issue
Block a user