Initial code drop of new hardware wallet package.
This commit is contained in:
parent
381a72ddaa
commit
2e8f5ca7ed
|
@ -0,0 +1,2 @@
|
|||
lib/bin/*.d.ts
|
||||
lib.esm/bin/*.d.ts
|
|
@ -0,0 +1,3 @@
|
|||
tsconfig.json
|
||||
src.ts/
|
||||
tsconfig.tsbuildinfo
|
|
@ -0,0 +1,29 @@
|
|||
Hardware Wallets
|
||||
================
|
||||
|
||||
Thid is still very experimental.
|
||||
|
||||
I only have 1 ledger nano, and testing is done locally (CirlceCI doesn't have
|
||||
ledgers plugged in ;)).
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
```
|
||||
import { LedgerSigner } from "@ethersproject/hardware-wallets";
|
||||
const signer = new LedgerSigner(provider, type, path);
|
||||
// By default:
|
||||
// - in node, type = "usb"
|
||||
// - path is the default Ethereum path (i.e. `m/44'/60'/0'/0/0`)
|
||||
```
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
All ethers code is MIT License.
|
||||
|
||||
Each hardware wallet manufacturer may impose additional license
|
||||
requirements so please check the related abstraction libraries
|
||||
they provide.
|
||||
|
||||
All Firefly abstraction is also MIT Licensed.
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"author": "Richard Moore <me@ricmoo.com>",
|
||||
"dependencies": {
|
||||
"@ethersproject/abstract-provider": ">=5.0.0-beta.136",
|
||||
"@ethersproject/abstract-signer": ">=5.0.0-beta.137",
|
||||
"@ethersproject/address": ">=5.0.0-beta.134",
|
||||
"@ethersproject/bytes": ">=5.0.0-beta.134",
|
||||
"@ethersproject/properties": ">=5.0.0-beta.136",
|
||||
"@ethersproject/strings": ">=5.0.0-beta.135",
|
||||
"@ethersproject/transactions": ">=5.0.0-beta.133",
|
||||
"@ledgerhq/hw-app-eth": "5.3.0",
|
||||
"@ledgerhq/hw-transport": "5.3.0",
|
||||
"@ledgerhq/hw-transport-node-hid": "5.3.0",
|
||||
"@ledgerhq/hw-transport-u2f": "5.3.0"
|
||||
},
|
||||
"description": "Hardware Wallet support for ethers.",
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.4"
|
||||
},
|
||||
"ethereum": "donations.ethers.eth",
|
||||
"keywords": [
|
||||
"Ethereum",
|
||||
"ethers",
|
||||
"cli"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib.esm/index.js",
|
||||
"name": "@ethersproject/hardware-wallets",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/ethers-io/ethers.js.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "exit 1"
|
||||
},
|
||||
"types": "./lib/index.d.ts",
|
||||
"version": "5.0.0-beta.1"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
import { LedgerSigner } from "./ledger";
|
||||
|
||||
export {
|
||||
LedgerSigner
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
import hid from "@ledgerhq/hw-transport-node-hid";
|
||||
|
||||
export type TransportCreator = {
|
||||
create: () => Promise<Transport>;
|
||||
};
|
||||
|
||||
export const transports: { [ name: string ]: TransportCreator } = {
|
||||
"hid": hid,
|
||||
"default": hid
|
||||
};
|
|
@ -0,0 +1,99 @@
|
|||
"use strict";
|
||||
|
||||
import { getAddress } from "@ethersproject/address";
|
||||
import { Bytes, hexlify, joinSignature } from "@ethersproject/bytes";
|
||||
import { Signer } from "@ethersproject/abstract-signer";
|
||||
import { Provider, TransactionRequest } from "@ethersproject/abstract-provider";
|
||||
import { defineReadOnly, resolveProperties } from "@ethersproject/properties";
|
||||
import { toUtf8Bytes } from "@ethersproject/strings";
|
||||
import { serialize as serializeTransaction } from "@ethersproject/transactions";
|
||||
|
||||
import Eth from "@ledgerhq/hw-app-eth";
|
||||
|
||||
// We store these in a separated import so it is easier to swap them out
|
||||
// at bundle time; browsers do not get HID, for example. This maps a string
|
||||
// "type" to a Transport with create.
|
||||
import { transports } from "./ledger-transport";
|
||||
|
||||
const defaultPath = "m/44'/60'/0'/0/0";
|
||||
|
||||
export class LedgerSigner extends Signer {
|
||||
readonly type: string;
|
||||
readonly path: string
|
||||
|
||||
readonly _eth: Promise<Eth>;
|
||||
|
||||
constructor(provider?: Provider, type?: string, path?: string) {
|
||||
super();
|
||||
if (path == null) { path = defaultPath; }
|
||||
if (type == null) { type = "default"; }
|
||||
|
||||
defineReadOnly(this, "path", path);
|
||||
defineReadOnly(this, "type", type);
|
||||
defineReadOnly(this, "provider", provider || null);
|
||||
|
||||
const transport = transports[type];
|
||||
if (!transport) { throw new Error("unknown or unsupport type"); }
|
||||
|
||||
defineReadOnly(this, "_eth", transport.create().then((transport) => {
|
||||
const eth = new Eth(transport);
|
||||
return eth.getAppConfiguration().then((config) => {
|
||||
return eth;
|
||||
}, (error) => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}, (error) => {
|
||||
return Promise.reject(error);
|
||||
}));
|
||||
}
|
||||
|
||||
async getAddress(): Promise<string> {
|
||||
const eth = await this._eth;
|
||||
if (eth == null) { throw new Error("failed to connect"); }
|
||||
const o = await eth.getAddress(this.path);
|
||||
return getAddress(o.address);
|
||||
}
|
||||
|
||||
async signMessage(message: Bytes | string): Promise<string> {
|
||||
if (typeof(message) === 'string') {
|
||||
message = toUtf8Bytes(message);
|
||||
}
|
||||
|
||||
const messageHex = hexlify(message).substring(2);
|
||||
|
||||
const eth = await this._eth;
|
||||
const sig = await eth.signPersonalMessage(this.path, messageHex);
|
||||
sig.r = '0x' + sig.r;
|
||||
sig.s = '0x' + sig.s;
|
||||
return joinSignature(sig);
|
||||
}
|
||||
|
||||
async signTransaction(transaction: TransactionRequest): Promise<string> {
|
||||
const eth = await this._eth;
|
||||
return resolveProperties(transaction).then((tx) => {
|
||||
const unsignedTx = serializeTransaction(tx).substring(2);
|
||||
return eth.signTransaction(this.path, unsignedTx).then((sig) => {
|
||||
return serializeTransaction(tx, {
|
||||
v: sig.v,
|
||||
r: ("0x" + sig.r),
|
||||
s: ("0x" + sig.s),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
connect(provider: Provider): Signer {
|
||||
return new LedgerSigner(provider, this.type, this.path);
|
||||
}
|
||||
}
|
||||
|
||||
(async function() {
|
||||
const signer = new LedgerSigner();
|
||||
console.log(signer);
|
||||
try {
|
||||
const sig = await signer.signMessage("Hello World");
|
||||
console.log(sig);
|
||||
} catch (error) {
|
||||
console.log("ERR", error);
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,38 @@
|
|||
declare module "@ledgerhq/hw-app-eth" {
|
||||
export type PublicAccount = {
|
||||
publicKey: string;
|
||||
address: string;
|
||||
chainCode: string;
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
arbitraryDataEnabled: number,
|
||||
version: string
|
||||
};
|
||||
|
||||
export type Signature = {
|
||||
r: string,
|
||||
s: string,
|
||||
v: number
|
||||
};
|
||||
|
||||
export class Transport { }
|
||||
|
||||
export class Eth {
|
||||
constructor(transport: Transport);
|
||||
getAppConfiguration(): Promise<Config>;
|
||||
getAddress(path: string): Promise<PublicAccount>;
|
||||
signPersonalMessage(path: string, message: string): Promise<Signature>;
|
||||
signTransaction(path: string, unsignedTx: string): Promise<Signature>;
|
||||
}
|
||||
|
||||
export default Eth;
|
||||
}
|
||||
|
||||
declare module "@ledgerhq/hw-transport-node-hid" {
|
||||
export function create(): Promise<Transport>;
|
||||
}
|
||||
|
||||
declare module "@ledgerhq/hw-transport-u2f" {
|
||||
export function create(): Promise<Transport>;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./lib/"
|
||||
},
|
||||
"include": [
|
||||
"./thirdparty.d.ts",
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
Loading…
Reference in New Issue