feat(instant): fallback to an empty wallet provider when none is injected

This commit is contained in:
Brandon Millman 2018-11-06 20:25:30 -08:00
parent c30dca6961
commit dfbf10c94b
15 changed files with 335 additions and 284 deletions

View File

@ -49,6 +49,7 @@
"@0x/asset-buyer": "^2.1.0",
"@0x/json-schemas": "^2.0.0",
"@0x/order-utils": "^2.0.0",
"@0x/subproviders": "^2.1.0",
"@0x/types": "^1.2.0",
"@0x/typescript-typings": "^3.0.3",
"@0x/utils": "^2.0.3",

View File

@ -17,7 +17,7 @@ import { Text } from './ui/text';
export interface BuyButtonProps {
buyQuote?: BuyQuote;
assetBuyer?: AssetBuyer;
assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
@ -34,7 +34,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
const shouldDisableButton = _.isUndefined(this.props.buyQuote);
return (
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
@ -46,11 +46,13 @@ export class BuyButton extends React.Component<BuyButtonProps> {
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
const { buyQuote, assetBuyer, affiliateInfo } = this.props;
if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) {
if (_.isUndefined(buyQuote)) {
return;
}
this.props.onValidationPending(buyQuote);
// TODO(bmillman): move address and balance fetching to the async state
const web3Wrapper = new Web3Wrapper(assetBuyer.provider);
const takerAddress = await getBestAddress(web3Wrapper);

View File

@ -15,7 +15,7 @@ import { Text } from './ui/text';
export interface BuyOrderStateButtonProps {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
assetBuyer?: AssetBuyer;
assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;

View File

@ -1,23 +1,22 @@
import { AssetBuyer } from '@0x/asset-buyer';
import { ObjectMap, SignedOrder } from '@0x/types';
import { ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import * as React from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import { oc } from 'ts-optchain';
import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider';
import { asyncData } from '../redux/async_data';
import { INITIAL_STATE, State } from '../redux/reducer';
import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
import { AffiliateInfo, AssetMetaData, Network } from '../types';
import { AffiliateInfo, AssetMetaData, Network, OrderSource, ProviderState, ProviderType } from '../types';
import { assetUtils } from '../util/asset';
import { assetBuyerFactory } from '../util/asset_buyer_factory';
import { errorFlasher } from '../util/error_flasher';
import { gasPriceEstimator } from '../util/gas_price_estimator';
import { getInjectedProvider } from '../util/injected_provider';
import { providerFactory } from '../util/provider_factory';
fonts.include();
@ -25,7 +24,7 @@ export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
Partial<ZeroExInstantProviderOptionalProps>;
export interface ZeroExInstantProviderRequiredProps {
orderSource: string | SignedOrder[];
orderSource: OrderSource;
}
export interface ZeroExInstantProviderOptionalProps {
@ -41,30 +40,43 @@ export interface ZeroExInstantProviderOptionalProps {
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
private readonly _store: Store;
// TODO(fragosti): Write tests for this beast once we inject a provider.
private static _mergeInitialStateWithProps(props: ZeroExInstantProviderProps, state: State = INITIAL_STATE): State {
const networkId = props.networkId || state.network;
// TODO: Proper wallet connect flow
const provider = props.provider || getInjectedProvider();
const assetBuyerOptions = {
networkId,
};
let assetBuyer;
if (_.isString(props.orderSource)) {
assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
provider,
props.orderSource,
assetBuyerOptions,
);
private static _mergeDefaultStateWithProps(
props: ZeroExInstantProviderProps,
defaultState: DefaultState = DEFAULT_STATE,
): State {
// use the networkId passed in with the props, otherwise default to that of the default state (1, mainnet)
const networkId = props.networkId || defaultState.network;
// construct the ProviderState
let provider: Provider;
let providerType: ProviderType;
if (!_.isUndefined(props.provider)) {
provider = props.provider;
providerType = ProviderType.Props;
} else {
assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.orderSource, assetBuyerOptions);
const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists();
if (!_.isUndefined(injectedProviderIfExists)) {
provider = injectedProviderIfExists;
providerType = ProviderType.Injected;
} else {
provider = providerFactory.getFallbackNoSigningProvider(networkId);
providerType = ProviderType.FallbackEmptyWallet;
}
}
const providerState: ProviderState = {
provider,
type: providerType,
web3Wrapper: new Web3Wrapper(provider),
assetBuyer: assetBuyerFactory.getAssetBuyer(provider, props.orderSource, networkId),
};
// merge the additional additionalAssetMetaDataMap with our default map
const completeAssetMetaDataMap = {
...props.additionalAssetMetaDataMap,
...state.assetMetaDataMap,
...defaultState.assetMetaDataMap,
};
// construct the final state
const storeStateFromProps: State = {
...state,
assetBuyer,
...defaultState,
providerState,
network: networkId,
selectedAsset: _.isUndefined(props.defaultSelectedAssetData)
? undefined
@ -74,7 +86,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
networkId,
),
selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount)
? state.selectedAssetAmount
? undefined
: new BigNumber(props.defaultAssetBuyAmount),
availableAssets: _.isUndefined(props.availableAssetDatas)
? undefined
@ -86,10 +98,9 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
constructor(props: ZeroExInstantProviderProps) {
super(props);
const initialAppState = ZeroExInstantProvider._mergeInitialStateWithProps(this.props, INITIAL_STATE);
const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props);
this._store = store.create(initialAppState);
}
public componentDidMount(): void {
const state = this._store.getState();
// tslint:disable-next-line:no-floating-promises
@ -108,7 +119,6 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
// tslint:disable-next-line:no-floating-promises
this._flashErrorIfWrongNetwork();
}
public render(): React.ReactNode {
return (
<ReduxProvider store={this._store}>
@ -116,19 +126,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
</ReduxProvider>
);
}
private readonly _flashErrorIfWrongNetwork = async (): Promise<void> => {
const msToShowError = 30000; // 30 seconds
const network = this._store.getState().network;
const assetBuyerIfExists = this._store.getState().assetBuyer;
const providerIfExists = oc(assetBuyerIfExists).provider();
if (!_.isUndefined(providerIfExists)) {
const web3Wrapper = new Web3Wrapper(providerIfExists);
const state = this._store.getState();
const network = state.network;
const web3Wrapper = state.providerState.web3Wrapper;
const networkOfProvider = await web3Wrapper.getNetworkIdAsync();
if (network !== networkOfProvider) {
const errorMessage = `Wrong network detected. Try switching to ${Network[network]}.`;
errorFlasher.flashNewErrorMessage(this._store.dispatch, errorMessage, msToShowError);
}
}
};
}

View File

@ -1,4 +1,7 @@
import { BigNumber } from '@0x/utils';
import { Network } from './types';
export const BIG_NUMBER_ZERO = new BigNumber(0);
export const ETH_DECIMALS = 18;
export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
@ -13,3 +16,8 @@ export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2';
export const PROGRESS_STALL_AT_WIDTH = '95%';
export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200;
export const ETHEREUM_NODE_URL_BY_NETWORK = {
[Network.Mainnet]: 'https://mainnet.infura.io/',
[Network.Kovan]: 'https://kovan.infura.io/',
};
export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s

View File

@ -14,7 +14,7 @@ import { etherscanUtil } from '../util/etherscan';
interface ConnectedState {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
assetBuyer?: AssetBuyer;
assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
}
@ -29,21 +29,22 @@ interface ConnectedDispatch {
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
}
export interface SelectedAssetBuyOrderStateButtons {}
const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => ({
const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
const assetBuyer = state.providerState.assetBuyer;
return {
buyOrderProcessingState: state.buyOrderState.processState,
assetBuyer: state.assetBuyer,
assetBuyer,
buyQuote: state.latestBuyQuote,
affiliateInfo: state.affiliateInfo,
onViewTransaction: () => {
if (
state.assetBuyer &&
(state.buyOrderState.processState === OrderProcessState.Processing ||
state.buyOrderState.processState === OrderProcessState.Processing ||
state.buyOrderState.processState === OrderProcessState.Success ||
state.buyOrderState.processState === OrderProcessState.Failure)
state.buyOrderState.processState === OrderProcessState.Failure
) {
const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
state.buyOrderState.txHash,
state.assetBuyer.networkId,
assetBuyer.networkId,
);
if (etherscanUrl) {
window.open(etherscanUrl, '_blank');
@ -51,7 +52,8 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButt
}
}
},
});
};
};
const mapDispatchToProps = (
dispatch: Dispatch<Action>,

View File

@ -23,7 +23,7 @@ export interface SelectedERC20AssetAmountInputProps {
}
interface ConnectedState {
assetBuyer?: AssetBuyer;
assetBuyer: AssetBuyer;
value?: BigNumber;
asset?: ERC20Asset;
isDisabled: boolean;
@ -33,7 +33,7 @@ interface ConnectedState {
interface ConnectedDispatch {
updateBuyQuote: (
assetBuyer?: AssetBuyer,
assetBuyer: AssetBuyer,
value?: BigNumber,
asset?: ERC20Asset,
affiliateInfo?: AffiliateInfo,
@ -59,8 +59,9 @@ const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputP
? (state.selectedAsset as ERC20Asset)
: undefined;
const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length;
const assetBuyer = state.providerState.assetBuyer;
return {
assetBuyer: state.assetBuyer,
assetBuyer,
value: state.selectedAssetAmount,
asset: selectedAsset,
isDisabled,
@ -128,7 +129,7 @@ const mapDispatchToProps = (
// reset our buy state
dispatch(actions.setBuyOrderStateNone());
if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) {
if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset)) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.setQuoteRequestStatePending());
// tslint:disable-next-line:no-floating-promises

View File

@ -20,8 +20,8 @@ export const asyncData = {
}
},
fetchAvailableAssetDatasAndDispatchToStore: async (store: Store) => {
const { assetBuyer, assetMetaDataMap, network } = store.getState();
if (!_.isUndefined(assetBuyer)) {
const { providerState, assetMetaDataMap, network } = store.getState();
const assetBuyer = providerState.assetBuyer;
try {
const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network);
@ -32,6 +32,5 @@ export const asyncData = {
// On error, just specify that none are available
store.dispatch(actions.setAvailableAssets([]));
}
}
},
};

View File

@ -1,4 +1,4 @@
import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
import { BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@ -14,41 +14,48 @@ import {
Network,
OrderProcessState,
OrderState,
ProviderState,
} from '../types';
import { Action, ActionTypes } from './actions';
export interface State {
// State that is required and we have defaults for, before props are passed in
export interface DefaultState {
network: Network;
assetBuyer?: AssetBuyer;
assetMetaDataMap: ObjectMap<AssetMetaData>;
selectedAsset?: Asset;
availableAssets?: Asset[];
selectedAssetAmount?: BigNumber;
buyOrderState: OrderState;
ethUsdPrice?: BigNumber;
latestBuyQuote?: BuyQuote;
quoteRequestState: AsyncProcessState;
latestErrorMessage?: string;
latestErrorDisplayStatus: DisplayStatus;
affiliateInfo?: AffiliateInfo;
quoteRequestState: AsyncProcessState;
}
export const INITIAL_STATE: State = {
// State that is required but needs to be derived from the props
interface PropsDerivedState {
providerState: ProviderState;
}
// State that is optional
interface OptionalState {
selectedAsset: Asset;
availableAssets: Asset[];
selectedAssetAmount: BigNumber;
ethUsdPrice: BigNumber;
latestBuyQuote: BuyQuote;
latestErrorMessage: string;
affiliateInfo: AffiliateInfo;
}
export type State = DefaultState & PropsDerivedState & Partial<OptionalState>;
export const DEFAULT_STATE: DefaultState = {
network: Network.Mainnet,
selectedAssetAmount: undefined,
availableAssets: undefined,
assetMetaDataMap,
buyOrderState: { processState: OrderProcessState.None },
ethUsdPrice: undefined,
latestBuyQuote: undefined,
latestErrorMessage: undefined,
latestErrorDisplayStatus: DisplayStatus.Hidden,
quoteRequestState: AsyncProcessState.None,
affiliateInfo: undefined,
};
export const reducer = (state: State = INITIAL_STATE, action: Action): State => {
export const createReducer = (initialState: State) => {
const reducer = (state: State = initialState, action: Action): State => {
switch (action.type) {
case ActionTypes.UPDATE_ETH_USD_PRICE:
return {
@ -180,6 +187,8 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
default:
return state;
}
};
return reducer;
};
const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {

View File

@ -2,12 +2,13 @@ import * as _ from 'lodash';
import { createStore, Store as ReduxStore } from 'redux';
import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly';
import { reducer, State } from './reducer';
import { createReducer, State } from './reducer';
export type Store = ReduxStore<State>;
export const store = {
create: (state: State): Store => {
return createStore(reducer, state, devToolsEnhancer({}));
create: (initialState: State): Store => {
const reducer = createReducer(initialState);
return createStore(reducer, initialState, devToolsEnhancer({}));
},
};

View File

@ -1,4 +1,7 @@
import { AssetProxyId, ObjectMap } from '@0x/types';
import { AssetBuyer } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
// Reusable
export type Maybe<T> = T | undefined;
@ -89,3 +92,24 @@ export interface AffiliateInfo {
feeRecipient: string;
feePercentage: number;
}
/**
* Our provider may be of 3 different types:
* Props: the provider was passed in a prop to the component
* Injected: the provider was injected into `window`
* FallbackEmptyWallet: the provider has no wallet but responds to data requests using infura
*/
export enum ProviderType {
Props = 'PROPS',
Injected = 'INJECTED',
FallbackEmptyWallet = 'FALLBACK_EMPTY_WALLET',
}
export interface ProviderState {
type: ProviderType;
provider: Provider;
assetBuyer: AssetBuyer;
web3Wrapper: Web3Wrapper;
}
export type OrderSource = string | SignedOrder[];

View File

@ -0,0 +1,17 @@
import { AssetBuyer, AssetBuyerOpts } from '@0x/asset-buyer';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { Network, OrderSource } from '../types';
export const assetBuyerFactory = {
getAssetBuyer: (provider: Provider, orderSource: OrderSource, network: Network): AssetBuyer => {
const assetBuyerOptions: Partial<AssetBuyerOpts> = {
networkId: network,
};
const assetBuyer = _.isString(orderSource)
? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, orderSource, assetBuyerOptions)
: AssetBuyer.getAssetBuyerForProvidedOrders(provider, orderSource, assetBuyerOptions);
return assetBuyer;
},
};

View File

@ -1,16 +0,0 @@
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
export const getInjectedProvider = (): Provider => {
const injectedProviderIfExists = (window as any).ethereum;
if (!_.isUndefined(injectedProviderIfExists)) {
// TODO: call enable here when implementing wallet connection flow
return injectedProviderIfExists;
}
const injectedWeb3IfExists = (window as any).web3;
if (!_.isUndefined(injectedWeb3IfExists.currentProvider)) {
return injectedWeb3IfExists.currentProvider;
} else {
throw new Error(`No injected web3 found`);
}
};

View File

@ -0,0 +1,34 @@
import { EmptyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { BLOCK_POLLING_INTERVAL_MS, ETHEREUM_NODE_URL_BY_NETWORK } from '../constants';
import { Maybe, Network } from '../types';
export const providerFactory = {
getInjectedProviderIfExists: (): Maybe<Provider> => {
const injectedProviderIfExists = (window as any).ethereum;
if (!_.isUndefined(injectedProviderIfExists)) {
return injectedProviderIfExists;
}
const injectedWeb3IfExists = (window as any).web3;
if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
return injectedWeb3IfExists.currentProvider;
}
return undefined;
},
getFallbackNoSigningProvider: (network: Network): Provider => {
const providerEngine = new Web3ProviderEngine({
pollingInterval: BLOCK_POLLING_INTERVAL_MS,
});
// Intercept calls to `eth_accounts` and always return empty
providerEngine.addProvider(new EmptyWalletSubprovider());
// Construct an RPC subprovider, all data based requests will be sent via the RPCSubprovider
// TODO(bmillman): make this more resilient to infura failures
const rpcUrl = ETHEREUM_NODE_URL_BY_NETWORK[network];
providerEngine.addProvider(new RPCSubprovider(rpcUrl));
// // Start the Provider Engine
providerEngine.start();
return providerEngine;
},
};

View File

@ -1900,10 +1900,6 @@ aes-js@^0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d"
aes-js@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.1.tgz#89fd1f94ae51b4c72d62466adc1a7323ff52f072"
agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
@ -3341,7 +3337,7 @@ bs-logger@0.x:
dependencies:
fast-json-stable-stringify "^2.0.0"
bs58@=4.0.1, bs58@^4.0.0:
bs58@=4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
dependencies:
@ -3364,14 +3360,6 @@ bs58check@^1.0.8:
bs58 "^3.1.0"
create-hash "^1.1.0"
bs58check@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
dependencies:
bs58 "^4.0.0"
create-hash "^1.1.0"
safe-buffer "^5.1.2"
bser@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
@ -5959,19 +5947,6 @@ ethereumjs-wallet@0.6.0:
utf8 "^2.1.1"
uuid "^2.0.1"
ethereumjs-wallet@~0.6.0:
version "0.6.2"
resolved "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda"
dependencies:
aes-js "^3.1.1"
bs58check "^2.1.2"
ethereumjs-util "^5.2.0"
hdkey "^1.0.0"
safe-buffer "^5.1.2"
scrypt.js "^0.2.0"
utf8 "^3.0.0"
uuid "^3.3.2"
ethers@~4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65"
@ -6776,7 +6751,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5"
ethereumjs-wallet "~0.6.0"
ethereumjs-wallet "0.6.0"
fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6"
js-scrypt "^0.2.0"
@ -7501,14 +7476,6 @@ hdkey@^0.7.0, hdkey@^0.7.1:
coinstring "^2.0.0"
secp256k1 "^3.0.1"
hdkey@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29"
dependencies:
coinstring "^2.0.0"
safe-buffer "^5.1.1"
secp256k1 "^3.0.1"
he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
@ -15595,10 +15562,6 @@ utf8@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96"
utf8@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"