Port subproviders over to mono repo, refactor LedgerSubprovider to no longer rely on hookedWalletSubprovider. Added unit and integration tests.
This commit is contained in:
		
							
								
								
									
										39
									
								
								packages/subproviders/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								packages/subproviders/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -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 Ethereum app
 | 
			
		||||
- Make sure "browser support" is disabled
 | 
			
		||||
 | 
			
		||||
Then run:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
yarn test:integration
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										60
									
								
								packages/subproviders/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								packages/subproviders/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@0xproject/subproviders",
 | 
			
		||||
  "version": "0.0.0",
 | 
			
		||||
  "main": "lib/src/index.js",
 | 
			
		||||
  "types": "lib/src/index.d.ts",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "prebuild": "npm run clean",
 | 
			
		||||
    "build": "run-p build:umd:dev build:commonjs; exit 0;",
 | 
			
		||||
    "clean": "shx rm -rf lib",
 | 
			
		||||
    "build:umd:dev": "webpack",
 | 
			
		||||
    "build:umd:prod": "NODE_ENV=production webpack",
 | 
			
		||||
    "build:commonjs": "tsc",
 | 
			
		||||
    "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
 | 
			
		||||
    "pretest:umd": "run-s clean build:umd:dev build:commonjs",
 | 
			
		||||
    "substitute_umd_bundle": "shx mv _bundles/* lib/src",
 | 
			
		||||
    "run_mocha_all": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit",
 | 
			
		||||
    "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": "run-s clean build:commonjs run_mocha_all",
 | 
			
		||||
    "test:unit": "run-s clean build:commonjs run_mocha_unit",
 | 
			
		||||
    "test:integration": "run-s clean build:commonjs 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",
 | 
			
		||||
    "sinon": "^4.0.0",
 | 
			
		||||
    "web3": "^0.20.0",
 | 
			
		||||
    "web3-provider-engine": "^13.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@0xproject/tslint-config": "^0.2.0",
 | 
			
		||||
    "@types/lodash": "^4.14.64",
 | 
			
		||||
    "@types/mocha": "^2.2.44",
 | 
			
		||||
    "@types/node": "^8.0.1",
 | 
			
		||||
    "@types/sinon": "^2.2.2",
 | 
			
		||||
    "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.0",
 | 
			
		||||
    "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"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								packages/subproviders/src/globals.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								packages/subproviders/src/globals.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										30
									
								
								packages/subproviders/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/subproviders/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								packages/subproviders/src/subproviders/injected_web3.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/subproviders/src/subproviders/injected_web3.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										320
									
								
								packages/subproviders/src/subproviders/ledger.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								packages/subproviders/src/subproviders/ledger.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								packages/subproviders/src/subproviders/redundant_rpc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								packages/subproviders/src/subproviders/redundant_rpc.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								packages/subproviders/src/subproviders/subprovider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								packages/subproviders/src/subproviders/subprovider.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								packages/subproviders/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/subproviders/src/types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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',
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/subproviders/test/chai_setup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/subproviders/test/chai_setup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,171 @@
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										229
									
								
								packages/subproviders/test/unit/ledger_subprovider_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								packages/subproviders/test/unit/ledger_subprovider_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,229 @@
 | 
			
		||||
import * as chai from 'chai';
 | 
			
		||||
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,
 | 
			
		||||
    LedgerSubprovider,
 | 
			
		||||
} from '../../src';
 | 
			
		||||
import {
 | 
			
		||||
    DoneCallback,
 | 
			
		||||
    ECSignatureString,
 | 
			
		||||
    LedgerCommunicationClient,
 | 
			
		||||
    LedgerGetAddressResult,
 | 
			
		||||
    LedgerSubproviderErrors,
 | 
			
		||||
} from '../../src/types';
 | 
			
		||||
import { chaiSetup } from '../chai_setup';
 | 
			
		||||
import {reportCallbackErrors} from '../utils/report_callback_errors';
 | 
			
		||||
 | 
			
		||||
const expect = chai.expect;
 | 
			
		||||
const FAKE_ADDRESS = '0x9901c66f2d4b95f7074b553da78084d708beca70';
 | 
			
		||||
 | 
			
		||||
