Initial code drop of new hardware wallet package.
This commit is contained in:
parent
381a72ddaa
commit
2e8f5ca7ed
2
packages/hardware-wallets/.gitignore
vendored
Normal file
2
packages/hardware-wallets/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
lib/bin/*.d.ts
|
||||||
|
lib.esm/bin/*.d.ts
|
3
packages/hardware-wallets/.npmignore
Normal file
3
packages/hardware-wallets/.npmignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
tsconfig.json
|
||||||
|
src.ts/
|
||||||
|
tsconfig.tsbuildinfo
|
29
packages/hardware-wallets/README.md
Normal file
29
packages/hardware-wallets/README.md
Normal file
@ -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.
|
42
packages/hardware-wallets/package.json
Normal file
42
packages/hardware-wallets/package.json
Normal file
@ -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"
|
||||||
|
}
|
7
packages/hardware-wallets/src.ts/index.ts
Normal file
7
packages/hardware-wallets/src.ts/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { LedgerSigner } from "./ledger";
|
||||||
|
|
||||||
|
export {
|
||||||
|
LedgerSigner
|
||||||
|
};
|
12
packages/hardware-wallets/src.ts/ledger-transport.ts
Normal file
12
packages/hardware-wallets/src.ts/ledger-transport.ts
Normal file
@ -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
|
||||||
|
};
|
99
packages/hardware-wallets/src.ts/ledger.ts
Normal file
99
packages/hardware-wallets/src.ts/ledger.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
})();
|
38
packages/hardware-wallets/thirdparty.d.ts
vendored
Normal file
38
packages/hardware-wallets/thirdparty.d.ts
vendored
Normal file
@ -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>;
|
||||||
|
}
|
12
packages/hardware-wallets/tsconfig.json
Normal file
12
packages/hardware-wallets/tsconfig.json
Normal file
@ -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
Block a user