From dd9f5adc2e771f3602461ae708d44536f146b902 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 28 Jan 2018 10:29:15 +0100 Subject: Initial Ledger support implementation --- packages/website/ts/blockchain.ts | 215 +++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 107 deletions(-) (limited to 'packages/website/ts/blockchain.ts') diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 5530701c0..6fc56aecd 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -64,6 +64,7 @@ export class Blockchain { private _exchangeAddress: string; private _userAddress: string; private _cachedProvider: Web3.Provider; + private _cachedProviderNetworkId: number; private _ledgerSubprovider: LedgerWalletSubprovider; private _zrxPollIntervalId: NodeJS.Timer; private static async _onPageLoadAsync(): Promise { @@ -133,14 +134,14 @@ export class Blockchain { } else if (this.networkId !== newNetworkId) { this.networkId = newNetworkId; this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); - await this._fetchTokenInformationAsync(); + await this.fetchTokenInformationAsync(); await this._rehydrateStoreWithContractEvents(); } } public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { if (this._userAddress !== newUserAddress) { this._userAddress = newUserAddress; - await this._fetchTokenInformationAsync(); + await this.fetchTokenInformationAsync(); await this._rehydrateStoreWithContractEvents(); } } @@ -180,63 +181,62 @@ export class Blockchain { } this._ledgerSubprovider.setPathIndex(pathIndex); } - public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + public async updateProviderToLedgerAsync(networkId: number) { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // Should actually be Web3.Provider|ProviderEngine union type but it causes issues - // later on in the logic. - let provider; - switch (providerType) { - case ProviderType.Ledger: { - 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 - this._cachedProvider = this._web3Wrapper.getProviderObj(); - - this._dispatcher.updateUserAddress(''); // Clear old userAddress - - provider = new ProviderEngine(); - const ledgerWalletConfigs = { - networkId: this.networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(this._ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED - ? constants.NETWORK_ID_MAINNET - : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - this._web3Wrapper.destroy(); - const shouldPollUserAddress = false; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - break; - } + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } - case ProviderType.Injected: { - if (_.isUndefined(this._cachedProvider)) { - return; // Going from injected to injected, so we noop - } - provider = this._cachedProvider; - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, this.networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - delete this._ledgerSubprovider; - delete this._cachedProvider; - break; - } + // Cache injected provider so that we can switch the user back to it easily + this._cachedProvider = this._web3Wrapper.getProviderObj(); + this._cachedProviderNetworkId = this.networkId; + + this._userAddress = ''; + this._dispatcher.updateUserAddress(''); // 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()); + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + this._web3Wrapper.destroy(); + this.networkId = networkId; + this._dispatcher.updateNetworkId(this.networkId); + const shouldPollUserAddress = false; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + this._dispatcher.updateProviderType(ProviderType.Ledger); + } + public async updateProviderToInjectedAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - default: - throw utils.spawnSwitchErr('providerType', providerType); + if (_.isUndefined(this._cachedProvider)) { + return; // Going from injected to injected, so we noop } + const provider = this._cachedProvider; + this.networkId = this._cachedProviderNetworkId; + this._dispatcher.updateNetworkId(this.networkId); - await this._fetchTokenInformationAsync(); + this._web3Wrapper.destroy(); + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + this._dispatcher.updateUserAddress(this._userAddress); + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + await this.fetchTokenInformationAsync(); + this._dispatcher.updateProviderType(ProviderType.Injected); + delete this._ledgerSubprovider; + delete this._cachedProvider; } public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise { utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); @@ -452,6 +452,7 @@ export class Blockchain { return [balance, allowance]; } public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { + const err = new Error('show stopper'); const tokenStateByAddress: TokenStateByAddress = {}; for (const token of tokens) { let balance = new BigNumber(0); @@ -483,6 +484,60 @@ export class Blockchain { this._web3Wrapper.destroy(); this._stopWatchingExchangeLogFillEvents(); } + public async fetchTokenInformationAsync() { + utils.assert( + !_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', + ); + + this._dispatcher.updateBlockchainIsLoaded(false); + // HACK: Without this timeout, the second call to dispatcher somehow causes blockchainIsLoaded + // to flicker... Need to debug further :(((()))) + await new Promise(resolve => setTimeout(resolve, 100)); + this._dispatcher.clearTokenByAddress(); + + const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); + + // HACK: This is a hack so that the loading spinner doesn't show up twice... + // Once for loading the blockchain, another for loading the userAddress + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + if (!_.isEmpty(this._userAddress)) { + this._dispatcher.updateUserAddress(this._userAddress); + } + + let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isUndefined(trackedTokensIfExists)) { + trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + return token; + }); + _.each(trackedTokensIfExists, token => { + trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensIfExists, trackedToken => { + if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { + tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; + } + }); + } + const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); + this._dispatcher.updateTokenByAddress(allTokens); + + // Get balance/allowance for tracked tokens + await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); + + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), + ]; + this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); + this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); + this._dispatcher.updateBlockchainIsLoaded(true); + } private async _showEtherScanLinkAndAwaitTransactionMinedAsync( txHash: string, ): Promise { @@ -690,60 +745,6 @@ export class Blockchain { : constants.PROVIDER_NAME_PUBLIC; this._dispatcher.updateInjectedProviderName(providerName); } - private async _fetchTokenInformationAsync() { - utils.assert( - !_.isUndefined(this.networkId), - 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', - ); - - this._dispatcher.updateBlockchainIsLoaded(false); - this._dispatcher.clearTokenByAddress(); - - const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); - - // HACK: We need to fetch the userAddress here because otherwise we cannot save the - // tracked tokens in localStorage under the users address nor fetch the token - // balances and allowances and we need to do this in order not to trigger the blockchain - // loading dialog to show up twice. First to load the contracts, and second to load the - // balances and allowances. - this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); - if (!_.isEmpty(this._userAddress)) { - this._dispatcher.updateUserAddress(this._userAddress); - } - - let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); - const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); - if (_.isUndefined(trackedTokensIfExists)) { - trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { - const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); - token.isTracked = true; - return token; - }); - _.each(trackedTokensIfExists, token => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); - }); - } else { - // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array - _.each(trackedTokensIfExists, trackedToken => { - if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { - tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; - } - }); - } - const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); - this._dispatcher.updateTokenByAddress(allTokens); - - // Get balance/allowance for tracked tokens - await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); - - const mostPopularTradingPairTokens: Token[] = [ - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), - ]; - this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); - this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); - this._dispatcher.updateBlockchainIsLoaded(true); - } private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise { const c = await contract(artifact); const providerObj = this._web3Wrapper.getProviderObj(); -- cgit v1.2.3