diff options
Diffstat (limited to 'packages')
-rw-r--r-- | packages/0x.js/CHANGELOG.json | 9 | ||||
-rw-r--r-- | packages/0x.js/package.json | 2 | ||||
-rw-r--r-- | packages/0x.js/src/order_watcher/expiration_watcher.ts | 13 | ||||
-rw-r--r-- | packages/0x.js/test/0x.js_test.ts | 7 | ||||
-rw-r--r-- | packages/0x.js/test/expiration_watcher_test.ts | 39 | ||||
-rw-r--r-- | packages/0x.js/test/global_hooks.ts | 7 | ||||
-rw-r--r-- | packages/migrations/src/migration.ts | 2 | ||||
-rw-r--r-- | packages/website/package.json | 2 | ||||
-rw-r--r-- | packages/website/public/images/landing/project_logos/imToken.png | bin | 0 -> 4933 bytes | |||
-rw-r--r-- | packages/website/ts/blockchain.ts | 16 | ||||
-rw-r--r-- | packages/website/ts/components/wallet/wallet.tsx | 105 | ||||
-rw-r--r-- | packages/website/ts/pages/landing/landing.tsx | 12 | ||||
-rw-r--r-- | packages/website/ts/pages/wiki/wiki.tsx | 53 | ||||
-rw-r--r-- | packages/website/ts/types.ts | 16 | ||||
-rw-r--r-- | packages/website/ts/utils/backend_client.ts | 59 | ||||
-rw-r--r-- | packages/website/ts/utils/constants.ts | 2 |
16 files changed, 251 insertions, 93 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 04d115809..ef4bb1e07 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.36.4", + "changes": [ + { + "note": "Fixed expiration watcher comparator to handle orders with equal expiration times", + "pr": 526 + } + ] + }, + { "version": "0.36.3", "changes": [ { diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 4a8c2bf7f..e7cdff09e 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -26,7 +26,7 @@ "build:umd:prod": "NODE_ENV=production webpack", "build:commonjs": "tsc && yarn update_artifacts && copyfiles -u 2 './src/compact_artifacts/**/*.json' ./lib/src/compact_artifacts && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", "test:commonjs": "run-s build:commonjs run_mocha", - "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit", + "run_mocha": "mocha lib/test/**/*_test.js lib/test/global_hooks.js --timeout 10000 --bail --exit", "manual:postpublish": "yarn build; node ./scripts/postpublish.js", "docs:stage": "yarn build && node ./scripts/stage_docs.js", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 8b306bf3b..27ec7107d 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -22,8 +22,17 @@ export class ExpirationWatcher { this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS; this._orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; - const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber(); - const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); + const comparator = (lhsOrderHash: string, rhsOrderHash: string) => { + const lhsExpiration = this._expiration[lhsOrderHash].toNumber(); + const rhsExpiration = this._expiration[rhsOrderHash].toNumber(); + if (lhsExpiration !== rhsExpiration) { + return lhsExpiration - rhsExpiration; + } else { + // HACK: If two orders have identical expirations, the order in which they are emitted by the + // ExpirationWatcher does not matter, so we emit them in alphabetical order by orderHash. + return lhsOrderHash.localeCompare(rhsOrderHash); + } + }; this._orderHashByExpirationRBTree = new RBTree(comparator); } public subscribe(callback: (orderHash: string) => void): void { diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index de5a6be58..838ee7080 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -1,9 +1,4 @@ -import { Deployer } from '@0xproject/deployer'; import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -// HACK: This dependency is optional since it is only available when run from within -// the monorepo. tslint doesn't handle optional dependencies -// tslint:disable-next-line:no-implicit-dependencies -import { runMigrationsAsync } from '@0xproject/migrations'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -15,7 +10,6 @@ import { ApprovalContractEventArgs, LogWithDecodedArgs, Order, TokenEvents, Zero import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; -import { deployer } from './utils/deployer'; import { TokenUtils } from './utils/token_utils'; import { provider, web3Wrapper } from './utils/web3_wrapper'; @@ -28,7 +22,6 @@ const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; describe('ZeroEx library', () => { let zeroEx: ZeroEx; before(async () => { - await runMigrationsAsync(deployer); const config = { networkId: constants.TESTRPC_NETWORK_ID, }; diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 29b111fa3..1b022539a 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -153,4 +153,43 @@ describe('ExpirationWatcher', () => { timer.tick(order2Lifetime * 1000); })().catch(done); }); + it('emits events in correct order when expirations are equal', (done: DoneCallback) => { + (async () => { + const order1Lifetime = 60; + const order2Lifetime = 60; + const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); + const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); + const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); + const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1]; + const expectToBeCalledOnce = false; + const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => { + const orderHash = expirationOrder.shift(); + expect(hash).to.be.equal(orderHash); + if (_.isEmpty(expirationOrder)) { + done(); + } + }); + expirationWatcher.subscribe(callbackAsync); + timer.tick(order2Lifetime * 1000); + })().catch(done); + }); }); diff --git a/packages/0x.js/test/global_hooks.ts b/packages/0x.js/test/global_hooks.ts new file mode 100644 index 000000000..e3c986524 --- /dev/null +++ b/packages/0x.js/test/global_hooks.ts @@ -0,0 +1,7 @@ +import { runMigrationsAsync } from '@0xproject/migrations'; + +import { deployer } from './utils/deployer'; + +before('migrate contracts', async () => { + await runMigrationsAsync(deployer); +}); diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 4827328fc..6313efcff 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -69,7 +69,7 @@ export const runMigrationsAsync = async (deployer: Deployer) => { }, ); for (const token of tokenInfo) { - const totalSupply = new BigNumber(0); + const totalSupply = new BigNumber(100000000000000000000); const args = [token.name, token.symbol, token.decimals, totalSupply]; const dummyToken = await deployer.deployAsync(ContractName.DummyToken, args); await tokenReg.addToken.sendTransactionAsync( diff --git a/packages/website/package.json b/packages/website/package.json index 79d4c95cc..169231fac 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -32,6 +32,7 @@ "lodash": "^4.17.4", "material-ui": "^0.17.1", "moment": "2.21.0", + "query-string": "^6.0.0", "react": "15.6.1", "react-copy-to-clipboard": "^4.2.3", "react-document-title": "^2.0.3", @@ -58,6 +59,7 @@ "@types/lodash": "4.14.104", "@types/material-ui": "0.18.0", "@types/node": "^8.0.53", + "@types/query-string": "^5.1.0", "@types/react": "^16.0.34", "@types/react-copy-to-clipboard": "^4.2.0", "@types/react-dom": "^16.0.3", diff --git a/packages/website/public/images/landing/project_logos/imToken.png b/packages/website/public/images/landing/project_logos/imToken.png Binary files differnew file mode 100644 index 000000000..ffb3b2fd3 --- /dev/null +++ b/packages/website/public/images/landing/project_logos/imToken.png diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 3edc00644..e90dfa747 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -47,6 +47,7 @@ import { 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'; @@ -854,14 +855,13 @@ export class Blockchain { } } private async _updateDefaultGasPriceAsync() { - const endpoint = `${configs.BACKEND_BASE_URL}/eth_gas_station`; - const response = await fetch(endpoint); - if (response.status !== 200) { - return; // noop and we keep hard-coded default + try { + const gasInfo = await backendClient.getGasInfoAsync(); + const gasPriceInGwei = new BigNumber(gasInfo.average / 10); + const gasPriceInWei = gasPriceInGwei.mul(1000000000); + this._defaultGasPrice = gasPriceInWei; + } catch (err) { + return; } - const gasInfo = await response.json(); - const gasPriceInGwei = new BigNumber(gasInfo.average / 10); - const gasPriceInWei = gasPriceInGwei.mul(1000000000); - this._defaultGasPrice = gasPriceInWei; } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index d3dc8e3dd..d1ae38550 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -28,6 +28,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { BalanceErrs, BlockchainErrs, + ItemByAddress, ProviderType, Side, Token, @@ -35,6 +36,7 @@ import { TokenState, TokenStateByAddress, } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles'; @@ -125,13 +127,15 @@ const HEADER_ITEM_KEY = 'HEADER'; const FOOTER_ITEM_KEY = 'FOOTER'; const DISCONNECTED_ITEM_KEY = 'DISCONNECTED'; const ETHER_ITEM_KEY = 'ETHER'; +const USD_DECIMAL_PLACES = 2; export class Wallet extends React.Component<WalletProps, WalletState> { private _isUnmounted: boolean; constructor(props: WalletProps) { super(props); this._isUnmounted = false; - const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); + const trackedTokenAddresses = _.map(props.trackedTokens, token => token.address); + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(trackedTokenAddresses); this.state = { trackedTokenStateByAddress: initialTrackedTokenStateByAddress, wrappedEtherDirection: undefined, @@ -161,13 +165,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { // Add placeholder entry for this token to the state, since fetching the // balance/allowance is asynchronous const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; - _.each(newTokenAddresses, (tokenAddress: string) => { - trackedTokenStateByAddress[tokenAddress] = { - balance: new BigNumber(0), - allowance: new BigNumber(0), - isLoaded: false, - }; - }); + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(newTokenAddresses); + _.assign(trackedTokenStateByAddress, initialTrackedTokenStateByAddress); this.setState({ trackedTokenStateByAddress, }); @@ -241,6 +240,13 @@ export class Wallet extends React.Component<WalletProps, WalletState> { constants.DECIMAL_PLACES_ETH, ETHER_SYMBOL, ); + const etherToken = this._getEthToken(); + const etherPrice = this.state.trackedTokenStateByAddress[etherToken.address].price; + const secondaryText = this._renderValue( + this.props.userEtherBalanceInWei, + constants.DECIMAL_PLACES_ETH, + etherPrice, + ); const accessoryItemConfig = { wrappedEtherDirection: Side.Deposit, }; @@ -250,11 +256,11 @@ export class Wallet extends React.Component<WalletProps, WalletState> { const style = isInWrappedEtherState ? { ...walletItemStyles.focusedItem, ...styles.paddedItem } : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem }; - const etherToken = this._getEthToken(); return ( <div key={ETHER_ITEM_KEY}> <ListItem primaryText={primaryText} + secondaryText={secondaryText} leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />} rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} disableTouchRipple={true} @@ -294,7 +300,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this.props.networkId, EtherscanLinkSuffixes.Address, ); - const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol); + const primaryText = this._renderAmount(tokenState.balance, token.decimals, token.symbol); + const secondaryText = this._renderValue(tokenState.balance, token.decimals, tokenState.price); const wrappedEtherDirection = token.symbol === ETHER_TOKEN_SYMBOL ? Side.Receive : undefined; const accessoryItemConfig: AccessoryItemConfig = { wrappedEtherDirection, @@ -313,7 +320,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { return ( <div key={token.address}> <ListItem - primaryText={amount} + primaryText={primaryText} + secondaryText={secondaryText} leftIcon={this._renderTokenIcon(token, tokenLink)} rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} disableTouchRipple={true} @@ -374,6 +382,16 @@ export class Wallet extends React.Component<WalletProps, WalletState> { const result = `${formattedAmount} ${symbol}`; return <div style={styles.amountLabel}>{result}</div>; } + private _renderValue(amount: BigNumber, decimals: number, price?: BigNumber) { + if (_.isUndefined(price)) { + return null; + } + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const value = unitAmount.mul(price); + const formattedAmount = value.toFixed(USD_DECIMAL_PLACES); + const result = `$${formattedAmount}`; + return result; + } private _renderTokenIcon(token: Token, tokenLink?: string) { const tooltipId = `tooltip-${token.address}`; const tokenIcon = <TokenIcon token={token} diameter={ICON_DIMENSION} />; @@ -422,10 +440,10 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } - private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) { + private _getInitialTrackedTokenStateByAddress(tokenAddresses: string[]) { const trackedTokenStateByAddress: TokenStateByAddress = {}; - _.each(trackedTokens, token => { - trackedTokenStateByAddress[token.address] = { + _.each(tokenAddresses, tokenAddress => { + trackedTokenStateByAddress[tokenAddress] = { balance: new BigNumber(0), allowance: new BigNumber(0), isLoaded: false, @@ -434,19 +452,32 @@ export class Wallet extends React.Component<WalletProps, WalletState> { return trackedTokenStateByAddress; } private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { - const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + const balanceAndAllowanceTupleByAddress: ItemByAddress<BigNumber[]> = {}; const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; for (const tokenAddress of tokenAddresses) { - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + const balanceAndAllowanceTuple = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, tokenAddress, ); - trackedTokenStateByAddress[tokenAddress] = { - balance, - allowance, - isLoaded: true, - }; + balanceAndAllowanceTupleByAddress[tokenAddress] = balanceAndAllowanceTuple; } + const pricesByAddress = await this._getPricesByAddressAsync(tokenAddresses); + const trackedTokenStateByAddress = _.reduce( + tokenAddresses, + (acc, address) => { + const [balance, allowance] = balanceAndAllowanceTupleByAddress[address]; + const price = pricesByAddress[address]; + acc[address] = { + balance, + allowance, + price, + isLoaded: true, + }; + return acc; + }, + this.state.trackedTokenStateByAddress, + ); + if (!this._isUnmounted) { this.setState({ trackedTokenStateByAddress, @@ -454,21 +485,21 @@ export class Wallet extends React.Component<WalletProps, WalletState> { } } private async _refetchTokenStateAsync(tokenAddress: string) { - const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - userAddressIfExists, - tokenAddress, - ); - this.setState({ - trackedTokenStateByAddress: { - ...this.state.trackedTokenStateByAddress, - [tokenAddress]: { - balance, - allowance, - isLoaded: true, - }, - }, - }); + await this._fetchBalancesAndAllowancesAsync([tokenAddress]); + } + private async _getPricesByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { + if (_.isEmpty(tokenAddresses)) { + return {}; + } + 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; + } catch (err) { + return {}; + } } private _openWrappedEtherActionRow(wrappedEtherDirection: Side) { this.setState({ @@ -485,4 +516,4 @@ export class Wallet extends React.Component<WalletProps, WalletState> { const etherToken = _.find(tokens, { symbol: ETHER_TOKEN_SYMBOL }); return etherToken; } -} +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index c943e3d79..f961220fd 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -92,8 +92,8 @@ const relayersAndDappProjects: Project[] = [ projectUrl: constants.PROJECT_URL_BLOCKNET, }, { - logoFileName: 'status.png', - projectUrl: constants.PROJECT_URL_STATUS, + logoFileName: 'imtoken.png', + projectUrl: constants.PROJECT_URL_IMTOKEN, }, { logoFileName: 'augur.png', @@ -123,10 +123,6 @@ const relayerProjects: Project[] = [ projectUrl: constants.PROJECT_URL_0CEAN, }, { - logoFileName: 'dydx.png', - projectUrl: constants.PROJECT_URL_DYDX, - }, - { logoFileName: 'amadeus.png', projectUrl: constants.PROJECT_URL_AMADEUS, }, @@ -154,6 +150,10 @@ const relayerProjects: Project[] = [ logoFileName: 'idt.png', projectUrl: constants.PROJECT_URL_IDT, }, + { + logoFileName: 'imtoken.png', + projectUrl: constants.PROJECT_URL_IMTOKEN, + }, ]; export interface LandingProps { diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index 1330cbf86..7ed2b750d 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -19,6 +19,7 @@ import { SidebarHeader } from 'ts/components/sidebar_header'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Article, ArticlesBySection, WebsitePaths } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -200,34 +201,30 @@ export class Wiki extends React.Component<WikiProps, WikiState> { ); } private async _fetchArticlesBySectionAsync(): Promise<void> { - const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; - const response = await fetch(endpoint); - if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { - // We need to backoff and try fetching again later - this._wikiBackoffTimeoutId = window.setTimeout(() => { - // tslint:disable-next-line:no-floating-promises - this._fetchArticlesBySectionAsync(); - }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); - return; - } - if (response.status !== 200) { - // TODO: Show the user an error message when the wiki fail to load - const errMsg = await response.text(); - logUtils.log(`Failed to load wiki: ${response.status} ${errMsg}`); - return; - } - const articlesBySection = await response.json(); - if (!this._isUnmounted) { - this.setState( - { - articlesBySection, - }, - async () => { - await utils.onPageLoadAsync(); - const hash = this.props.location.hash.slice(1); - sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); - }, - ); + try { + const articlesBySection = await backendClient.getWikiArticlesBySectionAsync(); + if (!this._isUnmounted) { + this.setState( + { + articlesBySection, + }, + async () => { + await utils.onPageLoadAsync(); + const hash = this.props.location.hash.slice(1); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); + }, + ); + } + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, `${constants.HTTP_NO_CONTENT_STATUS_CODE}`)) { + // We need to backoff and try fetching again later + this._wikiBackoffTimeoutId = window.setTimeout(() => { + // tslint:disable-next-line:no-floating-promises + this._fetchArticlesBySectionAsync(); + }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); + return; + } } } private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 2126c5c7b..98d080afb 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -487,14 +487,17 @@ export interface OutdatedWrappedEtherByNetworkId { }; } -export interface TokenStateByAddress { - [address: string]: TokenState; +export interface ItemByAddress<T> { + [address: string]: T; } +export type TokenStateByAddress = ItemByAddress<TokenState>; + export interface TokenState { balance: BigNumber; allowance: BigNumber; isLoaded: boolean; + price?: BigNumber; } export interface RelayerInfo { @@ -504,4 +507,13 @@ export interface RelayerInfo { marketShare: number; topTokens: Token[]; } + +export interface WebsiteBackendPriceInfo { + price: string; + address: string; +} + +export interface WebsiteBackendGasInfo { + average: number; +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts new file mode 100644 index 000000000..366519856 --- /dev/null +++ b/packages/website/ts/utils/backend_client.ts @@ -0,0 +1,59 @@ +import { BigNumber, logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as queryString from 'query-string'; + +import { ArticlesBySection, ItemByAddress, WebsiteBackendGasInfo, WebsiteBackendPriceInfo } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { errorReporter } from 'ts/utils/error_reporter'; + +const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station'; +const PRICES_ENDPOINT = '/prices'; +const WIKI_ENDPOINT = '/wiki'; + +export const backendClient = { + async getGasInfoAsync(): Promise<WebsiteBackendGasInfo> { + const result = await requestAsync(ETH_GAS_STATION_ENDPOINT); + return result; + }, + async getPriceInfosAsync(tokenAddresses: string[]): Promise<WebsiteBackendPriceInfo[]> { + if (_.isEmpty(tokenAddresses)) { + return []; + } + const joinedTokenAddresses = tokenAddresses.join(','); + const queryParams = { + tokens: joinedTokenAddresses, + }; + const result = await requestAsync(PRICES_ENDPOINT, queryParams); + return result; + }, + async getWikiArticlesBySectionAsync(): Promise<ArticlesBySection> { + const result = await requestAsync(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/constants.ts b/packages/website/ts/utils/constants.ts index 0584938eb..f8710b349 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -53,7 +53,7 @@ export const constants = { PROJECT_URL_ARAGON: 'https://aragon.one', PROJECT_URL_BLOCKNET: 'https://blocknet.co', PROJECT_URL_0CEAN: 'http://the0cean.com', - PROJECT_URL_STATUS: 'https://status.im', + PROJECT_URL_IMTOKEN: 'https://tokenlon.token.im/', PROJECT_URL_AUGUR: 'https://augur.net', PROJECT_URL_AUCTUS: 'https://auctus.org', PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', |