aboutsummaryrefslogtreecommitdiffstats
path: root/packages/subproviders/src
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-12-06 05:45:35 +0800
committerFabio Berger <me@fabioberger.com>2017-12-06 05:45:35 +0800
commit038668efdfdd2eac85c30206e17128b0af2b48ce (patch)
treedd89073024f6fc89ac6306629d4bd14cd1ac5e5e /packages/subproviders/src
parent47789d770d08c20f33dbd839fcbd7bfa23d252a3 (diff)
downloaddexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar.gz
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar.bz2
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar.lz
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar.xz
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.tar.zst
dexon-0x-contracts-038668efdfdd2eac85c30206e17128b0af2b48ce.zip
Port subproviders over to mono repo, refactor LedgerSubprovider to no longer rely on hookedWalletSubprovider. Added unit and integration tests.
Diffstat (limited to 'packages/subproviders/src')
-rw-r--r--packages/subproviders/src/globals.d.ts68
-rw-r--r--packages/subproviders/src/index.ts30
-rw-r--r--packages/subproviders/src/subproviders/injected_web3.ts47
-rw-r--r--packages/subproviders/src/subproviders/ledger.ts320
-rw-r--r--packages/subproviders/src/subproviders/redundant_rpc.ts46
-rw-r--r--packages/subproviders/src/subproviders/subprovider.ts45
-rw-r--r--packages/subproviders/src/types.ts115
7 files changed, 671 insertions, 0 deletions
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts
new file mode 100644
index 000000000..1f9c6e8a6
--- /dev/null
+++ b/packages/subproviders/src/globals.d.ts
@@ -0,0 +1,68 @@
+/// <reference types='chai-typescript-typings' />
+/// <reference types='chai-as-promised-typescript-typings' />
+declare module 'bn.js';
+declare module 'dirty-chai';
+declare module 'ledgerco';
+declare module 'ethereumjs-tx';
+declare module 'es6-promisify';
+declare module 'ethereum-address';
+declare module 'debug';
+
+// tslint:disable:max-classes-per-file
+// tslint:disable:class-name
+// tslint:disable:completed-docs
+declare module 'ledgerco' {
+ interface comm {
+ close_async: Promise<void>;
+ create_async: Promise<void>;
+ }
+ export class comm_node implements comm {
+ public create_async: Promise<void>;
+ public close_async: Promise<void>;
+ }
+ export class comm_u2f implements comm {
+ public create_async: Promise<void>;
+ public close_async: Promise<void>;
+ }
+}
+
+// Semaphore-async-await declarations
+declare module 'semaphore-async-await' {
+ class Semaphore {
+ constructor(permits: number);
+ public wait(): void;
+ public signal(): void;
+ }
+ export default Semaphore;
+}
+
+// web3-provider-engine declarations
+declare module 'web3-provider-engine/subproviders/subprovider' {
+ class Subprovider {}
+ export = Subprovider;
+}
+declare module 'web3-provider-engine/subproviders/rpc' {
+ import * as Web3 from 'web3';
+ class RpcSubprovider {
+ constructor(options: {rpcUrl: string});
+ public handleRequest(
+ payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, data?: any) => void,
+ ): void;
+ }
+ export = RpcSubprovider;
+}
+
+declare module 'web3-provider-engine' {
+ class Web3ProviderEngine {
+ public on(event: string, handler: () => void): void;
+ public send(payload: any): void;
+ public sendAsync(payload: any, callback: (error: any, response: any) => void): void;
+ public addProvider(provider: any): void;
+ public start(): void;
+ public stop(): void;
+ }
+ export = Web3ProviderEngine;
+}
+// tslint:enable:max-classes-per-file
+// tslint:enable:class-name
+// tslint:enable:completed-docs
diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts
new file mode 100644
index 000000000..9560c3597
--- /dev/null
+++ b/packages/subproviders/src/index.ts
@@ -0,0 +1,30 @@
+import {
+ comm_node as LedgerNodeCommunication,
+ comm_u2f as LedgerBrowserCommunication,
+ eth as LedgerEthereumClientFn,
+} from 'ledgerco';
+
+import {LedgerEthereumClient} from './types';
+
+export {InjectedWeb3Subprovider} from './subproviders/injected_web3';
+export {RedundantRPCSubprovider} from './subproviders/redundant_rpc';
+export {
+ LedgerSubprovider,
+} from './subproviders/ledger';
+export {
+ ECSignature,
+ LedgerWalletSubprovider,
+ LedgerCommunicationClient,
+} from './types';
+
+export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> {
+ const ledgerConnection = await LedgerBrowserCommunication.create_async();
+ const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection);
+ return ledgerEthClient;
+}
+
+export async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> {
+ const ledgerConnection = await LedgerNodeCommunication.create_async();
+ const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection);
+ return ledgerEthClient;
+}
diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts
new file mode 100644
index 000000000..a3308d142
--- /dev/null
+++ b/packages/subproviders/src/subproviders/injected_web3.ts
@@ -0,0 +1,47 @@
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import Web3ProviderEngine = require('web3-provider-engine');
+
+/*
+ * This class implements the web3-provider-engine subprovider interface and forwards
+ * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected
+ * web3 instance in their browser.
+ * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
+ */
+export class InjectedWeb3Subprovider {
+ private injectedWeb3: Web3;
+ constructor(injectedWeb3: Web3) {
+ this.injectedWeb3 = injectedWeb3;
+ }
+ public handleRequest(
+ payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error, result: any) => void,
+ ) {
+ switch (payload.method) {
+ case 'web3_clientVersion':
+ this.injectedWeb3.version.getNode(end);
+ return;
+ case 'eth_accounts':
+ this.injectedWeb3.eth.getAccounts(end);
+ return;
+
+ case 'eth_sendTransaction':
+ const [txParams] = payload.params;
+ this.injectedWeb3.eth.sendTransaction(txParams, end);
+ return;
+
+ case 'eth_sign':
+ const [address, message] = payload.params;
+ this.injectedWeb3.eth.sign(address, message, end);
+ return;
+
+ default:
+ next();
+ return;
+ }
+ }
+ // Required to implement this method despite not needing it for this subprovider
+ // tslint:disable-next-line:prefer-function-over-method
+ public setEngine(engine: Web3ProviderEngine) {
+ // noop
+ }
+}
diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts
new file mode 100644
index 000000000..931199ed1
--- /dev/null
+++ b/packages/subproviders/src/subproviders/ledger.ts
@@ -0,0 +1,320 @@
+import promisify = require('es6-promisify');
+import {isAddress} from 'ethereum-address';
+import * as EthereumTx from 'ethereumjs-tx';
+import ethUtil = require('ethereumjs-util');
+import * as ledger from 'ledgerco';
+import * as _ from 'lodash';
+import Semaphore from 'semaphore-async-await';
+import Web3 = require('web3');
+
+import {
+ LedgerEthereumClient,
+ LedgerEthereumClientFactoryAsync,
+ LedgerSubproviderConfigs,
+ LedgerSubproviderErrors,
+ PartialTxParams,
+ ResponseWithTxParams,
+ SignPersonalMessageParams,
+} from '../types';
+
+import {Subprovider} from './subprovider';
+
+const DEFAULT_DERIVATION_PATH = `44'/60'/0'`;
+const NUM_ADDRESSES_TO_FETCH = 10;
+const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
+const SHOULD_GET_CHAIN_CODE = false;
+const HEX_REGEX = /^[0-9A-Fa-f]+$/g;
+
+export class LedgerSubprovider extends Subprovider {
+ private _nonceLock: Semaphore;
+ private _connectionLock: Semaphore;
+ private _networkId: number;
+ private _derivationPath: string;
+ private _derivationPathIndex: number;
+ private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
+ private _ledgerClientIfExists?: LedgerEthereumClient;
+ private _shouldAlwaysAskForConfirmation: boolean;
+ private static isValidHex(data: string) {
+ if (!_.isString(data)) {
+ return false;
+ }
+ const isHexPrefixed = data.slice(0, 2) === '0x';
+ if (!isHexPrefixed) {
+ return false;
+ }
+ const nonPrefixed = data.slice(2);
+ const isValid = nonPrefixed.match(HEX_REGEX);
+ return isValid;
+ }
+ private static validatePersonalMessage(msgParams: PartialTxParams) {
+ if (_.isUndefined(msgParams.from) || !isAddress(msgParams.from)) {
+ throw new Error(LedgerSubproviderErrors.FromAddressMissingOrInvalid);
+ }
+ if (_.isUndefined(msgParams.data)) {
+ throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage);
+ }
+ if (!LedgerSubprovider.isValidHex(msgParams.data)) {
+ throw new Error(LedgerSubproviderErrors.DataNotValidHexForSignPersonalMessage);
+ }
+ }
+ private static validateSender(sender: string) {
+ if (_.isUndefined(sender) || !isAddress(sender)) {
+ throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied);
+ }
+ }
+ constructor(config: LedgerSubproviderConfigs) {
+ super();
+ this._nonceLock = new Semaphore(1);
+ this._connectionLock = new Semaphore(1);
+ this._networkId = config.networkId;
+ this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync;
+ this._derivationPath = config.derivationPath || DEFAULT_DERIVATION_PATH;
+ this._shouldAlwaysAskForConfirmation = !_.isUndefined(config.accountFetchingConfigs) &&
+ !_.isUndefined(
+ config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation,
+ ) ?
+ config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation :
+ ASK_FOR_ON_DEVICE_CONFIRMATION;
+ this._derivationPathIndex = 0;
+ }
+ public getPath(): string {
+ return this._derivationPath;
+ }
+ public setPath(derivationPath: string) {
+ this._derivationPath = derivationPath;
+ }
+ public setPathIndex(pathIndex: number) {
+ this._derivationPathIndex = pathIndex;
+ }
+ public async handleRequest(
+ payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, result?: any) => void,
+ ) {
+ let accounts;
+ let txParams;
+ switch (payload.method) {
+ case 'eth_coinbase':
+ try {
+ accounts = await this.getAccountsAsync();
+ end(null, accounts[0]);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ case 'eth_accounts':
+ try {
+ accounts = await this.getAccountsAsync();
+ end(null, accounts);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ case 'eth_sendTransaction':
+ txParams = payload.params[0];
+ try {
+ LedgerSubprovider.validateSender(txParams.from);
+ const result = await this.sendTransactionAsync(txParams);
+ end(null, result);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ case 'eth_signTransaction':
+ txParams = payload.params[0];
+ try {
+ const result = await this.signTransactionWithoutSendingAsync(txParams);
+ end(null, result);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ case 'personal_sign':
+ // non-standard "extraParams" to be appended to our "msgParams" obj
+ // good place for metadata
+ const extraParams = payload.params[2] || {};
+ const msgParams = _.assign({}, extraParams, {
+ from: payload.params[1],
+ data: payload.params[0],
+ });
+
+ try {
+ LedgerSubprovider.validatePersonalMessage(msgParams);
+ const ecSignatureHex = await this.signPersonalMessageAsync(msgParams);
+ end(null, ecSignatureHex);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ default:
+ next();
+ return;
+ }
+ }
+ public async getAccountsAsync(): Promise<string[]> {
+ this._ledgerClientIfExists = await this.createLedgerClientAsync();
+
+ const accounts = [];
+ for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) {
+ try {
+ const derivationPath = `${this._derivationPath}/${i + this._derivationPathIndex}`;
+ const result = await this._ledgerClientIfExists.getAddress_async(
+ derivationPath, this._shouldAlwaysAskForConfirmation, SHOULD_GET_CHAIN_CODE,
+ );
+ accounts.push(result.address.toLowerCase());
+ } catch (err) {
+ await this.destoryLedgerClientAsync();
+ throw err;
+ }
+ }
+ await this.destoryLedgerClientAsync();
+ return accounts;
+ }
+ public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
+ this._ledgerClientIfExists = await this.createLedgerClientAsync();
+
+ const tx = new EthereumTx(txParams);
+
+ // Set the EIP155 bits
+ tx.raw[6] = Buffer.from([this._networkId]); // v
+ tx.raw[7] = Buffer.from([]); // r
+ tx.raw[8] = Buffer.from([]); // s
+
+ const txHex = tx.serialize().toString('hex');
+ try {
+ const derivationPath = this.getDerivationPath();
+ const result = await this._ledgerClientIfExists.signTransaction_async(derivationPath, txHex);
+ // Store signature in transaction
+ tx.r = Buffer.from(result.r, 'hex');
+ tx.s = Buffer.from(result.s, 'hex');
+ tx.v = Buffer.from(result.v, 'hex');
+
+ // EIP155: v should be chain_id * 2 + {35, 36}
+ const signedChainId = Math.floor((tx.v[0] - 35) / 2);
+ if (signedChainId !== this._networkId) {
+ await this.destoryLedgerClientAsync();
+ const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware);
+ throw err;
+ }
+
+ const signedTxHex = `0x${tx.serialize().toString('hex')}`;
+ await this.destoryLedgerClientAsync();
+ return signedTxHex;
+ } catch (err) {
+ await this.destoryLedgerClientAsync();
+ throw err;
+ }
+ }
+ public async signPersonalMessageAsync(msgParams: SignPersonalMessageParams): Promise<string> {
+ this._ledgerClientIfExists = await this.createLedgerClientAsync();
+ try {
+ const derivationPath = this.getDerivationPath();
+ const result = await this._ledgerClientIfExists.signPersonalMessage_async(
+ derivationPath, ethUtil.stripHexPrefix(msgParams.data));
+ const v = result.v - 27;
+ let vHex = v.toString(16);
+ if (vHex.length < 2) {
+ vHex = `0${v}`;
+ }
+ const signature = `0x${result.r}${result.s}${vHex}`;
+ await this.destoryLedgerClientAsync();
+ return signature;
+ } catch (err) {
+ await this.destoryLedgerClientAsync();
+ throw err;
+ }
+ }
+ private getDerivationPath() {
+ const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`;
+ return derivationPath;
+ }
+ private async createLedgerClientAsync(): Promise<LedgerEthereumClient> {
+ await this._connectionLock.wait();
+ if (!_.isUndefined(this._ledgerClientIfExists)) {
+ this._connectionLock.signal();
+ throw new Error(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed);
+ }
+ const ledgerEthereumClient = await this._ledgerEthereumClientFactoryAsync();
+ this._connectionLock.signal();
+ return ledgerEthereumClient;
+ }
+ private async destoryLedgerClientAsync() {
+ await this._connectionLock.wait();
+ if (_.isUndefined(this._ledgerClientIfExists)) {
+ this._connectionLock.signal();
+ return;
+ }
+ await this._ledgerClientIfExists.comm.close_async();
+ this._ledgerClientIfExists = undefined;
+ this._connectionLock.signal();
+ }
+ private async sendTransactionAsync(txParams: PartialTxParams): Promise<any> {
+ await this._nonceLock.wait();
+ try {
+ // fill in the extras
+ const filledParams = await this.populateMissingTxParamsAsync(txParams);
+ // sign it
+ const signedTx = await this.signTransactionAsync(filledParams);
+ // emit a submit
+ const payload = {
+ method: 'eth_sendRawTransaction',
+ params: [signedTx],
+ };
+ const result = await this.emitPayloadAsync(payload);
+ this._nonceLock.signal();
+ return result;
+ } catch (err) {
+ this._nonceLock.signal();
+ throw err;
+ }
+ }
+ private async signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise<ResponseWithTxParams> {
+ await this._nonceLock.wait();
+ try {
+ // fill in the extras
+ const filledParams = await this.populateMissingTxParamsAsync(txParams);
+ // sign it
+ const signedTx = await this.signTransactionAsync(filledParams);
+
+ this._nonceLock.signal();
+ const result = {
+ raw: signedTx,
+ tx: txParams,
+ };
+ return result;
+ } catch (err) {
+ this._nonceLock.signal();
+ throw err;
+ }
+ }
+ private async populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> {
+ if (_.isUndefined(txParams.gasPrice)) {
+ const gasPriceResult = await this.emitPayloadAsync({
+ method: 'eth_gasPrice',
+ params: [],
+ });
+ const gasPrice = gasPriceResult.result.toString();
+ txParams.gasPrice = gasPrice;
+ }
+ if (_.isUndefined(txParams.nonce)) {
+ const nonceResult = await this.emitPayloadAsync({
+ method: 'eth_getTransactionCount',
+ params: [txParams.from, 'pending'],
+ });
+ const nonce = nonceResult.result;
+ txParams.nonce = nonce;
+ }
+ if (_.isUndefined(txParams.gas)) {
+ const gasResult = await this.emitPayloadAsync({
+ method: 'eth_estimateGas',
+ params: [txParams],
+ });
+ const gas = gasResult.result.toString();
+ txParams.gas = gas;
+ }
+ return txParams;
+ }
+}
diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts
new file mode 100644
index 000000000..43d711ee6
--- /dev/null
+++ b/packages/subproviders/src/subproviders/redundant_rpc.ts
@@ -0,0 +1,46 @@
+import promisify = require('es6-promisify');
+import * as _ from 'lodash';
+import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
+import Subprovider = require('web3-provider-engine/subproviders/subprovider');
+
+import {JSONRPCPayload} from '../types';
+
+export class RedundantRPCSubprovider extends Subprovider {
+ private rpcs: RpcSubprovider[];
+ private static async firstSuccessAsync(
+ rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void,
+ ): Promise<any> {
+ let lastErr: Error|undefined;
+ for (const rpc of rpcs) {
+ try {
+ const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next);
+ return data;
+ } catch (err) {
+ lastErr = err;
+ continue;
+ }
+ }
+ if (!_.isUndefined(lastErr)) {
+ throw lastErr;
+ }
+ }
+ constructor(endpoints: string[]) {
+ super();
+ this.rpcs = _.map(endpoints, endpoint => {
+ return new RpcSubprovider({
+ rpcUrl: endpoint,
+ });
+ });
+ }
+ public async handleRequest(payload: JSONRPCPayload, next: () => void,
+ end: (err?: Error, data?: any) => void): Promise<void> {
+ const rpcsCopy = this.rpcs.slice();
+ try {
+ const data = await RedundantRPCSubprovider.firstSuccessAsync(rpcsCopy, payload, next);
+ end(undefined, data);
+ } catch (err) {
+ end(err);
+ }
+
+ }
+}
diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts
new file mode 100644
index 000000000..07f4d6353
--- /dev/null
+++ b/packages/subproviders/src/subproviders/subprovider.ts
@@ -0,0 +1,45 @@
+import promisify = require('es6-promisify');
+import Web3 = require('web3');
+
+import {
+ JSONRPCPayload,
+} from '../types';
+/*
+ * A version of the base class Subprovider found in providerEngine
+ * This one has an async/await `emitPayloadAsync` and also defined types.
+ * Altered version of: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
+ */
+export class Subprovider {
+ private engine: any;
+ private currentBlock: any;
+ private static getRandomId() {
+ const extraDigits = 3;
+ // 13 time digits
+ const datePart = new Date().getTime() * Math.pow(10, extraDigits);
+ // 3 random digits
+ const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits));
+ // 16 digits
+ return datePart + extraPart;
+ }
+ private static createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload {
+ const finalPayload = {
+ // defaults
+ id: Subprovider.getRandomId(),
+ jsonrpc: '2.0',
+ params: [],
+ ...payload,
+ };
+ return finalPayload;
+ }
+ public setEngine(engine: any): void {
+ this.engine = engine;
+ engine.on('block', (block: any) => {
+ this.currentBlock = block;
+ });
+ }
+ public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> {
+ const finalPayload = Subprovider.createFinalPayload(payload);
+ const response = await promisify(this.engine.sendAsync, this.engine)(finalPayload);
+ return response;
+ }
+}
diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts
new file mode 100644
index 000000000..4564c5229
--- /dev/null
+++ b/packages/subproviders/src/types.ts
@@ -0,0 +1,115 @@
+import * as _ from 'lodash';
+import * as Web3 from 'web3';
+
+export interface LedgerCommunicationClient {
+ exchange: (apduHex: string, statusList: number[]) => Promise<any[]>;
+ setScrambleKey: (key: string) => void;
+ close_async: () => Promise<void>;
+}
+
+/*
+ * The LedgerEthereumClient sends Ethereum-specific requests to the Ledger Nano S
+ * It uses an internal LedgerCommunicationClient to relay these requests. Currently
+ * NodeJs and Browser communication are supported.
+ */
+export interface LedgerEthereumClient {
+ getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean,
+ shouldGetChainCode: boolean) => Promise<LedgerGetAddressResult>;
+ signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>;
+ signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>;
+ comm: LedgerCommunicationClient;
+}
+
+export interface ECSignatureString {
+ v: string;
+ r: string;
+ s: string;
+}
+
+export interface ECSignature {
+ v: number;
+ r: string;
+ s: string;
+}
+
+export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClient>;
+
+/*
+ * networkId: The ethereum networkId to set as the chainId from EIP155
+ * ledgerConnectionType: Environment in which you wish to connect to Ledger (nodejs or browser)
+ * derivationPath: Initial derivation path to use e.g 44'/60'/0'
+ * accountFetchingConfigs: configs related to fetching accounts from a Ledger
+ */
+export interface LedgerSubproviderConfigs {
+ networkId: number;
+ ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
+ derivationPath?: string;
+ accountFetchingConfigs?: AccountFetchingConfigs;
+}
+
+/*
+ * numAddressesToReturn: Number of addresses to return from 'eth_accounts' call
+ * shouldAskForOnDeviceConfirmation: Whether you wish to prompt the user on their Ledger
+ * before fetching their addresses
+ */
+export interface AccountFetchingConfigs {
+ numAddressesToReturn?: number;
+ shouldAskForOnDeviceConfirmation?: boolean;
+}
+
+export interface SignatureData {
+ hash: string;
+ r: string;
+ s: string;
+ v: number;
+}
+
+export interface LedgerGetAddressResult {
+ address: string;
+}
+
+export interface LedgerWalletSubprovider {
+ getPath: () => string;
+ setPath: (path: string) => void;
+ setPathIndex: (pathIndex: number) => void;
+}
+
+export interface SignPersonalMessageParams {
+ data: string;
+}
+
+export interface PartialTxParams {
+ nonce: string;
+ gasPrice?: string;
+ gas: string;
+ to: string;
+ from?: string;
+ value?: string;
+ data?: string;
+ chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
+}
+
+export type DoneCallback = (err?: Error) => void;
+
+export interface JSONRPCPayload {
+ params: any[];
+ method: string;
+}
+
+export interface LedgerCommunication {
+ close_async: () => Promise<void>;
+}
+
+export interface ResponseWithTxParams {
+ raw: string;
+ tx: PartialTxParams;
+}
+
+export enum LedgerSubproviderErrors {
+ TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE',
+ FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID',
+ DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE',
+ DataNotValidHexForSignPersonalMessage = 'DATA_NOT_VALID_HEX_FOR_SIGN_PERSONAL_MESSAGE',
+ SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
+ MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED',
+}