aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx73
-rw-r--r--packages/website/ts/utils/backend_client.ts46
-rw-r--r--packages/website/ts/utils/configs.ts1
-rw-r--r--packages/website/ts/utils/fetch_utils.ts33
4 files changed, 105 insertions, 48 deletions
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index d1ae38550..2097c0eab 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -37,7 +37,9 @@ import {
TokenStateByAddress,
} from 'ts/types';
import { backendClient } from 'ts/utils/backend_client';
+import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
+import { fetchUtils } from 'ts/utils/fetch_utils';
import { utils } from 'ts/utils/utils';
import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
@@ -128,9 +130,13 @@ const FOOTER_ITEM_KEY = 'FOOTER';
const DISCONNECTED_ITEM_KEY = 'DISCONNECTED';
const ETHER_ITEM_KEY = 'ETHER';
const USD_DECIMAL_PLACES = 2;
+const CRYPTO_COMPARE_MULTI_ENDPOINT = '/pricemulti';
+// Crypto compare recommends requesting no more than once every 10s: https://www.cryptocompare.com/api/?javascript#requests
+const CRYPTO_COMPARE_UPDATE_INTERVAL_MS = 10 * 1000;
export class Wallet extends React.Component<WalletProps, WalletState> {
private _isUnmounted: boolean;
+ private _cryptoCompareLastFetchTimestampMs?: number;
constructor(props: WalletProps) {
super(props);
this._isUnmounted = false;
@@ -461,16 +467,27 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
balanceAndAllowanceTupleByAddress[tokenAddress] = balanceAndAllowanceTuple;
}
- const pricesByAddress = await this._getPricesByAddressAsync(tokenAddresses);
+ // if we are allowed to fetch prices do so, if not, keep the old price state
+ const canFetchPrices = this._canGetPrice();
+ let priceByAddress: ItemByAddress<BigNumber> = {};
+ if (canFetchPrices) {
+ priceByAddress = await this._getPricesByAddressAsync(tokenAddresses);
+ } else {
+ const cachedPricesByAddress = _.mapValues(
+ this.state.trackedTokenStateByAddress,
+ tokenState => tokenState.price,
+ );
+ priceByAddress = cachedPricesByAddress;
+ }
const trackedTokenStateByAddress = _.reduce(
tokenAddresses,
(acc, address) => {
const [balance, allowance] = balanceAndAllowanceTupleByAddress[address];
- const price = pricesByAddress[address];
+ const priceIfExists = _.get(priceByAddress, address);
acc[address] = {
balance,
allowance,
- price,
+ price: priceIfExists,
isLoaded: true,
};
return acc;
@@ -491,16 +508,56 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
if (_.isEmpty(tokenAddresses)) {
return {};
}
+ // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists
+ // create a mapping from existing symbols -> address
+ const tokenAddressBySymbol = _.fromPairs(
+ _.compact(
+ _.map(tokenAddresses, address => {
+ const tokenIfExists = _.get(this.props.tokenByAddress, address);
+ if (!_.isUndefined(tokenIfExists)) {
+ const symbol = tokenIfExists.symbol;
+ // the crypto compare api doesn't understand 'WETH' so we need to replace it with 'ETH'
+ const key = symbol === ETHER_TOKEN_SYMBOL ? ETHER_SYMBOL : symbol;
+ return [key, address];
+ } else {
+ return undefined;
+ }
+ }),
+ ),
+ );
+ const joinedTokenSymbols = _.keys(tokenAddressBySymbol).join(',');
+ const quoteCurrency = 'USD';
+ const queryParams = {
+ fsyms: joinedTokenSymbols,
+ tsyms: quoteCurrency,
+ };
try {
- const websiteBackendPriceInfos = await backendClient.getPriceInfosAsync(tokenAddresses);
- const addresses = _.map(websiteBackendPriceInfos, info => info.address);
- const prices = _.map(websiteBackendPriceInfos, info => new BigNumber(info.price));
- const pricesByAddress = _.zipObject(addresses, prices);
- return pricesByAddress;
+ this._cryptoCompareLastFetchTimestampMs = Date.now();
+ const priceInfoBySymbol = await fetchUtils.requestAsync(
+ configs.CRYPTO_COMPARE_BASE_URL,
+ CRYPTO_COMPARE_MULTI_ENDPOINT,
+ queryParams,
+ );
+ const priceInfoByAddress = _.mapKeys(priceInfoBySymbol, (value, symbol) =>
+ _.get(tokenAddressBySymbol, symbol),
+ );
+ const result = _.mapValues(priceInfoByAddress, priceInfo => {
+ const price = _.get(priceInfo, quoteCurrency);
+ const priceBigNumber = new BigNumber(price);
+ return priceBigNumber;
+ });
+ return result;
} catch (err) {
return {};
}
}
+ private _canGetPrice() {
+ const currentTimeStamp = Date.now();
+ const result =
+ _.isUndefined(this._cryptoCompareLastFetchTimestampMs) ||
+ this._cryptoCompareLastFetchTimestampMs + CRYPTO_COMPARE_UPDATE_INTERVAL_MS < currentTimeStamp;
+ return result;
+ }
private _openWrappedEtherActionRow(wrappedEtherDirection: Side) {
this.setState({
wrappedEtherDirection,
diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts
index fdbb3e03a..ab0fb4f32 100644
--- a/packages/website/ts/utils/backend_client.ts
+++ b/packages/website/ts/utils/backend_client.ts
@@ -1,16 +1,8 @@
-import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
-import * as queryString from 'query-string';
-import {
- ArticlesBySection,
- ItemByAddress,
- WebsiteBackendGasInfo,
- WebsiteBackendPriceInfo,
- WebsiteBackendRelayerInfo,
-} from 'ts/types';
+import { ArticlesBySection, WebsiteBackendGasInfo, WebsiteBackendPriceInfo, WebsiteBackendRelayerInfo } from 'ts/types';
import { configs } from 'ts/utils/configs';
-import { errorReporter } from 'ts/utils/error_reporter';
+import { fetchUtils } from 'ts/utils/fetch_utils';
const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station';
const PRICES_ENDPOINT = '/prices';
@@ -19,7 +11,7 @@ const WIKI_ENDPOINT = '/wiki';
export const backendClient = {
async getGasInfoAsync(): Promise<WebsiteBackendGasInfo> {
- const result = await requestAsync(ETH_GAS_STATION_ENDPOINT);
+ const result = await fetchUtils.requestAsync(configs.BACKEND_BASE_URL, ETH_GAS_STATION_ENDPOINT);
return result;
},
async getPriceInfosAsync(tokenAddresses: string[]): Promise<WebsiteBackendPriceInfo[]> {
@@ -30,41 +22,15 @@ export const backendClient = {
const queryParams = {
tokens: joinedTokenAddresses,
};
- const result = await requestAsync(PRICES_ENDPOINT, queryParams);
+ const result = await fetchUtils.requestAsync(configs.BACKEND_BASE_URL, PRICES_ENDPOINT, queryParams);
return result;
},
async getRelayerInfosAsync(): Promise<WebsiteBackendRelayerInfo[]> {
- const result = await requestAsync(RELAYERS_ENDPOINT);
+ const result = await fetchUtils.requestAsync(configs.BACKEND_BASE_URL, RELAYERS_ENDPOINT);
return result;
},
async getWikiArticlesBySectionAsync(): Promise<ArticlesBySection> {
- const result = await requestAsync(WIKI_ENDPOINT);
+ const result = await fetchUtils.requestAsync(configs.BACKEND_BASE_URL, WIKI_ENDPOINT);
return result;
},
};
-
-async function requestAsync(endpoint: string, queryParams?: object): Promise<any> {
- const query = queryStringFromQueryParams(queryParams);
- const url = `${configs.BACKEND_BASE_URL}${endpoint}${query}`;
- const response = await fetch(url);
- if (response.status !== 200) {
- const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`;
- logUtils.log(errorText);
- const error = Error(errorText);
- // tslint:disable-next-line:no-floating-promises
- errorReporter.reportAsync(error);
- throw error;
- }
- const result = await response.json();
- return result;
-}
-
-function queryStringFromQueryParams(queryParams?: object): string {
- // if params are undefined or empty, return an empty string
- if (_.isUndefined(queryParams) || _.isEmpty(queryParams)) {
- return '';
- }
- // stringify the formatted object
- const stringifiedParams = queryString.stringify(queryParams);
- return `?${stringifiedParams}`;
-}
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
index a54fc56a8..edfd04291 100644
--- a/packages/website/ts/utils/configs.ts
+++ b/packages/website/ts/utils/configs.ts
@@ -13,6 +13,7 @@ export const configs = {
BACKEND_BASE_URL: 'https://website-api.0xproject.com',
BASE_URL,
BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
+ CRYPTO_COMPARE_BASE_URL: 'https://min-api.cryptocompare.com/data',
DEFAULT_DERIVATION_PATH: `44'/60'/0'`,
// WARNING: ZRX & WETH MUST always be default trackedTokens
DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'],
diff --git a/packages/website/ts/utils/fetch_utils.ts b/packages/website/ts/utils/fetch_utils.ts
new file mode 100644
index 000000000..d2e902db5
--- /dev/null
+++ b/packages/website/ts/utils/fetch_utils.ts
@@ -0,0 +1,33 @@
+import { logUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+import * as queryString from 'query-string';
+
+import { errorReporter } from 'ts/utils/error_reporter';
+
+export const fetchUtils = {
+ async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> {
+ const query = queryStringFromQueryParams(queryParams);
+ const url = `${baseUrl}${path}${query}`;
+ const response = await fetch(url);
+ if (response.status !== 200) {
+ const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`;
+ logUtils.log(errorText);
+ const error = Error(errorText);
+ // tslint:disable-next-line:no-floating-promises
+ errorReporter.reportAsync(error);
+ throw error;
+ }
+ const result = await response.json();
+ return result;
+ },
+};
+
+function queryStringFromQueryParams(queryParams?: object): string {
+ // if params are undefined or empty, return an empty string
+ if (_.isUndefined(queryParams) || _.isEmpty(queryParams)) {
+ return '';
+ }
+ // stringify the formatted object
+ const stringifiedParams = queryString.stringify(queryParams);
+ return `?${stringifiedParams}`;
+}