diff options
-rw-r--r-- | packages/website/package.json | 1 | ||||
-rw-r--r-- | packages/website/ts/blockchain.ts | 44 | ||||
-rw-r--r-- | packages/website/ts/globals.d.ts | 34 | ||||
-rw-r--r-- | packages/website/ts/subproviders/injected_web3_subprovider.ts | 45 | ||||
-rw-r--r-- | packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts | 172 | ||||
-rw-r--r-- | packages/website/ts/subproviders/redundant_rpc_subprovider.ts | 43 | ||||
-rw-r--r-- | packages/website/ts/types.ts | 6 |
7 files changed, 45 insertions, 300 deletions
diff --git a/packages/website/package.json b/packages/website/package.json index 72cbea0e6..a1990d958 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.0", "0x.js": "0xproject/0x.js/packages/0x.js#0x.js@0.27.1", "accounting": "^0.4.1", "basscss": "^8.0.3", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index e71f61ead..c32984477 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 BigNumber from 'bignumber.js'; import compareVersions = require('compare-versions'); import promisify = require('es6-promisify'); @@ -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; } @@ -652,11 +659,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 c5b94dc45..46897b429 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -4,7 +4,6 @@ declare module 'es6-promisify'; 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'; @@ -22,6 +21,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' { @@ -132,21 +133,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 { @@ -157,3 +163,5 @@ declare interface Artifact { }; }; } + +// tslint:enable:max-classes-per-file diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/website/ts/subproviders/injected_web3_subprovider.ts deleted file mode 100644 index 910fe3cdf..000000000 --- a/packages/website/ts/subproviders/injected_web3_subprovider.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as _ from 'lodash'; -import {constants} from 'ts/utils/constants'; -import Web3 = require('web3'); - -/* - * 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: any, 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: any) { - // noop - } -} 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/subproviders/redundant_rpc_subprovider.ts b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts deleted file mode 100644 index 8dffd4437..000000000 --- a/packages/website/ts/subproviders/redundant_rpc_subprovider.ts +++ /dev/null @@ -1,43 +0,0 @@ -import promisify = require('es6-promisify'); -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'); - -export class RedundantRPCSubprovider extends Subprovider { - private rpcs: RpcSubprovider[]; - private static async firstSuccessAsync( - rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void, - ): Promise<any> { - let lastErr; - for (const rpc of rpcs) { - try { - const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); - return data; - } catch (err) { - lastErr = err; - continue; - } - } - throw Error(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(null, data); - } catch (err) { - end(err); - } - - } -} 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; |