mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-11 14:09:15 +00:00
Merge pull request #417 from CosmWasm/268-ledger-offline-signer
Add LedgerWallet
This commit is contained in:
commit
034f4adc8c
@ -2,6 +2,7 @@
|
||||
|
||||
## 0.23.0 (unreleased)
|
||||
|
||||
- @cosmjs/cli: Expose `HdPath` type.
|
||||
- @cosmjs/cosmwasm: Rename `CosmWasmClient.postTx` method to `.broadcastTx`.
|
||||
- @cosmjs/cosmwasm: Rename `FeeTable` type to `CosmWasmFeeTable`.
|
||||
- @cosmjs/cosmwasm: `SigningCosmWasmClient` constructor now takes optional
|
||||
@ -13,6 +14,8 @@
|
||||
init, migrate and handle messages (in `WasmExtension.wasm.queryContractSmart`,
|
||||
`CosmWasmClient.queryContractSmart`, `SigningCosmWasmClient.instantiate`,
|
||||
`SigningCosmWasmClient.migrate`, `SigningCosmWasmClient.execute`).
|
||||
- @cosmjs/crypto: Export new type alias `HdPath`.
|
||||
- @cosmjs/crypto: Add `Secp256k1Signature.toFixedLength` method.
|
||||
- @cosmjs/demo-staking: Remove package and supporting scripts.
|
||||
- @cosmjs/encoding: Add `limit` parameter to `Bech32.encode` and `.decode`. The
|
||||
new default limit for decoding is infinity (was 90 before). Set it to 90 to
|
||||
@ -42,6 +45,9 @@
|
||||
`isSearchBySentFromOrToQuery` and `isSearchByTagsQuery`.
|
||||
- @cosmjs/launchpad: Change type of `TxsResponse.logs` and
|
||||
`BroadcastTxsResponse.logs` to `unknown[]`.
|
||||
- @cosmjs/launchpad-ledger: Add package supporting Ledger device integration for
|
||||
Launchpad. Two new classes are provided: `LedgerSigner` (for most use cases)
|
||||
and `LaunchpadLedger` for more fine-grained access.
|
||||
- @cosmjs/math: Add `.multiply` method to `Decimal` class.
|
||||
- @cosmjs/tendermint-rpc: Make `BroadcastTxCommitResponse.height` non-optional.
|
||||
- @cosmjs/tendermint-rpc: Change type of `GenesisResponse.appState` to
|
||||
|
@ -1,20 +1,20 @@
|
||||
interface Options {
|
||||
readonly httpUrl: string;
|
||||
readonly bech32prefix: string;
|
||||
readonly hdPath: readonly Slip10RawIndex[];
|
||||
readonly hdPath: HdPath;
|
||||
readonly gasPrice: GasPrice;
|
||||
readonly gasLimits: Partial<GasLimits<CosmWasmFeeTable>>; // only set the ones you want to override
|
||||
}
|
||||
|
||||
const coralnetOptions: Options = {
|
||||
httpUrl: 'https://lcd.coralnet.cosmwasm.com',
|
||||
httpUrl: "https://lcd.coralnet.cosmwasm.com",
|
||||
gasPrice: GasPrice.fromString("0.025ushell"),
|
||||
bech32prefix: 'coral',
|
||||
bech32prefix: "coral",
|
||||
hdPath: makeCosmoshubPath(0),
|
||||
gasLimits: {
|
||||
gasLimits: {
|
||||
upload: 1500000,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const wallet = await Secp256k1Wallet.generate(12, coralnetOptions.hdPath, coralnetOptions.bech32prefix);
|
||||
const [{ address }] = await wallet.getAccounts();
|
||||
|
@ -71,6 +71,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
|
||||
"Ed25519",
|
||||
"Ed25519Keypair",
|
||||
"EnglishMnemonic",
|
||||
"HdPath",
|
||||
"Random",
|
||||
"Secp256k1",
|
||||
"Sha256",
|
||||
|
@ -18,6 +18,7 @@ export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
|
||||
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
|
||||
export { Sha1, Sha256, Sha512 } from "./sha";
|
||||
export {
|
||||
HdPath,
|
||||
pathToString,
|
||||
stringToPath,
|
||||
Slip10,
|
||||
|
@ -76,6 +76,15 @@ describe("Secp256k1Signature", () => {
|
||||
).toThrowError(/unsigned integer s must be encoded as unpadded big endian./i);
|
||||
});
|
||||
|
||||
it("can be encoded as fixed length", () => {
|
||||
const signature = new Secp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]));
|
||||
expect(signature.toFixedLength()).toEqual(
|
||||
fromHex(
|
||||
"000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("can encode to DER", () => {
|
||||
// Signature 3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935
|
||||
// decoded by http://asn1-playground.oss.com/
|
||||
|
@ -119,6 +119,10 @@ export class Secp256k1Signature {
|
||||
}
|
||||
}
|
||||
|
||||
public toFixedLength(): Uint8Array {
|
||||
return new Uint8Array([...this.r(32), ...this.s(32)]);
|
||||
}
|
||||
|
||||
public toDer(): Uint8Array {
|
||||
// DER supports negative integers but our data is unsigned. Thus we need to prepend
|
||||
// a leading 0 byte when the higest bit is set to differentiate nagative values
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { fromHex } from "@cosmjs/encoding";
|
||||
|
||||
import {
|
||||
HdPath,
|
||||
pathToString,
|
||||
Slip10,
|
||||
Slip10Curve,
|
||||
@ -22,7 +23,7 @@ describe("Slip10", () => {
|
||||
const seed = fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
|
||||
it("can derive path m", () => {
|
||||
const path: readonly Slip10RawIndex[] = [];
|
||||
const path: HdPath = [];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"),
|
||||
@ -33,7 +34,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"),
|
||||
@ -44,7 +45,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1)];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"),
|
||||
@ -55,11 +56,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1/2'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.normal(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1), Slip10RawIndex.hardened(2)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"),
|
||||
@ -70,7 +67,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1/2'/2", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.normal(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
@ -86,7 +83,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1/2'/2/1000000000", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.normal(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
@ -110,7 +107,7 @@ describe("Slip10", () => {
|
||||
);
|
||||
|
||||
it("can derive path m", () => {
|
||||
const path: readonly Slip10RawIndex[] = [];
|
||||
const path: HdPath = [];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"),
|
||||
@ -121,7 +118,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0)];
|
||||
const path: HdPath = [Slip10RawIndex.normal(0)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"),
|
||||
@ -132,7 +129,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0/2147483647'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647)];
|
||||
const path: HdPath = [Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"),
|
||||
@ -143,7 +140,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0/2147483647'/1", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.normal(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.normal(1),
|
||||
@ -158,7 +155,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0/2147483647'/1/2147483646'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.normal(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.normal(1),
|
||||
@ -174,7 +171,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0/2147483647'/1/2147483646'/2", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.normal(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.normal(1),
|
||||
@ -196,7 +193,7 @@ describe("Slip10", () => {
|
||||
const seed = fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
|
||||
it("can derive path m", () => {
|
||||
const path: readonly Slip10RawIndex[] = [];
|
||||
const path: HdPath = [];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"),
|
||||
@ -207,7 +204,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"),
|
||||
@ -218,7 +215,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1)];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14"),
|
||||
@ -229,7 +226,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1'/2'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
@ -244,7 +241,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1'/2'/2'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
@ -260,7 +257,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/1'/2'/2'/1000000000'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(1),
|
||||
Slip10RawIndex.hardened(2),
|
||||
@ -284,7 +281,7 @@ describe("Slip10", () => {
|
||||
);
|
||||
|
||||
it("can derive path m", () => {
|
||||
const path: readonly Slip10RawIndex[] = [];
|
||||
const path: HdPath = [];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b"),
|
||||
@ -295,7 +292,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d"),
|
||||
@ -306,10 +303,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/2147483647'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
];
|
||||
const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(2147483647)];
|
||||
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
|
||||
expect(derived.chainCode).toEqual(
|
||||
fromHex("138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f"),
|
||||
@ -320,7 +314,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/2147483647'/1'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.hardened(1),
|
||||
@ -335,7 +329,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/2147483647'/1'/2147483646'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.hardened(1),
|
||||
@ -351,7 +345,7 @@ describe("Slip10", () => {
|
||||
});
|
||||
|
||||
it("can derive path m/0'/2147483647'/1'/2147483646'/2'", () => {
|
||||
const path: readonly Slip10RawIndex[] = [
|
||||
const path: HdPath = [
|
||||
Slip10RawIndex.hardened(0),
|
||||
Slip10RawIndex.hardened(2147483647),
|
||||
Slip10RawIndex.hardened(1),
|
||||
|
@ -49,16 +49,14 @@ export class Slip10RawIndex extends Uint32 {
|
||||
}
|
||||
}
|
||||
|
||||
export type HdPath = readonly Slip10RawIndex[];
|
||||
|
||||
const secp256k1 = new elliptic.ec("secp256k1");
|
||||
|
||||
// Universal private key derivation accoring to
|
||||
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md
|
||||
export class Slip10 {
|
||||
public static derivePath(
|
||||
curve: Slip10Curve,
|
||||
seed: Uint8Array,
|
||||
path: readonly Slip10RawIndex[],
|
||||
): Slip10Result {
|
||||
public static derivePath(curve: Slip10Curve, seed: Uint8Array, path: HdPath): Slip10Result {
|
||||
let result = this.master(curve, seed);
|
||||
for (const rawIndex of path) {
|
||||
result = this.child(curve, result.privkey, result.chainCode, rawIndex);
|
||||
@ -185,7 +183,7 @@ export class Slip10 {
|
||||
}
|
||||
}
|
||||
|
||||
export function pathToString(path: readonly Slip10RawIndex[]): string {
|
||||
export function pathToString(path: HdPath): string {
|
||||
return path.reduce((current, component): string => {
|
||||
const componentString = component.isHardened()
|
||||
? `${component.toNumber() - 2 ** 31}'`
|
||||
@ -194,7 +192,7 @@ export function pathToString(path: readonly Slip10RawIndex[]): string {
|
||||
}, "m");
|
||||
}
|
||||
|
||||
export function stringToPath(input: string): readonly Slip10RawIndex[] {
|
||||
export function stringToPath(input: string): HdPath {
|
||||
if (!input.startsWith("m")) throw new Error("Path string must start with 'm'");
|
||||
let rest = input.slice(1);
|
||||
|
||||
|
1
packages/crypto/types/index.d.ts
vendored
1
packages/crypto/types/index.d.ts
vendored
@ -18,6 +18,7 @@ export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
|
||||
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
|
||||
export { Sha1, Sha256, Sha512 } from "./sha";
|
||||
export {
|
||||
HdPath,
|
||||
pathToString,
|
||||
stringToPath,
|
||||
Slip10,
|
||||
|
@ -12,6 +12,7 @@ export declare class Secp256k1Signature {
|
||||
constructor(r: Uint8Array, s: Uint8Array);
|
||||
r(length?: number): Uint8Array;
|
||||
s(length?: number): Uint8Array;
|
||||
toFixedLength(): Uint8Array;
|
||||
toDer(): Uint8Array;
|
||||
}
|
||||
/**
|
||||
|
7
packages/crypto/types/slip10.d.ts
vendored
7
packages/crypto/types/slip10.d.ts
vendored
@ -21,8 +21,9 @@ export declare class Slip10RawIndex extends Uint32 {
|
||||
static normal(normalIndex: number): Slip10RawIndex;
|
||||
isHardened(): boolean;
|
||||
}
|
||||
export declare type HdPath = readonly Slip10RawIndex[];
|
||||
export declare class Slip10 {
|
||||
static derivePath(curve: Slip10Curve, seed: Uint8Array, path: readonly Slip10RawIndex[]): Slip10Result;
|
||||
static derivePath(curve: Slip10Curve, seed: Uint8Array, path: HdPath): Slip10Result;
|
||||
private static master;
|
||||
private static child;
|
||||
/**
|
||||
@ -36,5 +37,5 @@ export declare class Slip10 {
|
||||
private static isGteN;
|
||||
private static n;
|
||||
}
|
||||
export declare function pathToString(path: readonly Slip10RawIndex[]): string;
|
||||
export declare function stringToPath(input: string): readonly Slip10RawIndex[];
|
||||
export declare function pathToString(path: HdPath): string;
|
||||
export declare function stringToPath(input: string): HdPath;
|
||||
|
1
packages/launchpad-ledger/.eslintignore
Symbolic link
1
packages/launchpad-ledger/.eslintignore
Symbolic link
@ -0,0 +1 @@
|
||||
../../.eslintignore
|
3
packages/launchpad-ledger/.gitignore
vendored
Normal file
3
packages/launchpad-ledger/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
36
packages/launchpad-ledger/README.md
Normal file
36
packages/launchpad-ledger/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# @cosmjs/launchpad-ledger
|
||||
|
||||
[](https://www.npmjs.com/package/@cosmjs/launchpad-ledger)
|
||||
|
||||
## Supported platforms
|
||||
|
||||
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).
|
||||
|
||||
## Running the demo
|
||||
|
||||
Build the package for web:
|
||||
|
||||
```sh
|
||||
yarn pack-web
|
||||
```
|
||||
|
||||
Host the `launchpad-ledger` package directory, for example using Python 3:
|
||||
|
||||
```sh
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
Visit the demo page in a browser, for example if using the Python 3 option:
|
||||
[http://localhost:8000/demo]().
|
||||
|
||||
Then follow the instructions on that page.
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the cosmjs repository, licensed under the Apache License
|
||||
2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
|
||||
[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)).
|
53
packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts
vendored
Normal file
53
packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
declare module "ledger-cosmos-js" {
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
|
||||
export interface ErrorResponse {
|
||||
readonly return_code: number;
|
||||
readonly error_message: string;
|
||||
}
|
||||
|
||||
export interface VersionResponse {
|
||||
readonly major: number;
|
||||
readonly minor: number;
|
||||
readonly patch: number;
|
||||
readonly test_mode: boolean;
|
||||
readonly error_message: string;
|
||||
readonly device_locked: boolean;
|
||||
}
|
||||
|
||||
export interface AppInfoResponse {
|
||||
readonly appName: string;
|
||||
readonly error_message: string;
|
||||
}
|
||||
|
||||
export interface PublicKeyResponse {
|
||||
readonly compressed_pk: Buffer;
|
||||
readonly error_message: string;
|
||||
}
|
||||
|
||||
export interface AddressAndPublicKeyResponse {
|
||||
readonly compressed_pk: Buffer;
|
||||
readonly address: string;
|
||||
readonly error_message: string;
|
||||
}
|
||||
|
||||
export interface SignResponse {
|
||||
readonly signature: Buffer;
|
||||
readonly error_message: string;
|
||||
}
|
||||
|
||||
export default class CosmosApp {
|
||||
static getBech32FromPK(hrp: string, pk: Buffer): string;
|
||||
|
||||
constructor(transport: Transport, scrambleKey?: string);
|
||||
|
||||
getVersion: () => Promise<VersionResponse | ErrorResponse>;
|
||||
appInfo: () => Promise<AppInfoResponse | ErrorResponse>;
|
||||
publicKey: (path: Array<number>) => Promise<PublicKeyResponse | ErrorResponse>;
|
||||
showAddressAndPubKey: (
|
||||
path: Array<number>,
|
||||
hrp: string,
|
||||
) => Promise<AddressAndPublicKeyResponse | ErrorResponse>;
|
||||
sign: (path: Array<number>, message: string) => Promise<SignResponse | ErrorResponse>;
|
||||
}
|
||||
}
|
24
packages/launchpad-ledger/demo/index.css
Normal file
24
packages/launchpad-ledger/demo/index.css
Normal file
@ -0,0 +1,24 @@
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 32em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 32em;
|
||||
height: 28em;
|
||||
}
|
43
packages/launchpad-ledger/demo/index.html
Normal file
43
packages/launchpad-ledger/demo/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Ledger Demo</title>
|
||||
<link rel="stylesheet" href="index.css"></style>
|
||||
<script src="../dist/demo/ledger.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Ledger Demo</h1>
|
||||
</div>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Connect the Ledger device via USB</li>
|
||||
<li>Open the Cosmos app on the Ledger device</li>
|
||||
<li>Click the buttons below</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="getAccounts()">
|
||||
Get Accounts
|
||||
</button>
|
||||
</div>
|
||||
<div id="accounts"></div>
|
||||
<div>
|
||||
<label>Address</label>
|
||||
<input id="address" type="text" value=""></input>
|
||||
</div>
|
||||
<div>
|
||||
<label>Message</label>
|
||||
<textarea id="message">
|
||||
</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="sign()">
|
||||
Sign Message
|
||||
</button>
|
||||
</div>
|
||||
<div id="signature"></div>
|
||||
</body>
|
||||
</html>
|
52
packages/launchpad-ledger/package.json
Normal file
52
packages/launchpad-ledger/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@cosmjs/launchpad-ledger",
|
||||
"version": "0.22.2",
|
||||
"description": "A library for interacting with the Cosmos Launchpad Ledger Nano App",
|
||||
"contributors": [
|
||||
"Will Clark <willclarktech@users.noreply.github.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "build/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"build/",
|
||||
"types/",
|
||||
"*.md",
|
||||
"!*.spec.*",
|
||||
"!**/testdata/"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CosmWasm/cosmjs/tree/master/packages/launchpad-ledger"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc --options typedoc.js",
|
||||
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
|
||||
"format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"",
|
||||
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"",
|
||||
"lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix",
|
||||
"move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -rf ./types/demo",
|
||||
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
|
||||
"prebuild": "shx rm -rf ./build",
|
||||
"build": "tsc",
|
||||
"postbuild": "yarn move-types && yarn format-types",
|
||||
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
|
||||
"test": "echo 'Please check README for information on how to manually run the demo'",
|
||||
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.demo.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/launchpad": "^0.22.2",
|
||||
"@cosmjs/utils": "^0.22.2",
|
||||
"@ledgerhq/hw-transport-webusb": "^5.23.0",
|
||||
"ledger-cosmos-js": "^2.1.7",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ledgerhq__hw-transport-webusb": "^4.70.0",
|
||||
"@types/semver": "^7.3.3"
|
||||
}
|
||||
}
|
65
packages/launchpad-ledger/src/demo/index.ts
Normal file
65
packages/launchpad-ledger/src/demo/index.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { toHex, toUtf8 } from "@cosmjs/encoding";
|
||||
|
||||
import { LedgerSigner } from "../ledgersigner";
|
||||
|
||||
declare const window: any;
|
||||
declare const document: any;
|
||||
|
||||
function createMessage(address: string): string {
|
||||
return `{
|
||||
"account_number": 0,
|
||||
"chain_id": "testing",
|
||||
"fee": {
|
||||
"amount": [{ "amount": 100, "denom": "ucosm" }],
|
||||
"gas": 250
|
||||
},
|
||||
"memo": "Some memo",
|
||||
"msgs": [{
|
||||
"type": "cosmos-sdk/MsgSend",
|
||||
"value": {
|
||||
"amount": [{
|
||||
"amount": "1234567",
|
||||
"denom": "ucosm"
|
||||
}],
|
||||
"from_address": "${address}",
|
||||
"to_address": "${address}"
|
||||
}
|
||||
}],
|
||||
"sequence": 0
|
||||
}`;
|
||||
}
|
||||
|
||||
const signer = new LedgerSigner({ testModeAllowed: true });
|
||||
|
||||
window.getAccounts = async function getAccounts(): Promise<void> {
|
||||
const addressInput = document.getElementById("address");
|
||||
const accountsDiv = document.getElementById("accounts");
|
||||
const messageTextArea = document.getElementById("message");
|
||||
accountsDiv.textContent = "Loading...";
|
||||
|
||||
try {
|
||||
const accounts = await signer.getAccounts();
|
||||
const prettyAccounts = accounts.map((account) => ({ ...account, pubkey: toHex(account.pubkey) }));
|
||||
accountsDiv.textContent = JSON.stringify(prettyAccounts, null, "\t");
|
||||
const address = accounts[0].address;
|
||||
addressInput.value = address;
|
||||
messageTextArea.textContent = createMessage(address);
|
||||
} catch (error) {
|
||||
accountsDiv.textContent = error;
|
||||
}
|
||||
};
|
||||
|
||||
window.sign = async function sign(): Promise<void> {
|
||||
const signatureDiv = document.getElementById("signature");
|
||||
signatureDiv.textContent = "Loading...";
|
||||
|
||||
try {
|
||||
const address = document.getElementById("address").value;
|
||||
const rawMessage = document.getElementById("message").textContent;
|
||||
const message = JSON.stringify(JSON.parse(rawMessage));
|
||||
const signature = await signer.sign(address, toUtf8(message));
|
||||
signatureDiv.textContent = JSON.stringify(signature, null, "\t");
|
||||
} catch (error) {
|
||||
signatureDiv.textContent = error;
|
||||
}
|
||||
};
|
2
packages/launchpad-ledger/src/index.ts
Normal file
2
packages/launchpad-ledger/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { LaunchpadLedger } from "./launchpadledger";
|
||||
export { LedgerSigner } from "./ledgersigner";
|
237
packages/launchpad-ledger/src/launchpadledger.ts
Normal file
237
packages/launchpad-ledger/src/launchpadledger.ts
Normal file
@ -0,0 +1,237 @@
|
||||
import { HdPath, Secp256k1Signature } from "@cosmjs/crypto";
|
||||
import { fromUtf8 } from "@cosmjs/encoding";
|
||||
import { makeCosmoshubPath } from "@cosmjs/launchpad";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import Transport from "@ledgerhq/hw-transport";
|
||||
import TransportWebUsb from "@ledgerhq/hw-transport-webusb";
|
||||
import CosmosApp, {
|
||||
AppInfoResponse,
|
||||
PublicKeyResponse,
|
||||
SignResponse,
|
||||
VersionResponse,
|
||||
} from "ledger-cosmos-js";
|
||||
import semver from "semver";
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
export interface LedgerAppErrorResponse {
|
||||
readonly error_message?: string;
|
||||
readonly device_locked?: boolean;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
const defaultInteractionTimeout = 120; // seconds to wait for user action on Ledger, currently is always limited to 60
|
||||
const requiredCosmosAppVersion = "1.5.3";
|
||||
|
||||
function isWindows(platform: string): boolean {
|
||||
return platform.indexOf("Win") > -1;
|
||||
}
|
||||
|
||||
function verifyBrowserIsSupported(platform: string, userAgent: string): void {
|
||||
if (isWindows(platform)) {
|
||||
throw new Error("Windows is not currently supported.");
|
||||
}
|
||||
|
||||
const isChromeOrBrave = /chrome|crios/i.test(userAgent) && !/edge|opr\//i.test(userAgent);
|
||||
if (!isChromeOrBrave) {
|
||||
throw new Error("Your browser does not support Ledger devices.");
|
||||
}
|
||||
}
|
||||
|
||||
async function createTransport(timeout: number): Promise<Transport> {
|
||||
try {
|
||||
const transport = await TransportWebUsb.create(timeout * 1000);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function unharden(hdPath: HdPath): number[] {
|
||||
return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber()));
|
||||
}
|
||||
|
||||
const cosmosHdPath = makeCosmoshubPath(0);
|
||||
const cosmosBech32Prefix = "cosmos";
|
||||
|
||||
export interface LaunchpadLedgerOptions {
|
||||
readonly hdPaths?: readonly HdPath[];
|
||||
readonly prefix?: string;
|
||||
readonly testModeAllowed?: boolean;
|
||||
}
|
||||
|
||||
export class LaunchpadLedger {
|
||||
private readonly testModeAllowed: boolean;
|
||||
private readonly hdPaths: readonly HdPath[];
|
||||
private readonly prefix: string;
|
||||
private cosmosApp: CosmosApp | null;
|
||||
public readonly platform: string;
|
||||
public readonly userAgent: string;
|
||||
|
||||
constructor(options: LaunchpadLedgerOptions = {}) {
|
||||
const defaultOptions = {
|
||||
hdPaths: [cosmosHdPath],
|
||||
prefix: cosmosBech32Prefix,
|
||||
testModeAllowed: false,
|
||||
};
|
||||
const { hdPaths, prefix, testModeAllowed } = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
};
|
||||
this.testModeAllowed = testModeAllowed;
|
||||
this.hdPaths = hdPaths;
|
||||
this.prefix = prefix;
|
||||
this.cosmosApp = null;
|
||||
this.platform = navigator.platform;
|
||||
this.userAgent = navigator.userAgent;
|
||||
}
|
||||
|
||||
async connect(timeout = defaultInteractionTimeout): Promise<LaunchpadLedger> {
|
||||
// assume good connection if connected once
|
||||
if (this.cosmosApp) {
|
||||
return this;
|
||||
}
|
||||
|
||||
verifyBrowserIsSupported(this.platform, this.userAgent);
|
||||
|
||||
const transport = await createTransport(timeout * 1000);
|
||||
this.cosmosApp = new CosmosApp(transport);
|
||||
|
||||
await this.verifyDeviceIsReady();
|
||||
return this;
|
||||
}
|
||||
|
||||
async getCosmosAppVersion(): Promise<string> {
|
||||
await this.connect();
|
||||
assert(this.cosmosApp, "Cosmos Ledger App is not connected");
|
||||
|
||||
const response = await this.cosmosApp.getVersion();
|
||||
this.handleLedgerErrors(response);
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { major, minor, patch, test_mode: testMode } = response as VersionResponse;
|
||||
this.verifyAppMode(testMode);
|
||||
return `${major}.${minor}.${patch}`;
|
||||
}
|
||||
|
||||
async getPubkey(hdPath?: HdPath): Promise<Uint8Array> {
|
||||
await this.connect();
|
||||
assert(this.cosmosApp, "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));
|
||||
this.handleLedgerErrors(response);
|
||||
return Uint8Array.from((response as PublicKeyResponse).compressed_pk);
|
||||
}
|
||||
|
||||
async getPubkeys(): Promise<readonly Uint8Array[]> {
|
||||
return Promise.all(this.hdPaths.map(async (hdPath) => this.getPubkey(hdPath)));
|
||||
}
|
||||
|
||||
async getCosmosAddress(pubkey?: Uint8Array): Promise<string> {
|
||||
const pubkeyToUse = pubkey || (await this.getPubkey());
|
||||
return CosmosApp.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse));
|
||||
}
|
||||
|
||||
async sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array> {
|
||||
await this.connect();
|
||||
assert(this.cosmosApp, "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));
|
||||
this.handleLedgerErrors(response, "Transaction signing request was rejected by the user");
|
||||
return Secp256k1Signature.fromDer((response as SignResponse).signature).toFixedLength();
|
||||
}
|
||||
|
||||
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!`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getOpenAppName(): Promise<string> {
|
||||
await this.connect();
|
||||
assert(this.cosmosApp, "Cosmos Ledger App is not connected");
|
||||
|
||||
const response = await this.cosmosApp.appInfo();
|
||||
this.handleLedgerErrors(response);
|
||||
return (response as AppInfoResponse).appName;
|
||||
}
|
||||
|
||||
private async verifyAppVersion(): Promise<void> {
|
||||
const version = await this.getCosmosAppVersion();
|
||||
if (!semver.gte(version, requiredCosmosAppVersion)) {
|
||||
throw new Error("Outdated version: Please update Cosmos Ledger App to the latest version.");
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyCosmosAppIsOpen(): Promise<void> {
|
||||
const appName = await this.getOpenAppName();
|
||||
|
||||
if (appName.toLowerCase() === `dashboard`) {
|
||||
throw new Error(`Please open the Cosmos Ledger app on your Ledger device.`);
|
||||
}
|
||||
if (appName.toLowerCase() !== `cosmos`) {
|
||||
throw new Error(`Please close ${appName} and open the Cosmos Ledger app on your Ledger device.`);
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyDeviceIsReady(): Promise<void> {
|
||||
await this.verifyAppVersion();
|
||||
await this.verifyCosmosAppIsOpen();
|
||||
}
|
||||
|
||||
private handleLedgerErrors(
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
{
|
||||
error_message: errorMessage = "No errors",
|
||||
device_locked: deviceLocked = false,
|
||||
}: LedgerAppErrorResponse,
|
||||
/* eslint-enable */
|
||||
rejectionMessage = "Request was rejected by the user",
|
||||
): void {
|
||||
if (deviceLocked) {
|
||||
throw new Error("Ledger’s screensaver mode is on");
|
||||
}
|
||||
switch (errorMessage) {
|
||||
case "U2F: Timeout":
|
||||
throw new Error("Connection timed out. Please try again.");
|
||||
case "Cosmos app does not seem to be open":
|
||||
throw new Error("Cosmos app is not open");
|
||||
case "Command not allowed":
|
||||
throw new Error("Transaction rejected");
|
||||
case "Transaction rejected":
|
||||
throw new Error(rejectionMessage);
|
||||
case "Unknown Status Code: 26628":
|
||||
throw new Error("Ledger’s screensaver mode is on");
|
||||
case "Instruction not supported":
|
||||
throw new Error(
|
||||
`Your Cosmos Ledger App is not up to date. Please update to version ${requiredCosmosAppVersion}.`,
|
||||
);
|
||||
case "No errors":
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Ledger Native Error: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
}
|
43
packages/launchpad-ledger/src/ledgersigner.ts
Normal file
43
packages/launchpad-ledger/src/ledgersigner.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } from "@cosmjs/launchpad";
|
||||
|
||||
import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger";
|
||||
|
||||
export class LedgerSigner implements OfflineSigner {
|
||||
private readonly ledger: LaunchpadLedger;
|
||||
private accounts?: readonly AccountData[];
|
||||
|
||||
constructor(options?: LaunchpadLedgerOptions) {
|
||||
this.ledger = new LaunchpadLedger(options);
|
||||
}
|
||||
|
||||
public async getAccounts(): Promise<readonly AccountData[]> {
|
||||
await this.ledger.connect();
|
||||
|
||||
if (!this.accounts) {
|
||||
const pubkeys = await this.ledger.getPubkeys();
|
||||
this.accounts = await Promise.all(
|
||||
pubkeys.map(async (pubkey) => ({
|
||||
algo: "secp256k1" as const,
|
||||
address: await this.ledger.getCosmosAddress(pubkey),
|
||||
pubkey: pubkey,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return this.accounts;
|
||||
}
|
||||
|
||||
public async sign(address: string, message: Uint8Array): Promise<StdSignature> {
|
||||
await this.ledger.connect();
|
||||
|
||||
const accounts = this.accounts || (await this.getAccounts());
|
||||
const accountForAddress = accounts.find((account) => account.address === address);
|
||||
|
||||
if (!accountForAddress) {
|
||||
throw new Error(`Address ${address} not found in wallet`);
|
||||
}
|
||||
|
||||
const signature = await this.ledger.sign(message);
|
||||
return encodeSecp256k1Signature(accountForAddress.pubkey, signature);
|
||||
}
|
||||
}
|
11
packages/launchpad-ledger/tsconfig.json
Normal file
11
packages/launchpad-ledger/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"rootDir": "src",
|
||||
"lib": ["es2017", "dom"]
|
||||
},
|
||||
"include": ["src/**/*", "./custom_types/*"]
|
||||
}
|
13
packages/launchpad-ledger/typedoc.js
Normal file
13
packages/launchpad-ledger/typedoc.js
Normal file
@ -0,0 +1,13 @@
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
inputFiles: ["./src"],
|
||||
out: "docs",
|
||||
exclude: ["**/*.spec.ts", "./src/demo"],
|
||||
name: `${packageJson.name} Documentation`,
|
||||
readme: "README.md",
|
||||
mode: "file",
|
||||
excludeExternals: true,
|
||||
excludeNotExported: true,
|
||||
excludePrivate: true,
|
||||
};
|
2
packages/launchpad-ledger/types/index.d.ts
vendored
Normal file
2
packages/launchpad-ledger/types/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { LaunchpadLedger } from "./launchpadledger";
|
||||
export { LedgerSigner } from "./ledgersigner";
|
31
packages/launchpad-ledger/types/launchpadledger.d.ts
vendored
Normal file
31
packages/launchpad-ledger/types/launchpadledger.d.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { HdPath } from "@cosmjs/crypto";
|
||||
export interface LedgerAppErrorResponse {
|
||||
readonly error_message?: string;
|
||||
readonly device_locked?: boolean;
|
||||
}
|
||||
export interface LaunchpadLedgerOptions {
|
||||
readonly hdPaths?: readonly HdPath[];
|
||||
readonly prefix?: string;
|
||||
readonly testModeAllowed?: boolean;
|
||||
}
|
||||
export declare class LaunchpadLedger {
|
||||
private readonly testModeAllowed;
|
||||
private readonly hdPaths;
|
||||
private readonly prefix;
|
||||
private cosmosApp;
|
||||
readonly platform: string;
|
||||
readonly userAgent: string;
|
||||
constructor(options?: LaunchpadLedgerOptions);
|
||||
connect(timeout?: number): Promise<LaunchpadLedger>;
|
||||
getCosmosAppVersion(): Promise<string>;
|
||||
getPubkey(hdPath?: HdPath): Promise<Uint8Array>;
|
||||
getPubkeys(): Promise<readonly Uint8Array[]>;
|
||||
getCosmosAddress(pubkey?: Uint8Array): Promise<string>;
|
||||
sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array>;
|
||||
private verifyAppMode;
|
||||
private getOpenAppName;
|
||||
private verifyAppVersion;
|
||||
private verifyCosmosAppIsOpen;
|
||||
private verifyDeviceIsReady;
|
||||
private handleLedgerErrors;
|
||||
}
|
9
packages/launchpad-ledger/types/ledgersigner.d.ts
vendored
Normal file
9
packages/launchpad-ledger/types/ledgersigner.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad";
|
||||
import { LaunchpadLedgerOptions } from "./launchpadledger";
|
||||
export declare class LedgerSigner implements OfflineSigner {
|
||||
private readonly ledger;
|
||||
private accounts?;
|
||||
constructor(options?: LaunchpadLedgerOptions);
|
||||
getAccounts(): Promise<readonly AccountData[]>;
|
||||
sign(address: string, message: Uint8Array): Promise<StdSignature>;
|
||||
}
|
17
packages/launchpad-ledger/webpack.demo.config.js
Normal file
17
packages/launchpad-ledger/webpack.demo.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
|
||||
const target = "web";
|
||||
const demodir = path.join(__dirname, "dist", "demo");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
// bundle used for Ledger demo
|
||||
target: target,
|
||||
entry: glob.sync("./build/demo/index.js"),
|
||||
output: {
|
||||
path: demodir,
|
||||
filename: "ledger.js",
|
||||
},
|
||||
},
|
||||
];
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
Bip39,
|
||||
EnglishMnemonic,
|
||||
HdPath,
|
||||
pathToString,
|
||||
Random,
|
||||
Secp256k1,
|
||||
Slip10,
|
||||
Slip10Curve,
|
||||
Slip10RawIndex,
|
||||
stringToPath,
|
||||
} from "@cosmjs/crypto";
|
||||
import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
|
||||
@ -104,7 +104,7 @@ export function extractKdfConfiguration(serialization: string): KdfConfiguration
|
||||
* Derivation information required to derive a keypair and an address from a mnemonic.
|
||||
*/
|
||||
interface Secp256k1Derivation {
|
||||
readonly hdPath: readonly Slip10RawIndex[];
|
||||
readonly hdPath: HdPath;
|
||||
readonly prefix: string;
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
*/
|
||||
public static async fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0),
|
||||
hdPath: HdPath = makeCosmoshubPath(0),
|
||||
prefix = "cosmos",
|
||||
): Promise<Secp256k1Wallet> {
|
||||
const mnemonicChecked = new EnglishMnemonic(mnemonic);
|
||||
@ -143,7 +143,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
*/
|
||||
public static async generate(
|
||||
length: 12 | 15 | 18 | 21 | 24 = 12,
|
||||
hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0),
|
||||
hdPath: HdPath = makeCosmoshubPath(0),
|
||||
prefix = "cosmos",
|
||||
): Promise<Secp256k1Wallet> {
|
||||
const entropyLength = 4 * Math.floor((11 * length) / 33);
|
||||
@ -223,7 +223,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
|
||||
private constructor(
|
||||
mnemonic: EnglishMnemonic,
|
||||
hdPath: readonly Slip10RawIndex[],
|
||||
hdPath: HdPath,
|
||||
privkey: Uint8Array,
|
||||
pubkey: Uint8Array,
|
||||
prefix: string,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
Argon2id,
|
||||
HdPath,
|
||||
isArgon2idOptions,
|
||||
Random,
|
||||
Sha256,
|
||||
@ -52,7 +53,7 @@ export function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
|
||||
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
|
||||
* with 0-based account index `a`.
|
||||
*/
|
||||
export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] {
|
||||
export function makeCosmoshubPath(a: number): HdPath {
|
||||
return [
|
||||
Slip10RawIndex.hardened(44),
|
||||
Slip10RawIndex.hardened(118),
|
||||
|
10
packages/launchpad/types/secp256k1wallet.d.ts
vendored
10
packages/launchpad/types/secp256k1wallet.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { Slip10RawIndex } from "@cosmjs/crypto";
|
||||
import { HdPath } from "@cosmjs/crypto";
|
||||
import { StdSignature } from "./types";
|
||||
import { AccountData, EncryptionConfiguration, KdfConfiguration, OfflineSigner, PrehashType } from "./wallet";
|
||||
/**
|
||||
@ -40,11 +40,7 @@ export declare class Secp256k1Wallet implements OfflineSigner {
|
||||
* @param hdPath The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`.
|
||||
* @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos".
|
||||
*/
|
||||
static fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath?: readonly Slip10RawIndex[],
|
||||
prefix?: string,
|
||||
): Promise<Secp256k1Wallet>;
|
||||
static fromMnemonic(mnemonic: string, hdPath?: HdPath, prefix?: string): Promise<Secp256k1Wallet>;
|
||||
/**
|
||||
* Generates a new wallet with a BIP39 mnemonic of the given length.
|
||||
*
|
||||
@ -54,7 +50,7 @@ export declare class Secp256k1Wallet implements OfflineSigner {
|
||||
*/
|
||||
static generate(
|
||||
length?: 12 | 15 | 18 | 21 | 24,
|
||||
hdPath?: readonly Slip10RawIndex[],
|
||||
hdPath?: HdPath,
|
||||
prefix?: string,
|
||||
): Promise<Secp256k1Wallet>;
|
||||
/**
|
||||
|
4
packages/launchpad/types/wallet.d.ts
vendored
4
packages/launchpad/types/wallet.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { Slip10RawIndex } from "@cosmjs/crypto";
|
||||
import { HdPath } from "@cosmjs/crypto";
|
||||
import { StdSignature } from "./types";
|
||||
export declare type PrehashType = "sha256" | "sha512" | null;
|
||||
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
|
||||
@ -22,7 +22,7 @@ export declare function prehash(bytes: Uint8Array, type: PrehashType): Uint8Arra
|
||||
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
|
||||
* with 0-based account index `a`.
|
||||
*/
|
||||
export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[];
|
||||
export declare function makeCosmoshubPath(a: number): HdPath;
|
||||
/**
|
||||
* A fixed salt is chosen to archive a deterministic password to key derivation.
|
||||
* This reduces the scope of a potential rainbow attack to all CosmJS users.
|
||||
|
@ -4,6 +4,7 @@
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["es2017"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"newLine": "LF",
|
||||
@ -17,7 +18,6 @@
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
"target": "es2017"
|
||||
}
|
||||
}
|
||||
|
122
yarn.lock
122
yarn.lock
@ -144,6 +144,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315"
|
||||
integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==
|
||||
|
||||
"@babel/runtime@^7.7.4":
|
||||
version "7.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
|
||||
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.10.1", "@babel/template@^7.10.3":
|
||||
version "7.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8"
|
||||
@ -290,6 +297,72 @@
|
||||
dependencies:
|
||||
vary "^1.1.2"
|
||||
|
||||
"@ledgerhq/devices@^4.78.0":
|
||||
version "4.78.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.78.0.tgz#149b572f0616096e2bd5eb14ce14d0061c432be6"
|
||||
integrity sha512-tWKS5WM/UU82czihnVjRwz9SXNTQzWjGJ/7+j/xZ70O86nlnGJ1aaFbs5/WTzfrVKpOKgj1ZoZkAswX67i/JTw==
|
||||
dependencies:
|
||||
"@ledgerhq/errors" "^4.78.0"
|
||||
"@ledgerhq/logs" "^4.72.0"
|
||||
rxjs "^6.5.3"
|
||||
|
||||
"@ledgerhq/devices@^5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.23.0.tgz#e5b800de858e45d247be56708c832c1e51727fe0"
|
||||
integrity sha512-XR9qTwn14WwN8VSMsYD9NTX/TgkmrTnXEh0pIj6HMRZwFzBPzslExOcXuCm3V9ssgAEAxv3VevfV8UulvvZUXA==
|
||||
dependencies:
|
||||
"@ledgerhq/errors" "^5.23.0"
|
||||
"@ledgerhq/logs" "^5.23.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"
|
||||
integrity sha512-FX6zHZeiNtegBvXabK6M5dJ+8OV8kQGGaGtuXDeK/Ss5EmG4Ltxc6Lnhe8hiHpm9pCHtktOsnUVL7IFBdHhYUg==
|
||||
|
||||
"@ledgerhq/errors@^5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.23.0.tgz#30a0338dafba8264556011604abed08bf24979f3"
|
||||
integrity sha512-qtpX8aFrUUlYfOMu7BxTvxqUa8CniE+tEBpVEjYUhVbFdVJjM4ouwJD++RtQkMAU2c5jE7xb12WnUnf5BlAgLQ==
|
||||
|
||||
"@ledgerhq/hw-transport-webusb@^5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-5.23.0.tgz#f374415ab1bda6ba55898c0e74097f2005fd06f7"
|
||||
integrity sha512-kz0LhlxvE97gIU0TvvzY3Co4DsRudwXoWZ/RCW3/ewn4zJ5ZT/fvp9qCrqbRsXJ+RTuZ7JQOiR1kA+Iz3qJWCg==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^5.23.0"
|
||||
"@ledgerhq/errors" "^5.23.0"
|
||||
"@ledgerhq/hw-transport" "^5.23.0"
|
||||
"@ledgerhq/logs" "^5.23.0"
|
||||
|
||||
"@ledgerhq/hw-transport@^4.77.0":
|
||||
version "4.78.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.78.0.tgz#714786658e1f2fbc0569e06e2abf8d15d310d931"
|
||||
integrity sha512-xQu16OMPQjFYLjqCysij+8sXtdWv2YLxPrB6FoLvEWGTlQ7yL1nUBRQyzyQtWIYqZd4THQowQmzm1VjxuN6SZw==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^4.78.0"
|
||||
"@ledgerhq/errors" "^4.78.0"
|
||||
events "^3.0.0"
|
||||
|
||||
"@ledgerhq/hw-transport@^5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.23.0.tgz#ed3445b9579c43a58cd959610ad7e464b36b87ca"
|
||||
integrity sha512-ICTG3Bst62SkC+lYYFgpKk5G4bAOxeIvptXnTLOhf6VqeN7gdHfiRzZwNPnKzI2pxmcEVbBitgsxEIEQJmDKVA==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^5.23.0"
|
||||
"@ledgerhq/errors" "^5.23.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"
|
||||
integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA==
|
||||
|
||||
"@ledgerhq/logs@^5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.23.0.tgz#7a86b1e6479c8aa8e8b9affe00eb8e369efdbc3b"
|
||||
integrity sha512-88M8RkVHl44k6MAhfrYhx25opnJV24/2XpuTUVklID11f9rBdE+6RZ9OMs39dyX2sDv7TuzIPi5nTRoCqZMDYw==
|
||||
|
||||
"@lerna/add@3.21.0":
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b"
|
||||
@ -1301,6 +1374,21 @@
|
||||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/ledgerhq__hw-transport-webusb@^4.70.0":
|
||||
version "4.70.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-webusb/-/ledgerhq__hw-transport-webusb-4.70.0.tgz#8e03b1f8bec52a8291ef41b00470b07f28285788"
|
||||
integrity sha512-7YY+q85pwZfja9foc0RxmcgMwsxbBo9u/Qe9BFg0uKVcF6TZFcBT99+AHWj+40EmXL3BxzK3QB+aoSNed+v8Gg==
|
||||
dependencies:
|
||||
"@types/ledgerhq__hw-transport" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ledgerhq__hw-transport@*":
|
||||
version "4.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport/-/ledgerhq__hw-transport-4.21.2.tgz#92eaea9e4669df2c8ec5292b694097d1629b05c1"
|
||||
integrity sha512-NhJwkdxdsqj/ZTq9KuBYtN+rDYOVYrlR3ByYNfK78iUOIlmXcXFBtfkhBUpX8c7dbE5uJZOjDdM3EC7Kf3Fakg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/libsodium-wrappers@^0.7.7":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.7.tgz#cdb25e85458612ec80f0157c3815fac187d0b6d2"
|
||||
@ -1370,6 +1458,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver@^7.3.3":
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.3.tgz#3ad6ed949e7487e7bda6f886b4a2434a2c3d7b1a"
|
||||
integrity sha512-jQxClWFzv9IXdLdhSaTf16XI3NYe6zrEbckSpb5xhKfPbWgIyAY0AFyWWWfaiDcBuj3UHmMkCIwSRqpKMTZL2Q==
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
|
||||
@ -2051,7 +2144,7 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bech32@^1.1.4:
|
||||
bech32@^1.1.3, bech32@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
|
||||
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
|
||||
@ -3660,6 +3753,11 @@ events@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
|
||||
integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==
|
||||
|
||||
events@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
|
||||
integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
|
||||
|
||||
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
|
||||
@ -5304,6 +5402,16 @@ koa@^2.11.0:
|
||||
type-is "^1.6.16"
|
||||
vary "^1.1.2"
|
||||
|
||||
ledger-cosmos-js@^2.1.7:
|
||||
version "2.1.7"
|
||||
resolved "https://registry.yarnpkg.com/ledger-cosmos-js/-/ledger-cosmos-js-2.1.7.tgz#362d1139ac2504ccb56029abda053b2c5b290e8d"
|
||||
integrity sha512-RyaP+6lRhllQTgk5X14PiZtsUE8bYP7mOiFkOMAzPQvUDdy8IZr17mDaH9whqHtE0wZd95YPN89VAhbfV+Re8w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.4"
|
||||
"@ledgerhq/hw-transport" "^4.77.0"
|
||||
bech32 "^1.1.3"
|
||||
ripemd160 "^2.0.2"
|
||||
|
||||
lerna@^3.22.1:
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62"
|
||||
@ -7134,6 +7242,11 @@ redent@^3.0.0:
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.7"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
|
||||
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
|
||||
|
||||
regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
|
||||
@ -7332,6 +7445,13 @@ rxjs@^6.4.0:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^6.5.3, rxjs@^6.6.3:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
Loading…
x
Reference in New Issue
Block a user