aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-12-09 01:22:25 +0800
committerGitHub <noreply@github.com>2017-12-09 01:22:25 +0800
commit828dffffedf64e7ab5301284e2586082b6bc8f9c (patch)
tree6f429e6f2b3050de6dd7ce16e42c7f387bd5018a
parent5eea829be9f5e8669c40dac965231051b668ed37 (diff)
parentaf8d24d0eb5af781f4731b5559979f2b02579785 (diff)
downloaddexon-0x-contracts-828dffffedf64e7ab5301284e2586082b6bc8f9c.tar.gz
dexon-0x-contracts-828dffffedf64e7ab5301284e2586082b6bc8f9c.tar.zst
dexon-0x-contracts-828dffffedf64e7ab5301284e2586082b6bc8f9c.zip
Merge pull request #252 from 0xProject/feature/addSubproviders
Add Subproviders Subpackage
-rw-r--r--package.json3
-rw-r--r--packages/0x.js/package.json1
-rw-r--r--packages/abi-gen/package.json4
-rw-r--r--packages/subproviders/README.md39
-rw-r--r--packages/subproviders/package.json53
-rw-r--r--packages/subproviders/src/globals.d.ts97
-rw-r--r--packages/subproviders/src/index.ts38
-rw-r--r--packages/subproviders/src/subproviders/injected_web3.ts (renamed from packages/website/ts/subproviders/injected_web3_subprovider.ts)10
-rw-r--r--packages/subproviders/src/subproviders/ledger.ts306
-rw-r--r--packages/subproviders/src/subproviders/redundant_rpc.ts (renamed from packages/website/ts/subproviders/redundant_rpc_subprovider.ts)14
-rw-r--r--packages/subproviders/src/subproviders/subprovider.ts46
-rw-r--r--packages/subproviders/src/types.ts108
-rw-r--r--packages/subproviders/test/chai_setup.ts11
-rw-r--r--packages/subproviders/test/integration/ledger_subprovider_test.ts172
-rw-r--r--packages/subproviders/test/unit/ledger_subprovider_test.ts212
-rw-r--r--packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts62
-rw-r--r--packages/subproviders/test/utils/report_callback_errors.ts14
-rw-r--r--packages/subproviders/tsconfig.json22
-rw-r--r--packages/subproviders/tslint.json5
-rw-r--r--packages/website/package.json2
-rw-r--r--packages/website/ts/blockchain.ts44
-rw-r--r--packages/website/ts/globals.d.ts32
-rw-r--r--packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts172
-rw-r--r--packages/website/ts/types.ts6
-rw-r--r--yarn.lock30
25 files changed, 1270 insertions, 233 deletions
diff --git a/package.json b/package.json
index dcaf34eba..54a2148d2 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"lerna": "^2.5.1",
"async-child-process": "^1.1.1",
"semver-sort": "^0.0.4",
- "publish-release": "0xproject/publish-release"
+ "publish-release": "0xproject/publish-release",
+ "ethereumjs-testrpc": "6.0.3"
}
}
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json
index 4468bae09..7b267fa8e 100644
--- a/packages/0x.js/package.json
+++ b/packages/0x.js/package.json
@@ -65,7 +65,6 @@
"copyfiles": "^1.2.0",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
- "ethereumjs-testrpc": "6.0.3",
"json-loader": "^0.5.4",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json
index 0d61891e0..9ce64f818 100644
--- a/packages/abi-gen/package.json
+++ b/packages/abi-gen/package.json
@@ -22,7 +22,7 @@
},
"homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md",
"dependencies": {
- "bignumber.js": "^5.0.0",
+ "bignumber.js": "~4.1.0",
"chalk": "^2.3.0",
"glob": "^7.1.2",
"handlebars": "^4.0.11",
@@ -39,7 +39,7 @@
"@types/mkdirp": "^0.5.1",
"@types/node": "^8.0.53",
"@types/yargs": "^8.0.2",
- "npm-run-all": "^4.1.1",
+ "npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.8.0",
"typescript": "~2.6.1",
diff --git a/packages/subproviders/README.md b/packages/subproviders/README.md
new file mode 100644
index 000000000..5fa31611a
--- /dev/null
+++ b/packages/subproviders/README.md
@@ -0,0 +1,39 @@
+Subproviders
+-----------
+
+A few useful subproviders.
+
+## Installation
+
+```
+npm install @0xproject/subproviders --save
+```
+
+## Subproviders
+
+#### Ledger Nano S subprovider
+
+A subprovider that enables your dApp to send signing requests to a user's Ledger Nano S hardware wallet. These can be requests to sign transactions or messages.
+
+#### Redundant RPC subprovider
+
+A subprovider which attempts to send an RPC call to a list of RPC endpoints sequentially, until one of them returns a successful response.
+
+#### Injected Web3 subprovider
+
+A subprovider that relays all signing related requests to a particular provider (in our case the provider injected onto the web page), while sending all other requests to a different provider (perhaps your own backing Ethereum node or Infura).
+
+### Integration tests
+
+In order to run the integration tests, make sure you have a Ledger Nano S available.
+
+- Plug it into your computer
+- Unlock the device
+- Open the on-device Ethereum app
+- Make sure "browser support" is disabled
+
+Then run:
+
+```
+yarn test:integration
+```
diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json
new file mode 100644
index 000000000..c3ecfd2f0
--- /dev/null
+++ b/packages/subproviders/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@0xproject/subproviders",
+ "version": "0.0.1",
+ "main": "lib/src/index.js",
+ "types": "lib/src/index.d.ts",
+ "license": "Apache-2.0",
+ "scripts": {
+ "clean": "shx rm -rf lib",
+ "build": "tsc",
+ "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
+ "run_mocha_unit": "mocha lib/test/unit/**/*_test.js --timeout 10000 --bail --exit",
+ "run_mocha_integration": "mocha lib/test/integration/**/*_test.js --timeout 10000 --bail --exit",
+ "test": "npm run test:unit",
+ "test:circleci": "npm run test:unit",
+ "test:all": "run-s test:unit test:integration",
+ "test:unit": "run-s clean build run_mocha_unit",
+ "test:integration": "run-s clean build run_mocha_integration"
+ },
+ "dependencies": {
+ "@0xproject/assert": "^0.0.6",
+ "bn.js": "^4.11.8",
+ "es6-promisify": "^5.0.0",
+ "ethereum-address": "^0.0.4",
+ "ethereumjs-tx": "^1.3.3",
+ "ethereumjs-util": "^5.1.1",
+ "ledgerco": "0xProject/ledger-node-js-api",
+ "lodash": "^4.17.4",
+ "semaphore-async-await": "^1.5.1",
+ "web3": "^0.20.0",
+ "web3-provider-engine": "^13.0.1"
+ },
+ "devDependencies": {
+ "@0xproject/tslint-config": "^0.2.0",
+ "@types/lodash": "^4.14.86",
+ "@types/mocha": "^2.2.42",
+ "@types/node": "^8.0.53",
+ "awesome-typescript-loader": "^3.1.3",
+ "chai": "^4.0.1",
+ "chai-as-promised": "^7.1.0",
+ "chai-as-promised-typescript-typings": "^0.0.3",
+ "chai-typescript-typings": "^0.0.1",
+ "dirty-chai": "^2.0.1",
+ "mocha": "^4.0.1",
+ "npm-run-all": "^4.1.2",
+ "shx": "^0.2.2",
+ "tslint": "5.8.0",
+ "types-bn": "^0.0.1",
+ "types-ethereumjs-util": "0xproject/types-ethereumjs-util",
+ "typescript": "~2.6.1",
+ "web3-typescript-typings": "^0.7.2",
+ "webpack": "^3.1.0"
+ }
+}
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts
new file mode 100644
index 000000000..520ca9232
--- /dev/null
+++ b/packages/subproviders/src/globals.d.ts
@@ -0,0 +1,97 @@
+/// <reference types='chai-typescript-typings' />
+/// <reference types='chai-as-promised-typescript-typings' />
+declare module 'dirty-chai';
+declare module 'es6-promisify';
+
+// tslint:disable:max-classes-per-file
+// tslint:disable:class-name
+// tslint:disable:completed-docs
+
+// Ethereumjs-tx declarations
+declare module 'ethereumjs-tx' {
+ class EthereumTx {
+ public raw: Buffer[];
+ public r: Buffer;
+ public s: Buffer;
+ public v: Buffer;
+ public serialize(): Buffer;
+ constructor(txParams: any);
+ }
+ export = EthereumTx;
+}
+
+// Ledgerco declarations
+interface ECSignatureString {
+ v: string;
+ r: string;
+ s: string;
+}
+interface ECSignature {
+ v: number;
+ r: string;
+ s: string;
+}
+declare module 'ledgerco' {
+ interface comm {
+ close_async(): Promise<void>;
+ }
+ export class comm_node implements comm {
+ public static create_async(timeoutMilliseconds?: number): Promise<comm_node>;
+ public close_async(): Promise<void>;
+ }
+ export class comm_u2f implements comm {
+ public static create_async(): Promise<comm_u2f>;
+ public close_async(): Promise<void>;
+ }
+ export class eth {
+ public comm: comm;
+ constructor(comm: comm);
+ public getAddress_async(path: string, display?: boolean, chaincode?: boolean):
+ Promise<{publicKey: string; address: string}>;
+ public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>;
+ public getAppConfiguration_async(): Promise<{ arbitraryDataEnabled: number; version: string }>;
+ public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>;
+ }
+}
+
+// ethereum-address declarations
+declare module 'ethereum-address' {
+ export const isAddress: (address: string) => boolean;
+}
+
+// Semaphore-async-await declarations
+declare module 'semaphore-async-await' {
+ class Semaphore {
+ constructor(permits: number);
+ public wait(): Promise<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;
+}
diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts
new file mode 100644
index 000000000..9b7cab3fa
--- /dev/null
+++ b/packages/subproviders/src/index.ts
@@ -0,0 +1,38 @@
+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';
+
+/**
+ * A factory method for creating a LedgerEthereumClient usable in a browser context.
+ * @return LedgerEthereumClient A browser client
+ */
+export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> {
+ const ledgerConnection = await LedgerBrowserCommunication.create_async();
+ const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection);
+ return ledgerEthClient;
+}
+
+/**
+ * A factory for creating a LedgerEthereumClient usable in a Node.js context.
+ * @return LedgerEthereumClient A Node.js client
+ */
+export async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> {
+ const ledgerConnection = await LedgerNodeCommunication.create_async();
+ const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection);
+ return ledgerEthClient;
+}
diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/subproviders/src/subproviders/injected_web3.ts
index 910fe3cdf..25d747a62 100644
--- a/packages/website/ts/subproviders/injected_web3_subprovider.ts
+++ b/packages/subproviders/src/subproviders/injected_web3.ts
@@ -1,6 +1,6 @@
import * as _ from 'lodash';
-import {constants} from 'ts/utils/constants';
import Web3 = require('web3');
+import Web3ProviderEngine = require('web3-provider-engine');
/*
* This class implements the web3-provider-engine subprovider interface and forwards
@@ -8,12 +8,14 @@ import Web3 = require('web3');
* web3 instance in their browser.
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
*/
-export class InjectedWeb3SubProvider {
+export class InjectedWeb3Subprovider {
private injectedWeb3: Web3;
constructor(injectedWeb3: Web3) {
this.injectedWeb3 = injectedWeb3;
}
- public handleRequest(payload: any, next: () => void, end: (err: Error, result: any) => void) {
+ public handleRequest(
+ payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, result: any) => void,
+ ) {
switch (payload.method) {
case 'web3_clientVersion':
this.injectedWeb3.version.getNode(end);
@@ -39,7 +41,7 @@ export class InjectedWeb3SubProvider {
}
// Required to implement this method despite not needing it for this subprovider
// tslint:disable-next-line:prefer-function-over-method
- public setEngine(engine: any) {
+ 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..e0a08f792
--- /dev/null
+++ b/packages/subproviders/src/subproviders/ledger.ts
@@ -0,0 +1,306 @@
+import {assert} from '@0xproject/assert';
+import promisify = require('es6-promisify');
+import {isAddress} from 'ethereum-address';
+import EthereumTx = require('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,
+} 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 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':
+ const data = payload.params[0];
+ try {
+ if (_.isUndefined(data)) {
+ throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage);
+ }
+ assert.isHexString('data', data);
+ const ecSignatureHex = await this.signPersonalMessageAsync(data);
+ end(null, ecSignatureHex);
+ } catch (err) {
+ end(err);
+ }
+ return;
+
+ default:
+ next();
+ return;
+ }
+ }
+ public async getAccountsAsync(): Promise<string[]> {
+ this._ledgerClientIfExists = await this.createLedgerClientAsync();
+
+ // TODO: replace with generating addresses without hitting Ledger
+ 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(data: string): Promise<string> {
+ this._ledgerClientIfExists = await this.createLedgerClientAsync();
+ try {
+ const derivationPath = this.getDerivationPath();
+ const result = await this._ledgerClientIfExists.signPersonalMessage_async(
+ derivationPath, ethUtil.stripHexPrefix(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<Web3.JSONRPCResponsePayload> {
+ 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/website/ts/subproviders/redundant_rpc_subprovider.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts
index d540e6e7b..80462bbfb 100644
--- a/packages/website/ts/subproviders/redundant_rpc_subprovider.ts
+++ b/packages/subproviders/src/subproviders/redundant_rpc.ts
@@ -1,15 +1,17 @@
import {promisify} from '@0xproject/utils';
import * as _ from 'lodash';
-import {JSONRPCPayload} from 'ts/types';
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
-import Subprovider = require('web3-provider-engine/subproviders/subprovider');
+
+import {JSONRPCPayload} from '../types';
+
+import {Subprovider} from './subprovider';
export class RedundantRPCSubprovider extends Subprovider {
private rpcs: RpcSubprovider[];
private static async firstSuccessAsync(
rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void,
): Promise<any> {
- let lastErr;
+ let lastErr: Error|undefined;
for (const rpc of rpcs) {
try {
const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next);
@@ -19,7 +21,9 @@ export class RedundantRPCSubprovider extends Subprovider {
continue;
}
}
- throw Error(lastErr);
+ if (!_.isUndefined(lastErr)) {
+ throw lastErr;
+ }
}
constructor(endpoints: string[]) {
super();
@@ -30,7 +34,7 @@ export class RedundantRPCSubprovider extends Subprovider {
});
}
public async handleRequest(payload: JSONRPCPayload, next: () => void,
- end: (err?: Error, data?: any) => void): Promise<void> {
+ end: (err: Error|null, data?: any) => void): Promise<void> {
const rpcsCopy = this.rpcs.slice();
try {
const data = await RedundantRPCSubprovider.firstSuccessAsync(rpcsCopy, payload, next);
diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts
new file mode 100644
index 000000000..64d97b958
--- /dev/null
+++ b/packages/subproviders/src/subproviders/subprovider.ts
@@ -0,0 +1,46 @@
+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;
+ // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js
+ 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..38dc1e67e
--- /dev/null
+++ b/packages/subproviders/src/types.ts
@@ -0,0 +1,108 @@
+import * as _ from 'lodash';
+import * as Web3 from 'web3';
+
+export interface LedgerCommunicationClient {
+ 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 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',
+ SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
+ MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED',
+}
diff --git a/packages/subproviders/test/chai_setup.ts b/packages/subproviders/test/chai_setup.ts
new file mode 100644
index 000000000..a281bab6c
--- /dev/null
+++ b/packages/subproviders/test/chai_setup.ts
@@ -0,0 +1,11 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure() {
+ chai.config.includeStack = true;
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};
diff --git a/packages/subproviders/test/integration/ledger_subprovider_test.ts b/packages/subproviders/test/integration/ledger_subprovider_test.ts
new file mode 100644
index 000000000..75f6d47fe
--- /dev/null
+++ b/packages/subproviders/test/integration/ledger_subprovider_test.ts
@@ -0,0 +1,172 @@
+import * as chai from 'chai';
+import promisify = require('es6-promisify');
+import * as ethUtils from 'ethereumjs-util';
+import * as _ from 'lodash';
+import * as mocha from 'mocha';
+import Web3 = require('web3');
+import Web3ProviderEngine = require('web3-provider-engine');
+import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
+
+import {
+ ECSignature,
+ ledgerEthereumNodeJsClientFactoryAsync,
+ LedgerSubprovider,
+} from '../../src';
+import {
+ DoneCallback,
+ LedgerGetAddressResult,
+ PartialTxParams,
+} from '../../src/types';
+import {chaiSetup} from '../chai_setup';
+import {reportCallbackErrors} from '../utils/report_callback_errors';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
+
+describe('LedgerSubprovider', () => {
+ let ledgerSubprovider: LedgerSubprovider;
+ const networkId: number = 42;
+ before(async () => {
+ ledgerSubprovider = new LedgerSubprovider({
+ networkId,
+ ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync,
+ });
+ });
+ describe('direct method calls', () => {
+ it('returns a list of accounts', async () => {
+ const accounts = await ledgerSubprovider.getAccountsAsync();
+ expect(accounts[0]).to.not.be.an('undefined');
+ expect(accounts.length).to.be.equal(10);
+ });
+ it('signs a personal message', async () => {
+ const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
+ const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data);
+ expect(ecSignatureHex.length).to.be.equal(132);
+ expect(ecSignatureHex.substr(0, 2)).to.be.equal('0x');
+ });
+ it('signs a transaction', async () => {
+ const tx = {
+ nonce: '0x00',
+ gas: '0x2710',
+ to: '0x0000000000000000000000000000000000000000',
+ value: '0x00',
+ chainId: 3,
+ };
+ const txHex = await ledgerSubprovider.signTransactionAsync(tx);
+ // tslint:disable-next-line:max-line-length
+ expect(txHex).to.be.equal('0xf85f8080822710940000000000000000000000000000000000000000808077a088a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98ba0019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa');
+ });
+ });
+ describe('calls through a provider', () => {
+ let defaultProvider: Web3ProviderEngine;
+ let ledgerProvider: Web3ProviderEngine;
+ before(() => {
+ ledgerProvider = new Web3ProviderEngine();
+ ledgerProvider.addProvider(ledgerSubprovider);
+ const httpProvider = new RpcSubprovider({
+ rpcUrl: 'http://localhost:8545',
+ });
+ ledgerProvider.addProvider(httpProvider);
+ ledgerProvider.start();
+
+ defaultProvider = new Web3ProviderEngine();
+ defaultProvider.addProvider(httpProvider);
+ defaultProvider.start();
+ });
+ it('returns a list of accounts', (done: DoneCallback) => {
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_accounts',
+ params: [],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.length).to.be.equal(10);
+ done();
+ });
+ ledgerProvider.sendAsync(payload, callback);
+ });
+ it('signs a personal message', (done: DoneCallback) => {
+ (async () => {
+ const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
+ const accounts = await ledgerSubprovider.getAccountsAsync();
+ const signer = accounts[0];
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'personal_sign',
+ params: [messageHex, signer],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.length).to.be.equal(132);
+ expect(response.result.substr(0, 2)).to.be.equal('0x');
+ done();
+ });
+ ledgerProvider.sendAsync(payload, callback);
+ })().catch(done);
+ });
+ it('signs a transaction', (done: DoneCallback) => {
+ const tx = {
+ to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
+ value: '0x00',
+ };
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_signTransaction',
+ params: [tx],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.raw.length).to.be.equal(206);
+ expect(response.result.raw.substr(0, 2)).to.be.equal('0x');
+ done();
+ });
+ ledgerProvider.sendAsync(payload, callback);
+ });
+ it('signs and sends a transaction', (done: DoneCallback) => {
+ (async () => {
+ const accounts = await ledgerSubprovider.getAccountsAsync();
+
+ // Give first account on Ledger sufficient ETH to complete tx send
+ let tx = {
+ to: accounts[0],
+ from: TEST_RPC_ACCOUNT_0,
+ value: '0x8ac7230489e80000', // 10 ETH
+ };
+ let payload = {
+ jsonrpc: '2.0',
+ method: 'eth_sendTransaction',
+ params: [tx],
+ id: 1,
+ };
+ await promisify(defaultProvider.sendAsync, defaultProvider)(payload);
+
+ // Send transaction from Ledger
+ tx = {
+ to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
+ from: accounts[0],
+ value: '0xde0b6b3a7640000',
+ };
+ payload = {
+ jsonrpc: '2.0',
+ method: 'eth_sendTransaction',
+ params: [tx],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ const result = response.result.result;
+ expect(result.length).to.be.equal(66);
+ expect(result.substr(0, 2)).to.be.equal('0x');
+ done();
+ });
+ ledgerProvider.sendAsync(payload, callback);
+ })().catch(done);
+ });
+ });
+});
diff --git a/packages/subproviders/test/unit/ledger_subprovider_test.ts b/packages/subproviders/test/unit/ledger_subprovider_test.ts
new file mode 100644
index 000000000..964df5db9
--- /dev/null
+++ b/packages/subproviders/test/unit/ledger_subprovider_test.ts
@@ -0,0 +1,212 @@
+import * as chai from 'chai';
+import * as ethUtils from 'ethereumjs-util';
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import Web3ProviderEngine = require('web3-provider-engine');
+import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
+
+import {
+ ECSignature,
+ LedgerSubprovider,
+} from '../../src';
+import {
+ DoneCallback,
+ ECSignatureString,
+ LedgerCommunicationClient,
+ LedgerGetAddressResult,
+ LedgerSubproviderErrors,
+} from '../../src/types';
+import {chaiSetup} from '../chai_setup';
+import {reportCallbackErrors} from '../utils/report_callback_errors';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const FAKE_ADDRESS = '0x9901c66f2d4b95f7074b553da78084d708beca70';
+
+describe('LedgerSubprovider', () => {
+ const networkId: number = 42;
+ let ledgerSubprovider: LedgerSubprovider;
+ before(async () => {
+ const ledgerEthereumClientFactoryAsync = async () => {
+ // tslint:disable:no-object-literal-type-assertion
+ const ledgerEthClient = {
+ getAddress_async: async () => {
+ return {
+ address: FAKE_ADDRESS,
+ };
+ },
+ signPersonalMessage_async: async () => {
+ const ecSignature = {
+ v: 28,
+ r: 'a6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae49148',
+ s: '0652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d0',
+ };
+ return ecSignature;
+ },
+ signTransaction_async: async (derivationPath: string, txHex: string) => {
+ const ecSignature = {
+ v: '77',
+ r: '88a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98b',
+ s: '019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa',
+ };
+ return ecSignature;
+ },
+ comm: {
+ close_async: _.noop,
+ } as LedgerCommunicationClient,
+ };
+ // tslint:enable:no-object-literal-type-assertion
+ return ledgerEthClient;
+ };
+ ledgerSubprovider = new LedgerSubprovider({
+ networkId,
+ ledgerEthereumClientFactoryAsync,
+ });
+ });
+ describe('direct method calls', () => {
+ describe('success cases', () => {
+ it('returns a list of accounts', async () => {
+ const accounts = await ledgerSubprovider.getAccountsAsync();
+ expect(accounts[0]).to.be.equal(FAKE_ADDRESS);
+ expect(accounts.length).to.be.equal(10);
+ });
+ it('signs a personal message', async () => {
+ const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
+ const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data);
+ // tslint:disable-next-line:max-line-length
+ expect(ecSignatureHex).to.be.equal('0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001');
+ });
+ });
+ describe('failure cases', () => {
+ it('cannot open multiple simultaneous connections to the Ledger device', async () => {
+ const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
+ return expect(Promise.all([
+ ledgerSubprovider.getAccountsAsync(),
+ ledgerSubprovider.signPersonalMessageAsync(data),
+ ])).to.be.rejectedWith(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed);
+ });
+ });
+ });
+ describe('calls through a provider', () => {
+ let provider: Web3ProviderEngine;
+ before(() => {
+ provider = new Web3ProviderEngine();
+ provider.addProvider(ledgerSubprovider);
+ const httpProvider = new RpcSubprovider({
+ rpcUrl: 'http://localhost:8545',
+ });
+ provider.addProvider(httpProvider);
+ provider.start();
+ });
+ describe('success cases', () => {
+ it('returns a list of accounts', (done: DoneCallback) => {
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_accounts',
+ params: [],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.length).to.be.equal(10);
+ expect(response.result[0]).to.be.equal(FAKE_ADDRESS);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ it('signs a personal message', (done: DoneCallback) => {
+ const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'personal_sign',
+ params: [messageHex, '0x0000000000000000000000000000000000000000'],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ // tslint:disable-next-line:max-line-length
+ expect(response.result).to.be.equal('0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001');
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ it('signs a transaction', (done: DoneCallback) => {
+ const tx = {
+ to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
+ value: '0x00',
+ gasPrice: '0x00',
+ nonce: '0x00',
+ gas: '0x00',
+ };
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_signTransaction',
+ params: [tx],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.raw.length).to.be.equal(192);
+ expect(response.result.raw.substr(0, 2)).to.be.equal('0x');
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ });
+ describe('failure cases', () => {
+ it('should throw if `data` param not hex when calling personal_sign', (done: DoneCallback) => {
+ const nonHexMessage = 'hello world';
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'personal_sign',
+ params: [nonHexMessage, '0x0000000000000000000000000000000000000000'],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.not.be.a('null');
+ expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world');
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => {
+ const tx = {
+ to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
+ value: '0xde0b6b3a7640000',
+ };
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_sendTransaction',
+ params: [tx],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.not.be.a('null');
+ expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ it('should throw if `from` param invalid address when calling eth_sendTransaction',
+ (done: DoneCallback) => {
+ const tx = {
+ to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
+ from: '0xIncorrectEthereumAddress',
+ value: '0xde0b6b3a7640000',
+ };
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_sendTransaction',
+ params: [tx],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.not.be.a('null');
+ expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ });
+ });
+});
diff --git a/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts b/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts
new file mode 100644
index 000000000..edeb1d5a2
--- /dev/null
+++ b/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts
@@ -0,0 +1,62 @@
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import Web3 = require('web3');
+import Web3ProviderEngine = require('web3-provider-engine');
+
+import {RedundantRPCSubprovider} from '../../src';
+import {
+ DoneCallback,
+} from '../../src/types';
+import {chaiSetup} from '../chai_setup';
+import {reportCallbackErrors} from '../utils/report_callback_errors';
+
+const expect = chai.expect;
+
+describe('RedundantRpcSubprovider', () => {
+ let provider: Web3ProviderEngine;
+ it('succeeds when supplied a healthy endpoint', (done: DoneCallback) => {
+ provider = new Web3ProviderEngine();
+ const endpoints = [
+ 'http://localhost:8545',
+ ];
+ const redundantSubprovider = new RedundantRPCSubprovider(endpoints);
+ provider.addProvider(redundantSubprovider);
+ provider.start();
+
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_accounts',
+ params: [],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.length).to.be.equal(10);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+ it('succeeds when supplied at least one healthy endpoint', (done: DoneCallback) => {
+ provider = new Web3ProviderEngine();
+ const endpoints = [
+ 'http://does-not-exist:3000',
+ 'http://localhost:8545',
+ ];
+ const redundantSubprovider = new RedundantRPCSubprovider(endpoints);
+ provider.addProvider(redundantSubprovider);
+ provider.start();
+
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_accounts',
+ params: [],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result.length).to.be.equal(10);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
+});
diff --git a/packages/subproviders/test/utils/report_callback_errors.ts b/packages/subproviders/test/utils/report_callback_errors.ts
new file mode 100644
index 000000000..8a8f4d966
--- /dev/null
+++ b/packages/subproviders/test/utils/report_callback_errors.ts
@@ -0,0 +1,14 @@
+import { DoneCallback } from '../../src/types';
+
+export const reportCallbackErrors = (done: DoneCallback) => {
+ return (f: (...args: any[]) => void) => {
+ const wrapped = async (...args: any[]) => {
+ try {
+ f(...args);
+ } catch (err) {
+ done(err);
+ }
+ };
+ return wrapped;
+ };
+};
diff --git a/packages/subproviders/tsconfig.json b/packages/subproviders/tsconfig.json
new file mode 100644
index 000000000..24adf4637
--- /dev/null
+++ b/packages/subproviders/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "lib": [ "es2015", "dom" ],
+ "outDir": "lib",
+ "sourceMap": true,
+ "declaration": true,
+ "noImplicitAny": true,
+ "experimentalDecorators": true,
+ "strictNullChecks": true
+ },
+ "include": [
+ "./src/**/*",
+ "./test/**/*",
+ "../../node_modules/web3-typescript-typings/index.d.ts",
+ "../../node_modules/chai-typescript-typings/index.d.ts",
+ "../../node_modules/types-bn/index.d.ts",
+ "../../node_modules/types-ethereumjs-util/index.d.ts",
+ "../../node_modules/chai-as-promised-typescript-typings/index.d.ts"
+ ]
+}
diff --git a/packages/subproviders/tslint.json b/packages/subproviders/tslint.json
new file mode 100644
index 000000000..a07795151
--- /dev/null
+++ b/packages/subproviders/tslint.json
@@ -0,0 +1,5 @@
+{
+ "extends": [
+ "@0xproject/tslint-config"
+ ]
+}
diff --git a/packages/website/package.json b/packages/website/package.json
index fdc5de5d7..9234ad601 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -18,6 +18,7 @@
"author": "Fabio Berger",
"license": "Apache-2.0",
"dependencies": {
+ "@0xproject/subproviders": "0.0.1",
"0x.js": "0xproject/0x.js/packages/0x.js#0x.js@0.27.1",
"accounting": "^0.4.1",
"basscss": "^8.0.3",
@@ -61,7 +62,6 @@
"thenby": "^1.2.3",
"truffle-contract": "2.0.1",
"tslint-config-0xproject": "^0.0.2",
- "typescript": "^2.4.1",
"web3": "^0.20.0",
"web3-provider-engine": "^13.0.1",
"whatwg-fetch": "^2.0.3",
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 24205802d..76640a072 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -16,6 +16,13 @@ import {
ZeroEx,
ZeroExError,
} from '0x.js';
+import {
+ InjectedWeb3Subprovider,
+ ledgerEthereumBrowserClientFactoryAsync,
+ LedgerSubprovider,
+ LedgerWalletSubprovider,
+ RedundantRPCSubprovider,
+} from '@0xproject/subproviders';
import {promisify} from '@0xproject/utils';
import BigNumber from 'bignumber.js';
import compareVersions = require('compare-versions');
@@ -25,20 +32,16 @@ import * as _ from 'lodash';
import * as React from 'react';
import contract = require('truffle-contract');
import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed';
-import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
+import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted';
import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage';
import {Dispatcher} from 'ts/redux/dispatcher';
-import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider';
-import {ledgerWalletSubproviderFactory} from 'ts/subproviders/ledger_wallet_subprovider_factory';
-import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider';
import {
BlockchainCallErrs,
BlockchainErrs,
ContractInstance,
ContractResponse,
EtherscanLinkSuffixes,
- LedgerWalletSubprovider,
ProviderType,
Side,
SignatureData,
@@ -71,7 +74,7 @@ export class Blockchain {
private tokenRegistry: ContractInstance;
private userAddress: string;
private cachedProvider: Web3.Provider;
- private ledgerSubProvider: LedgerWalletSubprovider;
+ private ledgerSubprovider: LedgerWalletSubprovider;
private zrxPollIntervalId: number;
private static async onPageLoadAsync() {
if (document.readyState === 'complete') {
@@ -105,7 +108,7 @@ export class Blockchain {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
provider = new ProviderEngine();
- provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3));
+ provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3));
provider.addProvider(new FilterSubprovider());
provider.addProvider(new RedundantRPCSubprovider(
publicNodeUrlsIfExistsForNetworkId,
@@ -168,23 +171,23 @@ export class Blockchain {
return !_.isUndefined(tokenIfExists);
}
public getLedgerDerivationPathIfExists(): string {
- if (_.isUndefined(this.ledgerSubProvider)) {
+ if (_.isUndefined(this.ledgerSubprovider)) {
return undefined;
}
- const path = this.ledgerSubProvider.getPath();
+ const path = this.ledgerSubprovider.getPath();
return path;
}
public updateLedgerDerivationPathIfExists(path: string) {
- if (_.isUndefined(this.ledgerSubProvider)) {
+ if (_.isUndefined(this.ledgerSubprovider)) {
return; // noop
}
- this.ledgerSubProvider.setPath(path);
+ this.ledgerSubprovider.setPath(path);
}
public updateLedgerDerivationIndex(pathIndex: number) {
- if (_.isUndefined(this.ledgerSubProvider)) {
+ if (_.isUndefined(this.ledgerSubprovider)) {
return; // noop
}
- this.ledgerSubProvider.setPathIndex(pathIndex);
+ this.ledgerSubprovider.setPathIndex(pathIndex);
}
public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) {
utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
@@ -204,8 +207,12 @@ export class Blockchain {
this.dispatcher.updateUserAddress(''); // Clear old userAddress
provider = new ProviderEngine();
- this.ledgerSubProvider = ledgerWalletSubproviderFactory(this.getBlockchainNetworkId.bind(this));
- provider.addProvider(this.ledgerSubProvider);
+ const ledgerWalletConfigs = {
+ networkId: this.networkId,
+ ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
+ };
+ this.ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
+ provider.addProvider(this.ledgerSubprovider);
provider.addProvider(new FilterSubprovider());
const networkId = configs.isMainnetEnabled ?
constants.MAINNET_NETWORK_ID :
@@ -231,7 +238,7 @@ export class Blockchain {
this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress);
this.zeroEx.setProvider(provider, this.networkId);
await this.postInstantiationOrUpdatingProviderZeroExAsync();
- delete this.ledgerSubProvider;
+ delete this.ledgerSubprovider;
delete this.cachedProvider;
break;
}
@@ -657,11 +664,6 @@ export class Blockchain {
constants.PUBLIC_PROVIDER_NAME;
this.dispatcher.updateInjectedProviderName(providerName);
}
- // This is only ever called by the LedgerWallet subprovider in order to retrieve
- // the current networkId without this value going stale.
- private getBlockchainNetworkId() {
- return this.networkId;
- }
private async fetchTokenInformationAsync() {
utils.assert(!_.isUndefined(this.networkId),
'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node');
diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts
index 7bbbb3a98..b4611f583 100644
--- a/packages/website/ts/globals.d.ts
+++ b/packages/website/ts/globals.d.ts
@@ -3,7 +3,6 @@ declare module 'react-router-hash-link';
declare module 'truffle-contract';
declare module 'ethereumjs-util';
declare module 'keccak';
-declare module 'web3-provider-engine';
declare module 'whatwg-fetch';
declare module 'react-html5video';
declare module 'web3-provider-engine/subproviders/filters';
@@ -21,6 +20,8 @@ declare module '*.json' {
/* tslint:enable */
}
+// tslint:disable:max-classes-per-file
+
// find-version declarations
declare function findVersions(version: string): string[];
declare module 'find-versions' {
@@ -131,21 +132,26 @@ declare class Subprovider {}
declare module 'web3-provider-engine/subproviders/subprovider' {
export = Subprovider;
}
-
-// tslint:disable-next-line:max-classes-per-file
-declare class RpcSubprovider {
- constructor(options: {rpcUrl: string});
- public handleRequest(payload: any, next: any, end: (err?: Error, data?: any) => void): void;
-}
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;
}
-// tslint:disable-next-line:max-classes-per-file
-declare class HookedWalletSubprovider {
- constructor(wallet: any);
-}
-declare module 'web3-provider-engine/subproviders/hooked-wallet' {
- export = HookedWalletSubprovider;
+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;
}
declare interface Artifact {
diff --git a/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts
deleted file mode 100644
index bfabc90ae..000000000
--- a/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import * as EthereumTx from 'ethereumjs-tx';
-import ethUtil = require('ethereumjs-util');
-import * as ledger from 'ledgerco';
-import * as _ from 'lodash';
-import {LedgerEthConnection, SignPersonalMessageParams, TxParams} from 'ts/types';
-import {constants} from 'ts/utils/constants';
-import Web3 = require('web3');
-import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet');
-
-const NUM_ADDRESSES_TO_FETCH = 10;
-const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
-const SHOULD_GET_CHAIN_CODE = false;
-
-export class LedgerWallet {
- public isU2FSupported: boolean;
- public getAccounts: (callback: (err: Error, accounts: string[]) => void) => void;
- public signMessage: (msgParams: SignPersonalMessageParams,
- callback: (err: Error, result?: string) => void) => void;
- public signTransaction: (txParams: TxParams,
- callback: (err: Error, result?: string) => void) => void;
- private getNetworkId: () => number;
- private path: string;
- private pathIndex: number;
- private ledgerEthConnection: LedgerEthConnection;
- private accounts: string[];
- constructor(getNetworkIdFn: () => number) {
- this.path = constants.DEFAULT_DERIVATION_PATH;
- this.pathIndex = 0;
- this.isU2FSupported = false;
- this.getNetworkId = getNetworkIdFn;
- this.getAccounts = this.getAccountsAsync.bind(this);
- this.signMessage = this.signPersonalMessageAsync.bind(this);
- this.signTransaction = this.signTransactionAsync.bind(this);
- }
- public getPath(): string {
- return this.path;
- }
- public setPath(derivationPath: string) {
- this.path = derivationPath;
- // HACK: Must re-assign getAccounts, signMessage and signTransaction since they were
- // previously bound to old values of this.path
- this.getAccounts = this.getAccountsAsync.bind(this);
- this.signMessage = this.signPersonalMessageAsync.bind(this);
- this.signTransaction = this.signTransactionAsync.bind(this);
- }
- public setPathIndex(pathIndex: number) {
- this.pathIndex = pathIndex;
- // HACK: Must re-assign signMessage & signTransaction since they it was previously bound to
- // old values of this.path
- this.signMessage = this.signPersonalMessageAsync.bind(this);
- this.signTransaction = this.signTransactionAsync.bind(this);
- }
- public async getAccountsAsync(callback: (err: Error, accounts: string[]) => void) {
- if (!_.isUndefined(this.ledgerEthConnection)) {
- callback(null, []);
- return;
- }
- this.ledgerEthConnection = await this.createLedgerConnectionAsync();
-
- const accounts = [];
- for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) {
- try {
- const derivationPath = `${this.path}/${i}`;
- const result = await this.ledgerEthConnection.getAddress_async(
- derivationPath, ASK_FOR_ON_DEVICE_CONFIRMATION, SHOULD_GET_CHAIN_CODE,
- );
- accounts.push(result.address.toLowerCase());
- } catch (err) {
- await this.closeLedgerConnectionAsync();
- callback(err, null);
- return;
- }
- }
-
- await this.closeLedgerConnectionAsync();
- callback(null, accounts);
- }
- public async signTransactionAsync(txParams: TxParams, callback: (err: Error, result?: string) => void) {
- const tx = new EthereumTx(txParams);
-
- const networkId = this.getNetworkId();
- const chainId = networkId; // Same thing
-
- // Set the EIP155 bits
- tx.raw[6] = Buffer.from([chainId]); // v
- tx.raw[7] = Buffer.from([]); // r
- tx.raw[8] = Buffer.from([]); // s
-
- const txHex = tx.serialize().toString('hex');
-
- this.ledgerEthConnection = await this.createLedgerConnectionAsync();
-
- try {
- const derivationPath = this.getDerivationPath();
- const result = await this.ledgerEthConnection.signTransaction_async(derivationPath, txHex);
-
- // Store signature in transaction
- tx.v = new Buffer(result.v, 'hex');
- tx.r = new Buffer(result.r, 'hex');
- tx.s = new Buffer(result.s, 'hex');
-
- // EIP155: v should be chain_id * 2 + {35, 36}
- const signedChainId = Math.floor((tx.v[0] - 35) / 2);
- if (signedChainId !== chainId) {
- const err = new Error('TOO_OLD_LEDGER_FIRMWARE');
- callback(err, null);
- return;
- }
-
- const signedTxHex = `0x${tx.serialize().toString('hex')}`;
- await this.closeLedgerConnectionAsync();
- callback(null, signedTxHex);
- } catch (err) {
- await this.closeLedgerConnectionAsync();
- callback(err, null);
- }
- }
- public async signPersonalMessageAsync(msgParams: SignPersonalMessageParams,
- callback: (err: Error, result?: string) => void) {
- if (!_.isUndefined(this.ledgerEthConnection)) {
- callback(new Error('Another request is in progress.'));
- return;
- }
- this.ledgerEthConnection = await this.createLedgerConnectionAsync();
-
- try {
- const derivationPath = this.getDerivationPath();
- const result = await this.ledgerEthConnection.signPersonalMessage_async(
- derivationPath, ethUtil.stripHexPrefix(msgParams.data),
- );
- const v = _.parseInt(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.closeLedgerConnectionAsync();
- callback(null, signature);
- } catch (err) {
- await this.closeLedgerConnectionAsync();
- callback(err, null);
- }
- }
- private async createLedgerConnectionAsync() {
- if (!_.isUndefined(this.ledgerEthConnection)) {
- throw new Error('Multiple open connections to the Ledger disallowed.');
- }
- const ledgerConnection = await ledger.comm_u2f.create_async();
- const ledgerEthConnection = new ledger.eth(ledgerConnection);
- return ledgerEthConnection;
- }
- private async closeLedgerConnectionAsync() {
- if (_.isUndefined(this.ledgerEthConnection)) {
- return;
- }
- await this.ledgerEthConnection.comm.close_async();
- this.ledgerEthConnection = undefined;
- }
- private getDerivationPath() {
- const derivationPath = `${this.path}/${this.pathIndex}`;
- return derivationPath;
- }
-}
-
-export const ledgerWalletSubproviderFactory = (getNetworkIdFn: () => number): LedgerWallet => {
- const ledgerWallet = new LedgerWallet(getNetworkIdFn);
- const ledgerWalletSubprovider = new HookedWalletSubprovider(ledgerWallet) as LedgerWallet;
- ledgerWalletSubprovider.getPath = ledgerWallet.getPath.bind(ledgerWallet);
- ledgerWalletSubprovider.setPath = ledgerWallet.setPath.bind(ledgerWallet);
- ledgerWalletSubprovider.setPathIndex = ledgerWallet.setPathIndex.bind(ledgerWallet);
- return ledgerWalletSubprovider;
-};
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index d2c690ce1..d225e7784 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -521,12 +521,6 @@ export interface SignPersonalMessageParams {
data: string;
}
-export interface LedgerWalletSubprovider {
- getPath: () => string;
- setPath: (path: string) => void;
- setPathIndex: (pathIndex: number) => void;
-}
-
export interface TxParams {
nonce: string;
gasPrice?: number;
diff --git a/yarn.lock b/yarn.lock
index 7adc2837b..245b67b33 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2183,10 +2183,14 @@ conventional-recommended-bump@^1.0.1:
meow "^3.3.0"
object-assign "^4.0.1"
-convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.5.0:
+convert-source-map@^1.1.0, convert-source-map@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
+convert-source-map@^1.3.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -4737,7 +4741,7 @@ lcov-parse@^0.0.10:
ledgerco@0xProject/ledger-node-js-api:
version "1.1.3"
- resolved "https://codeload.github.com/0xProject/ledger-node-js-api/tar.gz/dc2024bac997bf023f12203f118d10ba84d15ded"
+ resolved "https://codeload.github.com/0xProject/ledger-node-js-api/tar.gz/24aed21b8b362f2afc86faa578f38955ae2319ba"
dependencies:
async "2.1.4"
node-hid "0.5.4"
@@ -5443,11 +5447,11 @@ mute-stream@0.0.7, mute-stream@~0.0.4:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-nan@^2.0.5, nan@^2.0.8, nan@^2.2.1, nan@^2.3.0, nan@^2.3.3:
+nan@^2.0.5, nan@^2.2.1, nan@^2.3.0, nan@^2.3.3:
version "2.7.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
-nan@^2.4.0:
+nan@^2.0.8, nan@^2.4.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
@@ -7298,6 +7302,10 @@ selfsigned@^1.9.1:
dependencies:
node-forge "0.6.33"
+semaphore-async-await@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa"
+
semaphore@>=1.0.1, semaphore@^1.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa"
@@ -8385,8 +8393,16 @@ types-bn@^0.0.1:
bn.js "4.11.7"
types-ethereumjs-util@0xProject/types-ethereumjs-util:
- version "0.0.5"
- resolved "https://codeload.github.com/0xProject/types-ethereumjs-util/tar.gz/b9ae55d2c2711d89f63f7fc53a78579f2d4fbd74"
+ version "0.0.6"
+ resolved "https://codeload.github.com/0xProject/types-ethereumjs-util/tar.gz/a3b236df39d9fbfcb3b832a1fea7110649eeb616"
+ dependencies:
+ bn.js "^4.11.7"
+ buffer "^5.0.6"
+ rlp "^2.0.0"
+
+types-ethereumjs-util@0xproject/types-ethereumjs-util:
+ version "0.0.6"
+ resolved "https://codeload.github.com/0xproject/types-ethereumjs-util/tar.gz/a3b236df39d9fbfcb3b832a1fea7110649eeb616"
dependencies:
bn.js "^4.11.7"
buffer "^5.0.6"
@@ -8396,7 +8412,7 @@ typescript@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
-typescript@^2.4.1, typescript@~2.6.1:
+typescript@~2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631"