Have basic network switching working

This commit is contained in:
fragosti 2018-06-25 16:26:34 -07:00
parent 53e2cda4c8
commit c4e2dcafa4
6 changed files with 158 additions and 120 deletions

View File

@ -50,6 +50,8 @@ import {
SideToAssetToken,
Token,
TokenByAddress,
InjectedProviderObservable,
InjectedProviderUpdate,
} from 'ts/types';
import { backendClient } from 'ts/utils/backend_client';
import { configs } from 'ts/utils/configs';
@ -79,9 +81,8 @@ export class Blockchain {
private _dispatcher: Dispatcher;
private _web3Wrapper?: Web3Wrapper;
private _blockchainWatcher?: BlockchainWatcher;
private _injectedProviderObservable?: InjectedProviderObservable;
private _userAddressIfExists: string;
private _cachedProvider: Provider;
private _cachedProviderNetworkId: number;
private _ledgerSubprovider: LedgerSubprovider;
private _defaultGasPrice: BigNumber;
private static _getNameGivenProvider(provider: Provider): string {
@ -92,13 +93,62 @@ export class Blockchain {
}
return providerNameIfExists;
}
private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number): Promise<Provider> {
private static _getInjectedWeb3(): any {
return (window as any).web3;
}
private static _getFallbackNetworkId(): number {
return configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
}
private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number> {
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
// order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot
// retrieve it from within the web3Wrapper constructor. This is and should remain the only
// call to a web3 instance outside of web3Wrapper in the entire dapp.
// In addition, if the user has an injectedWeb3 instance that is disconnected from a backing
// Ethereum node, this call will throw. We need to handle this case gracefully
const injectedWeb3 = Blockchain._getInjectedWeb3();
let networkIdIfExists: number;
if (!_.isUndefined(injectedWeb3)) {
try {
networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)());
} catch (err) {
// Ignore error and proceed with networkId undefined
}
}
return networkIdIfExists;
}
private static async _getProviderAsync(
injectedWeb3: Web3,
networkIdIfExists: number,
userLedgerProvider: boolean = false,
): Promise<Provider> {
const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
const isNetworkIdDefined = !_.isUndefined(networkIdIfExists);
const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists];
const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId);
let provider;
if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
if (userLedgerProvider && isNetworkIdDefined) {
const isU2FSupported = await utils.isU2FSupportedAsync();
if (!isU2FSupported) {
throw new Error('Cannot update providerType to LEDGER without U2F support');
}
provider = new ProviderEngine();
const ledgerWalletConfigs = {
networkId: networkIdIfExists,
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
};
const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
provider.addProvider(ledgerSubprovider);
provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => {
return new RpcSubprovider({
rpcUrl: publicNodeUrl,
});
});
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
provider.start();
} else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
provider = new ProviderEngine();
@ -120,7 +170,7 @@ export class Blockchain {
// injected into their browser.
provider = new ProviderEngine();
provider.addProvider(new FilterSubprovider());
const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
const networkId = Blockchain._getFallbackNetworkId();
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
return new RpcSubprovider({
rpcUrl: publicNodeUrl,
@ -141,6 +191,7 @@ export class Blockchain {
// tslint:disable-next-line:no-floating-promises
this._onPageLoadInitFireAndForgetAsync();
}
// TODO: Investigate if we need this.
public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number): Promise<void> {
const isConnected = !_.isUndefined(newNetworkId);
if (!isConnected) {
@ -185,84 +236,14 @@ export class Blockchain {
this._ledgerSubprovider.setPath(path);
}
public async updateProviderToLedgerAsync(networkId: number): Promise<void> {
utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.');
const isU2FSupported = await utils.isU2FSupportedAsync();
if (!isU2FSupported) {
throw new Error('Cannot update providerType to LEDGER without U2F support');
}
// Cache injected provider so that we can switch the user back to it easily
if (_.isUndefined(this._cachedProvider)) {
this._cachedProvider = this._web3Wrapper.getProvider();
this._cachedProviderNetworkId = this.networkId;
}
this._blockchainWatcher.destroy();
delete this._userAddressIfExists;
this._dispatcher.updateUserAddress(undefined); // Clear old userAddress
const provider = new ProviderEngine();
const ledgerWalletConfigs = {
networkId,
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
};
this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
provider.addProvider(this._ledgerSubprovider);
provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
return new RpcSubprovider({
rpcUrl: publicNodeUrl,
});
});
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
provider.start();
this.networkId = networkId;
this._dispatcher.updateNetworkId(this.networkId);
const shouldPollUserAddress = false;
this._web3Wrapper = new Web3Wrapper(provider);
this._blockchainWatcher = new BlockchainWatcher(
this._dispatcher,
this._web3Wrapper,
this.networkId,
shouldPollUserAddress,
);
this._contractWrappers.setProvider(provider, this.networkId);
await this._blockchainWatcher.startEmittingUserBalanceStateAsync();
this._dispatcher.updateProviderType(ProviderType.Ledger);
const useLedgerProvider = true;
await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, useLedgerProvider);
}
public async updateProviderToInjectedAsync(): Promise<void> {
utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.');
if (_.isUndefined(this._cachedProvider)) {
return; // Going from injected to injected, so we noop
}
this._blockchainWatcher.destroy();
const provider = this._cachedProvider;
this.networkId = this._cachedProviderNetworkId;
const shouldPollUserAddress = true;
this._web3Wrapper = new Web3Wrapper(provider);
this._blockchainWatcher = new BlockchainWatcher(
this._dispatcher,
this._web3Wrapper,
this.networkId,
shouldPollUserAddress,
);
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
this._userAddressIfExists = userAddresses[0];
this._contractWrappers.setProvider(provider, this.networkId);
await this.fetchTokenInformationAsync();
await this._blockchainWatcher.startEmittingUserBalanceStateAsync();
this._dispatcher.updateProviderType(ProviderType.Injected);
delete this._ledgerSubprovider;
delete this._cachedProvider;
const userLedgerProvider = false;
await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, userLedgerProvider);
}
public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
@ -632,6 +613,19 @@ export class Blockchain {
private _doesUserAddressExist(): boolean {
return !_.isUndefined(this._userAddressIfExists);
}
private async _handleInjectedProviderUpdateAsync(update: InjectedProviderUpdate): Promise<void> {
if (update.networkVersion === 'loading') {
// Who comes up with this stuff.
return;
}
const updatedNetworkId = _.parseInt(update.networkVersion);
if (this.networkId === updatedNetworkId) {
return;
}
const shouldPollUserAddress = true;
const useLedgerProvider = false;
await this._resetOrInitializeAsync(updatedNetworkId, shouldPollUserAddress, useLedgerProvider);
}
private async _rehydrateStoreWithContractEventsAsync(): Promise<void> {
// Ensure we are only ever listening to one set of events
this._stopWatchingExchangeLogFillEvents();
@ -774,36 +768,37 @@ export class Blockchain {
}
private async _onPageLoadInitFireAndForgetAsync(): Promise<void> {
await utils.onPageLoadAsync(); // wait for page to load
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
// order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot
// retrieve it from within the web3Wrapper constructor. This is and should remain the only
// call to a web3 instance outside of web3Wrapper in the entire dapp.
// In addition, if the user has an injectedWeb3 instance that is disconnected from a backing
// Ethereum node, this call will throw. We need to handle this case gracefully
const injectedWeb3 = (window as any).web3;
let networkIdIfExists: number;
if (!_.isUndefined(injectedWeb3)) {
try {
networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)());
} catch (err) {
// Ignore error and proceed with networkId undefined
const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : Blockchain._getFallbackNetworkId();
const injectedWeb3 = Blockchain._getInjectedWeb3();
if (injectedWeb3) {
const injectedProviderObservable = injectedWeb3.currentProvider.publicConfigStore;
if (injectedProviderObservable && !this._injectedProviderObservable) {
this._injectedProviderObservable = injectedProviderObservable;
this._injectedProviderObservable.subscribe(this._handleInjectedProviderUpdateAsync.bind(this));
}
}
const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists);
this.networkId = !_.isUndefined(networkIdIfExists)
? networkIdIfExists
: configs.IS_MAINNET_ENABLED
? constants.NETWORK_ID_MAINNET
: constants.NETWORK_ID_KOVAN;
this._dispatcher.updateNetworkId(this.networkId);
const zeroExConfigs = {
networkId: this.networkId,
};
this._contractWrappers = new ContractWrappers(provider, zeroExConfigs);
this._updateProviderName(injectedWeb3);
const shouldPollUserAddress = true;
const shouldUseLedger = false;
await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedger);
}
private async _resetOrInitializeAsync(
networkId: number,
shouldPollUserAddress: boolean = false,
useLedgerProvider: boolean = false,
): Promise<void> {
this.networkId = networkId;
this._dispatcher.updateNetworkId(networkId);
const injectedWeb3 = Blockchain._getInjectedWeb3();
const provider = await Blockchain._getProviderAsync(injectedWeb3, networkId, useLedgerProvider);
// if (!_.isUndefined(this._contractWrappers)) {
// this._contractWrappers.setProvider(provider, networkId);
// } else {
// }
this._contractWrappers = new ContractWrappers(provider, { networkId });
if (!_.isUndefined(this._blockchainWatcher)) {
this._blockchainWatcher.destroy();
}
this._web3Wrapper = new Web3Wrapper(provider);
this._blockchainWatcher = new BlockchainWatcher(
this._dispatcher,
@ -811,10 +806,19 @@ export class Blockchain {
this.networkId,
shouldPollUserAddress,
);
if (useLedgerProvider) {
// TODO: why?
delete this._userAddressIfExists;
this._dispatcher.updateUserAddress(undefined);
this._dispatcher.updateProviderType(ProviderType.Ledger);
} else {
delete this._ledgerSubprovider;
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
this._userAddressIfExists = userAddresses[0];
this._dispatcher.updateUserAddress(this._userAddressIfExists);
this._updateProviderName(injectedWeb3);
}
// TOOD: should not call this in ledger case?
await this.fetchTokenInformationAsync();
await this._blockchainWatcher.startEmittingUserBalanceStateAsync();
await this._rehydrateStoreWithContractEventsAsync();

View File

@ -7,6 +7,7 @@ export class BlockchainWatcher {
private _dispatcher: Dispatcher;
private _web3Wrapper: Web3Wrapper;
private _prevNetworkId: number;
private _isWatchingNetworkId: boolean = false;
private _shouldPollUserAddress: boolean;
private _watchBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInWei?: BigNumber;
@ -18,8 +19,8 @@ export class BlockchainWatcher {
shouldPollUserAddress: boolean,
) {
this._dispatcher = dispatcher;
this._prevNetworkId = networkIdIfExists;
this._shouldPollUserAddress = shouldPollUserAddress;
this._prevNetworkId = networkIdIfExists;
this._web3Wrapper = web3Wrapper;
}
public destroy(): void {
@ -34,6 +35,16 @@ export class BlockchainWatcher {
public updatePrevUserAddress(userAddress: string): void {
this._prevUserAddressIfExists = userAddress;
}
// public async startEmittingInjectedProviderNetworkIdAsync(injectedProvider: any): Promise<void> {
// if (this._isWatchingNetworkId) {
// return; // we are already watching the network id
// }
// const observable = injectedProvider.publicConfigStore;
// if (observable && observable.subscribe) {
// observable.subscribe(this._handleInjectedProviderUpdate.bind(this));
// this._isWatchingNetworkId = true;
// }
// }
public async startEmittingUserBalanceStateAsync(): Promise<void> {
if (!_.isUndefined(this._watchBalanceIntervalId)) {
return; // we are already emitting the state
@ -49,6 +60,15 @@ export class BlockchainWatcher {
},
);
}
// private _handleInjectedProviderUpdate(update: InjectedProviderUpdate): void {
// const updatedNetworkId = _.parseInt(update.networkVersion);
// if (this._prevNetworkId === updatedNetworkId) {
// return;
// }
// this._prevNetworkId = updatedNetworkId;
// this._dispatcher.updateNetworkId(updatedNetworkId);
// this._updateBalanceAsync();
// }
private async _updateBalanceAsync(): Promise<void> {
let prevNodeVersion: string;
// Check for node version changes

View File

@ -159,9 +159,11 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
const ethToken = utils.getEthToken(this.props.tokenByAddress);
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
if (ethToken && zrxToken) {
const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance;
const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance;
return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0);
const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
const zrxTokenState = this.props.trackedTokenStateByAddress[zrxToken.address];
if (ethTokenState && zrxTokenState) {
return ethTokenState.allowance > new BigNumber(0) && zrxTokenState.allowance > new BigNumber(0);
}
}
return false;
}
@ -222,6 +224,9 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
return null;
}
const tokenState = this.props.trackedTokenStateByAddress[token.address];
if (!tokenState) {
return null;
}
return (
<AllowanceToggle
token={token}

View File

@ -151,11 +151,11 @@ export class Portal extends React.Component<PortalProps, PortalState> {
this.props.dispatcher.resetState();
}
public componentDidUpdate(prevProps: PortalProps): void {
if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
// tslint:disable-next-line:no-floating-promises
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
}
// if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
// const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
// // tslint:disable-next-line:no-floating-promises
// this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
// }
}
public componentWillReceiveProps(nextProps: PortalProps): void {
if (nextProps.networkId !== this.state.prevNetworkId) {

View File

@ -58,7 +58,6 @@ const styles: Styles = {
width: '100%',
position: 'relative',
top: 0,
zIndex: zIndex.topBar,
paddingBottom: 1,
},
bottomBar: {

View File

@ -488,6 +488,16 @@ export enum Providers {
Mist = 'MIST',
}
export interface InjectedProviderUpdate {
selectedAddress: string;
networkVersion: string;
}
export interface InjectedProviderObservable {
subscribe(updateHandler: (update: InjectedProviderUpdate) => void): void;
unsubscribe(updateHandler: (update: InjectedProviderUpdate) => void): void;
}
export interface TimestampMsRange {
startTimestampMs: number;
endTimestampMs: number;