Merge pull request #507 from 0xProject/feature/subproviders/mnemonic-wallet-subprovider
Mnemonic wallet subprovider and multiple accounts in ledger
This commit is contained in:
commit
ed0c64fdcf
@ -8,8 +8,25 @@
|
|||||||
"pr": 500
|
"pr": 500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Add private key subprovider and refactor shared functionality into a base wallet subprovider",
|
"note": "Add PrivateKeySubprovider and refactor shared functionality into a base wallet subprovider",
|
||||||
"pr": 506
|
"pr": 506
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add MnemonicWalletsubprovider, deprecating our truffle-hdwallet-provider fork",
|
||||||
|
"pr": 507
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Support multiple addresses in ledger and mnemonic wallets",
|
||||||
|
"pr": 507
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Refactors LedgerSubprovider such that explicitly setting the `pathIndex` is no longer required. Simply set the request `from` address as desired",
|
||||||
|
"pr": 507
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Renamed derivationPath to baseDerivationPath.",
|
||||||
|
"pr": 507
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timestamp": 1523462196
|
"timestamp": 1523462196
|
||||||
|
@ -21,15 +21,14 @@
|
|||||||
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
|
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
|
||||||
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
|
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
|
||||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
|
"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"
|
"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": {
|
"config": {
|
||||||
"postpublish": {
|
"postpublish": {
|
||||||
"assets": [],
|
"assets": [],
|
||||||
"docPublishConfigs": {
|
"docPublishConfigs": {
|
||||||
"extraFileIncludes": [
|
"extraFileIncludes": ["../types/src/index.ts"],
|
||||||
"../types/src/index.ts"
|
|
||||||
],
|
|
||||||
"s3BucketPath": "s3://doc-jsons/subproviders/",
|
"s3BucketPath": "s3://doc-jsons/subproviders/",
|
||||||
"s3StagingBucketPath": "s3://staging-doc-jsons/subproviders/"
|
"s3StagingBucketPath": "s3://staging-doc-jsons/subproviders/"
|
||||||
}
|
}
|
||||||
@ -46,6 +45,7 @@
|
|||||||
"ethereumjs-tx": "^1.3.3",
|
"ethereumjs-tx": "^1.3.3",
|
||||||
"ethereumjs-util": "^5.1.1",
|
"ethereumjs-util": "^5.1.1",
|
||||||
"ganache-core": "0xProject/ganache-core",
|
"ganache-core": "0xProject/ganache-core",
|
||||||
|
"bip39": "^2.5.0",
|
||||||
"hdkey": "^0.7.1",
|
"hdkey": "^0.7.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"semaphore-async-await": "^1.5.1",
|
"semaphore-async-await": "^1.5.1",
|
||||||
@ -56,6 +56,7 @@
|
|||||||
"@0xproject/monorepo-scripts": "^0.1.17",
|
"@0xproject/monorepo-scripts": "^0.1.17",
|
||||||
"@0xproject/tslint-config": "^0.4.15",
|
"@0xproject/tslint-config": "^0.4.15",
|
||||||
"@0xproject/utils": "^0.5.1",
|
"@0xproject/utils": "^0.5.1",
|
||||||
|
"@types/bip39": "^2.4.0",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^2.2.42",
|
"@types/mocha": "^2.2.42",
|
||||||
"@types/node": "^8.0.53",
|
"@types/node": "^8.0.53",
|
||||||
|
11
packages/subproviders/src/globals.d.ts
vendored
11
packages/subproviders/src/globals.d.ts
vendored
@ -51,17 +51,6 @@ declare module '@ledgerhq/hw-transport-node-hid' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hdkey declarations
|
|
||||||
declare module 'hdkey' {
|
|
||||||
class HDNode {
|
|
||||||
public publicKey: Buffer;
|
|
||||||
public chainCode: Buffer;
|
|
||||||
public constructor();
|
|
||||||
public derive(path: string): HDNode;
|
|
||||||
}
|
|
||||||
export = HDNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.json' {
|
declare module '*.json' {
|
||||||
const json: any;
|
const json: any;
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
|
@ -12,12 +12,12 @@ export { LedgerSubprovider } from './subproviders/ledger';
|
|||||||
export { GanacheSubprovider } from './subproviders/ganache';
|
export { GanacheSubprovider } from './subproviders/ganache';
|
||||||
export { Subprovider } from './subproviders/subprovider';
|
export { Subprovider } from './subproviders/subprovider';
|
||||||
export { NonceTrackerSubprovider } from './subproviders/nonce_tracker';
|
export { NonceTrackerSubprovider } from './subproviders/nonce_tracker';
|
||||||
export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet_subprovider';
|
export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet';
|
||||||
|
export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet';
|
||||||
export {
|
export {
|
||||||
Callback,
|
Callback,
|
||||||
ErrorCallback,
|
ErrorCallback,
|
||||||
NextCallback,
|
NextCallback,
|
||||||
LedgerWalletSubprovider,
|
|
||||||
LedgerCommunicationClient,
|
LedgerCommunicationClient,
|
||||||
NonceSubproviderErrors,
|
NonceSubproviderErrors,
|
||||||
LedgerSubproviderConfigs,
|
LedgerSubproviderConfigs,
|
||||||
|
@ -21,7 +21,7 @@ export abstract class BaseWalletSubprovider extends Subprovider {
|
|||||||
|
|
||||||
public abstract async getAccountsAsync(): Promise<string[]>;
|
public abstract async getAccountsAsync(): Promise<string[]>;
|
||||||
public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>;
|
public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>;
|
||||||
public abstract async signPersonalMessageAsync(data: string): Promise<string>;
|
public abstract async signPersonalMessageAsync(data: string, address: string): Promise<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method conforms to the web3-provider-engine interface.
|
* This method conforms to the web3-provider-engine interface.
|
||||||
@ -85,8 +85,9 @@ export abstract class BaseWalletSubprovider extends Subprovider {
|
|||||||
case 'eth_sign':
|
case 'eth_sign':
|
||||||
case 'personal_sign':
|
case 'personal_sign':
|
||||||
const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0];
|
const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0];
|
||||||
|
const address = payload.method === 'eth_sign' ? payload.params[0] : payload.params[1];
|
||||||
try {
|
try {
|
||||||
const ecSignatureHex = await this.signPersonalMessageAsync(data);
|
const ecSignatureHex = await this.signPersonalMessageAsync(data, address);
|
||||||
end(null, ecSignatureHex);
|
end(null, ecSignatureHex);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
end(err);
|
end(err);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { assert } from '@0xproject/assert';
|
import { assert } from '@0xproject/assert';
|
||||||
import { JSONRPCRequestPayload } from '@0xproject/types';
|
|
||||||
import { addressUtils } from '@0xproject/utils';
|
import { addressUtils } from '@0xproject/utils';
|
||||||
import EthereumTx = require('ethereumjs-tx');
|
import EthereumTx = require('ethereumjs-tx');
|
||||||
import ethUtil = require('ethereumjs-util');
|
import ethUtil = require('ethereumjs-util');
|
||||||
@ -9,6 +8,7 @@ import { Lock } from 'semaphore-async-await';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Callback,
|
Callback,
|
||||||
|
DerivedHDKeyInfo,
|
||||||
LedgerEthereumClient,
|
LedgerEthereumClient,
|
||||||
LedgerEthereumClientFactoryAsync,
|
LedgerEthereumClientFactoryAsync,
|
||||||
LedgerSubproviderConfigs,
|
LedgerSubproviderConfigs,
|
||||||
@ -17,13 +17,15 @@ import {
|
|||||||
ResponseWithTxParams,
|
ResponseWithTxParams,
|
||||||
WalletSubproviderErrors,
|
WalletSubproviderErrors,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
import { walletUtils } from '../utils/wallet_utils';
|
||||||
|
|
||||||
import { BaseWalletSubprovider } from './base_wallet_subprovider';
|
import { BaseWalletSubprovider } from './base_wallet_subprovider';
|
||||||
|
|
||||||
const DEFAULT_DERIVATION_PATH = `44'/60'/0'`;
|
const DEFAULT_BASE_DERIVATION_PATH = `44'/60'/0'`;
|
||||||
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
|
|
||||||
const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
|
const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
|
||||||
const SHOULD_GET_CHAIN_CODE = true;
|
const SHOULD_GET_CHAIN_CODE = true;
|
||||||
|
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
|
||||||
|
const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subprovider for interfacing with a user's [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s).
|
* Subprovider for interfacing with a user's [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s).
|
||||||
@ -34,11 +36,11 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
private _nonceLock = new Lock();
|
private _nonceLock = new Lock();
|
||||||
private _connectionLock = new Lock();
|
private _connectionLock = new Lock();
|
||||||
private _networkId: number;
|
private _networkId: number;
|
||||||
private _derivationPath: string;
|
private _baseDerivationPath: string;
|
||||||
private _derivationPathIndex: number;
|
|
||||||
private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
|
private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
|
||||||
private _ledgerClientIfExists?: LedgerEthereumClient;
|
private _ledgerClientIfExists?: LedgerEthereumClient;
|
||||||
private _shouldAlwaysAskForConfirmation: boolean;
|
private _shouldAlwaysAskForConfirmation: boolean;
|
||||||
|
private _addressSearchLimit: number;
|
||||||
/**
|
/**
|
||||||
* Instantiates a LedgerSubprovider. Defaults to derivationPath set to `44'/60'/0'`.
|
* 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.
|
* TestRPC/Ganache defaults to `m/44'/60'/0'/0`, so set this in the configs if desired.
|
||||||
@ -49,40 +51,35 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
super();
|
super();
|
||||||
this._networkId = config.networkId;
|
this._networkId = config.networkId;
|
||||||
this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync;
|
this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync;
|
||||||
this._derivationPath = config.derivationPath || DEFAULT_DERIVATION_PATH;
|
this._baseDerivationPath = config.baseDerivationPath || DEFAULT_BASE_DERIVATION_PATH;
|
||||||
this._shouldAlwaysAskForConfirmation =
|
this._shouldAlwaysAskForConfirmation =
|
||||||
!_.isUndefined(config.accountFetchingConfigs) &&
|
!_.isUndefined(config.accountFetchingConfigs) &&
|
||||||
!_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation)
|
!_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation)
|
||||||
? config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation
|
? config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation
|
||||||
: ASK_FOR_ON_DEVICE_CONFIRMATION;
|
: ASK_FOR_ON_DEVICE_CONFIRMATION;
|
||||||
this._derivationPathIndex = 0;
|
this._addressSearchLimit =
|
||||||
|
!_.isUndefined(config.accountFetchingConfigs) &&
|
||||||
|
!_.isUndefined(config.accountFetchingConfigs.addressSearchLimit)
|
||||||
|
? config.accountFetchingConfigs.addressSearchLimit
|
||||||
|
: DEFAULT_ADDRESS_SEARCH_LIMIT;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Retrieve the set derivation path
|
* Retrieve the set derivation path
|
||||||
* @returns derivation path
|
* @returns derivation path
|
||||||
*/
|
*/
|
||||||
public getPath(): string {
|
public getPath(): string {
|
||||||
return this._derivationPath;
|
return this._baseDerivationPath;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Set a desired derivation path when computing the available user addresses
|
* Set a desired derivation path when computing the available user addresses
|
||||||
* @param derivationPath The desired derivation path (e.g `44'/60'/0'`)
|
* @param basDerivationPath The desired derivation path (e.g `44'/60'/0'`)
|
||||||
*/
|
*/
|
||||||
public setPath(derivationPath: string) {
|
public setPath(basDerivationPath: string) {
|
||||||
this._derivationPath = derivationPath;
|
this._baseDerivationPath = basDerivationPath;
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Retrieve a users Ledger accounts. The accounts are derived from the derivationPath,
|
* 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
|
* master public key and chain code. 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
|
* 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
|
* is automatically called when issuing a `eth_accounts` JSON RPC request via your providerEngine
|
||||||
* instance.
|
* instance.
|
||||||
@ -90,46 +87,27 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
* @return An array of accounts
|
* @return An array of accounts
|
||||||
*/
|
*/
|
||||||
public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> {
|
public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> {
|
||||||
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync();
|
||||||
|
const derivedKeyInfos = walletUtils.calculateDerivedHDKeyInfos(initialDerivedKeyInfo, numberOfAccounts);
|
||||||
let ledgerResponse;
|
const accounts = _.map(derivedKeyInfos, k => k.address);
|
||||||
try {
|
|
||||||
ledgerResponse = await this._ledgerClientIfExists.getAddress(
|
|
||||||
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 accounts: string[] = [];
|
|
||||||
for (let i = 0; i < numberOfAccounts; 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;
|
return accounts;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a transaction with the Ledger. If you've added the LedgerSubprovider to your
|
* Signs a transaction on the Ledger with the account specificed by the `from` field in txParams.
|
||||||
* app's provider, you can simply send an `eth_sendTransaction` JSON RPC request, and
|
* If you've added the LedgerSubprovider to your app's provider, you can simply send an `eth_sendTransaction`
|
||||||
* this method will be called auto-magically. If you are not using this via a ProviderEngine
|
* 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.
|
* instance, you can call it directly.
|
||||||
* @param txParams Parameters of the transaction to sign
|
* @param txParams Parameters of the transaction to sign
|
||||||
* @return Signed transaction hex string
|
* @return Signed transaction hex string
|
||||||
*/
|
*/
|
||||||
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
|
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
|
||||||
LedgerSubprovider._validateTxParams(txParams);
|
LedgerSubprovider._validateTxParams(txParams);
|
||||||
|
if (_.isUndefined(txParams.from) || !addressUtils.isAddress(txParams.from)) {
|
||||||
|
throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid);
|
||||||
|
}
|
||||||
|
const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync();
|
||||||
|
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, txParams.from);
|
||||||
|
|
||||||
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
||||||
|
|
||||||
const tx = new EthereumTx(txParams);
|
const tx = new EthereumTx(txParams);
|
||||||
@ -141,8 +119,8 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
|
|
||||||
const txHex = tx.serialize().toString('hex');
|
const txHex = tx.serialize().toString('hex');
|
||||||
try {
|
try {
|
||||||
const derivationPath = this._getDerivationPath();
|
const fullDerivationPath = derivedKeyInfo.derivationPath;
|
||||||
const result = await this._ledgerClientIfExists.signTransaction(derivationPath, txHex);
|
const result = await this._ledgerClientIfExists.signTransaction(fullDerivationPath, txHex);
|
||||||
// Store signature in transaction
|
// Store signature in transaction
|
||||||
tx.r = Buffer.from(result.r, 'hex');
|
tx.r = Buffer.from(result.r, 'hex');
|
||||||
tx.s = Buffer.from(result.s, 'hex');
|
tx.s = Buffer.from(result.s, 'hex');
|
||||||
@ -165,25 +143,30 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a personal Ethereum signed message. The signing address will be the one
|
* Sign a personal Ethereum signed message. The signing account will be the account
|
||||||
* retrieved given a derivationPath and pathIndex set on the subprovider.
|
* associated with the provided address.
|
||||||
* The Ledger adds the Ethereum signed message prefix on-device. If you've added
|
* 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`
|
* 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.
|
* 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.
|
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
||||||
* @param data Message to sign
|
* @param data Hex string message to sign
|
||||||
|
* @param address Address of the account to sign with
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signPersonalMessageAsync(data: string): Promise<string> {
|
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
|
||||||
if (_.isUndefined(data)) {
|
if (_.isUndefined(data)) {
|
||||||
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
||||||
}
|
}
|
||||||
assert.isHexString('data', data);
|
assert.isHexString('data', data);
|
||||||
|
assert.isETHAddressHex('address', address);
|
||||||
|
const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync();
|
||||||
|
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, address);
|
||||||
|
|
||||||
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
||||||
try {
|
try {
|
||||||
const derivationPath = this._getDerivationPath();
|
const fullDerivationPath = derivedKeyInfo.derivationPath;
|
||||||
const result = await this._ledgerClientIfExists.signPersonalMessage(
|
const result = await this._ledgerClientIfExists.signPersonalMessage(
|
||||||
derivationPath,
|
fullDerivationPath,
|
||||||
ethUtil.stripHexPrefix(data),
|
ethUtil.stripHexPrefix(data),
|
||||||
);
|
);
|
||||||
const v = result.v - 27;
|
const v = result.v - 27;
|
||||||
@ -199,10 +182,6 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private _getDerivationPath() {
|
|
||||||
const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`;
|
|
||||||
return derivationPath;
|
|
||||||
}
|
|
||||||
private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> {
|
private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> {
|
||||||
await this._connectionLock.acquire();
|
await this._connectionLock.acquire();
|
||||||
if (!_.isUndefined(this._ledgerClientIfExists)) {
|
if (!_.isUndefined(this._ledgerClientIfExists)) {
|
||||||
@ -223,4 +202,41 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
|
|||||||
this._ledgerClientIfExists = undefined;
|
this._ledgerClientIfExists = undefined;
|
||||||
this._connectionLock.release();
|
this._connectionLock.release();
|
||||||
}
|
}
|
||||||
|
private async _initialDerivedKeyInfoAsync(): Promise<DerivedHDKeyInfo> {
|
||||||
|
this._ledgerClientIfExists = await this._createLedgerClientAsync();
|
||||||
|
|
||||||
|
const parentKeyDerivationPath = `m/${this._baseDerivationPath}`;
|
||||||
|
let ledgerResponse;
|
||||||
|
try {
|
||||||
|
ledgerResponse = await this._ledgerClientIfExists.getAddress(
|
||||||
|
parentKeyDerivationPath,
|
||||||
|
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 address = walletUtils.addressOfHDKey(hdKey);
|
||||||
|
const initialDerivedKeyInfo = {
|
||||||
|
hdKey,
|
||||||
|
address,
|
||||||
|
derivationPath: parentKeyDerivationPath,
|
||||||
|
baseDerivationPath: this._baseDerivationPath,
|
||||||
|
};
|
||||||
|
return initialDerivedKeyInfo;
|
||||||
|
}
|
||||||
|
private _findDerivedKeyInfoForAddress(initalHDKey: DerivedHDKeyInfo, address: string): DerivedHDKeyInfo {
|
||||||
|
const matchedDerivedKeyInfo = walletUtils.findDerivedKeyInfoForAddressIfExists(
|
||||||
|
address,
|
||||||
|
initalHDKey,
|
||||||
|
this._addressSearchLimit,
|
||||||
|
);
|
||||||
|
if (_.isUndefined(matchedDerivedKeyInfo)) {
|
||||||
|
throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`);
|
||||||
|
}
|
||||||
|
return matchedDerivedKeyInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
145
packages/subproviders/src/subproviders/mnemonic_wallet.ts
Normal file
145
packages/subproviders/src/subproviders/mnemonic_wallet.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { assert } from '@0xproject/assert';
|
||||||
|
import { addressUtils } from '@0xproject/utils';
|
||||||
|
import * as bip39 from 'bip39';
|
||||||
|
import ethUtil = require('ethereumjs-util');
|
||||||
|
import HDNode = require('hdkey');
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { DerivedHDKeyInfo, MnemonicWalletSubproviderConfigs, PartialTxParams, WalletSubproviderErrors } from '../types';
|
||||||
|
import { walletUtils } from '../utils/wallet_utils';
|
||||||
|
|
||||||
|
import { BaseWalletSubprovider } from './base_wallet_subprovider';
|
||||||
|
import { PrivateKeyWalletSubprovider } from './private_key_wallet';
|
||||||
|
|
||||||
|
const DEFAULT_BASE_DERIVATION_PATH = `44'/60'/0'/0`;
|
||||||
|
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
|
||||||
|
const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
|
||||||
|
* This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) and handles
|
||||||
|
* all requests with accounts derived from the supplied mnemonic.
|
||||||
|
*/
|
||||||
|
export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
|
||||||
|
private _addressSearchLimit: number;
|
||||||
|
private _baseDerivationPath: string;
|
||||||
|
private _derivedKeyInfo: DerivedHDKeyInfo;
|
||||||
|
private _mnemonic: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a MnemonicWalletSubprovider. Defaults to baseDerivationPath set to `44'/60'/0'/0`.
|
||||||
|
* This is the default in TestRPC/Ganache, it can be overridden if desired.
|
||||||
|
* @param config Configuration for the mnemonic wallet, must contain the mnemonic
|
||||||
|
* @return MnemonicWalletSubprovider instance
|
||||||
|
*/
|
||||||
|
constructor(config: MnemonicWalletSubproviderConfigs) {
|
||||||
|
assert.isString('mnemonic', config.mnemonic);
|
||||||
|
const baseDerivationPath = config.baseDerivationPath || DEFAULT_BASE_DERIVATION_PATH;
|
||||||
|
assert.isString('baseDerivationPath', baseDerivationPath);
|
||||||
|
const addressSearchLimit = config.addressSearchLimit || DEFAULT_ADDRESS_SEARCH_LIMIT;
|
||||||
|
assert.isNumber('addressSearchLimit', addressSearchLimit);
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._mnemonic = config.mnemonic;
|
||||||
|
this._baseDerivationPath = baseDerivationPath;
|
||||||
|
this._addressSearchLimit = addressSearchLimit;
|
||||||
|
this._derivedKeyInfo = this._initialDerivedKeyInfo(this._baseDerivationPath);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve the set derivation path
|
||||||
|
* @returns derivation path
|
||||||
|
*/
|
||||||
|
public getPath(): string {
|
||||||
|
return this._baseDerivationPath;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set a desired derivation path when computing the available user addresses
|
||||||
|
* @param baseDerivationPath The desired derivation path (e.g `44'/60'/0'`)
|
||||||
|
*/
|
||||||
|
public setPath(baseDerivationPath: string) {
|
||||||
|
this._baseDerivationPath = baseDerivationPath;
|
||||||
|
this._derivedKeyInfo = this._initialDerivedKeyInfo(this._baseDerivationPath);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts associated with the mnemonic.
|
||||||
|
* This method is implicitly 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[]> {
|
||||||
|
const derivedKeys = walletUtils.calculateDerivedHDKeyInfos(this._derivedKeyInfo, numberOfAccounts);
|
||||||
|
const accounts = _.map(derivedKeys, k => k.address);
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs a transaction with the account specificed by the `from` field in txParams.
|
||||||
|
* If you've added this Subprovider 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> {
|
||||||
|
if (_.isUndefined(txParams.from) || !addressUtils.isAddress(txParams.from)) {
|
||||||
|
throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid);
|
||||||
|
}
|
||||||
|
const privateKeyWallet = this._privateKeyWalletForAddress(txParams.from);
|
||||||
|
const signedTx = privateKeyWallet.signTransactionAsync(txParams);
|
||||||
|
return signedTx;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sign a personal Ethereum signed message. The signing account will be the account
|
||||||
|
* associated with the provided address.
|
||||||
|
* If you've added the MnemonicWalletSubprovider 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 Hex string message to sign
|
||||||
|
* @param address Address of the account to sign with
|
||||||
|
* @return Signature hex string (order: rsv)
|
||||||
|
*/
|
||||||
|
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
|
||||||
|
if (_.isUndefined(data)) {
|
||||||
|
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
||||||
|
}
|
||||||
|
assert.isHexString('data', data);
|
||||||
|
assert.isETHAddressHex('address', address);
|
||||||
|
const privateKeyWallet = this._privateKeyWalletForAddress(address);
|
||||||
|
const sig = await privateKeyWallet.signPersonalMessageAsync(data, address);
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
private _privateKeyWalletForAddress(address: string): PrivateKeyWalletSubprovider {
|
||||||
|
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address);
|
||||||
|
const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex');
|
||||||
|
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKeyHex);
|
||||||
|
return privateKeyWallet;
|
||||||
|
}
|
||||||
|
private _findDerivedKeyInfoForAddress(address: string): DerivedHDKeyInfo {
|
||||||
|
const matchedDerivedKeyInfo = walletUtils.findDerivedKeyInfoForAddressIfExists(
|
||||||
|
address,
|
||||||
|
this._derivedKeyInfo,
|
||||||
|
this._addressSearchLimit,
|
||||||
|
);
|
||||||
|
if (_.isUndefined(matchedDerivedKeyInfo)) {
|
||||||
|
throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`);
|
||||||
|
}
|
||||||
|
return matchedDerivedKeyInfo;
|
||||||
|
}
|
||||||
|
private _initialDerivedKeyInfo(baseDerivationPath: string): DerivedHDKeyInfo {
|
||||||
|
const seed = bip39.mnemonicToSeed(this._mnemonic);
|
||||||
|
const hdKey = HDNode.fromMasterSeed(seed);
|
||||||
|
// Walk down to base derivation level (i.e m/44'/60'/0') and create an initial key at that level
|
||||||
|
// all children will then be walked relative (i.e m/0)
|
||||||
|
const parentKeyDerivationPath = `m/${baseDerivationPath}`;
|
||||||
|
const parentHDKeyAtDerivationPath = hdKey.derive(parentKeyDerivationPath);
|
||||||
|
const address = walletUtils.addressOfHDKey(parentHDKeyAtDerivationPath);
|
||||||
|
const derivedKeyInfo = {
|
||||||
|
address,
|
||||||
|
baseDerivationPath,
|
||||||
|
derivationPath: parentKeyDerivationPath,
|
||||||
|
hdKey: parentHDKeyAtDerivationPath,
|
||||||
|
};
|
||||||
|
return derivedKeyInfo;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,11 @@
|
|||||||
import { assert } from '@0xproject/assert';
|
import { assert } from '@0xproject/assert';
|
||||||
import { JSONRPCRequestPayload } from '@0xproject/types';
|
|
||||||
import EthereumTx = require('ethereumjs-tx');
|
import EthereumTx = require('ethereumjs-tx');
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { Callback, ErrorCallback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types';
|
import { PartialTxParams, WalletSubproviderErrors } from '../types';
|
||||||
|
|
||||||
import { BaseWalletSubprovider } from './base_wallet_subprovider';
|
import { BaseWalletSubprovider } from './base_wallet_subprovider';
|
||||||
import { Subprovider } from './subprovider';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
|
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
|
||||||
@ -17,6 +15,11 @@ import { Subprovider } from './subprovider';
|
|||||||
export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
|
export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
|
||||||
private _address: string;
|
private _address: string;
|
||||||
private _privateKeyBuffer: Buffer;
|
private _privateKeyBuffer: Buffer;
|
||||||
|
/**
|
||||||
|
* Instantiates a PrivateKeyWalletSubprovider.
|
||||||
|
* @param privateKey The corresponding private key to an Ethereum address
|
||||||
|
* @return PrivateKeyWalletSubprovider instance
|
||||||
|
*/
|
||||||
constructor(privateKey: string) {
|
constructor(privateKey: string) {
|
||||||
assert.isString('privateKey', privateKey);
|
assert.isString('privateKey', privateKey);
|
||||||
super();
|
super();
|
||||||
@ -42,26 +45,40 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
|
|||||||
*/
|
*/
|
||||||
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
|
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
|
||||||
PrivateKeyWalletSubprovider._validateTxParams(txParams);
|
PrivateKeyWalletSubprovider._validateTxParams(txParams);
|
||||||
|
if (!_.isUndefined(txParams.from) && txParams.from !== this._address) {
|
||||||
|
throw new Error(
|
||||||
|
`Requested to sign transaction with address: ${txParams.from}, instantiated with address: ${
|
||||||
|
this._address
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
const tx = new EthereumTx(txParams);
|
const tx = new EthereumTx(txParams);
|
||||||
tx.sign(this._privateKeyBuffer);
|
tx.sign(this._privateKeyBuffer);
|
||||||
const rawTx = `0x${tx.serialize().toString('hex')}`;
|
const rawTx = `0x${tx.serialize().toString('hex')}`;
|
||||||
return rawTx;
|
return rawTx;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a personal Ethereum signed message. The signing address will be
|
* Sign a personal Ethereum signed message. The signing address will be calculated from the private key.
|
||||||
* calculated from the private key.
|
* The address must be provided it must match the address calculated from the private key.
|
||||||
* If you've added the PKWalletSubprovider to your app's provider, you can simply send an `eth_sign`
|
* If you've added this Subprovider 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.
|
* 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.
|
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
||||||
* @param data Message to sign
|
* @param data Hex string message to sign
|
||||||
|
* @param address Address of the account to sign with
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signPersonalMessageAsync(dataIfExists: string): Promise<string> {
|
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
|
||||||
if (_.isUndefined(dataIfExists)) {
|
if (_.isUndefined(data)) {
|
||||||
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
||||||
}
|
}
|
||||||
assert.isHexString('data', dataIfExists);
|
assert.isHexString('data', data);
|
||||||
const dataBuff = ethUtil.toBuffer(dataIfExists);
|
assert.isETHAddressHex('address', address);
|
||||||
|
if (address !== this._address) {
|
||||||
|
throw new Error(
|
||||||
|
`Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const dataBuff = ethUtil.toBuffer(data);
|
||||||
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
||||||
const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer);
|
const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer);
|
||||||
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
|
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
|
@ -1,4 +1,5 @@
|
|||||||
import { ECSignature, JSONRPCRequestPayload } from '@0xproject/types';
|
import { ECSignature, JSONRPCRequestPayload } from '@0xproject/types';
|
||||||
|
import HDNode = require('hdkey');
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
export interface LedgerCommunicationClient {
|
export interface LedgerCommunicationClient {
|
||||||
@ -40,20 +41,33 @@ export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClien
|
|||||||
export interface LedgerSubproviderConfigs {
|
export interface LedgerSubproviderConfigs {
|
||||||
networkId: number;
|
networkId: number;
|
||||||
ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
|
ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync;
|
||||||
derivationPath?: string;
|
baseDerivationPath?: string;
|
||||||
accountFetchingConfigs?: AccountFetchingConfigs;
|
accountFetchingConfigs?: AccountFetchingConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* addressSearchLimit: The maximum number of addresses to search through, defaults to 1000
|
||||||
* numAddressesToReturn: Number of addresses to return from 'eth_accounts' call
|
* numAddressesToReturn: Number of addresses to return from 'eth_accounts' call
|
||||||
* shouldAskForOnDeviceConfirmation: Whether you wish to prompt the user on their Ledger
|
* shouldAskForOnDeviceConfirmation: Whether you wish to prompt the user on their Ledger
|
||||||
* before fetching their addresses
|
* before fetching their addresses
|
||||||
*/
|
*/
|
||||||
export interface AccountFetchingConfigs {
|
export interface AccountFetchingConfigs {
|
||||||
|
addressSearchLimit?: number;
|
||||||
numAddressesToReturn?: number;
|
numAddressesToReturn?: number;
|
||||||
shouldAskForOnDeviceConfirmation?: boolean;
|
shouldAskForOnDeviceConfirmation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mnemonic: The string mnemonic seed
|
||||||
|
* addressSearchLimit: The maximum number of addresses to search through, defaults to 1000
|
||||||
|
* baseDerivationPath: The base derivation path (e.g 44'/60'/0'/0)
|
||||||
|
*/
|
||||||
|
export interface MnemonicWalletSubproviderConfigs {
|
||||||
|
mnemonic: string;
|
||||||
|
addressSearchLimit?: number;
|
||||||
|
baseDerivationPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SignatureData {
|
export interface SignatureData {
|
||||||
hash: string;
|
hash: string;
|
||||||
r: string;
|
r: string;
|
||||||
@ -67,18 +81,12 @@ export interface LedgerGetAddressResult {
|
|||||||
chainCode: string;
|
chainCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LedgerWalletSubprovider {
|
|
||||||
getPath: () => string;
|
|
||||||
setPath: (path: string) => void;
|
|
||||||
setPathIndex: (pathIndex: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PartialTxParams {
|
export interface PartialTxParams {
|
||||||
nonce: string;
|
nonce: string;
|
||||||
gasPrice?: string;
|
gasPrice?: string;
|
||||||
gas: string;
|
gas: string;
|
||||||
to: string;
|
to: string;
|
||||||
from?: string;
|
from: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
data?: string;
|
data?: string;
|
||||||
chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
|
chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
|
||||||
@ -96,12 +104,13 @@ export interface ResponseWithTxParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum WalletSubproviderErrors {
|
export enum WalletSubproviderErrors {
|
||||||
|
AddressNotFound = 'ADDRESS_NOT_FOUND',
|
||||||
DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE',
|
DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE',
|
||||||
SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
|
SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
|
||||||
|
FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID',
|
||||||
}
|
}
|
||||||
export enum LedgerSubproviderErrors {
|
export enum LedgerSubproviderErrors {
|
||||||
TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE',
|
TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE',
|
||||||
FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID',
|
|
||||||
MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED',
|
MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +118,12 @@ export enum NonceSubproviderErrors {
|
|||||||
EmptyParametersFound = 'EMPTY_PARAMETERS_FOUND',
|
EmptyParametersFound = 'EMPTY_PARAMETERS_FOUND',
|
||||||
CannotDetermineAddressFromPayload = 'CANNOT_DETERMINE_ADDRESS_FROM_PAYLOAD',
|
CannotDetermineAddressFromPayload = 'CANNOT_DETERMINE_ADDRESS_FROM_PAYLOAD',
|
||||||
}
|
}
|
||||||
|
export interface DerivedHDKeyInfo {
|
||||||
|
address: string;
|
||||||
|
baseDerivationPath: string;
|
||||||
|
derivationPath: string;
|
||||||
|
hdKey: HDNode;
|
||||||
|
}
|
||||||
|
|
||||||
export type ErrorCallback = (err: Error | null, data?: any) => void;
|
export type ErrorCallback = (err: Error | null, data?: any) => void;
|
||||||
export type Callback = () => void;
|
export type Callback = () => void;
|
||||||
|
79
packages/subproviders/src/utils/wallet_utils.ts
Normal file
79
packages/subproviders/src/utils/wallet_utils.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import ethUtil = require('ethereumjs-util');
|
||||||
|
import HDNode = require('hdkey');
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { DerivedHDKeyInfo, WalletSubproviderErrors } from '../types';
|
||||||
|
|
||||||
|
const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
|
||||||
|
|
||||||
|
class DerivedHDKeyInfoIterator implements IterableIterator<DerivedHDKeyInfo> {
|
||||||
|
private _parentDerivedKeyInfo: DerivedHDKeyInfo;
|
||||||
|
private _searchLimit: number;
|
||||||
|
private _index: number;
|
||||||
|
|
||||||
|
constructor(initialDerivedKey: DerivedHDKeyInfo, searchLimit: number = DEFAULT_ADDRESS_SEARCH_LIMIT) {
|
||||||
|
this._searchLimit = searchLimit;
|
||||||
|
this._parentDerivedKeyInfo = initialDerivedKey;
|
||||||
|
this._index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): IteratorResult<DerivedHDKeyInfo> {
|
||||||
|
const baseDerivationPath = this._parentDerivedKeyInfo.baseDerivationPath;
|
||||||
|
const derivationIndex = this._index;
|
||||||
|
const fullDerivationPath = `m/${baseDerivationPath}/${derivationIndex}`;
|
||||||
|
const path = `m/${derivationIndex}`;
|
||||||
|
const hdKey = this._parentDerivedKeyInfo.hdKey.derive(path);
|
||||||
|
const address = walletUtils.addressOfHDKey(hdKey);
|
||||||
|
const derivedKey: DerivedHDKeyInfo = {
|
||||||
|
address,
|
||||||
|
hdKey,
|
||||||
|
baseDerivationPath,
|
||||||
|
derivationPath: fullDerivationPath,
|
||||||
|
};
|
||||||
|
const done = this._index === this._searchLimit;
|
||||||
|
this._index++;
|
||||||
|
return {
|
||||||
|
done,
|
||||||
|
value: derivedKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public [Symbol.iterator](): IterableIterator<DerivedHDKeyInfo> {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const walletUtils = {
|
||||||
|
calculateDerivedHDKeyInfos(parentDerivedKeyInfo: DerivedHDKeyInfo, numberOfKeys: number): DerivedHDKeyInfo[] {
|
||||||
|
const derivedKeys: DerivedHDKeyInfo[] = [];
|
||||||
|
const derivedKeyIterator = new DerivedHDKeyInfoIterator(parentDerivedKeyInfo, numberOfKeys);
|
||||||
|
for (const key of derivedKeyIterator) {
|
||||||
|
derivedKeys.push(key);
|
||||||
|
}
|
||||||
|
return derivedKeys;
|
||||||
|
},
|
||||||
|
findDerivedKeyInfoForAddressIfExists(
|
||||||
|
address: string,
|
||||||
|
parentDerivedKeyInfo: DerivedHDKeyInfo,
|
||||||
|
searchLimit: number,
|
||||||
|
): DerivedHDKeyInfo | undefined {
|
||||||
|
let matchedKey: DerivedHDKeyInfo | undefined;
|
||||||
|
const derivedKeyIterator = new DerivedHDKeyInfoIterator(parentDerivedKeyInfo, searchLimit);
|
||||||
|
for (const key of derivedKeyIterator) {
|
||||||
|
if (key.address === address) {
|
||||||
|
matchedKey = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchedKey;
|
||||||
|
},
|
||||||
|
addressOfHDKey(hdKey: HDNode): string {
|
||||||
|
const shouldSanitizePublicKey = true;
|
||||||
|
const derivedPublicKey = hdKey.publicKey;
|
||||||
|
const ethereumAddressUnprefixed = ethUtil
|
||||||
|
.publicToAddress(derivedPublicKey, shouldSanitizePublicKey)
|
||||||
|
.toString('hex');
|
||||||
|
const address = ethUtil.addHexPrefix(ethereumAddressUnprefixed).toLowerCase();
|
||||||
|
return address;
|
||||||
|
},
|
||||||
|
};
|
@ -33,7 +33,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
ledgerSubprovider = new LedgerSubprovider({
|
ledgerSubprovider = new LedgerSubprovider({
|
||||||
networkId,
|
networkId,
|
||||||
ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync,
|
ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync,
|
||||||
derivationPath: fixtureData.TESTRPC_DERIVATION_PATH,
|
baseDerivationPath: fixtureData.TESTRPC_BASE_DERIVATION_PATH,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('direct method calls', () => {
|
describe('direct method calls', () => {
|
||||||
@ -42,9 +42,10 @@ describe('LedgerSubprovider', () => {
|
|||||||
expect(accounts[0]).to.not.be.an('undefined');
|
expect(accounts[0]).to.not.be.an('undefined');
|
||||||
expect(accounts.length).to.be.equal(10);
|
expect(accounts.length).to.be.equal(10);
|
||||||
});
|
});
|
||||||
it('returns the expected first account from a ledger set up with the test mnemonic', async () => {
|
it('returns the expected accounts from a ledger set up with the test mnemonic', async () => {
|
||||||
const accounts = await ledgerSubprovider.getAccountsAsync();
|
const accounts = await ledgerSubprovider.getAccountsAsync();
|
||||||
expect(accounts[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
|
expect(accounts[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
|
||||||
|
expect(accounts[1]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_1);
|
||||||
});
|
});
|
||||||
it('returns requested number of accounts', async () => {
|
it('returns requested number of accounts', async () => {
|
||||||
const numberOfAccounts = 20;
|
const numberOfAccounts = 20;
|
||||||
@ -54,14 +55,29 @@ describe('LedgerSubprovider', () => {
|
|||||||
});
|
});
|
||||||
it('signs a personal message', async () => {
|
it('signs a personal message', async () => {
|
||||||
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data);
|
const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(
|
||||||
expect(ecSignatureHex.length).to.be.equal(132);
|
data,
|
||||||
|
fixtureData.TEST_RPC_ACCOUNT_0,
|
||||||
|
);
|
||||||
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
||||||
});
|
});
|
||||||
|
it('signs a personal message with second address', async () => {
|
||||||
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(
|
||||||
|
data,
|
||||||
|
fixtureData.TEST_RPC_ACCOUNT_1,
|
||||||
|
);
|
||||||
|
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_ACCOUNT_1_SIGNED_RESULT);
|
||||||
|
});
|
||||||
it('signs a transaction', async () => {
|
it('signs a transaction', async () => {
|
||||||
const txHex = await ledgerSubprovider.signTransactionAsync(fixtureData.TX_DATA);
|
const txHex = await ledgerSubprovider.signTransactionAsync(fixtureData.TX_DATA);
|
||||||
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
||||||
});
|
});
|
||||||
|
it('signs a transaction with the second address', async () => {
|
||||||
|
const txData = { ...fixtureData.TX_DATA, from: fixtureData.TEST_RPC_ACCOUNT_1 };
|
||||||
|
const txHex = await ledgerSubprovider.signTransactionAsync(txData);
|
||||||
|
expect(txHex).to.be.equal(fixtureData.TX_DATA_ACCOUNT_1_SIGNED_RESULT);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('calls through a provider', () => {
|
describe('calls through a provider', () => {
|
||||||
let defaultProvider: Web3ProviderEngine;
|
let defaultProvider: Web3ProviderEngine;
|
||||||
|
@ -82,7 +82,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
});
|
});
|
||||||
it('signs a personal message', async () => {
|
it('signs a personal message', async () => {
|
||||||
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data);
|
const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data, FAKE_ADDRESS);
|
||||||
expect(ecSignatureHex).to.be.equal(
|
expect(ecSignatureHex).to.be.equal(
|
||||||
'0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001',
|
'0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001',
|
||||||
);
|
);
|
||||||
@ -94,7 +94,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
return expect(
|
return expect(
|
||||||
Promise.all([
|
Promise.all([
|
||||||
ledgerSubprovider.getAccountsAsync(),
|
ledgerSubprovider.getAccountsAsync(),
|
||||||
ledgerSubprovider.signPersonalMessageAsync(data),
|
ledgerSubprovider.signPersonalMessageAsync(data, FAKE_ADDRESS),
|
||||||
]),
|
]),
|
||||||
).to.be.rejectedWith(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed);
|
).to.be.rejectedWith(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed);
|
||||||
});
|
});
|
||||||
@ -129,7 +129,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'eth_sign',
|
method: 'eth_sign',
|
||||||
params: ['0x0000000000000000000000000000000000000000', messageHex],
|
params: [FAKE_ADDRESS, messageHex],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -146,7 +146,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'personal_sign',
|
method: 'personal_sign',
|
||||||
params: [messageHex, '0x0000000000000000000000000000000000000000'],
|
params: [messageHex, FAKE_ADDRESS],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -165,6 +165,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
gasPrice: '0x00',
|
gasPrice: '0x00',
|
||||||
nonce: '0x00',
|
nonce: '0x00',
|
||||||
gas: '0x00',
|
gas: '0x00',
|
||||||
|
from: FAKE_ADDRESS,
|
||||||
};
|
};
|
||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
@ -187,7 +188,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'eth_sign',
|
method: 'eth_sign',
|
||||||
params: ['0x0000000000000000000000000000000000000000', nonHexMessage],
|
params: [FAKE_ADDRESS, nonHexMessage],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -202,7 +203,7 @@ describe('LedgerSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'personal_sign',
|
method: 'personal_sign',
|
||||||
params: [nonHexMessage, '0x0000000000000000000000000000000000000000'],
|
params: [nonHexMessage, FAKE_ADDRESS],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
import { JSONRPCResponsePayload } from '@0xproject/types';
|
||||||
|
import * as chai from 'chai';
|
||||||
|
import * as ethUtils from 'ethereumjs-util';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import Web3ProviderEngine = require('web3-provider-engine');
|
||||||
|
|
||||||
|
import { GanacheSubprovider, MnemonicWalletSubprovider } from '../../src/';
|
||||||
|
import {
|
||||||
|
DoneCallback,
|
||||||
|
LedgerCommunicationClient,
|
||||||
|
LedgerSubproviderErrors,
|
||||||
|
WalletSubproviderErrors,
|
||||||
|
} from '../../src/types';
|
||||||
|
import { chaiSetup } from '../chai_setup';
|
||||||
|
import { fixtureData } from '../utils/fixture_data';
|
||||||
|
import { reportCallbackErrors } from '../utils/report_callback_errors';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
describe('MnemonicWalletSubprovider', () => {
|
||||||
|
let subprovider: MnemonicWalletSubprovider;
|
||||||
|
before(async () => {
|
||||||
|
subprovider = new MnemonicWalletSubprovider({
|
||||||
|
mnemonic: fixtureData.TEST_RPC_MNEMONIC,
|
||||||
|
baseDerivationPath: fixtureData.TEST_RPC_MNEMONIC_BASE_DERIVATION_PATH,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('direct method calls', () => {
|
||||||
|
describe('success cases', () => {
|
||||||
|
it('returns the accounts', async () => {
|
||||||
|
const accounts = await subprovider.getAccountsAsync();
|
||||||
|
expect(accounts[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
|
||||||
|
expect(accounts[1]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_1);
|
||||||
|
expect(accounts.length).to.be.equal(10);
|
||||||
|
});
|
||||||
|
it('signs a personal message', async () => {
|
||||||
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const ecSignatureHex = await subprovider.signPersonalMessageAsync(data, fixtureData.TEST_RPC_ACCOUNT_0);
|
||||||
|
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
||||||
|
});
|
||||||
|
it('signs a personal message with second address', async () => {
|
||||||
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const ecSignatureHex = await subprovider.signPersonalMessageAsync(data, fixtureData.TEST_RPC_ACCOUNT_1);
|
||||||
|
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_ACCOUNT_1_SIGNED_RESULT);
|
||||||
|
});
|
||||||
|
it('signs a transaction', async () => {
|
||||||
|
const txHex = await subprovider.signTransactionAsync(fixtureData.TX_DATA);
|
||||||
|
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
||||||
|
});
|
||||||
|
it('signs a transaction with the second address', async () => {
|
||||||
|
const txData = { ...fixtureData.TX_DATA, from: fixtureData.TEST_RPC_ACCOUNT_1 };
|
||||||
|
const txHex = await subprovider.signTransactionAsync(txData);
|
||||||
|
expect(txHex).to.be.equal(fixtureData.TX_DATA_ACCOUNT_1_SIGNED_RESULT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('failure cases', () => {
|
||||||
|
it('throws an error if address is invalid ', async () => {
|
||||||
|
const txData = { ...fixtureData.TX_DATA, from: '0x0' };
|
||||||
|
return expect(subprovider.signTransactionAsync(txData)).to.be.rejectedWith(
|
||||||
|
WalletSubproviderErrors.FromAddressMissingOrInvalid,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('throws an error if address is valid format but not found', async () => {
|
||||||
|
const txData = { ...fixtureData.TX_DATA, from: fixtureData.NULL_ADDRESS };
|
||||||
|
return expect(subprovider.signTransactionAsync(txData)).to.be.rejectedWith(
|
||||||
|
`${WalletSubproviderErrors.AddressNotFound}: ${fixtureData.NULL_ADDRESS}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('calls through a provider', () => {
|
||||||
|
let provider: Web3ProviderEngine;
|
||||||
|
before(() => {
|
||||||
|
provider = new Web3ProviderEngine();
|
||||||
|
provider.addProvider(subprovider);
|
||||||
|
const ganacheSubprovider = new GanacheSubprovider({});
|
||||||
|
provider.addProvider(ganacheSubprovider);
|
||||||
|
provider.start();
|
||||||
|
});
|
||||||
|
describe('success cases', () => {
|
||||||
|
it('returns a list of accounts', (done: DoneCallback) => {
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_accounts',
|
||||||
|
params: [],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.be.a('null');
|
||||||
|
expect(response.result[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
|
||||||
|
expect(response.result.length).to.be.equal(10);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('signs a personal message with eth_sign', (done: DoneCallback) => {
|
||||||
|
const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_sign',
|
||||||
|
params: [fixtureData.TEST_RPC_ACCOUNT_0, messageHex],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.be.a('null');
|
||||||
|
expect(response.result).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('signs a personal message with personal_sign', (done: DoneCallback) => {
|
||||||
|
const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'personal_sign',
|
||||||
|
params: [messageHex, fixtureData.TEST_RPC_ACCOUNT_0],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.be.a('null');
|
||||||
|
expect(response.result).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('failure cases', () => {
|
||||||
|
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
||||||
|
const nonHexMessage = 'hello world';
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_sign',
|
||||||
|
params: [fixtureData.TEST_RPC_ACCOUNT_0, nonHexMessage],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('should throw if `data` param not hex when calling personal_sign', (done: DoneCallback) => {
|
||||||
|
const nonHexMessage = 'hello world';
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'personal_sign',
|
||||||
|
params: [nonHexMessage, fixtureData.TEST_RPC_ACCOUNT_0],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('should throw if `address` param not found when calling personal_sign', (done: DoneCallback) => {
|
||||||
|
const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'personal_sign',
|
||||||
|
params: [messageHex, fixtureData.NULL_ADDRESS],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal(
|
||||||
|
`${WalletSubproviderErrors.AddressNotFound}: ${fixtureData.NULL_ADDRESS}`,
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => {
|
||||||
|
const tx = {
|
||||||
|
to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
|
||||||
|
value: '0xde0b6b3a7640000',
|
||||||
|
};
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_sendTransaction',
|
||||||
|
params: [tx],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal(WalletSubproviderErrors.SenderInvalidOrNotSupplied);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
it('should throw if `from` param invalid address when calling eth_sendTransaction', (done: DoneCallback) => {
|
||||||
|
const tx = {
|
||||||
|
to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
|
||||||
|
from: '0xIncorrectEthereumAddress',
|
||||||
|
value: '0xde0b6b3a7640000',
|
||||||
|
};
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_sendTransaction',
|
||||||
|
params: [tx],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal(WalletSubproviderErrors.SenderInvalidOrNotSupplied);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -32,7 +32,7 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
});
|
});
|
||||||
it('signs a personal message', async () => {
|
it('signs a personal message', async () => {
|
||||||
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
const ecSignatureHex = await subprovider.signPersonalMessageAsync(data);
|
const ecSignatureHex = await subprovider.signPersonalMessageAsync(data, fixtureData.TEST_RPC_ACCOUNT_0);
|
||||||
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
expect(ecSignatureHex).to.be.equal(fixtureData.PERSONAL_MESSAGE_SIGNED_RESULT);
|
||||||
});
|
});
|
||||||
it('signs a transaction', async () => {
|
it('signs a transaction', async () => {
|
||||||
@ -71,7 +71,7 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'eth_sign',
|
method: 'eth_sign',
|
||||||
params: ['0x0000000000000000000000000000000000000000', messageHex],
|
params: [fixtureData.TEST_RPC_ACCOUNT_0, messageHex],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -86,7 +86,7 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'personal_sign',
|
method: 'personal_sign',
|
||||||
params: [messageHex, '0x0000000000000000000000000000000000000000'],
|
params: [messageHex, fixtureData.TEST_RPC_ACCOUNT_0],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -103,7 +103,7 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'eth_sign',
|
method: 'eth_sign',
|
||||||
params: ['0x0000000000000000000000000000000000000000', nonHexMessage],
|
params: [fixtureData.TEST_RPC_ACCOUNT_0, nonHexMessage],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -118,7 +118,7 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
const payload = {
|
const payload = {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'personal_sign',
|
method: 'personal_sign',
|
||||||
params: [nonHexMessage, '0x0000000000000000000000000000000000000000'],
|
params: [nonHexMessage, fixtureData.TEST_RPC_ACCOUNT_0],
|
||||||
id: 1,
|
id: 1,
|
||||||
};
|
};
|
||||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
@ -128,6 +128,25 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
});
|
});
|
||||||
provider.sendAsync(payload, callback);
|
provider.sendAsync(payload, callback);
|
||||||
});
|
});
|
||||||
|
it('should throw if `address` param is not the address from private key when calling personal_sign', (done: DoneCallback) => {
|
||||||
|
const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'personal_sign',
|
||||||
|
params: [messageHex, fixtureData.TEST_RPC_ACCOUNT_1],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal(
|
||||||
|
`Requested to sign message with address: ${
|
||||||
|
fixtureData.TEST_RPC_ACCOUNT_1
|
||||||
|
}, instantiated with address: ${fixtureData.TEST_RPC_ACCOUNT_0}`,
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => {
|
it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => {
|
||||||
const tx = {
|
const tx = {
|
||||||
to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
|
to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66',
|
||||||
@ -165,6 +184,21 @@ describe('PrivateKeyWalletSubprovider', () => {
|
|||||||
});
|
});
|
||||||
provider.sendAsync(payload, callback);
|
provider.sendAsync(payload, callback);
|
||||||
});
|
});
|
||||||
|
it('should throw if `address` param not found when calling personal_sign', (done: DoneCallback) => {
|
||||||
|
const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'personal_sign',
|
||||||
|
params: [messageHex, '0x0'],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.not.be.a('null');
|
||||||
|
expect(err.message).to.be.equal(`Expected address to be of type ETHAddressHex, encountered: 0x0`);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||||
|
const TEST_RPC_ACCOUNT_1 = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb';
|
||||||
|
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
const networkId = 42;
|
const networkId = 42;
|
||||||
export const fixtureData = {
|
export const fixtureData = {
|
||||||
|
NULL_ADDRESS,
|
||||||
TEST_RPC_ACCOUNT_0,
|
TEST_RPC_ACCOUNT_0,
|
||||||
TEST_RPC_ACCOUNT_0_ACCOUNT_PRIVATE_KEY: 'F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D',
|
TEST_RPC_ACCOUNT_0_ACCOUNT_PRIVATE_KEY: 'F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D',
|
||||||
|
TEST_RPC_ACCOUNT_1,
|
||||||
|
TEST_RPC_MNEMONIC: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
|
||||||
|
TEST_RPC_MNEMONIC_BASE_DERIVATION_PATH: `44'/60'/0'/0`,
|
||||||
PERSONAL_MESSAGE_STRING: 'hello world',
|
PERSONAL_MESSAGE_STRING: 'hello world',
|
||||||
PERSONAL_MESSAGE_SIGNED_RESULT:
|
PERSONAL_MESSAGE_SIGNED_RESULT:
|
||||||
'0x1b0ec5e2908e993d0c8ab6b46da46be2688fdf03c7ea6686075de37392e50a7d7fcc531446699132fbda915bd989882e0064d417018773a315fb8d43ed063c9b00',
|
'0x1b0ec5e2908e993d0c8ab6b46da46be2688fdf03c7ea6686075de37392e50a7d7fcc531446699132fbda915bd989882e0064d417018773a315fb8d43ed063c9b00',
|
||||||
TESTRPC_DERIVATION_PATH: `m/44'/60'/0'/0`,
|
PERSONAL_MESSAGE_ACCOUNT_1_SIGNED_RESULT:
|
||||||
|
'0xe7ae0c21d02eb38f2c2a20d9d7876a98cc7ef035b7a4559d49375e2ec735e06f0d0ab0ff92ee56c5ffc28d516e6ed0692d0270feae8796408dbef060c6c7100f01',
|
||||||
|
TESTRPC_BASE_DERIVATION_PATH: `m/44'/60'/0'/0`,
|
||||||
NETWORK_ID: networkId,
|
NETWORK_ID: networkId,
|
||||||
TX_DATA: {
|
TX_DATA: {
|
||||||
nonce: '0x00',
|
nonce: '0x00',
|
||||||
gasPrice: '0x0',
|
gasPrice: '0x0',
|
||||||
gas: '0x2710',
|
gas: '0x2710',
|
||||||
to: '0x0000000000000000000000000000000000000000',
|
to: NULL_ADDRESS,
|
||||||
value: '0x00',
|
value: '0x00',
|
||||||
chainId: networkId,
|
chainId: networkId,
|
||||||
from: TEST_RPC_ACCOUNT_0,
|
from: TEST_RPC_ACCOUNT_0,
|
||||||
@ -20,4 +28,6 @@ export const fixtureData = {
|
|||||||
// This is the signed result of the abouve Transaction Data
|
// This is the signed result of the abouve Transaction Data
|
||||||
TX_DATA_SIGNED_RESULT:
|
TX_DATA_SIGNED_RESULT:
|
||||||
'0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178',
|
'0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178',
|
||||||
|
TX_DATA_ACCOUNT_1_SIGNED_RESULT:
|
||||||
|
'0xf85f8080822710940000000000000000000000000000000000000000808078a04b02af7ff3f18ce114b601542cc8ebdc50921354f75dd510d31793453a0710e6a0540082a01e475465801b8186a2edc79ec1a2dcf169b9781c25a58a417023c9ca',
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig",
|
"extends": "../../tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "lib"
|
"outDir": "lib",
|
||||||
|
"downlevelIteration": true
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*", "./test/**/*"]
|
"include": ["./src/**/*", "./test/**/*"]
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Add types for more packages",
|
"note": "Add types for more packages",
|
||||||
"pr": 501
|
"pr": 501
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add types for HDKey",
|
||||||
|
"pr": 507
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timestamp": 1523462196
|
"timestamp": 1523462196
|
||||||
|
11
packages/typescript-typings/types/hdkey/index.d.ts
vendored
Normal file
11
packages/typescript-typings/types/hdkey/index.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
declare module 'hdkey' {
|
||||||
|
class HDNode {
|
||||||
|
public static fromMasterSeed(seed: Buffer): HDNode;
|
||||||
|
public publicKey: Buffer;
|
||||||
|
public privateKey: Buffer;
|
||||||
|
public chainCode: Buffer;
|
||||||
|
public constructor();
|
||||||
|
public derive(path: string): HDNode;
|
||||||
|
}
|
||||||
|
export = HDNode;
|
||||||
|
}
|
@ -78,7 +78,7 @@ export class Blockchain {
|
|||||||
private _userAddressIfExists: string;
|
private _userAddressIfExists: string;
|
||||||
private _cachedProvider: Provider;
|
private _cachedProvider: Provider;
|
||||||
private _cachedProviderNetworkId: number;
|
private _cachedProviderNetworkId: number;
|
||||||
private _ledgerSubprovider: LedgerWalletSubprovider;
|
private _ledgerSubprovider: LedgerSubprovider;
|
||||||
private _defaultGasPrice: BigNumber;
|
private _defaultGasPrice: BigNumber;
|
||||||
private static _getNameGivenProvider(provider: Provider): string {
|
private static _getNameGivenProvider(provider: Provider): string {
|
||||||
const providerType = utils.getProviderType(provider);
|
const providerType = utils.getProviderType(provider);
|
||||||
@ -180,12 +180,6 @@ export class Blockchain {
|
|||||||
}
|
}
|
||||||
this._ledgerSubprovider.setPath(path);
|
this._ledgerSubprovider.setPath(path);
|
||||||
}
|
}
|
||||||
public updateLedgerDerivationIndex(pathIndex: number) {
|
|
||||||
if (_.isUndefined(this._ledgerSubprovider)) {
|
|
||||||
return; // noop
|
|
||||||
}
|
|
||||||
this._ledgerSubprovider.setPathIndex(pathIndex);
|
|
||||||
}
|
|
||||||
public async updateProviderToLedgerAsync(networkId: number) {
|
public async updateProviderToLedgerAsync(networkId: number) {
|
||||||
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
||||||
|
|
||||||
|
@ -199,7 +199,6 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
|
|||||||
}
|
}
|
||||||
private _onAddressSelected(selectedRowIndexes: number[]) {
|
private _onAddressSelected(selectedRowIndexes: number[]) {
|
||||||
const selectedRowIndex = selectedRowIndexes[0];
|
const selectedRowIndex = selectedRowIndexes[0];
|
||||||
this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex);
|
|
||||||
const selectedAddress = this.state.userAddresses[selectedRowIndex];
|
const selectedAddress = this.state.userAddresses[selectedRowIndex];
|
||||||
const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
|
const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
|
||||||
this.props.dispatcher.updateUserAddress(selectedAddress);
|
this.props.dispatcher.updateUserAddress(selectedAddress);
|
||||||
|
@ -30,6 +30,8 @@ const docSections = {
|
|||||||
redundantRPCSubprovider: 'redundantRPCSubprovider',
|
redundantRPCSubprovider: 'redundantRPCSubprovider',
|
||||||
ganacheSubprovider: 'ganacheSubprovider',
|
ganacheSubprovider: 'ganacheSubprovider',
|
||||||
nonceTrackerSubprovider: 'nonceTrackerSubprovider',
|
nonceTrackerSubprovider: 'nonceTrackerSubprovider',
|
||||||
|
privateKeyWalletSubprovider: 'privateKeyWalletSubprovider',
|
||||||
|
mnemonicWalletSubprovider: 'mnemonicWalletSubprovider',
|
||||||
types: docConstants.TYPES_SECTION_NAME,
|
types: docConstants.TYPES_SECTION_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ const docsInfoConfig: DocsInfoConfig = {
|
|||||||
subprovider: [docSections.subprovider],
|
subprovider: [docSections.subprovider],
|
||||||
['ledger-subprovider']: [docSections.ledgerSubprovider],
|
['ledger-subprovider']: [docSections.ledgerSubprovider],
|
||||||
['ledger-node-hid-issue']: [docSections.ledgerNodeHid],
|
['ledger-node-hid-issue']: [docSections.ledgerNodeHid],
|
||||||
|
['private-key-wallet-subprovider']: [docSections.privateKeyWalletSubprovider],
|
||||||
|
['mnemonic-wallet-subprovider']: [docSections.mnemonicWalletSubprovider],
|
||||||
['factory-methods']: [docSections.factoryMethods],
|
['factory-methods']: [docSections.factoryMethods],
|
||||||
['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider],
|
['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider],
|
||||||
['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider],
|
['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider],
|
||||||
@ -61,6 +65,8 @@ const docsInfoConfig: DocsInfoConfig = {
|
|||||||
sectionNameToModulePath: {
|
sectionNameToModulePath: {
|
||||||
[docSections.subprovider]: ['"subproviders/src/subproviders/subprovider"'],
|
[docSections.subprovider]: ['"subproviders/src/subproviders/subprovider"'],
|
||||||
[docSections.ledgerSubprovider]: ['"subproviders/src/subproviders/ledger"'],
|
[docSections.ledgerSubprovider]: ['"subproviders/src/subproviders/ledger"'],
|
||||||
|
[docSections.privateKeyWalletSubprovider]: ['"subproviders/src/subproviders/private_key_wallet"'],
|
||||||
|
[docSections.mnemonicWalletSubprovider]: ['"subproviders/src/subproviders/mnemonic_wallet"'],
|
||||||
[docSections.factoryMethods]: ['"subproviders/src/index"'],
|
[docSections.factoryMethods]: ['"subproviders/src/index"'],
|
||||||
[docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'],
|
[docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'],
|
||||||
[docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'],
|
[docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'],
|
||||||
@ -75,6 +81,8 @@ const docsInfoConfig: DocsInfoConfig = {
|
|||||||
visibleConstructors: [
|
visibleConstructors: [
|
||||||
docSections.subprovider,
|
docSections.subprovider,
|
||||||
docSections.ledgerSubprovider,
|
docSections.ledgerSubprovider,
|
||||||
|
docSections.privateKeyWalletSubprovider,
|
||||||
|
docSections.mnemonicWalletSubprovider,
|
||||||
docSections.emptyWalletSubprovider,
|
docSections.emptyWalletSubprovider,
|
||||||
docSections.fakeGasEstimateSubprovider,
|
docSections.fakeGasEstimateSubprovider,
|
||||||
docSections.injectedWeb3Subprovider,
|
docSections.injectedWeb3Subprovider,
|
||||||
@ -98,6 +106,7 @@ const docsInfoConfig: DocsInfoConfig = {
|
|||||||
'PartialTxParams',
|
'PartialTxParams',
|
||||||
'LedgerEthereumClient',
|
'LedgerEthereumClient',
|
||||||
'LedgerSubproviderConfigs',
|
'LedgerSubproviderConfigs',
|
||||||
|
'MnemonicWalletSubproviderConfigs',
|
||||||
'OnNextCompleted',
|
'OnNextCompleted',
|
||||||
'Provider',
|
'Provider',
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user