diff options
author | Fabio Berger <me@fabioberger.com> | 2017-11-22 04:03:08 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2017-11-22 04:03:08 +0800 |
commit | 3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b (patch) | |
tree | f101656799da807489253e17bea7abfaea90b62d /packages/website/ts/subproviders | |
parent | 037f466e1f80f635b48f3235258402e2ce75fb7b (diff) | |
download | dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.gz dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.bz2 dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.lz dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.xz dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.zst dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.zip |
Add website to mono repo, update packages to align with existing sub-packages, use new subscribeAsync 0x.js method
Diffstat (limited to 'packages/website/ts/subproviders')
3 files changed, 257 insertions, 0 deletions
diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/website/ts/subproviders/injected_web3_subprovider.ts new file mode 100644 index 000000000..b9e5af3ef --- /dev/null +++ b/packages/website/ts/subproviders/injected_web3_subprovider.ts @@ -0,0 +1,44 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import {constants} from 'ts/utils/constants'; + +/* + * 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 + 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 new file mode 100644 index 000000000..df0c5a4db --- /dev/null +++ b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts @@ -0,0 +1,172 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import * as EthereumTx from 'ethereumjs-tx'; +import ethUtil = require('ethereumjs-util'); +import * as ledger from 'ledgerco'; +import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet'); +import {constants} from 'ts/utils/constants'; +import {LedgerEthConnection, SignPersonalMessageParams, TxParams} from 'ts/types'; + +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 new file mode 100644 index 000000000..a6c53ebd1 --- /dev/null +++ b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts @@ -0,0 +1,41 @@ +import * as _ from 'lodash'; +import {JSONRPCPayload} from 'ts/types'; +import promisify = require('es6-promisify'); +import Subprovider = require('web3-provider-engine/subproviders/subprovider'); +import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); + +export class RedundantRPCSubprovider extends Subprovider { + private rpcs: RpcSubprovider[]; + 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 this.firstSuccessAsync(rpcsCopy, payload, next); + end(null, data); + } catch (err) { + end(err); + } + + } + private 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); + } +} |