describe('LedgerSubprovider', () => {
 | 
			
		||||
    const networkId: number = 42;
 | 
			
		||||
    let ledgerSubprovider: LedgerSubprovider;
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        const ledgerEthereumClientFactoryAsync = async () => {
 | 
			
		||||
            const ledgerEthClient = {
 | 
			
		||||
                getAddress_async: async () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        address: FAKE_ADDRESS,
 | 
			
		||||
                    };
 | 
			
		||||
                },
 | 
			
		||||
                signPersonalMessage_async: async () => {
 | 
			
		||||
                    const ecSignature: ECSignature = {
 | 
			
		||||
                        v: 28,
 | 
			
		||||
                        r: 'a6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae49148',
 | 
			
		||||
                        s: '0652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d0',
 | 
			
		||||
                    };
 | 
			
		||||
                    return ecSignature;
 | 
			
		||||
                },
 | 
			
		||||
                signTransaction_async: async (derivationPath: string, txHex: string) => {
 | 
			
		||||
                    const ecSignature: ECSignatureString = {
 | 
			
		||||
                        v: '77',
 | 
			
		||||
                        r: '88a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98b',
 | 
			
		||||
                        s: '019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa',
 | 
			
		||||
                    };
 | 
			
		||||
                    return ecSignature;
 | 
			
		||||
                },
 | 
			
		||||
                comm: {
 | 
			
		||||
                    close_async: _.noop,
 | 
			
		||||
                } as LedgerCommunicationClient,
 | 
			
		||||
            };
 | 
			
		||||
            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 msgParams = {data};
 | 
			
		||||
                const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(msgParams);
 | 
			
		||||
                // 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'));
 | 
			
		||||
                const msgParams = {data};
 | 
			
		||||
                try {
 | 
			
		||||
                    const result = await Promise.all([
 | 
			
		||||
                        ledgerSubprovider.getAccountsAsync(),
 | 
			
		||||
                        ledgerSubprovider.signPersonalMessageAsync(msgParams),
 | 
			
		||||
                    ]);
 | 
			
		||||
                    throw new Error('Multiple simultaneous calls succeeded when they should have failed');
 | 
			
		||||
                } catch (err) {
 | 
			
		||||
                    expect(err.message).to.be.equal(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',
 | 
			
		||||
                };
 | 
			
		||||
                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();
 | 
			
		||||
                });
 | 
			
		||||
                provider.sendAsync(payload, callback);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        describe('failure cases', () => {
 | 
			
		||||
            it('should throw if `from` param missing when calling personal_sign', (done: DoneCallback) => {
 | 
			
		||||
                const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world'));
 | 
			
		||||
                const payload = {
 | 
			
		||||
                    jsonrpc: '2.0',
 | 
			
		||||
                    method: 'personal_sign',
 | 
			
		||||
                    params: [messageHex], // Missing from param
 | 
			
		||||
                    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.FromAddressMissingOrInvalid);
 | 
			
		||||
                    done();
 | 
			
		||||
                });
 | 
			
		||||
                provider.sendAsync(payload, callback);
 | 
			
		||||
            });
 | 
			
		||||
            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(LedgerSubproviderErrors.DataNotValidHexForSignPersonalMessage);
 | 
			
		||||
                    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);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/subproviders/test/utils/report_callback_errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/subproviders/test/utils/report_callback_errors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										22
									
								
								packages/subproviders/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/subproviders/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/subproviders/tslint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/subproviders/tslint.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": [
 | 
			
		||||
    "@0xproject/tslint-config"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								packages/subproviders/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								packages/subproviders/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This is to generate the umd bundle only
 | 
			
		||||
 */
 | 
			
		||||
const _ = require('lodash');
 | 
			
		||||
const webpack = require('webpack');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const production = process.env.NODE_ENV === 'production';
 | 
			
		||||
 | 
			
		||||
let entry = {
 | 
			
		||||
    'index': './src/index.ts',
 | 
			
		||||
};
 | 
			
		||||
if (production) {
 | 
			
		||||
    entry = _.assign({}, entry, {'index.min': './src/index.ts'});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    entry,
 | 
			
		||||
    output: {
 | 
			
		||||
        path: path.resolve(__dirname, '_bundles'),
 | 
			
		||||
        filename: '[name].js',
 | 
			
		||||
        libraryTarget: 'umd',
 | 
			
		||||
        library: '0x Subproviders',
 | 
			
		||||
        umdNamedDefine: true,
 | 
			
		||||
    },
 | 
			
		||||
    resolve: {
 | 
			
		||||
        extensions: ['.ts', '.js', '.json'],
 | 
			
		||||
    },
 | 
			
		||||
    devtool: 'source-map',
 | 
			
		||||
    plugins: [
 | 
			
		||||
        new webpack.optimize.UglifyJsPlugin({
 | 
			
		||||
            minimize: true,
 | 
			
		||||
            sourceMap: true,
 | 
			
		||||
            include: /\.min\.js$/,
 | 
			
		||||
        }),
 | 
			
		||||
    ],
 | 
			
		||||
    module: {
 | 
			
		||||
        rules: [
 | 
			
		||||
            {
 | 
			
		||||
                test: /\.ts$/,
 | 
			
		||||
                use: [
 | 
			
		||||
                    {
 | 
			
		||||
                        loader: 'awesome-typescript-loader',
 | 
			
		||||
                        query: {
 | 
			
		||||
                            declaration: false,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                exclude: /node_modules/,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                test: /\.json$/,
 | 
			
		||||
                loader: 'json-loader',
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										14
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -1285,7 +1285,7 @@ bn.js@4.11.7:
 | 
			
		||||
  version "4.11.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
 | 
			
		||||
 | 
			
		||||
bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.3, bn.js@^4.11.7, bn.js@^4.4.0, bn.js@^4.8.0:
 | 
			
		||||
bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.3, bn.js@^4.11.7, bn.js@^4.11.8, bn.js@^4.4.0, bn.js@^4.8.0:
 | 
			
		||||
  version "4.11.8"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
 | 
			
		||||
 | 
			
		||||
@@ -8066,8 +8066,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"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user