In web3 wrapper when a response contains an error field we throw this rather than return response.result which is often undefined. In Signature Utils we handle the error thrown when a user rejects the signing dialogue to prevent double signing. Exposed the ZeroExTransaction JSON schema. In Website only use the MetamaskSubprovider if we can detect the provider is Metamask
953 lines
46 KiB
TypeScript
953 lines
46 KiB
TypeScript
import { ZeroEx } from '0x.js';
|
|
import {
|
|
BlockRange,
|
|
ContractWrappers,
|
|
DecodedLogEvent,
|
|
ExchangeCancelEventArgs,
|
|
ExchangeEventArgs,
|
|
ExchangeEvents,
|
|
ExchangeFillEventArgs,
|
|
IndexedFilterValues,
|
|
} from '@0xproject/contract-wrappers';
|
|
import { assetDataUtils, orderHashUtils, signatureUtils } from '@0xproject/order-utils';
|
|
import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
|
|
import {
|
|
ledgerEthereumBrowserClientFactoryAsync,
|
|
LedgerSubprovider,
|
|
MetamaskSubprovider,
|
|
RedundantSubprovider,
|
|
RPCSubprovider,
|
|
SignerSubprovider,
|
|
Web3ProviderEngine,
|
|
} from '@0xproject/subproviders';
|
|
import { SignedOrder, Token as ZeroExToken } from '@0xproject/types';
|
|
import { BigNumber, intervalUtils, logUtils, promisify } from '@0xproject/utils';
|
|
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
|
import { BlockParam, LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
import * as moment from 'moment';
|
|
import * as React from 'react';
|
|
import contract = require('truffle-contract');
|
|
import { BlockchainWatcher } from 'ts/blockchain_watcher';
|
|
import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed';
|
|
import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
|
|
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
|
|
import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage';
|
|
import { Dispatcher } from 'ts/redux/dispatcher';
|
|
import {
|
|
BlockchainCallErrs,
|
|
BlockchainErrs,
|
|
ContractInstance,
|
|
Fill,
|
|
InjectedProviderObservable,
|
|
InjectedProviderUpdate,
|
|
InjectedWeb3,
|
|
Providers,
|
|
ProviderType,
|
|
Side,
|
|
SideToAssetToken,
|
|
Token,
|
|
TokenByAddress,
|
|
} from 'ts/types';
|
|
import { backendClient } from 'ts/utils/backend_client';
|
|
import { configs } from 'ts/utils/configs';
|
|
import { constants } from 'ts/utils/constants';
|
|
import { errorReporter } from 'ts/utils/error_reporter';
|
|
import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
|
|
import { utils } from 'ts/utils/utils';
|
|
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
|
|
|
|
import * as MintableArtifacts from '../contracts/Mintable.json';
|
|
|
|
const BLOCK_NUMBER_BACK_TRACK = 50;
|
|
const GWEI_IN_WEI = 1000000000;
|
|
|
|
const providerToName: { [provider: string]: string } = {
|
|
[Providers.Metamask]: constants.PROVIDER_NAME_METAMASK,
|
|
[Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER,
|
|
[Providers.Mist]: constants.PROVIDER_NAME_MIST,
|
|
[Providers.CoinbaseWallet]: constants.PROVIDER_NAME_COINBASE_WALLET,
|
|
[Providers.Cipher]: constants.PROVIDER_NAME_CIPHER,
|
|
};
|
|
|
|
export class Blockchain {
|
|
public networkId: number;
|
|
public nodeVersion: string;
|
|
private _contractWrappers: ContractWrappers;
|
|
private _zeroEx: ZeroEx;
|
|
private readonly _dispatcher: Dispatcher;
|
|
private _web3Wrapper?: Web3Wrapper;
|
|
private _blockchainWatcher?: BlockchainWatcher;
|
|
private _injectedProviderObservable?: InjectedProviderObservable;
|
|
private readonly _injectedProviderUpdateHandler: (update: InjectedProviderUpdate) => Promise<void>;
|
|
private _userAddressIfExists: string;
|
|
private _ledgerSubprovider: LedgerSubprovider;
|
|
private _defaultGasPrice: BigNumber;
|
|
private _watchGasPriceIntervalId: NodeJS.Timer;
|
|
private static _getNameGivenProvider(provider: Provider): string {
|
|
const providerType = utils.getProviderType(provider);
|
|
const providerNameIfExists = providerToName[providerType];
|
|
if (_.isUndefined(providerNameIfExists)) {
|
|
return constants.PROVIDER_NAME_GENERIC;
|
|
}
|
|
return providerNameIfExists;
|
|
}
|
|
private static _getInjectedWeb3(): InjectedWeb3 {
|
|
const injectedWeb3IfExists = (window as any).web3;
|
|
// Our core assumptions about the injected web3 object is that it has the following
|
|
// properties and methods.
|
|
if (
|
|
_.isUndefined(injectedWeb3IfExists) ||
|
|
_.isUndefined(injectedWeb3IfExists.version) ||
|
|
_.isUndefined(injectedWeb3IfExists.version.getNetwork) ||
|
|
_.isUndefined(injectedWeb3IfExists.currentProvider)
|
|
) {
|
|
return undefined;
|
|
}
|
|
return injectedWeb3IfExists;
|
|
}
|
|
private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> {
|
|
// 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 injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
|
let networkIdIfExists: number;
|
|
if (!_.isUndefined(injectedWeb3IfExists)) {
|
|
try {
|
|
networkIdIfExists = _.parseInt(
|
|
await promisify<string>(
|
|
injectedWeb3IfExists.version.getNetwork.bind(injectedWeb3IfExists.version),
|
|
)(),
|
|
);
|
|
} catch (err) {
|
|
// Ignore error and proceed with networkId undefined
|
|
}
|
|
}
|
|
return networkIdIfExists;
|
|
}
|
|
private static async _getProviderAsync(
|
|
injectedWeb3: InjectedWeb3,
|
|
networkIdIfExists: number,
|
|
shouldUserLedgerProvider: boolean = false,
|
|
): Promise<[Provider, LedgerSubprovider | undefined]> {
|
|
const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
|
|
const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists);
|
|
const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists];
|
|
const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId);
|
|
|
|
if (shouldUserLedgerProvider && isNetworkIdAvailable) {
|
|
const isU2FSupported = await utils.isU2FSupportedAsync();
|
|
if (!isU2FSupported) {
|
|
throw new Error('Cannot update providerType to LEDGER without U2F support');
|
|
}
|
|
const provider = new Web3ProviderEngine();
|
|
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(publicNodeUrl);
|
|
});
|
|
provider.addProvider(new RedundantSubprovider(rpcSubproviders));
|
|
provider.start();
|
|
return [provider, ledgerSubprovider];
|
|
} 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.
|
|
const provider = new Web3ProviderEngine();
|
|
const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider);
|
|
// Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
|
|
const signerSubprovider =
|
|
providerName === Providers.Metamask
|
|
? new MetamaskSubprovider(injectedWeb3.currentProvider)
|
|
: new SignerSubprovider(injectedWeb3.currentProvider);
|
|
provider.addProvider(signerSubprovider);
|
|
provider.addProvider(new FilterSubprovider());
|
|
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
|
return new RPCSubprovider(publicNodeUrl);
|
|
});
|
|
provider.addProvider(new RedundantSubprovider(rpcSubproviders));
|
|
provider.start();
|
|
return [provider, undefined];
|
|
} else if (doesInjectedWeb3Exist) {
|
|
// Since no public node for this network, all requests go to injectedWeb3 instance
|
|
return [injectedWeb3.currentProvider, undefined];
|
|
} else {
|
|
// If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node
|
|
// We do this so that users can still browse the 0x Portal DApp even if they do not have web3
|
|
// injected into their browser.
|
|
const provider = new Web3ProviderEngine();
|
|
provider.addProvider(new FilterSubprovider());
|
|
const networkId = constants.NETWORK_ID_MAINNET;
|
|
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
|
|
return new RPCSubprovider(publicNodeUrl);
|
|
});
|
|
provider.addProvider(new RedundantSubprovider(rpcSubproviders));
|
|
provider.start();
|
|
return [provider, undefined];
|
|
}
|
|
}
|
|
constructor(dispatcher: Dispatcher) {
|
|
this._dispatcher = dispatcher;
|
|
const defaultGasPrice = GWEI_IN_WEI * 40;
|
|
this._defaultGasPrice = new BigNumber(defaultGasPrice);
|
|
// We need a unique reference to this function so we can use it to unsubcribe.
|
|
this._injectedProviderUpdateHandler = this._handleInjectedProviderUpdateAsync.bind(this);
|
|
// tslint:disable-next-line:no-floating-promises
|
|
this._onPageLoadInitFireAndForgetAsync();
|
|
}
|
|
public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number): Promise<void> {
|
|
const isConnected = !_.isUndefined(newNetworkId);
|
|
if (!isConnected) {
|
|
this.networkId = newNetworkId;
|
|
this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode);
|
|
this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
|
|
} else if (this.networkId !== newNetworkId) {
|
|
this.networkId = newNetworkId;
|
|
this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError);
|
|
await this.fetchTokenInformationAsync();
|
|
await this._rehydrateStoreWithContractEventsAsync();
|
|
}
|
|
}
|
|
public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string): Promise<void> {
|
|
if (this._userAddressIfExists !== newUserAddress) {
|
|
this._userAddressIfExists = newUserAddress;
|
|
await this.fetchTokenInformationAsync();
|
|
await this._rehydrateStoreWithContractEventsAsync();
|
|
}
|
|
}
|
|
public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string): Promise<void> {
|
|
if (this.nodeVersion !== nodeVersion) {
|
|
this.nodeVersion = nodeVersion;
|
|
}
|
|
}
|
|
public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> {
|
|
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
|
const tokenIfExists = await this._zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress);
|
|
// HACK: Override token addresses on testnets
|
|
const tokenSymbolToAddressOverrides = tokenAddressOverrides[this.networkId];
|
|
let isTokenAddressInOverrides = false;
|
|
if (!_.isUndefined(tokenSymbolToAddressOverrides)) {
|
|
isTokenAddressInOverrides = _.values(tokenSymbolToAddressOverrides).includes(tokenAddress);
|
|
}
|
|
return !_.isUndefined(tokenIfExists) || isTokenAddressInOverrides;
|
|
}
|
|
public getLedgerDerivationPathIfExists(): string {
|
|
if (_.isUndefined(this._ledgerSubprovider)) {
|
|
return undefined;
|
|
}
|
|
const path = this._ledgerSubprovider.getPath();
|
|
return path;
|
|
}
|
|
public updateLedgerDerivationPathIfExists(path: string): void {
|
|
if (_.isUndefined(this._ledgerSubprovider)) {
|
|
return; // noop
|
|
}
|
|
this._ledgerSubprovider.setPath(path);
|
|
}
|
|
public async updateProviderToLedgerAsync(networkId: number): Promise<void> {
|
|
const shouldPollUserAddress = false;
|
|
const shouldUserLedgerProvider = true;
|
|
await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
|
}
|
|
public async updateProviderToInjectedAsync(): Promise<void> {
|
|
const shouldPollUserAddress = true;
|
|
const shouldUserLedgerProvider = false;
|
|
this._dispatcher.updateBlockchainIsLoaded(false);
|
|
// We don't want to be out of sync with the network the injected provider declares.
|
|
const networkId = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
|
|
await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
|
}
|
|
public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
|
|
utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.');
|
|
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
token.address,
|
|
this._userAddressIfExists,
|
|
amountInBaseUnits,
|
|
{
|
|
gasPrice: this._defaultGasPrice,
|
|
},
|
|
);
|
|
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
}
|
|
public async sendAsync(toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
const transaction = {
|
|
from: this._userAddressIfExists,
|
|
to: toAddress,
|
|
value: amountInBaseUnits,
|
|
gasPrice: this._defaultGasPrice,
|
|
};
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._web3Wrapper.sendTransactionAsync(transaction);
|
|
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists(
|
|
txHash,
|
|
this.networkId,
|
|
EtherscanLinkSuffixes.Tx,
|
|
);
|
|
this._dispatcher.showFlashMessage(
|
|
React.createElement(AssetSendCompleted, {
|
|
etherScanLinkIfExists,
|
|
toAddress,
|
|
amountInBaseUnits,
|
|
decimals: constants.DECIMAL_PLACES_ETH,
|
|
symbol: constants.ETHER_SYMBOL,
|
|
}),
|
|
);
|
|
}
|
|
public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.erc20Token.transferAsync(
|
|
token.address,
|
|
this._userAddressIfExists,
|
|
toAddress,
|
|
amountInBaseUnits,
|
|
{
|
|
gasPrice: this._defaultGasPrice,
|
|
},
|
|
);
|
|
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists(
|
|
txHash,
|
|
this.networkId,
|
|
EtherscanLinkSuffixes.Tx,
|
|
);
|
|
this._dispatcher.showFlashMessage(
|
|
React.createElement(AssetSendCompleted, {
|
|
etherScanLinkIfExists,
|
|
toAddress,
|
|
amountInBaseUnits,
|
|
decimals: token.decimals,
|
|
symbol: token.symbol,
|
|
}),
|
|
);
|
|
}
|
|
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.exchange.fillOrderAsync(
|
|
signedOrder,
|
|
fillTakerTokenAmount,
|
|
this._userAddressIfExists,
|
|
{
|
|
gasPrice: this._defaultGasPrice,
|
|
},
|
|
);
|
|
const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
const logs: Array<LogWithDecodedArgs<ExchangeEventArgs>> = receipt.logs as any;
|
|
const logFill = _.find(logs, { event: ExchangeEvents.Fill });
|
|
const args = (logFill.args as any) as ExchangeFillEventArgs;
|
|
const takerAssetFilledAmount = args.takerAssetFilledAmount;
|
|
return takerAssetFilledAmount;
|
|
}
|
|
public async cancelOrderAsync(signedOrder: SignedOrder): Promise<string> {
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.exchange.cancelOrderAsync(signedOrder, {
|
|
gasPrice: this._defaultGasPrice,
|
|
});
|
|
const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
const logs: Array<LogWithDecodedArgs<ExchangeEventArgs>> = receipt.logs as any;
|
|
const logCancel = _.find(logs, { event: ExchangeEvents.Cancel });
|
|
const args = (logCancel.args as any) as ExchangeCancelEventArgs;
|
|
const cancelledOrderHash = args.orderHash;
|
|
return cancelledOrderHash;
|
|
}
|
|
public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
|
utils.assert(orderHashUtils.isValidOrderHash(orderHash), 'Must be valid orderHash');
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
const unavailableTakerAmount = await this._contractWrappers.exchange.getFilledTakerAssetAmountAsync(orderHash);
|
|
return unavailableTakerAmount;
|
|
}
|
|
public getExchangeContractAddressIfExists(): string | undefined {
|
|
return this._contractWrappers.exchange.getContractAddress();
|
|
}
|
|
public async validateFillOrderThrowIfInvalidAsync(
|
|
signedOrder: SignedOrder,
|
|
fillTakerTokenAmount: BigNumber,
|
|
takerAddress: string,
|
|
): Promise<void> {
|
|
await this._contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(
|
|
signedOrder,
|
|
fillTakerTokenAmount,
|
|
takerAddress,
|
|
);
|
|
}
|
|
public isValidAddress(address: string): boolean {
|
|
const lowercaseAddress = address.toLowerCase();
|
|
return Web3Wrapper.isAddress(lowercaseAddress);
|
|
}
|
|
public async isValidSignatureAsync(data: string, signature: string, signerAddress: string): Promise<boolean> {
|
|
const result = await signatureUtils.isValidSignatureAsync(
|
|
this._contractWrappers.getProvider(),
|
|
data,
|
|
signature,
|
|
signerAddress,
|
|
);
|
|
return result;
|
|
}
|
|
public async pollTokenBalanceAsync(token: Token): Promise<BigNumber> {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddressIfExists, token.address);
|
|
|
|
const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => {
|
|
const tokenPollInterval = intervalUtils.setAsyncExcludingInterval(
|
|
async () => {
|
|
const [balance] = await this.getTokenBalanceAndAllowanceAsync(
|
|
this._userAddressIfExists,
|
|
token.address,
|
|
);
|
|
if (!balance.eq(currBalance)) {
|
|
intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
|
|
resolve(balance);
|
|
}
|
|
},
|
|
5000,
|
|
(err: Error) => {
|
|
logUtils.log(`Polling tokenBalance failed: ${err}`);
|
|
intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
|
|
reject(err);
|
|
},
|
|
);
|
|
});
|
|
|
|
return newTokenBalancePromise;
|
|
}
|
|
public async signOrderHashAsync(orderHash: string): Promise<string> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
const makerAddress = this._userAddressIfExists;
|
|
// If makerAddress is undefined, this means they have a web3 instance injected into their browser
|
|
// but no account addresses associated with it.
|
|
if (_.isUndefined(makerAddress)) {
|
|
throw new Error('Tried to send a sign request but user has no associated addresses');
|
|
}
|
|
this._showFlashMessageIfLedger();
|
|
const provider = this._contractWrappers.getProvider();
|
|
const ecSignatureString = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress);
|
|
this._dispatcher.updateSignature(ecSignatureString);
|
|
return ecSignatureString;
|
|
}
|
|
public async mintTestTokensAsync(token: Token): Promise<void> {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address);
|
|
this._showFlashMessageIfLedger();
|
|
await mintableContract.mint(constants.MINT_AMOUNT, {
|
|
from: this._userAddressIfExists,
|
|
gasPrice: this._defaultGasPrice,
|
|
});
|
|
}
|
|
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
|
|
const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(owner);
|
|
return balanceInWei;
|
|
}
|
|
public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.etherToken.depositAsync(
|
|
etherTokenAddress,
|
|
amount,
|
|
this._userAddressIfExists,
|
|
{
|
|
gasPrice: this._defaultGasPrice,
|
|
},
|
|
);
|
|
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
}
|
|
public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
this._showFlashMessageIfLedger();
|
|
const txHash = await this._contractWrappers.etherToken.withdrawAsync(
|
|
etherTokenAddress,
|
|
amount,
|
|
this._userAddressIfExists,
|
|
{
|
|
gasPrice: this._defaultGasPrice,
|
|
},
|
|
);
|
|
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
|
}
|
|
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
|
|
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address);
|
|
return doesContractExist;
|
|
}
|
|
public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(
|
|
this._userAddressIfExists,
|
|
tokenAddress,
|
|
);
|
|
return tokenBalanceAndAllowance;
|
|
}
|
|
public async getTokenBalanceAndAllowanceAsync(
|
|
ownerAddressIfExists: string,
|
|
tokenAddress: string,
|
|
): Promise<[BigNumber, BigNumber]> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
|
|
if (_.isUndefined(ownerAddressIfExists)) {
|
|
const zero = new BigNumber(0);
|
|
return [zero, zero];
|
|
}
|
|
let balance = new BigNumber(0);
|
|
let allowance = new BigNumber(0);
|
|
if (this._doesUserAddressExist()) {
|
|
[balance, allowance] = await Promise.all([
|
|
this._contractWrappers.erc20Token.getBalanceAsync(tokenAddress, ownerAddressIfExists),
|
|
this._contractWrappers.erc20Token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists),
|
|
]);
|
|
}
|
|
return [balance, allowance];
|
|
}
|
|
public async getUserAccountsAsync(): Promise<string[]> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
const provider = this._contractWrappers.getProvider();
|
|
const web3Wrapper = new Web3Wrapper(provider);
|
|
const userAccountsIfExists = await web3Wrapper.getAvailableAddressesAsync();
|
|
return userAccountsIfExists;
|
|
}
|
|
// HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which
|
|
// by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to
|
|
// manually update it. This should only be called by the LedgerConfigDialog.
|
|
public updateWeb3WrapperPrevUserAddress(newUserAddress: string): void {
|
|
this._blockchainWatcher.updatePrevUserAddress(newUserAddress);
|
|
}
|
|
public destroy(): void {
|
|
this._blockchainWatcher.destroy();
|
|
if (this._injectedProviderObservable) {
|
|
this._injectedProviderObservable.unsubscribe(this._injectedProviderUpdateHandler);
|
|
}
|
|
this._stopWatchingExchangeLogFillEvents();
|
|
this._stopWatchingGasPrice();
|
|
}
|
|
public async fetchTokenInformationAsync(): Promise<void> {
|
|
utils.assert(
|
|
!_.isUndefined(this.networkId),
|
|
'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
|
|
);
|
|
|
|
this._dispatcher.updateBlockchainIsLoaded(false);
|
|
|
|
const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
|
|
|
|
const trackedTokensByAddress = _.isUndefined(this._userAddressIfExists)
|
|
? {}
|
|
: trackedTokenStorage.getTrackedTokensByAddress(this._userAddressIfExists, this.networkId);
|
|
const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
|
|
const tokenRegistryTokenSymbols = _.map(tokenRegistryTokens, t => t.symbol);
|
|
const defaultTrackedTokensInRegistry = _.intersection(
|
|
tokenRegistryTokenSymbols,
|
|
configs.DEFAULT_TRACKED_TOKEN_SYMBOLS,
|
|
);
|
|
const currentTimestamp = moment().unix();
|
|
if (defaultTrackedTokensInRegistry.length !== configs.DEFAULT_TRACKED_TOKEN_SYMBOLS.length) {
|
|
this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
|
|
this._dispatcher.encounteredBlockchainError(BlockchainErrs.DefaultTokensNotInTokenRegistry);
|
|
const err = new Error(
|
|
`Default tracked tokens (${JSON.stringify(
|
|
configs.DEFAULT_TRACKED_TOKEN_SYMBOLS,
|
|
)}) not found in tokenRegistry: ${JSON.stringify(tokenRegistryTokens)}`,
|
|
);
|
|
errorReporter.report(err);
|
|
return;
|
|
}
|
|
if (_.isEmpty(trackedTokensByAddress)) {
|
|
_.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
|
|
const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
|
|
token.trackedTimestamp = currentTimestamp;
|
|
trackedTokensByAddress[token.address] = token;
|
|
});
|
|
if (!_.isUndefined(this._userAddressIfExists)) {
|
|
_.each(trackedTokensByAddress, (token: Token) => {
|
|
trackedTokenStorage.addTrackedTokenToUser(this._userAddressIfExists, this.networkId, token);
|
|
});
|
|
}
|
|
} else {
|
|
// Properly set all tokenRegistry tokens `trackedTimestamp` if they are in the existing trackedTokens array
|
|
_.each(trackedTokensByAddress, (trackedToken: Token, address: string) => {
|
|
if (!_.isUndefined(tokenRegistryTokensByAddress[address])) {
|
|
tokenRegistryTokensByAddress[address].trackedTimestamp = trackedToken.trackedTimestamp;
|
|
}
|
|
});
|
|
}
|
|
const allTokensByAddress = {
|
|
...tokenRegistryTokensByAddress,
|
|
...trackedTokensByAddress,
|
|
};
|
|
const allTokens = _.values(allTokensByAddress);
|
|
const mostPopularTradingPairTokens: Token[] = [
|
|
_.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
|
|
_.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
|
|
];
|
|
const sideToAssetToken: SideToAssetToken = {
|
|
[Side.Deposit]: {
|
|
address: mostPopularTradingPairTokens[0].address,
|
|
},
|
|
[Side.Receive]: {
|
|
address: mostPopularTradingPairTokens[1].address,
|
|
},
|
|
};
|
|
this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddressIfExists, sideToAssetToken);
|
|
|
|
this._dispatcher.updateBlockchainIsLoaded(true);
|
|
}
|
|
private async _showEtherScanLinkAndAwaitTransactionMinedAsync(
|
|
txHash: string,
|
|
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists(
|
|
txHash,
|
|
this.networkId,
|
|
EtherscanLinkSuffixes.Tx,
|
|
);
|
|
this._dispatcher.showFlashMessage(
|
|
React.createElement(TransactionSubmitted, {
|
|
etherScanLinkIfExists,
|
|
}),
|
|
);
|
|
const provider = this._contractWrappers.getProvider();
|
|
const web3Wrapper = new Web3Wrapper(provider);
|
|
const exchangeAbi = this._contractWrappers.exchange.abi;
|
|
web3Wrapper.abiDecoder.addABI(exchangeAbi);
|
|
const receipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
|
return receipt;
|
|
}
|
|
private _doesUserAddressExist(): boolean {
|
|
return !_.isUndefined(this._userAddressIfExists);
|
|
}
|
|
private async _handleInjectedProviderUpdateAsync(update: InjectedProviderUpdate): Promise<void> {
|
|
if (update.networkVersion === 'loading' || !_.isUndefined(this._ledgerSubprovider)) {
|
|
return;
|
|
}
|
|
const updatedNetworkId = _.parseInt(update.networkVersion);
|
|
if (this.networkId === updatedNetworkId) {
|
|
return;
|
|
}
|
|
const shouldPollUserAddress = true;
|
|
const shouldUserLedgerProvider = false;
|
|
await this._resetOrInitializeAsync(updatedNetworkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
|
}
|
|
private async _rehydrateStoreWithContractEventsAsync(): Promise<void> {
|
|
// Ensure we are only ever listening to one set of events
|
|
this._stopWatchingExchangeLogFillEvents();
|
|
|
|
if (!this._doesUserAddressExist()) {
|
|
return; // short-circuit
|
|
}
|
|
|
|
if (!_.isUndefined(this._contractWrappers)) {
|
|
// Since we do not have an index on the `taker` address and want to show
|
|
// transactions where an account is either the `maker` or `taker`, we loop
|
|
// through all fill events, and filter/cache them client-side.
|
|
const filterIndexObj = {};
|
|
await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj);
|
|
}
|
|
}
|
|
private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> {
|
|
utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.');
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
// Fetch historical logs
|
|
await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues);
|
|
|
|
// Start a subscription for new logs
|
|
this._contractWrappers.exchange.subscribe(
|
|
ExchangeEvents.Fill,
|
|
indexFilterValues,
|
|
async (err: Error, decodedLogEvent: DecodedLogEvent<ExchangeFillEventArgs>) => {
|
|
if (err) {
|
|
// Note: it's not entirely clear from the documentation which
|
|
// errors will be thrown by `watch`. For now, let's log the error
|
|
// to rollbar and stop watching when one occurs
|
|
errorReporter.report(err); // fire and forget
|
|
return;
|
|
} else {
|
|
const decodedLog = decodedLogEvent.log;
|
|
if (!this._doesLogEventInvolveUser(decodedLog)) {
|
|
return; // We aren't interested in the fill event
|
|
}
|
|
this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber);
|
|
const fill = await this._convertDecodedLogToFillAsync(decodedLog);
|
|
if (decodedLogEvent.isRemoved) {
|
|
tradeHistoryStorage.removeFillFromUser(this._userAddressIfExists, this.networkId, fill);
|
|
} else {
|
|
tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill);
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddressIfExists, this.networkId);
|
|
const blockRange: BlockRange = {
|
|
fromBlock,
|
|
toBlock: 'latest' as BlockParam,
|
|
};
|
|
const decodedLogs = await this._contractWrappers.exchange.getLogsAsync<ExchangeFillEventArgs>(
|
|
ExchangeEvents.Fill,
|
|
blockRange,
|
|
indexFilterValues,
|
|
);
|
|
for (const decodedLog of decodedLogs) {
|
|
if (!this._doesLogEventInvolveUser(decodedLog)) {
|
|
continue; // We aren't interested in the fill event
|
|
}
|
|
this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber);
|
|
const fill = await this._convertDecodedLogToFillAsync(decodedLog);
|
|
tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill);
|
|
}
|
|
}
|
|
private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<ExchangeFillEventArgs>): Promise<Fill> {
|
|
const args = decodedLog.args;
|
|
const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash);
|
|
const makerToken = assetDataUtils.decodeERC20AssetData(args.makerAssetData).tokenAddress;
|
|
const takerToken = assetDataUtils.decodeERC20AssetData(args.takerAssetData).tokenAddress;
|
|
const fill = {
|
|
filledTakerTokenAmount: args.takerAssetFilledAmount,
|
|
filledMakerTokenAmount: args.makerAssetFilledAmount,
|
|
logIndex: decodedLog.logIndex,
|
|
maker: args.makerAddress,
|
|
orderHash: args.orderHash,
|
|
taker: args.takerAddress,
|
|
makerToken,
|
|
takerToken,
|
|
paidMakerFee: args.makerFeePaid,
|
|
paidTakerFee: args.takerFeePaid,
|
|
transactionHash: decodedLog.transactionHash,
|
|
blockTimestamp,
|
|
};
|
|
return fill;
|
|
}
|
|
private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<ExchangeFillEventArgs>): boolean {
|
|
const args = decodedLog.args;
|
|
const isUserMakerOrTaker = args.maker === this._userAddressIfExists || args.taker === this._userAddressIfExists;
|
|
return isUserMakerOrTaker;
|
|
}
|
|
private _updateLatestFillsBlockIfNeeded(blockNumber: number): void {
|
|
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
|
|
|
|
const isBlockPending = _.isNull(blockNumber);
|
|
if (!isBlockPending) {
|
|
// Hack: I've observed the behavior where a client won't register certain fill events
|
|
// and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply
|
|
// set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user
|
|
// would still attempt to re-fetch events from the previous 50 blocks, but won't need to
|
|
// re-fetch all events in all blocks.
|
|
// TODO: Debug if this is a race condition, and apply a more precise fix
|
|
const blockNumberToSet =
|
|
blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK;
|
|
tradeHistoryStorage.setFillsLatestBlock(this._userAddressIfExists, this.networkId, blockNumberToSet);
|
|
}
|
|
}
|
|
private _stopWatchingExchangeLogFillEvents(): void {
|
|
this._contractWrappers.exchange.unsubscribeAll();
|
|
}
|
|
private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> {
|
|
let tokenRegistryTokens;
|
|
if (this.networkId === constants.NETWORK_ID_MAINNET) {
|
|
tokenRegistryTokens = await backendClient.getTokenInfosAsync();
|
|
} else {
|
|
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
|
tokenRegistryTokens = await this._zeroEx.tokenRegistry.getTokensAsync();
|
|
const tokenSymbolToAddressOverrides = tokenAddressOverrides[this.networkId];
|
|
if (!_.isUndefined(tokenAddressOverrides)) {
|
|
// HACK: Override token addresses on testnets
|
|
tokenRegistryTokens = _.map(tokenRegistryTokens, (token: ZeroExToken) => {
|
|
const overrideIfExists = tokenSymbolToAddressOverrides[token.symbol];
|
|
if (!_.isUndefined(overrideIfExists)) {
|
|
return {
|
|
...token,
|
|
address: overrideIfExists,
|
|
};
|
|
}
|
|
return token;
|
|
});
|
|
}
|
|
}
|
|
const tokenByAddress: TokenByAddress = {};
|
|
_.each(tokenRegistryTokens, (t: ZeroExToken) => {
|
|
// HACK: For now we have a hard-coded list of iconUrls for the dummyTokens
|
|
// TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry
|
|
const iconUrl = utils.getTokenIconUrl(t.symbol);
|
|
const token: Token = {
|
|
iconUrl,
|
|
address: t.address,
|
|
name: t.name,
|
|
symbol: t.symbol,
|
|
decimals: t.decimals,
|
|
trackedTimestamp: undefined,
|
|
isRegistered: true,
|
|
};
|
|
tokenByAddress[token.address] = token;
|
|
});
|
|
return tokenByAddress;
|
|
}
|
|
private async _onPageLoadInitFireAndForgetAsync(): Promise<void> {
|
|
await utils.onPageLoadPromise; // wait for page to load
|
|
const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
|
|
this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET;
|
|
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
|
if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
|
|
const injectedProviderObservable = injectedWeb3IfExists.currentProvider.publicConfigStore;
|
|
if (!_.isUndefined(injectedProviderObservable) && _.isUndefined(this._injectedProviderObservable)) {
|
|
this._injectedProviderObservable = injectedProviderObservable;
|
|
this._injectedProviderObservable.subscribe(this._injectedProviderUpdateHandler);
|
|
}
|
|
}
|
|
this._updateProviderName(injectedWeb3IfExists);
|
|
const shouldPollUserAddress = true;
|
|
const shouldUseLedgerProvider = false;
|
|
this._startWatchingGasPrice();
|
|
await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedgerProvider);
|
|
}
|
|
private _startWatchingGasPrice(): void {
|
|
if (!_.isUndefined(this._watchGasPriceIntervalId)) {
|
|
return; // we are already watching
|
|
}
|
|
const oneMinuteInMs = 60000;
|
|
// tslint:disable-next-line:no-floating-promises
|
|
this._updateDefaultGasPriceAsync();
|
|
this._watchGasPriceIntervalId = intervalUtils.setAsyncExcludingInterval(
|
|
this._updateDefaultGasPriceAsync.bind(this),
|
|
oneMinuteInMs,
|
|
(err: Error) => {
|
|
logUtils.log(`Watching gas price failed: ${err.stack}`);
|
|
this._stopWatchingGasPrice();
|
|
},
|
|
);
|
|
}
|
|
private _stopWatchingGasPrice(): void {
|
|
if (!_.isUndefined(this._watchGasPriceIntervalId)) {
|
|
intervalUtils.clearAsyncExcludingInterval(this._watchGasPriceIntervalId);
|
|
}
|
|
}
|
|
private async _resetOrInitializeAsync(
|
|
networkId: number,
|
|
shouldPollUserAddress: boolean = false,
|
|
shouldUserLedgerProvider: boolean = false,
|
|
): Promise<void> {
|
|
if (!shouldUserLedgerProvider) {
|
|
this._dispatcher.updateBlockchainIsLoaded(false);
|
|
}
|
|
this._dispatcher.updateUserWeiBalance(undefined);
|
|
this.networkId = networkId;
|
|
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
|
const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync(
|
|
injectedWeb3IfExists,
|
|
networkId,
|
|
shouldUserLedgerProvider,
|
|
);
|
|
if (!_.isUndefined(this._contractWrappers)) {
|
|
this._contractWrappers.setProvider(provider, networkId);
|
|
} else {
|
|
this._contractWrappers = new ContractWrappers(provider, { networkId });
|
|
}
|
|
if (!_.isUndefined(this._zeroEx)) {
|
|
this._zeroEx.setProvider(provider, networkId);
|
|
} else {
|
|
this._zeroEx = new ZeroEx(provider, { networkId });
|
|
}
|
|
if (!_.isUndefined(this._blockchainWatcher)) {
|
|
this._blockchainWatcher.destroy();
|
|
}
|
|
this._web3Wrapper = new Web3Wrapper(provider);
|
|
this._blockchainWatcher = new BlockchainWatcher(this._dispatcher, this._web3Wrapper, shouldPollUserAddress);
|
|
if (shouldUserLedgerProvider && !_.isUndefined(ledgerSubproviderIfExists)) {
|
|
delete this._userAddressIfExists;
|
|
this._ledgerSubprovider = ledgerSubproviderIfExists;
|
|
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);
|
|
if (!_.isUndefined(injectedWeb3IfExists)) {
|
|
this._dispatcher.updateProviderType(ProviderType.Injected);
|
|
}
|
|
await this.fetchTokenInformationAsync();
|
|
}
|
|
await this._blockchainWatcher.startEmittingUserBalanceStateAsync();
|
|
this._dispatcher.updateNetworkId(networkId);
|
|
await this._rehydrateStoreWithContractEventsAsync();
|
|
}
|
|
private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void {
|
|
const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists);
|
|
const providerName = doesInjectedWeb3Exist
|
|
? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider)
|
|
: constants.PROVIDER_NAME_PUBLIC;
|
|
this._dispatcher.updateInjectedProviderName(providerName);
|
|
}
|
|
private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
|
|
const c = await contract(artifact);
|
|
const providerObj = this._web3Wrapper.getProvider();
|
|
c.setProvider(providerObj);
|
|
|
|
const artifactNetworkConfigs = artifact.networks[this.networkId];
|
|
let contractAddress;
|
|
if (!_.isUndefined(address)) {
|
|
contractAddress = address;
|
|
} else if (!_.isUndefined(artifactNetworkConfigs)) {
|
|
contractAddress = artifactNetworkConfigs.address;
|
|
}
|
|
|
|
if (!_.isUndefined(contractAddress)) {
|
|
const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
|
|
if (!doesContractExist) {
|
|
logUtils.log(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`);
|
|
throw new Error(BlockchainCallErrs.ContractDoesNotExist);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address);
|
|
return contractInstance;
|
|
} catch (err) {
|
|
const errMsg = `${err}`;
|
|
logUtils.log(`Notice: Error encountered: ${err} ${err.stack}`);
|
|
if (_.includes(errMsg, 'not been deployed to detected network')) {
|
|
throw new Error(BlockchainCallErrs.ContractDoesNotExist);
|
|
} else {
|
|
errorReporter.report(err);
|
|
throw new Error(BlockchainCallErrs.UnhandledError);
|
|
}
|
|
}
|
|
}
|
|
private _showFlashMessageIfLedger(): void {
|
|
if (!_.isUndefined(this._ledgerSubprovider)) {
|
|
this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S');
|
|
}
|
|
}
|
|
private async _updateDefaultGasPriceAsync(): Promise<void> {
|
|
try {
|
|
const gasInfo = await backendClient.getGasInfoAsync();
|
|
const gasPriceInGwei = new BigNumber(gasInfo.fast / 10);
|
|
const gasPriceInWei = gasPriceInGwei.mul(1000000000);
|
|
this._defaultGasPrice = gasPriceInWei;
|
|
} catch (err) {
|
|
return;
|
|
}
|
|
}
|
|
} // tslint:disable:max-file-line-count
|