-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
ledger.ts
112 lines (88 loc) · 3.77 KB
/
ledger.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"use strict";
import { ethers } from "ethers";
import { version } from "./_version";
const logger = new ethers.utils.Logger(version);
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";
function waiter(duration: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
export class LedgerSigner extends ethers.Signer {
readonly type: string;
readonly path: string
readonly _eth: Promise<Eth>;
constructor(provider?: ethers.providers.Provider, type?: string, path?: string) {
super();
if (path == null) { path = defaultPath; }
if (type == null) { type = "default"; }
ethers.utils.defineReadOnly(this, "path", path);
ethers.utils.defineReadOnly(this, "type", type);
ethers.utils.defineReadOnly(this, "provider", provider || null);
const transport = transports[type];
if (!transport) { logger.throwArgumentError("unknown or unsupported type", "type", type); }
ethers.utils.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);
}));
}
_retry<T = any>(callback: (eth: Eth) => Promise<T>, timeout?: number): Promise<T> {
return new Promise(async (resolve, reject) => {
if (timeout && timeout > 0) {
setTimeout(() => { reject(new Error("timeout")); }, timeout);
}
const eth = await this._eth;
// Wait up to 5 seconds
for (let i = 0; i < 50; i++) {
try {
const result = await callback(eth);
return resolve(result);
} catch (error) {
if (error.id !== "TransportLocked") {
return reject(error);
}
}
await waiter(100);
}
return reject(new Error("timeout"));
});
}
async getAddress(): Promise<string> {
const account = await this._retry((eth) => eth.getAddress(this.path));
return ethers.utils.getAddress(account.address);
}
async signMessage(message: ethers.utils.Bytes | string): Promise<string> {
if (typeof(message) === 'string') {
message = ethers.utils.toUtf8Bytes(message);
}
const messageHex = ethers.utils.hexlify(message).substring(2);
const sig = await this._retry((eth) => eth.signPersonalMessage(this.path, messageHex));
sig.r = '0x' + sig.r;
sig.s = '0x' + sig.s;
return ethers.utils.joinSignature(sig);
}
async signTransaction(transaction: ethers.providers.TransactionRequest): Promise<string> {
const tx = transaction = await ethers.utils.resolveProperties(transaction);
const unsignedTx = ethers.utils.serializeTransaction(<ethers.utils.UnsignedTransaction>tx).substring(2);
const sig = await this._retry((eth) => eth.signTransaction(this.path, unsignedTx));
return ethers.utils.serializeTransaction(<ethers.utils.UnsignedTransaction>tx, {
v: sig.v,
r: ("0x" + sig.r),
s: ("0x" + sig.s),
});
}
connect(provider: ethers.providers.Provider): ethers.Signer {
return new LedgerSigner(provider, this.type, this.path);
}
}