aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/subproviders
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
committerFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
commit3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b (patch)
treef101656799da807489253e17bea7abfaea90b62d /packages/website/ts/subproviders
parent037f466e1f80f635b48f3235258402e2ce75fb7b (diff)
downloaddexon-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')
-rw-r--r--packages/website/ts/subproviders/injected_web3_subprovider.ts44
-rw-r--r--packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts172
-rw-r--r--packages/website/ts/subproviders/redundant_rpc_subprovider.ts41
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);
+ }
+}