diff options
Diffstat (limited to 'packages/subproviders')
-rw-r--r-- | packages/subproviders/CHANGELOG.md | 7 | ||||
-rw-r--r-- | packages/subproviders/README.md | 56 | ||||
-rw-r--r-- | packages/subproviders/package.json | 16 | ||||
-rw-r--r-- | packages/subproviders/src/index.ts | 14 | ||||
-rw-r--r-- | packages/subproviders/src/monorepo_scripts/stage_docs.ts | 8 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/empty_wallet_subprovider.ts | 19 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts | 31 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ganache.ts | 23 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/injected_web3.ts | 28 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ledger.ts | 191 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/nonce_tracker.ts | 18 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/redundant_rpc.ts | 21 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/subprovider.ts | 31 | ||||
-rw-r--r-- | packages/subproviders/src/types.ts | 14 |
14 files changed, 273 insertions, 204 deletions
diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index b7c5545ea..2947bb32e 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.8.1 - _TBD_ + + * Introduce `JSONRPCRequestPayloadWithMethod` type (#465) + * Export `ErrorCallback` type. (#465) + * Make `handleRequest` private in `LedgerSubprovider` since it should only be used + internally by the providerEngine. (#465) + ## v0.8.0 - _March 18, 2018_ * Export `GanacheSubprovider` and `Subprovider` (#426) diff --git a/packages/subproviders/README.md b/packages/subproviders/README.md index 4614342b2..a2bf75768 100644 --- a/packages/subproviders/README.md +++ b/packages/subproviders/README.md @@ -4,6 +4,8 @@ A few useful web3 subproviders including a LedgerSubprovider useful for adding L We have written up a [Wiki](https://0xproject.com/wiki#Web3-Provider-Examples) article detailing some use cases of this subprovider package. +### Read the [Documentation](0xproject.com/docs/subproviders). + ## Installation ``` @@ -18,60 +20,6 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol ] ``` -## Usage - -Simply import the subprovider you are interested in using: - -```javascript -import { - ledgerEthereumBrowserClientFactoryAsync as ledgerEthereumClientFactoryAsync, - LedgerSubprovider, -} from '@0xproject/subproviders'; - -const ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync, -}); - -const accounts = await ledgerSubprovider.getAccountsAsync(); -``` - -### 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. - -Ledger Nano (and this library) by default uses a derivation path of `44'/60'/0'`. This is different to TestRPC which by default uses `m/44'/60'/0'/0`. This is a configuration option in the Ledger Subprovider package. - -##### Ledger Nano S + Node-hid (usb) - -By default, node-hid transport support is an optional dependency. This is due to the requirement of native usb developer packages on the host system. If these aren't installed the entire `npm install` fails. We also no longer export node-hid transport client factories. To re-create this see our integration tests or follow the example below: - -```typescript -import Eth from '@ledgerhq/hw-app-eth'; -import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; -async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await TransportNodeHid.create(); - const ledgerEthClient = new Eth(ledgerConnection); - return ledgerEthClient; -} - -// Create a LedgerSubprovider with the node-hid transport -ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, -}); -``` - -#### 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). - ## Contributing We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index f40148b5e..96d3e7f00 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -17,7 +17,20 @@ "test:circleci": "npm run test:unit:coverage", "test:all": "run-s test:unit test:integration", "test:unit": "run-s clean build run_mocha_unit", - "test:integration": "run-s clean build run_mocha_integration" + "test:integration": "run-s clean build run_mocha_integration", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "assets": [], + "docPublishConfigs": { + "extraFileIncludes": ["../types/src/index.ts"], + "s3BucketPath": "s3://doc-jsons/subproviders/", + "s3StagingBucketPath": "s3://staging-doc-jsons/subproviders/" + } + } }, "dependencies": { "@0xproject/assert": "^0.2.3", @@ -57,6 +70,7 @@ "tslint": "5.8.0", "types-bn": "^0.0.1", "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1", "webpack": "^3.1.0" }, diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index cafb50fe5..d88792fd0 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -2,7 +2,6 @@ import Eth from '@ledgerhq/hw-app-eth'; import TransportU2F from '@ledgerhq/hw-transport-u2f'; import { LedgerEthereumClient } from './types'; -export { Callback, NextCallback } from './types'; export { EmptyWalletSubprovider } from './subproviders/empty_wallet_subprovider'; export { FakeGasEstimateSubprovider } from './subproviders/fake_gas_estimate_subprovider'; @@ -12,11 +11,20 @@ export { LedgerSubprovider } from './subproviders/ledger'; export { GanacheSubprovider } from './subproviders/ganache'; export { Subprovider } from './subproviders/subprovider'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; -export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient, NonceSubproviderErrors } from './types'; +export { + Callback, + ErrorCallback, + NextCallback, + ECSignature, + LedgerWalletSubprovider, + LedgerCommunicationClient, + NonceSubproviderErrors, + LedgerSubproviderConfigs, +} from './types'; /** * A factory method for creating a LedgerEthereumClient usable in a browser context. - * @return LedgerEthereumClient A browser client + * @return LedgerEthereumClient A browser client for the LedgerSubprovider */ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> { const ledgerConnection = await TransportU2F.create(); diff --git a/packages/subproviders/src/monorepo_scripts/stage_docs.ts b/packages/subproviders/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/subproviders/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts b/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts index f5983dd9b..4cf3ece69 100644 --- a/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts +++ b/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts @@ -1,20 +1,17 @@ import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * that the provider has no addresses when queried. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts the `eth_accounts` JSON RPC requests and never returns any addresses when queried. */ export class EmptyWalletSubprovider extends Subprovider { - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'eth_accounts': end(null, []); diff --git a/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts b/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts index 2421dcd02..eff4439b4 100644 --- a/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts +++ b/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts @@ -1,28 +1,31 @@ import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * the constant gas estimate when queried. - * HACK: We need this so that our tests don't use testrpc gas estimation which sometimes kills the node. - * Source: https://github.com/trufflesuite/ganache-cli/issues/417 - * Source: https://github.com/trufflesuite/ganache-cli/issues/437 - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +// HACK: We need this so that our tests don't use testrpc gas estimation which sometimes kills the node. +// Source: https://github.com/trufflesuite/ganache-cli/issues/417 +// Source: https://github.com/trufflesuite/ganache-cli/issues/437 +// Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts the `eth_estimateGas` JSON RPC call and always returns a constant gas amount when queried. */ export class FakeGasEstimateSubprovider extends Subprovider { private _constantGasAmount: number; + /** + * Instantiates an instance of the FakeGasEstimateSubprovider + * @param constantGasAmount The constant gas amount you want returned + */ constructor(constantGasAmount: number) { super(); this._constantGasAmount = constantGasAmount; } - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'eth_estimateGas': end(null, this._constantGasAmount); diff --git a/packages/subproviders/src/subproviders/ganache.ts b/packages/subproviders/src/subproviders/ganache.ts index a979aecf4..feb17f8c5 100644 --- a/packages/subproviders/src/subproviders/ganache.ts +++ b/packages/subproviders/src/subproviders/ganache.ts @@ -1,26 +1,27 @@ import * as Ganache from 'ganache-core'; import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * the provider connected to a in-process ganache. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts all JSON RPC requests and relays them to an in-process ganache instance. */ export class GanacheSubprovider extends Subprovider { private _ganacheProvider: Web3.Provider; + /** + * Instantiates a GanacheSubprovider + * @param opts The desired opts with which to instantiate the Ganache provider + */ constructor(opts: any) { super(); this._ganacheProvider = Ganache.provider(opts); } - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { this._ganacheProvider.sendAsync(payload, (err: Error | null, result: any) => { end(err, result && result.result); }); diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts index 6d4e2b27b..73c7e59db 100644 --- a/packages/subproviders/src/subproviders/injected_web3.ts +++ b/packages/subproviders/src/subproviders/injected_web3.ts @@ -1,25 +1,29 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and forwards - * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected - * provider instance in their browser. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) + * subprovider interface. It forwards JSON RPC requests involving user accounts (getAccounts, + * sendTransaction, etc...) to the provider instance supplied at instantiation. All other requests + * are passed onwards for subsequent subproviders to handle. */ export class InjectedWeb3Subprovider extends Subprovider { private _injectedWeb3: Web3; - constructor(subprovider: Web3.Provider) { + /** + * Instantiates a new InjectedWeb3Subprovider + * @param provider Web3 provider that should handle all user account related requests + */ + constructor(provider: Web3.Provider) { super(); - this._injectedWeb3 = new Web3(subprovider); + this._injectedWeb3 = new Web3(provider); } - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'web3_clientVersion': this._injectedWeb3.version.getNode(end); diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index b67b49bee..3a63ff80f 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -8,6 +8,7 @@ import { Lock } from 'semaphore-async-await'; import * as Web3 from 'web3'; import { + Callback, LedgerEthereumClient, LedgerEthereumClientFactoryAsync, LedgerSubproviderConfigs, @@ -23,6 +24,11 @@ const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10; const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const SHOULD_GET_CHAIN_CODE = true; +/** + * Subprovider for interfacing with a user's [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s). + * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) and + * re-routes them to a Ledger device plugged into the users computer. + */ export class LedgerSubprovider extends Subprovider { private _nonceLock = new Lock(); private _connectionLock = new Lock(); @@ -37,6 +43,12 @@ export class LedgerSubprovider extends Subprovider { throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); } } + /** + * Instantiates a LedgerSubprovider. Defaults to derivationPath set to `44'/60'/0'`. + * TestRPC/Ganache defaults to `m/44'/60'/0'/0`, so set this in the configs if desired. + * @param config Several available configurations + * @return LedgerSubprovider instance + */ constructor(config: LedgerSubproviderConfigs) { super(); this._networkId = config.networkId; @@ -49,84 +61,38 @@ export class LedgerSubprovider extends Subprovider { : ASK_FOR_ON_DEVICE_CONFIRMATION; this._derivationPathIndex = 0; } + /** + * Retrieve the set derivation path + * @returns derivation path + */ public getPath(): string { return this._derivationPath; } + /** + * Set a desired derivation path when computing the available user addresses + * @param derivationPath The desired derivation path (e.g `44'/60'/0'`) + */ public setPath(derivationPath: string) { this._derivationPath = derivationPath; } + /** + * Set the final derivation path index. If a user wishes to sign a message with the + * 6th address in a derivation path, before calling `signPersonalMessageAsync`, you must + * call this method with pathIndex `6`. + * @param pathIndex Desired derivation path index + */ public setPathIndex(pathIndex: number) { this._derivationPathIndex = pathIndex; } - // Required to implement this public interface which doesn't conform to our linting rule. - // tslint:disable-next-line:async-suffix - 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 '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; - } - } + /** + * Retrieve a users Ledger accounts. The accounts are derived from the derivationPath, + * master public key and chainCode. Because of this, you can request as many accounts + * as you wish and it only requires a single request to the Ledger device. This method + * is automatically called when issuing a `eth_accounts` JSON RPC request via your providerEngine + * instance. + * @param numberOfAccounts Number of accounts to retrieve (default: 10) + * @return An array of accounts + */ public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); @@ -158,6 +124,14 @@ export class LedgerSubprovider extends Subprovider { } return accounts; } + /** + * Sign a transaction with the Ledger. If you've added the LedgerSubprovider to your + * app's provider, you can simply send an `eth_sendTransaction` JSON RPC request, and + * this method will be called auto-magically. If you are not using this via a ProviderEngine + * instance, you can call it directly. + * @param txParams Parameters of the transaction to sign + * @return Signed transaction hex string + */ public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); @@ -193,6 +167,16 @@ export class LedgerSubprovider extends Subprovider { throw err; } } + /** + * Sign a personal Ethereum signed message. The signing address will be to one + * retrieved given a derivationPath and pathIndex set on the subprovider. + * The Ledger adds the Ethereum signed message prefix on-device. If you've added + * the LedgerSubprovider to your app's provider, you can simply send an `eth_sign` + * or `personal_sign` JSON RPC request, and this method will be called auto-magically. + * If you are not using this via a ProviderEngine instance, you can call it directly. + * @param data Message to sign + * @return Signature hex string (order: rsv) + */ public async signPersonalMessageAsync(data: string): Promise<string> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); try { @@ -214,6 +198,75 @@ export class LedgerSubprovider extends Subprovider { throw err; } } + // Required to implement this public interface which doesn't conform to our linting rule. + // tslint:disable-next-line:async-suffix underscore-private-and-protected + private async handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: Callback, + 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 '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; + } + } private _getDerivationPath() { const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; return derivationPath; diff --git a/packages/subproviders/src/subproviders/nonce_tracker.ts b/packages/subproviders/src/subproviders/nonce_tracker.ts index 82eab4a9a..919aa861f 100644 --- a/packages/subproviders/src/subproviders/nonce_tracker.ts +++ b/packages/subproviders/src/subproviders/nonce_tracker.ts @@ -10,13 +10,13 @@ import { Callback, ErrorCallback, NextCallback, NonceSubproviderErrors } from '. import { Subprovider } from './subprovider'; -// We do not export this since this is not our error, and we do not throw this error const NONCE_TOO_LOW_ERROR_MESSAGE = 'Transaction nonce is too low'; -/* - This class is heavily inspiried by the Web3ProviderEngine NonceSubprovider - We have added the additional feature of clearing any nonce balues when an error message - describes a nonce value being too low. -*/ + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It is heavily inspired by the [NonceSubprovider](https://github.com/MetaMask/provider-engine/blob/master/subproviders/nonce-tracker.js). + * We added the additional feature of clearing the cached nonce value when a `nonce value too low` error occurs. + */ export class NonceTrackerSubprovider extends Subprovider { private _nonceCache: { [address: string]: string } = {}; private static _reconstructTransaction(payload: Web3.JSONRPCRequestPayload): EthereumTx { @@ -46,9 +46,9 @@ export class NonceTrackerSubprovider extends Subprovider { throw new Error(NonceSubproviderErrors.CannotDetermineAddressFromPayload); } } - // Required to implement this public interface which doesn't conform to our linting rule. - // tslint:disable-next-line:async-suffix - public async handleRequest( + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private async handleRequest( payload: Web3.JSONRPCRequestPayload, next: NextCallback, end: ErrorCallback, diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index 0df2f91f4..55b4128f4 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -3,14 +3,21 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); +import { Callback } from '../types'; + import { Subprovider } from './subprovider'; +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It attempts to handle each JSON RPC request by sequentially attempting to receive a valid response from one of a + * set of JSON RPC endpoints. + */ export class RedundantRPCSubprovider extends Subprovider { private _rpcs: RpcSubprovider[]; private static async _firstSuccessAsync( rpcs: RpcSubprovider[], payload: Web3.JSONRPCRequestPayload, - next: () => void, + next: Callback, ): Promise<any> { let lastErr: Error | undefined; for (const rpc of rpcs) { @@ -26,6 +33,10 @@ export class RedundantRPCSubprovider extends Subprovider { throw lastErr; } } + /** + * Instantiates a new RedundantRPCSubprovider + * @param endpoints JSON RPC endpoints to attempt. Attempts are made in the order of the endpoints. + */ constructor(endpoints: string[]) { super(); this._rpcs = _.map(endpoints, endpoint => { @@ -34,11 +45,11 @@ export class RedundantRPCSubprovider extends Subprovider { }); }); } - // Required to implement this public interface which doesn't conform to our linting rule. - // tslint:disable-next-line:async-suffix - public async handleRequest( + // This method must conform to the web3-provider-engine interface + // tslint:disable-next-line:prefer-function-over-method underscore-private-and-protected + private async handleRequest( payload: Web3.JSONRPCRequestPayload, - next: () => void, + next: Callback, end: (err: Error | null, data?: any) => void, ): Promise<void> { const rpcsCopy = this._rpcs.slice(); diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index d4e0de67a..0b30c6397 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -1,9 +1,10 @@ import promisify = require('es6-promisify'); import * as Web3 from 'web3'; -/* - * A version of the base class Subprovider found in providerEngine + +import { JSONRPCRequestPayloadWithMethod } from '../types'; +/** + * A altered version of the base class Subprovider found in [web3-provider-engine](https://github.com/MetaMask/provider-engine). * 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; @@ -18,8 +19,8 @@ export class Subprovider { return datePart + extraPart; } private static _createFinalPayload( - payload: Partial<Web3.JSONRPCRequestPayload> & { method: string }, - ): Web3.JSONRPCRequestPayload { + payload: Partial<JSONRPCRequestPayloadWithMethod>, + ): Partial<JSONRPCRequestPayloadWithMethod> { const finalPayload = { // defaults id: Subprovider._getRandomId(), @@ -29,14 +30,26 @@ export class Subprovider { }; return finalPayload; } - public setEngine(engine: any): void { - this._engine = engine; - } + /** + * Emits a JSON RPC payload that will then be handled by the ProviderEngine instance + * this subprovider is a part of. The payload will cascade down the subprovider middleware + * stack until finding the responsible entity for handling the request. + * @param payload JSON RPC payload + * @returns JSON RPC response payload + */ public async emitPayloadAsync( - payload: Partial<Web3.JSONRPCRequestPayload> & { method: string }, + payload: Partial<JSONRPCRequestPayloadWithMethod>, ): Promise<Web3.JSONRPCResponsePayload> { const finalPayload = Subprovider._createFinalPayload(payload); const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); return response; } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code + */ + // tslint:disable-next-line:underscore-private-and-protected + private setEngine(engine: any): void { + this._engine = engine; + } } diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 543da5947..9bb9ff696 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -1,4 +1,8 @@ +import { ECSignature } from '@0xproject/types'; import * as _ from 'lodash'; +import * as Web3 from 'web3'; + +export { ECSignature } from '@0xproject/types'; export interface LedgerCommunicationClient { close: () => Promise<void>; @@ -28,12 +32,6 @@ export interface ECSignatureString { s: string; } -export interface ECSignature { - v: number; - r: string; - s: string; -} - export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClient>; /* @@ -117,3 +115,7 @@ export type ErrorCallback = (err: Error | null, data?: any) => void; export type Callback = () => void; export type OnNextCompleted = (err: Error | null, result: any, cb: Callback) => void; export type NextCallback = (callback?: OnNextCompleted) => void; + +export interface JSONRPCRequestPayloadWithMethod extends Web3.JSONRPCRequestPayload { + method: string; +} |