aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src/util
diff options
context:
space:
mode:
authorRemco Bloemen <remco@wicked.ventures>2018-11-09 01:32:40 +0800
committerRemco Bloemen <remco@wicked.ventures>2018-11-09 01:32:40 +0800
commitd71362af993d3797dbdbfcac245ad57f0086bce3 (patch)
tree888826fe23c2d06d6c9191fb3a238e14f9fe4aac /packages/instant/src/util
parenta5665a68756c905637c551fc48c9b7011a55c237 (diff)
parentf6abc007ffb249e4bbf85b8a7a77309d43e0a147 (diff)
downloaddexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar.gz
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar.bz2
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar.lz
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar.xz
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.tar.zst
dexon-sol-tools-d71362af993d3797dbdbfcac245ad57f0086bce3.zip
Merge remote-tracking branch 'origin/development' into feature/utils/prettybignum
Diffstat (limited to 'packages/instant/src/util')
-rw-r--r--packages/instant/src/util/address.ts6
-rw-r--r--packages/instant/src/util/assert.ts55
-rw-r--r--packages/instant/src/util/asset.ts111
-rw-r--r--packages/instant/src/util/asset_buyer_factory.ts17
-rw-r--r--packages/instant/src/util/balance.ts13
-rw-r--r--packages/instant/src/util/coinbase_api.ts11
-rw-r--r--packages/instant/src/util/error_flasher.ts26
-rw-r--r--packages/instant/src/util/etherscan.ts24
-rw-r--r--packages/instant/src/util/format.ts53
-rw-r--r--packages/instant/src/util/gas_price_estimator.ts62
-rw-r--r--packages/instant/src/util/maybe_big_number.ts25
-rw-r--r--packages/instant/src/util/provider_factory.ts34
-rw-r--r--packages/instant/src/util/provider_state_factory.ts69
-rw-r--r--packages/instant/src/util/time.ts39
-rw-r--r--packages/instant/src/util/util.ts5
15 files changed, 550 insertions, 0 deletions
diff --git a/packages/instant/src/util/address.ts b/packages/instant/src/util/address.ts
new file mode 100644
index 000000000..b21863a8e
--- /dev/null
+++ b/packages/instant/src/util/address.ts
@@ -0,0 +1,6 @@
+import { Web3Wrapper } from '@0x/web3-wrapper';
+
+export const getBestAddress = async (web3Wrapper: Web3Wrapper): Promise<string | undefined> => {
+ const addresses = await web3Wrapper.getAvailableAddressesAsync();
+ return addresses[0];
+};
diff --git a/packages/instant/src/util/assert.ts b/packages/instant/src/util/assert.ts
new file mode 100644
index 000000000..971c1eb96
--- /dev/null
+++ b/packages/instant/src/util/assert.ts
@@ -0,0 +1,55 @@
+import { assert as sharedAssert } from '@0x/assert';
+import { schemas } from '@0x/json-schemas';
+import { assetDataUtils } from '@0x/order-utils';
+import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
+import * as _ from 'lodash';
+
+import { AffiliateInfo, AssetMetaData } from '../types';
+
+export const assert = {
+ ...sharedAssert,
+ isValidOrderSource(variableName: string, orderSource: string | SignedOrder[]): void {
+ if (_.isString(orderSource)) {
+ sharedAssert.isUri(variableName, orderSource);
+ return;
+ }
+ sharedAssert.doesConformToSchema(variableName, orderSource, schemas.signedOrdersSchema);
+ },
+ areValidAssetDatas(variableName: string, assetDatas: string[]): void {
+ _.forEach(assetDatas, (assetData, index) => assert.isHexString(`${variableName}[${index}]`, assetData));
+ },
+ isValidAssetMetaDataMap(variableName: string, metaDataMap: ObjectMap<AssetMetaData>): void {
+ _.forEach(metaDataMap, (metaData, assetData) => {
+ assert.isHexString(`key ${assetData} of ${variableName}`, assetData);
+ assert.isValidAssetMetaData(`${variableName}.${assetData}`, metaData);
+ const assetDataProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ assert.assert(
+ metaData.assetProxyId === assetDataProxyId,
+ `Expected meta data for assetData ${assetData} to have asset proxy id of ${assetDataProxyId}, but instead got ${
+ metaData.assetProxyId
+ }`,
+ );
+ });
+ },
+ isValidAssetMetaData(variableName: string, metaData: AssetMetaData): void {
+ assert.isHexString(`${variableName}.assetProxyId`, metaData.assetProxyId);
+ if (!_.isUndefined(metaData.primaryColor)) {
+ assert.isString(`${variableName}.primaryColor`, metaData.primaryColor);
+ }
+ if (metaData.assetProxyId === AssetProxyId.ERC20) {
+ assert.isNumber(`${variableName}.decimals`, metaData.decimals);
+ assert.isString(`${variableName}.symbol`, metaData.symbol);
+ } else if (metaData.assetProxyId === AssetProxyId.ERC721) {
+ assert.isString(`${variableName}.name`, metaData.name);
+ assert.isUri(`${variableName}.imageUrl`, metaData.imageUrl);
+ }
+ },
+ isValidAffiliateInfo(variableName: string, affiliateInfo: AffiliateInfo): void {
+ assert.isETHAddressHex(`${variableName}.recipientAddress`, affiliateInfo.feeRecipient);
+ assert.isNumber(`${variableName}.percentage`, affiliateInfo.feePercentage);
+ assert.assert(
+ affiliateInfo.feePercentage >= 0 && affiliateInfo.feePercentage <= 0.05,
+ `Expected ${variableName}.percentage to be between 0 and 0.05, but is ${affiliateInfo.feePercentage}`,
+ );
+ },
+};
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
new file mode 100644
index 000000000..fbfbb19f3
--- /dev/null
+++ b/packages/instant/src/util/asset.ts
@@ -0,0 +1,111 @@
+import { AssetProxyId, ObjectMap } from '@0x/types';
+import * as _ from 'lodash';
+
+import { assetDataNetworkMapping } from '../data/asset_data_network_mapping';
+import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types';
+
+export const assetUtils = {
+ createAssetsFromAssetDatas: (
+ assetDatas: string[],
+ assetMetaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): Asset[] => {
+ const arrayOfAssetOrUndefined = _.map(assetDatas, assetData =>
+ assetUtils.createAssetFromAssetDataIfExists(assetData, assetMetaDataMap, network),
+ );
+ return _.compact(arrayOfAssetOrUndefined);
+ },
+ createAssetFromAssetDataIfExists: (
+ assetData: string,
+ assetMetaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): Asset | undefined => {
+ const metaData = assetUtils.getMetaDataIfExists(assetData, assetMetaDataMap, network);
+ if (_.isUndefined(metaData)) {
+ return;
+ }
+ return {
+ assetData,
+ metaData,
+ };
+ },
+ createAssetFromAssetDataOrThrow: (
+ assetData: string,
+ assetMetaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): Asset => {
+ return {
+ assetData,
+ metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap, network),
+ };
+ },
+ getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => {
+ const metaDataIfExists = assetUtils.getMetaDataIfExists(assetData, metaDataMap, network);
+ if (_.isUndefined(metaDataIfExists)) {
+ throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
+ }
+ return metaDataIfExists;
+ },
+ getMetaDataIfExists: (
+ assetData: string,
+ metaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): AssetMetaData | undefined => {
+ let mainnetAssetData: string | undefined = assetData;
+ if (network !== Network.Mainnet) {
+ const mainnetAssetDataIfExists = assetUtils.getAssociatedAssetDataIfExists(
+ assetData.toLowerCase(),
+ network,
+ );
+ // Just so we don't fail in the case where we are on a non-mainnet network,
+ // but pass in a valid mainnet assetData.
+ mainnetAssetData = mainnetAssetDataIfExists || assetData;
+ }
+ if (_.isUndefined(mainnetAssetData)) {
+ return;
+ }
+ const metaData = metaDataMap[mainnetAssetData.toLowerCase()];
+ if (_.isUndefined(metaData)) {
+ return;
+ }
+ return metaData;
+ },
+ bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => {
+ if (_.isUndefined(asset)) {
+ return defaultName;
+ }
+ const metaData = asset.metaData;
+ switch (metaData.assetProxyId) {
+ case AssetProxyId.ERC20:
+ return metaData.symbol.toUpperCase();
+ case AssetProxyId.ERC721:
+ return metaData.name;
+ default:
+ return defaultName;
+ }
+ },
+ formattedSymbolForAsset: (asset?: ERC20Asset, defaultName: string = '???'): string => {
+ if (_.isUndefined(asset)) {
+ return defaultName;
+ }
+ const symbol = asset.metaData.symbol;
+ if (symbol.length <= 5) {
+ return symbol;
+ }
+ return `${symbol.slice(0, 3)}…`;
+ },
+ getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => {
+ const assetDataGroupIfExists = _.find(assetDataNetworkMapping, value => value[network] === assetData);
+ if (_.isUndefined(assetDataGroupIfExists)) {
+ return;
+ }
+ return assetDataGroupIfExists[Network.Mainnet];
+ },
+ getERC20AssetsFromAssets: (assets: Asset[]): ERC20Asset[] => {
+ const erc20sOrUndefined = _.map(
+ assets,
+ asset => (asset.metaData.assetProxyId === AssetProxyId.ERC20 ? (asset as ERC20Asset) : undefined),
+ );
+ return _.compact(erc20sOrUndefined);
+ },
+};
diff --git a/packages/instant/src/util/asset_buyer_factory.ts b/packages/instant/src/util/asset_buyer_factory.ts
new file mode 100644
index 000000000..5ba46223c
--- /dev/null
+++ b/packages/instant/src/util/asset_buyer_factory.ts
@@ -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;
+ },
+};
diff --git a/packages/instant/src/util/balance.ts b/packages/instant/src/util/balance.ts
new file mode 100644
index 000000000..f2271495b
--- /dev/null
+++ b/packages/instant/src/util/balance.ts
@@ -0,0 +1,13 @@
+import { BuyQuote } from '@0x/asset-buyer';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as _ from 'lodash';
+
+export const balanceUtil = {
+ hasSufficientEth: async (takerAddress: string | undefined, buyQuote: BuyQuote, web3Wrapper: Web3Wrapper) => {
+ if (_.isUndefined(takerAddress)) {
+ return false;
+ }
+ const balanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ return balanceWei.gte(buyQuote.worstCaseQuoteInfo.totalEthAmount);
+ },
+};
diff --git a/packages/instant/src/util/coinbase_api.ts b/packages/instant/src/util/coinbase_api.ts
new file mode 100644
index 000000000..faac8d82d
--- /dev/null
+++ b/packages/instant/src/util/coinbase_api.ts
@@ -0,0 +1,11 @@
+import { BigNumber, fetchAsync } from '@0x/utils';
+
+import { COINBASE_API_BASE_URL } from '../constants';
+
+export const coinbaseApi = {
+ getEthUsdPrice: async (): Promise<BigNumber> => {
+ const res = await fetchAsync(`${COINBASE_API_BASE_URL}/prices/ETH-USD/buy`);
+ const resJson = await res.json();
+ return new BigNumber(resJson.data.amount);
+ },
+};
diff --git a/packages/instant/src/util/error_flasher.ts b/packages/instant/src/util/error_flasher.ts
new file mode 100644
index 000000000..068c12fe2
--- /dev/null
+++ b/packages/instant/src/util/error_flasher.ts
@@ -0,0 +1,26 @@
+import { Dispatch } from 'redux';
+
+import { Action, actions } from '../redux/actions';
+
+class ErrorFlasher {
+ private _timeoutId?: number;
+ public flashNewErrorMessage(dispatch: Dispatch<Action>, errorMessage?: string, delayMs: number = 7000): void {
+ this._clearTimeout();
+ // dispatch new message
+ dispatch(actions.setErrorMessage(errorMessage || 'Something went wrong...'));
+ this._timeoutId = window.setTimeout(() => {
+ dispatch(actions.hideError());
+ }, delayMs);
+ }
+ public clearError(dispatch: Dispatch<Action>): void {
+ this._clearTimeout();
+ dispatch(actions.hideError());
+ }
+ private _clearTimeout(): void {
+ if (this._timeoutId) {
+ window.clearTimeout(this._timeoutId);
+ }
+ }
+}
+
+export const errorFlasher = new ErrorFlasher();
diff --git a/packages/instant/src/util/etherscan.ts b/packages/instant/src/util/etherscan.ts
new file mode 100644
index 000000000..cfc2578a3
--- /dev/null
+++ b/packages/instant/src/util/etherscan.ts
@@ -0,0 +1,24 @@
+import * as _ from 'lodash';
+
+import { Network } from '../types';
+
+const etherscanPrefix = (networkId: number): string | undefined => {
+ switch (networkId) {
+ case Network.Kovan:
+ return 'kovan.';
+ case Network.Mainnet:
+ return '';
+ default:
+ return undefined;
+ }
+};
+
+export const etherscanUtil = {
+ getEtherScanTxnAddressIfExists: (txHash: string, networkId: number) => {
+ const prefix = etherscanPrefix(networkId);
+ if (_.isUndefined(prefix)) {
+ return;
+ }
+ return `https://${prefix}etherscan.io/tx/${txHash}`;
+ },
+};
diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts
new file mode 100644
index 000000000..4a48dec9d
--- /dev/null
+++ b/packages/instant/src/util/format.ts
@@ -0,0 +1,53 @@
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as _ from 'lodash';
+
+import { ETH_DECIMALS } from '../constants';
+
+export const format = {
+ ethBaseAmount: (
+ ethBaseAmount?: BigNumber,
+ decimalPlaces: number = 4,
+ defaultText: React.ReactNode = '0 ETH',
+ ): React.ReactNode => {
+ if (_.isUndefined(ethBaseAmount)) {
+ return defaultText;
+ }
+ const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS);
+ return format.ethUnitAmount(ethUnitAmount, decimalPlaces);
+ },
+ ethUnitAmount: (
+ ethUnitAmount?: BigNumber,
+ decimalPlaces: number = 4,
+ defaultText: React.ReactNode = '0 ETH',
+ ): React.ReactNode => {
+ if (_.isUndefined(ethUnitAmount)) {
+ return defaultText;
+ }
+ const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces);
+ return `${roundedAmount} ETH`;
+ },
+ ethBaseAmountInUsd: (
+ ethBaseAmount?: BigNumber,
+ ethUsdPrice?: BigNumber,
+ decimalPlaces: number = 2,
+ defaultText: React.ReactNode = '$0.00',
+ ): React.ReactNode => {
+ if (_.isUndefined(ethBaseAmount) || _.isUndefined(ethUsdPrice)) {
+ return defaultText;
+ }
+ const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS);
+ return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces);
+ },
+ ethUnitAmountInUsd: (
+ ethUnitAmount?: BigNumber,
+ ethUsdPrice?: BigNumber,
+ decimalPlaces: number = 2,
+ defaultText: React.ReactNode = '$0.00',
+ ): React.ReactNode => {
+ if (_.isUndefined(ethUnitAmount) || _.isUndefined(ethUsdPrice)) {
+ return defaultText;
+ }
+ return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`;
+ },
+};
diff --git a/packages/instant/src/util/gas_price_estimator.ts b/packages/instant/src/util/gas_price_estimator.ts
new file mode 100644
index 000000000..6b15809a3
--- /dev/null
+++ b/packages/instant/src/util/gas_price_estimator.ts
@@ -0,0 +1,62 @@
+import { BigNumber, fetchAsync } from '@0x/utils';
+
+import {
+ DEFAULT_ESTIMATED_TRANSACTION_TIME_MS,
+ DEFAULT_GAS_PRICE,
+ ETH_GAS_STATION_API_BASE_URL,
+ GWEI_IN_WEI,
+} from '../constants';
+
+interface EthGasStationResult {
+ average: number;
+ fastestWait: number;
+ fastWait: number;
+ fast: number;
+ safeLowWait: number;
+ blockNum: number;
+ avgWait: number;
+ block_time: number;
+ speed: number;
+ fastest: number;
+ safeLow: number;
+}
+
+interface GasInfo {
+ gasPriceInWei: BigNumber;
+ estimatedTimeMs: number;
+}
+
+const fetchFastAmountInWeiAsync = async (): Promise<GasInfo> => {
+ const res = await fetchAsync(`${ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`);
+ const gasInfo = (await res.json()) as EthGasStationResult;
+ // Eth Gas Station result is gwei * 10
+ const gasPriceInGwei = new BigNumber(gasInfo.fast / 10);
+ // Time is in minutes
+ const estimatedTimeMs = gasInfo.fastWait * 60 * 1000; // Minutes to MS
+ return { gasPriceInWei: gasPriceInGwei.mul(GWEI_IN_WEI), estimatedTimeMs };
+};
+
+export class GasPriceEstimator {
+ private _lastFetched?: GasInfo;
+ public async getGasInfoAsync(): Promise<GasInfo> {
+ let fetchedAmount: GasInfo | undefined;
+ try {
+ fetchedAmount = await fetchFastAmountInWeiAsync();
+ } catch {
+ fetchedAmount = undefined;
+ }
+
+ if (fetchedAmount) {
+ this._lastFetched = fetchedAmount;
+ }
+
+ return (
+ fetchedAmount ||
+ this._lastFetched || {
+ gasPriceInWei: DEFAULT_GAS_PRICE,
+ estimatedTimeMs: DEFAULT_ESTIMATED_TRANSACTION_TIME_MS,
+ }
+ );
+ }
+}
+export const gasPriceEstimator = new GasPriceEstimator();
diff --git a/packages/instant/src/util/maybe_big_number.ts b/packages/instant/src/util/maybe_big_number.ts
new file mode 100644
index 000000000..9d3746e10
--- /dev/null
+++ b/packages/instant/src/util/maybe_big_number.ts
@@ -0,0 +1,25 @@
+import { BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
+
+import { Maybe } from '../types';
+
+export const maybeBigNumberUtil = {
+ // converts a string to a Maybe<BigNumber>
+ // if string is a NaN, considered undefined
+ stringToMaybeBigNumber: (stringValue: string): Maybe<BigNumber> => {
+ let validBigNumber: BigNumber;
+ try {
+ validBigNumber = new BigNumber(stringValue);
+ } catch {
+ return undefined;
+ }
+
+ return validBigNumber.isNaN() ? undefined : validBigNumber;
+ },
+ areMaybeBigNumbersEqual: (val1: Maybe<BigNumber>, val2: Maybe<BigNumber>): boolean => {
+ if (!_.isUndefined(val1) && !_.isUndefined(val2)) {
+ return val1.equals(val2);
+ }
+ return _.isUndefined(val1) && _.isUndefined(val2);
+ },
+};
diff --git a/packages/instant/src/util/provider_factory.ts b/packages/instant/src/util/provider_factory.ts
new file mode 100644
index 000000000..603f7674d
--- /dev/null
+++ b/packages/instant/src/util/provider_factory.ts
@@ -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;
+ },
+};
diff --git a/packages/instant/src/util/provider_state_factory.ts b/packages/instant/src/util/provider_state_factory.ts
new file mode 100644
index 000000000..18b188d89
--- /dev/null
+++ b/packages/instant/src/util/provider_state_factory.ts
@@ -0,0 +1,69 @@
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { AccountNotReady, AccountState, Maybe, Network, OrderSource, ProviderState } from '../types';
+
+import { assetBuyerFactory } from './asset_buyer_factory';
+import { providerFactory } from './provider_factory';
+
+const LOADING_ACCOUNT: AccountNotReady = {
+ state: AccountState.Loading,
+};
+const NO_ACCOUNT: AccountNotReady = {
+ state: AccountState.None,
+};
+
+export const providerStateFactory = {
+ getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => {
+ if (!_.isUndefined(provider)) {
+ return providerStateFactory.getInitialProviderStateFromProvider(orderSource, network, provider);
+ }
+ const providerStateFromWindowIfExits = providerStateFactory.getInitialProviderStateFromWindowIfExists(
+ orderSource,
+ network,
+ );
+ if (providerStateFromWindowIfExits) {
+ return providerStateFromWindowIfExits;
+ } else {
+ return providerStateFactory.getInitialProviderStateFallback(orderSource, network);
+ }
+ },
+ getInitialProviderStateFromProvider: (
+ orderSource: OrderSource,
+ network: Network,
+ provider: Provider,
+ ): ProviderState => {
+ const providerState: ProviderState = {
+ provider,
+ web3Wrapper: new Web3Wrapper(provider),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
+ account: LOADING_ACCOUNT,
+ };
+ return providerState;
+ },
+ getInitialProviderStateFromWindowIfExists: (orderSource: OrderSource, network: Network): Maybe<ProviderState> => {
+ const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists();
+ if (!_.isUndefined(injectedProviderIfExists)) {
+ const providerState: ProviderState = {
+ provider: injectedProviderIfExists,
+ web3Wrapper: new Web3Wrapper(injectedProviderIfExists),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network),
+ account: LOADING_ACCOUNT,
+ };
+ return providerState;
+ } else {
+ return undefined;
+ }
+ },
+ getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => {
+ const provider = providerFactory.getFallbackNoSigningProvider(network);
+ const providerState: ProviderState = {
+ provider,
+ web3Wrapper: new Web3Wrapper(provider),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
+ account: NO_ACCOUNT,
+ };
+ return providerState;
+ },
+};
diff --git a/packages/instant/src/util/time.ts b/packages/instant/src/util/time.ts
new file mode 100644
index 000000000..bfe69cad5
--- /dev/null
+++ b/packages/instant/src/util/time.ts
@@ -0,0 +1,39 @@
+const secondsToMinutesAndRemainingSeconds = (seconds: number): { minutes: number; remainingSeconds: number } => {
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = seconds - minutes * 60;
+
+ return {
+ minutes,
+ remainingSeconds,
+ };
+};
+
+const padZero = (aNumber: number): string => {
+ return aNumber < 10 ? `0${aNumber}` : aNumber.toString();
+};
+
+export const timeUtil = {
+ // converts seconds to human readable version of seconds or minutes
+ secondsToHumanDescription: (seconds: number): string => {
+ const { minutes, remainingSeconds } = secondsToMinutesAndRemainingSeconds(seconds);
+
+ if (minutes === 0) {
+ const suffix = seconds > 1 ? 's' : '';
+ return `${seconds} second${suffix}`;
+ }
+
+ const minuteSuffix = minutes > 1 ? 's' : '';
+ const minuteText = `${minutes} minute${minuteSuffix}`;
+
+ const secondsSuffix = remainingSeconds > 1 ? 's' : '';
+ const secondsText = remainingSeconds === 0 ? '' : ` ${remainingSeconds} second${secondsSuffix}`;
+
+ return `${minuteText}${secondsText}`;
+ },
+ // converts seconds to stopwatch time (i.e. 05:30 and 00:30)
+ // only goes up to minutes, not hours
+ secondsToStopwatchTime: (seconds: number): string => {
+ const { minutes, remainingSeconds } = secondsToMinutesAndRemainingSeconds(seconds);
+ return `${padZero(minutes)}:${padZero(remainingSeconds)}`;
+ },
+};
diff --git a/packages/instant/src/util/util.ts b/packages/instant/src/util/util.ts
new file mode 100644
index 000000000..232a86850
--- /dev/null
+++ b/packages/instant/src/util/util.ts
@@ -0,0 +1,5 @@
+import * as _ from 'lodash';
+
+export const util = {
+ boundNoop: _.noop.bind(_),
+};