diff options
Diffstat (limited to 'packages/subproviders/src')
-rw-r--r-- | packages/subproviders/src/globals.d.ts | 150 | ||||
-rw-r--r-- | packages/subproviders/src/index.ts | 18 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/injected_web3.ts | 68 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ledger.ts | 544 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/redundant_rpc.ts | 84 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/subprovider.ts | 58 | ||||
-rw-r--r-- | packages/subproviders/src/types.ts | 102 |
7 files changed, 512 insertions, 512 deletions
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 53457fa24..ed4fbc145 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -8,103 +8,103 @@ declare module 'es6-promisify'; // Ethereumjs-tx declarations declare module 'ethereumjs-tx' { - class EthereumTx { - public raw: Buffer[]; - public r: Buffer; - public s: Buffer; - public v: Buffer; - public serialize(): Buffer; - constructor(txParams: any); - } - export = EthereumTx; + class EthereumTx { + public raw: Buffer[]; + public r: Buffer; + public s: Buffer; + public v: Buffer; + public serialize(): Buffer; + constructor(txParams: any); + } + export = EthereumTx; } // Ledgerco declarations interface ECSignatureString { - v: string; - r: string; - s: string; + v: string; + r: string; + s: string; } interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } declare module 'ledgerco' { - interface comm { - close_async(): Promise<void>; - } - export class comm_node implements comm { - public static create_async(timeoutMilliseconds?: number): Promise<comm_node>; - public close_async(): Promise<void>; - } - export class comm_u2f implements comm { - public static create_async(): Promise<comm_u2f>; - public close_async(): Promise<void>; - } - export class eth { - public comm: comm; - constructor(comm: comm); - public getAddress_async( - path: string, - display?: boolean, - chaincode?: boolean, - ): Promise<{ publicKey: string; address: string; chainCode: string }>; - public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; - public getAppConfiguration_async(): Promise<{ - arbitraryDataEnabled: number; - version: string; - }>; - public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; - } + interface comm { + close_async(): Promise<void>; + } + export class comm_node implements comm { + public static create_async(timeoutMilliseconds?: number): Promise<comm_node>; + public close_async(): Promise<void>; + } + export class comm_u2f implements comm { + public static create_async(): Promise<comm_u2f>; + public close_async(): Promise<void>; + } + export class eth { + public comm: comm; + constructor(comm: comm); + public getAddress_async( + path: string, + display?: boolean, + chaincode?: boolean, + ): Promise<{ publicKey: string; address: string; chainCode: string }>; + public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; + public getAppConfiguration_async(): Promise<{ + arbitraryDataEnabled: number; + version: string; + }>; + public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; + } } // Semaphore-async-await declarations declare module 'semaphore-async-await' { - class Semaphore { - constructor(permits: number); - public wait(): Promise<void>; - public signal(): void; - } - export default Semaphore; + class Semaphore { + constructor(permits: number); + public wait(): Promise<void>; + public signal(): void; + } + export default Semaphore; } // web3-provider-engine declarations declare module 'web3-provider-engine/subproviders/subprovider' { - class Subprovider {} - export = Subprovider; + 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; + 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; + 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; } // hdkey declarations declare module 'hdkey' { - class HDNode { - public publicKey: Buffer; - public chainCode: Buffer; - public constructor(); - public derive(path: string): HDNode; - } - export = HDNode; + class HDNode { + public publicKey: Buffer; + public chainCode: Buffer; + public constructor(); + public derive(path: string): HDNode; + } + export = HDNode; } diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index 720c4362f..ecba186df 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -1,7 +1,7 @@ import { - comm_node as LedgerNodeCommunication, - comm_u2f as LedgerBrowserCommunication, - eth as LedgerEthereumClientFn, + comm_node as LedgerNodeCommunication, + comm_u2f as LedgerBrowserCommunication, + eth as LedgerEthereumClientFn, } from 'ledgerco'; import { LedgerEthereumClient } from './types'; @@ -16,9 +16,9 @@ export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient } from * @return LedgerEthereumClient A browser client */ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await LedgerBrowserCommunication.create_async(); - const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); - return ledgerEthClient; + const ledgerConnection = await LedgerBrowserCommunication.create_async(); + const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); + return ledgerEthClient; } /** @@ -26,7 +26,7 @@ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerE * @return LedgerEthereumClient A Node.js client */ export async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await LedgerNodeCommunication.create_async(); - const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); - return ledgerEthClient; + const ledgerConnection = await LedgerNodeCommunication.create_async(); + const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); + return ledgerEthClient; } diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts index bd29acb22..dc1b8b24c 100644 --- a/packages/subproviders/src/subproviders/injected_web3.ts +++ b/packages/subproviders/src/subproviders/injected_web3.ts @@ -9,41 +9,41 @@ import Web3ProviderEngine = require('web3-provider-engine'); * 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 | null, result: any) => void, - ) { - switch (payload.method) { - case 'web3_clientVersion': - this._injectedWeb3.version.getNode(end); - return; - case 'eth_accounts': - this._injectedWeb3.eth.getAccounts(end); - return; + private _injectedWeb3: Web3; + constructor(injectedWeb3: Web3) { + this._injectedWeb3 = injectedWeb3; + } + public handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, result: any) => void, + ) { + switch (payload.method) { + case 'web3_clientVersion': + this._injectedWeb3.version.getNode(end); + 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_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; + 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 - } + 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 + } } diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 7267a793e..00649873b 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -8,12 +8,12 @@ import Semaphore from 'semaphore-async-await'; import Web3 = require('web3'); import { - LedgerEthereumClient, - LedgerEthereumClientFactoryAsync, - LedgerSubproviderConfigs, - LedgerSubproviderErrors, - PartialTxParams, - ResponseWithTxParams, + LedgerEthereumClient, + LedgerEthereumClientFactoryAsync, + LedgerSubproviderConfigs, + LedgerSubproviderErrors, + PartialTxParams, + ResponseWithTxParams, } from '../types'; import { Subprovider } from './subprovider'; @@ -24,284 +24,284 @@ const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const SHOULD_GET_CHAIN_CODE = true; 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 _validateSender(sender: string) { - if (_.isUndefined(sender) || !addressUtils.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; + 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 _validateSender(sender: string) { + if (_.isUndefined(sender) || !addressUtils.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_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_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 'eth_signTransaction': + txParams = payload.params[0]; + try { + const result = await this._signTransactionWithoutSendingAsync(txParams); + end(null, result); + } catch (err) { + end(err); + } + return; - case 'eth_sign': - case 'personal_sign': - const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; - try { - if (_.isUndefined(data)) { - throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); - } - assert.isHexString('data', data); - const ecSignatureHex = await this.signPersonalMessageAsync(data); - end(null, ecSignatureHex); - } catch (err) { - end(err); - } - return; + case 'eth_sign': + case 'personal_sign': + const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; + try { + if (_.isUndefined(data)) { + throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', data); + const ecSignatureHex = await this.signPersonalMessageAsync(data); + end(null, ecSignatureHex); + } catch (err) { + end(err); + } + return; - default: - next(); - return; - } - } - public async getAccountsAsync(): Promise<string[]> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); + default: + next(); + return; + } + } + public async getAccountsAsync(): Promise<string[]> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); - let ledgerResponse; - try { - ledgerResponse = await this._ledgerClientIfExists.getAddress_async( - this._derivationPath, - this._shouldAlwaysAskForConfirmation, - SHOULD_GET_CHAIN_CODE, - ); - } finally { - await this._destroyLedgerClientAsync(); - } + let ledgerResponse; + try { + ledgerResponse = await this._ledgerClientIfExists.getAddress_async( + this._derivationPath, + this._shouldAlwaysAskForConfirmation, + SHOULD_GET_CHAIN_CODE, + ); + } finally { + await this._destroyLedgerClientAsync(); + } - const hdKey = new HDNode(); - hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); - hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); + const hdKey = new HDNode(); + hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); + hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); - const accounts = []; - for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { - const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`); - const derivedPublicKey = derivedHDNode.publicKey; - const shouldSanitizePublicKey = true; - const ethereumAddressUnprefixed = ethUtil - .publicToAddress(derivedPublicKey, shouldSanitizePublicKey) - .toString('hex'); - const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed); - accounts.push(ethereumAddressPrefixed.toLowerCase()); - } - return accounts; - } - public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); + const accounts = []; + for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { + const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`); + const derivedPublicKey = derivedHDNode.publicKey; + const shouldSanitizePublicKey = true; + const ethereumAddressUnprefixed = ethUtil + .publicToAddress(derivedPublicKey, shouldSanitizePublicKey) + .toString('hex'); + const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed); + accounts.push(ethereumAddressPrefixed.toLowerCase()); + } + return accounts; + } + public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); - const tx = new EthereumTx(txParams); + 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 + // 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'); + 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._destroyLedgerClientAsync(); - const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); - throw err; - } + // EIP155: v should be chain_id * 2 + {35, 36} + const signedChainId = Math.floor((tx.v[0] - 35) / 2); + if (signedChainId !== this._networkId) { + await this._destroyLedgerClientAsync(); + const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); + throw err; + } - const signedTxHex = `0x${tx.serialize().toString('hex')}`; - await this._destroyLedgerClientAsync(); - return signedTxHex; - } catch (err) { - await this._destroyLedgerClientAsync(); - throw err; - } - } - public async signPersonalMessageAsync(data: string): Promise<string> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); - try { - const derivationPath = this._getDerivationPath(); - const result = await this._ledgerClientIfExists.signPersonalMessage_async( - derivationPath, - ethUtil.stripHexPrefix(data), - ); - const v = result.v - 27; - let vHex = v.toString(16); - if (vHex.length < 2) { - vHex = `0${v}`; - } - const signature = `0x${result.r}${result.s}${vHex}`; - await this._destroyLedgerClientAsync(); - return signature; - } catch (err) { - await this._destroyLedgerClientAsync(); - 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 _destroyLedgerClientAsync() { - 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<string> { - 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.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); + const signedTxHex = `0x${tx.serialize().toString('hex')}`; + await this._destroyLedgerClientAsync(); + return signedTxHex; + } catch (err) { + await this._destroyLedgerClientAsync(); + throw err; + } + } + public async signPersonalMessageAsync(data: string): Promise<string> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); + try { + const derivationPath = this._getDerivationPath(); + const result = await this._ledgerClientIfExists.signPersonalMessage_async( + derivationPath, + ethUtil.stripHexPrefix(data), + ); + const v = result.v - 27; + let vHex = v.toString(16); + if (vHex.length < 2) { + vHex = `0${v}`; + } + const signature = `0x${result.r}${result.s}${vHex}`; + await this._destroyLedgerClientAsync(); + return signature; + } catch (err) { + await this._destroyLedgerClientAsync(); + 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 _destroyLedgerClientAsync() { + 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<string> { + 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.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; - } + this._nonceLock.signal(); + const result = { + raw: signedTx, + tx: txParams, + }; + return result; + } catch (err) { + this._nonceLock.signal(); + throw err; + } + } + private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> { + if (_.isUndefined(txParams.gasPrice)) { + const gasPriceResult = await this.emitPayloadAsync({ + method: 'eth_gasPrice', + params: [], + }); + const gasPrice = gasPriceResult.result.toString(); + txParams.gasPrice = gasPrice; + } + if (_.isUndefined(txParams.nonce)) { + const nonceResult = await this.emitPayloadAsync({ + method: 'eth_getTransactionCount', + params: [txParams.from, 'pending'], + }); + const nonce = nonceResult.result; + txParams.nonce = nonce; + } + if (_.isUndefined(txParams.gas)) { + const gasResult = await this.emitPayloadAsync({ + method: 'eth_estimateGas', + params: [txParams], + }); + const gas = gasResult.result.toString(); + txParams.gas = gas; + } + return txParams; + } } diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index a3cb463a8..92d7ae8ae 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -7,46 +7,46 @@ import { JSONRPCPayload } from '../types'; import { Subprovider } from './subprovider'; export class RedundantRPCSubprovider extends Subprovider { - private _rpcs: RpcSubprovider[]; - private static async _firstSuccessAsync( - rpcs: RpcSubprovider[], - payload: JSONRPCPayload, - next: () => void, - ): Promise<any> { - let lastErr: 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, - }); - }); - } - // tslint:disable-next-line:async-suffix - public async handleRequest( - payload: JSONRPCPayload, - next: () => void, - end: (err: Error | null, 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); - } - } + 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, + }); + }); + } + // tslint:disable-next-line:async-suffix + public async handleRequest( + payload: JSONRPCPayload, + next: () => void, + end: (err: Error | null, 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/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index 6435c9f65..b6bb16918 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -8,33 +8,33 @@ import { JSONRPCPayload } from '../types'; * Altered version of: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class Subprovider { - private _engine: any; - // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js - private static _getRandomId() { - const extraDigits = 3; - // 13 time digits - const datePart = new Date().getTime() * Math.pow(10, extraDigits); - // 3 random digits - const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)); - // 16 digits - return datePart + extraPart; - } - private static _createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { - const finalPayload = { - // defaults - id: Subprovider._getRandomId(), - jsonrpc: '2.0', - params: [], - ...payload, - }; - return finalPayload; - } - public setEngine(engine: any): void { - this._engine = engine; - } - public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> { - const finalPayload = Subprovider._createFinalPayload(payload); - const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); - return response; - } + private _engine: any; + // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js + private static _getRandomId() { + const extraDigits = 3; + // 13 time digits + const datePart = new Date().getTime() * Math.pow(10, extraDigits); + // 3 random digits + const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)); + // 16 digits + return datePart + extraPart; + } + private static _createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { + const finalPayload = { + // defaults + id: Subprovider._getRandomId(), + jsonrpc: '2.0', + params: [], + ...payload, + }; + return finalPayload; + } + public setEngine(engine: any): void { + this._engine = engine; + } + public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> { + const finalPayload = Subprovider._createFinalPayload(payload); + const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); + return response; + } } diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 3db8be943..ec1eedd34 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; export interface LedgerCommunicationClient { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } /* @@ -10,28 +10,28 @@ export interface LedgerCommunicationClient { * NodeJs and Browser communication are supported. */ export interface LedgerEthereumClient { - // shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption - // that we get back the chain code and we don't have dependent types to express it properly - getAddress_async: ( - derivationPath: string, - askForDeviceConfirmation: boolean, - shouldGetChainCode: true, - ) => Promise<LedgerGetAddressResult>; - signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>; - signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>; - comm: LedgerCommunicationClient; + // shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption + // that we get back the chain code and we don't have dependent types to express it properly + getAddress_async: ( + derivationPath: string, + askForDeviceConfirmation: boolean, + shouldGetChainCode: true, + ) => 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; + v: string; + r: string; + s: string; } export interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClient>; @@ -43,10 +43,10 @@ export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClien * accountFetchingConfigs: configs related to fetching accounts from a Ledger */ export interface LedgerSubproviderConfigs { - networkId: number; - ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; - derivationPath?: string; - accountFetchingConfigs?: AccountFetchingConfigs; + networkId: number; + ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; + derivationPath?: string; + accountFetchingConfigs?: AccountFetchingConfigs; } /* @@ -55,60 +55,60 @@ export interface LedgerSubproviderConfigs { * before fetching their addresses */ export interface AccountFetchingConfigs { - numAddressesToReturn?: number; - shouldAskForOnDeviceConfirmation?: boolean; + numAddressesToReturn?: number; + shouldAskForOnDeviceConfirmation?: boolean; } export interface SignatureData { - hash: string; - r: string; - s: string; - v: number; + hash: string; + r: string; + s: string; + v: number; } export interface LedgerGetAddressResult { - address: string; - publicKey: string; - chainCode: string; + address: string; + publicKey: string; + chainCode: string; } export interface LedgerWalletSubprovider { - getPath: () => string; - setPath: (path: string) => void; - setPathIndex: (pathIndex: number) => void; + getPath: () => string; + setPath: (path: string) => void; + setPathIndex: (pathIndex: number) => void; } export interface PartialTxParams { - nonce: string; - gasPrice?: string; - gas: string; - to: string; - from?: string; - value?: string; - data?: string; - chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 + 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; + params: any[]; + method: string; } export interface LedgerCommunication { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } export interface ResponseWithTxParams { - raw: string; - tx: PartialTxParams; + raw: string; + tx: PartialTxParams; } export enum LedgerSubproviderErrors { - TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', - FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', - DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', - SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', - MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', + TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', + FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', + DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', + SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', + MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', } |