diff options
Diffstat (limited to 'packages')
36 files changed, 442 insertions, 221 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 4b2d92240..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", @@ -109,7 +109,7 @@ "ethereumjs-abi": "^0.6.4", "ethereumjs-blockstream": "^2.0.6", "ethereumjs-util": "^5.1.1", - "ethers-contracts": "^2.2.1", + "ethers": "^3.0.15", "js-sha3": "^0.7.0", "lodash": "^4.17.4", "uuid": "^3.1.0", 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/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index ed3c86465..c5d467b7f 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.3.0", + "changes": [ + { + "note": "Update ethers-contracts to ethers.js", + "pr": 540 + } + ] + }, + { "timestamp": 1524044013, "version": "0.2.1", "changes": [ diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 22ae43cdc..78dcbfe7f 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -42,7 +42,7 @@ "@0xproject/typescript-typings": "^0.2.0", "@0xproject/utils": "^0.5.2", "@0xproject/web3-wrapper": "^0.6.1", - "ethers-contracts": "^2.2.1", + "ethers": "^3.0.15", "lodash": "^4.17.4" }, "publishConfig": { diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts index bfa99fac1..e95b18db6 100644 --- a/packages/base-contract/src/index.ts +++ b/packages/base-contract/src/index.ts @@ -10,13 +10,13 @@ import { } from '@0xproject/types'; import { abiUtils, BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as ethersContracts from 'ethers-contracts'; +import * as ethers from 'ethers'; import * as _ from 'lodash'; import { formatABIDataItem } from './utils'; export interface EthersInterfaceByFunctionSignature { - [key: string]: ethersContracts.Interface; + [key: string]: ethers.Interface; } export class BaseContract { @@ -62,7 +62,7 @@ export class BaseContract { } return txDataWithDefaults; } - protected _lookupEthersInterface(functionSignature: string): ethersContracts.Interface { + protected _lookupEthersInterface(functionSignature: string): ethers.Interface { const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature]; if (_.isUndefined(ethersInterface)) { throw new Error(`Failed to lookup method with function signature '${functionSignature}'`); @@ -92,7 +92,7 @@ export class BaseContract { this._ethersInterfacesByFunctionSignature = {}; _.each(methodAbis, methodAbi => { const functionSignature = abiUtils.getFunctionSignature(methodAbi); - this._ethersInterfacesByFunctionSignature[functionSignature] = new ethersContracts.Interface([methodAbi]); + this._ethersInterfacesByFunctionSignature[functionSignature] = new ethers.Interface([methodAbi]); }); } } diff --git a/packages/contract_templates/contract.handlebars b/packages/contract_templates/contract.handlebars index 472452d74..cf51dad90 100644 --- a/packages/contract_templates/contract.handlebars +++ b/packages/contract_templates/contract.handlebars @@ -8,7 +8,7 @@ import { BaseContract } from '@0xproject/base-contract'; import { BlockParam, BlockParamLiteral, CallData, ContractAbi, DataItem, MethodAbi, Provider, TxData, TxDataPayable } from '@0xproject/types'; import { BigNumber, classUtils, promisify } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as ethersContracts from 'ethers-contracts'; +import * as ethers from 'ethers'; import * as _ from 'lodash'; {{#if events}} diff --git a/packages/contract_templates/partials/callAsync.handlebars b/packages/contract_templates/partials/callAsync.handlebars index a6f4abdf2..58fb2f09c 100644 --- a/packages/contract_templates/partials/callAsync.handlebars +++ b/packages/contract_templates/partials/callAsync.handlebars @@ -5,20 +5,21 @@ async callAsync( defaultBlock?: BlockParam, ): Promise<{{> return_type outputs=outputs}}> { const self = this as any as {{contractName}}Contract; - const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs; + const functionSignature = '{{this.functionSignature}}'; + const inputAbi = self._lookupAbi(functionSignature).inputs; [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); - const encodedData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}( + const ethersFunction = self._lookupEthersInterface(functionSignature).functions.{{this.name}}( {{> params inputs=inputs}} - ).data; + ) as ethers.CallDescription; + const encodedData = ethersFunction.data; const callDataWithDefaults = await self._applyDefaultsToTxDataAsync( { data: encodedData, } ) const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + let resultArray = ethersFunction.parse(rawCallResult); const outputAbi = (_.find(self.abi, {name: '{{this.name}}'}) as MethodAbi).outputs; - const outputParamsTypes = _.map(outputAbi, 'type'); - let resultArray = ethersContracts.Interface.decodeParams(outputParamsTypes, rawCallResult) as any; resultArray = BaseContract._formatABIDataItemList(outputAbi, resultArray, BaseContract._lowercaseAddress.bind(this)); resultArray = BaseContract._formatABIDataItemList(outputAbi, resultArray, BaseContract._bnToBigNumber.bind(this)); return resultArray{{#singleReturnValue}}[0]{{/singleReturnValue}}; diff --git a/packages/contract_templates/partials/return_type.handlebars b/packages/contract_templates/partials/return_type.handlebars index 9dd509953..77d96e8c4 100644 --- a/packages/contract_templates/partials/return_type.handlebars +++ b/packages/contract_templates/partials/return_type.handlebars @@ -1,6 +1,6 @@ {{#if outputs.length}} {{#singleReturnValue}} -{{#returnType outputs.0.type components}}{{/returnType}} +{{#returnType outputs.0.type outputs.0.components}}{{/returnType}} {{/singleReturnValue}} {{^singleReturnValue}} [{{#each outputs}}{{#returnType type components}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] diff --git a/packages/contract_templates/partials/tx.handlebars b/packages/contract_templates/partials/tx.handlebars index 22fe0c597..1bdf80c42 100644 --- a/packages/contract_templates/partials/tx.handlebars +++ b/packages/contract_templates/partials/tx.handlebars @@ -52,7 +52,7 @@ public {{this.tsName}} = { const self = this as any as {{contractName}}Contract; const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs; [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); - const abiEncodedTransactionData = self._lookupEthersInterface('{{this.name}}').functions.{{this.name}}( + const abiEncodedTransactionData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}( {{> params inputs=inputs}} ).data; return abiEncodedTransactionData; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 1c22b2bec..30867476f 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -54,7 +54,7 @@ "npm-run-all": "^4.1.2", "prettier": "^1.11.1", "shx": "^0.2.2", - "solc": "^0.4.22", + "solc": "^0.4.23", "tslint": "5.8.0", "typescript": "2.7.1", "yargs": "^10.0.3" diff --git a/packages/deployer/CHANGELOG.json b/packages/deployer/CHANGELOG.json index 6185aa7ce..943d4a6f5 100644 --- a/packages/deployer/CHANGELOG.json +++ b/packages/deployer/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.4.2", + "changes": [ + { + "note": "Add support for solidity 0.4.23", + "pr": 545 + } + ] + }, + { "version": "0.4.1", "changes": [ { diff --git a/packages/deployer/package.json b/packages/deployer/package.json index 5e855e393..2d5538c97 100644 --- a/packages/deployer/package.json +++ b/packages/deployer/package.json @@ -81,7 +81,7 @@ "lodash": "^4.17.4", "require-from-string": "^2.0.1", "semver": "^5.5.0", - "solc": "^0.4.22", + "solc": "^0.4.23", "web3": "^0.20.0", "web3-eth-abi": "^1.0.0-beta.24", "yargs": "^10.0.3" diff --git a/packages/deployer/src/solc/bin_paths.ts b/packages/deployer/src/solc/bin_paths.ts index 9ac00ea9d..1b5e8c6f1 100644 --- a/packages/deployer/src/solc/bin_paths.ts +++ b/packages/deployer/src/solc/bin_paths.ts @@ -16,4 +16,5 @@ export const binPaths: BinaryPaths = { '0.4.20': 'soljson-v0.4.20+commit.3155dd80.js', '0.4.21': 'soljson-v0.4.21+commit.dfe3193c.js', '0.4.22': 'soljson-v0.4.22+commit.4cb486ee.js', + '0.4.23': 'soljson-v0.4.23+commit.124ca40d.js', }; diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 80bd22791..e944d725b 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -32,7 +32,7 @@ "@0xproject/types": "^0.6.1", "@0xproject/utils": "^0.5.2", "@0xproject/web3-wrapper": "^0.6.1", - "ethers-contracts": "^2.2.1", + "ethers": "^3.0.15", "lodash": "^4.17.4", "web3-provider-engine": "^13.0.1" }, 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/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index a2f1e7615..f3f68d509 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.3.0", + "changes": [ + { + "note": "Add types for `ethers.js`, removing `ethers-contracts`", + "pr": 540 + } + ] + }, + { "version": "0.2.0", "changes": [ { diff --git a/packages/typescript-typings/types/ethers-contracts/index.d.ts b/packages/typescript-typings/types/ethers/index.d.ts index e5d38819e..7e04c7dd1 100644 --- a/packages/typescript-typings/types/ethers-contracts/index.d.ts +++ b/packages/typescript-typings/types/ethers/index.d.ts @@ -1,4 +1,4 @@ -declare module 'ethers-contracts' { +declare module 'ethers' { export interface TransactionDescription { name: string; signature: string; @@ -12,17 +12,20 @@ declare module 'ethers-contracts' { (...params: any[]): TransactionDescription | CallDescription; inputs: { names: string[]; types: string[] }; outputs: { names: string[]; types: string[] }; + type: string; } export interface EventDescription { parse: (...args: any[]) => any; inputs: { names: string[]; types: string[] }; signature: string; - topic: string; + topics: string[]; } export class Interface { public functions: { [functionName: string]: FunctionDescription }; public events: { [eventName: string]: EventDescription }; - public static decodeParams(types: string[], data: string): any[]; constructor(abi: any); } + export class Contract { + constructor(address: string, abi: any, provider: any); + } } diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 1aeb3b457..e7f3c052c 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.6.0", + "changes": [ + { + "note": "Update ethers-contracts to ethers.js", + "pr": 540 + } + ] + }, + { "version": "0.5.2", "changes": [ { diff --git a/packages/utils/package.json b/packages/utils/package.json index bbc87c53f..f309c2126 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -35,7 +35,7 @@ "@0xproject/typescript-typings": "^0.2.0", "@types/node": "^8.0.53", "bignumber.js": "~4.1.0", - "ethers-contracts": "^2.2.1", + "ethers": "^3.0.15", "js-sha3": "^0.7.0", "lodash": "^4.17.4", "web3": "^0.20.0" diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index 36c7839cf..150f74a15 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -9,7 +9,7 @@ import { RawLog, SolidityTypes, } from '@0xproject/types'; -import * as ethersContracts from 'ethers-contracts'; +import * as ethers from 'ethers'; import * as _ from 'lodash'; import { BigNumber } from './configured_bignumber'; @@ -36,7 +36,7 @@ export class AbiDecoder { if (_.isUndefined(event)) { return log; } - const ethersInterface = new ethersContracts.Interface([event]); + const ethersInterface = new ethers.Interface([event]); const logData = log.data; const decodedParams: DecodedLogArgs = {}; let topicsIndex = 1; @@ -77,10 +77,10 @@ export class AbiDecoder { if (_.isUndefined(abiArray)) { return; } - const ethersInterface = new ethersContracts.Interface(abiArray); + const ethersInterface = new ethers.Interface(abiArray); _.map(abiArray, (abi: AbiDefinition) => { if (abi.type === AbiType.Event) { - const topic = ethersInterface.events[abi.name].topic; + const topic = ethersInterface.events[abi.name].topics[0]; this._methodIds[topic] = abi; } }); diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 61a886e46..118330707 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -61,7 +61,7 @@ "@0xproject/types": "^0.6.1", "@0xproject/typescript-typings": "^0.2.0", "@0xproject/utils": "^0.5.2", - "ethers-contracts": "^2.2.1", + "ethers": "^3.0.15", "lodash": "^4.17.4", "web3": "^0.20.0" }, 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/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index 530576828..0c4b2841c 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -5,13 +5,45 @@ import * as React from 'react'; import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens'; import { TokenIcon } from 'ts/components/ui/token_icon'; -import { RelayerInfo, Token } from 'ts/types'; +import { Token, WebsiteBackendRelayerInfo } from 'ts/types'; export interface RelayerGridTileProps { - relayerInfo: RelayerInfo; + relayerInfo: WebsiteBackendRelayerInfo; networkId: number; } +// TODO: Get top tokens and headerurl from remote +const headerUrl = '/images/og_image.png'; +const topTokens = [ + { + address: '0x1dad4783cf3fe3085c1426157ab175a6119a04ba', + decimals: 18, + iconUrl: '/images/token_icons/makerdao.png', + isRegistered: true, + isTracked: true, + name: 'Maker DAO', + symbol: 'MKR', + }, + { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + decimals: 18, + iconUrl: '/images/token_icons/melon.png', + isRegistered: true, + isTracked: true, + name: 'Melon Token', + symbol: 'MLN', + }, + { + address: '0xb18845c260f680d5b9d84649638813e342e4f8c9', + decimals: 18, + iconUrl: '/images/token_icons/augur.png', + isRegistered: true, + isTracked: true, + name: 'Augur Reputation Token', + symbol: 'REP', + }, +]; + const styles: Styles = { root: { backgroundColor: colors.white, @@ -44,10 +76,9 @@ const styles: Styles = { width: '100%', boxSizing: 'border-box', }, - marketShareBar: { - height: 14, - width: '100%', - backgroundColor: colors.mediumBlue, + dailyTradeVolumeLabel: { + fontSize: 14, + color: colors.mediumBlue, }, subLabel: { fontSize: 12, @@ -64,16 +95,16 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( return ( <GridTile style={styles.root}> <div style={styles.innerDiv}> - <img src={props.relayerInfo.headerUrl} style={styles.header} /> + <img src={headerUrl} style={styles.header} /> <div style={styles.body}> <div className="py1" style={styles.relayerNameLabel}> {props.relayerInfo.name} </div> - <div style={styles.marketShareBar} /> + <div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div> <div className="py1" style={styles.subLabel}> - Market share + Daily Trade Volume </div> - <TopTokens tokens={props.relayerInfo.topTokens} networkId={props.networkId} /> + <TopTokens tokens={topTokens} networkId={props.networkId} /> <div className="py1" style={styles.subLabel}> Top tokens </div> diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index db3cf07b5..50760c32d 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -1,14 +1,21 @@ import { colors, Styles } from '@0xproject/react-shared'; +import * as _ from 'lodash'; import { GridList } from 'material-ui/GridList'; import * as React from 'react'; import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile'; -import { RelayerInfo } from 'ts/types'; +import { WebsiteBackendRelayerInfo } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; export interface RelayerIndexProps { networkId: number; } +interface RelayerIndexState { + relayerInfos?: WebsiteBackendRelayerInfo[]; + error?: Error; +} + const styles: Styles = { root: { width: '100%', @@ -25,94 +32,67 @@ const styles: Styles = { }, }; -// TODO: replace fake data with real, remote data -const topTokens = [ - { - address: '0x1dad4783cf3fe3085c1426157ab175a6119a04ba', - decimals: 18, - iconUrl: '/images/token_icons/makerdao.png', - isRegistered: true, - isTracked: true, - name: 'Maker DAO', - symbol: 'MKR', - }, - { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - decimals: 18, - iconUrl: '/images/token_icons/melon.png', - isRegistered: true, - isTracked: true, - name: 'Melon Token', - symbol: 'MLN', - }, - { - address: '0xb18845c260f680d5b9d84649638813e342e4f8c9', - decimals: 18, - iconUrl: '/images/token_icons/augur.png', - isRegistered: true, - isTracked: true, - name: 'Augur Reputation Token', - symbol: 'REP', - }, -]; - -const relayerInfos: RelayerInfo[] = [ - { - id: '1', - headerUrl: '/images/og_image.png', - name: 'Radar Relay', - marketShare: 0.5, - topTokens, - }, - { - id: '2', - headerUrl: '/images/og_image.png', - name: 'Paradex', - marketShare: 0.5, - topTokens, - }, - { - id: '3', - headerUrl: '/images/og_image.png', - name: 'yo', - marketShare: 0.5, - topTokens, - }, - { - id: '4', - headerUrl: '/images/og_image.png', - name: 'test', - marketShare: 0.5, - topTokens, - }, - { - id: '5', - headerUrl: '/images/og_image.png', - name: 'blahg', - marketShare: 0.5, - topTokens, - }, - { - id: '6', - headerUrl: '/images/og_image.png', - name: 'hello', - marketShare: 0.5, - topTokens, - }, -]; - const CELL_HEIGHT = 260; const NUMBER_OF_COLUMNS = 4; const GRID_PADDING = 16; -export const RelayerIndex: React.StatelessComponent<RelayerIndexProps> = (props: RelayerIndexProps) => { - return ( - <div style={styles.root}> - <GridList cellHeight={CELL_HEIGHT} cols={NUMBER_OF_COLUMNS} padding={GRID_PADDING} style={styles.gridList}> - {relayerInfos.map((relayerInfo: RelayerInfo) => ( - <RelayerGridTile key={relayerInfo.id} relayerInfo={relayerInfo} networkId={props.networkId} /> - ))} - </GridList> - </div> - ); -}; +export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> { + private _isUnmounted: boolean; + constructor(props: RelayerIndexProps) { + super(props); + this._isUnmounted = false; + this.state = { + relayerInfos: undefined, + error: undefined, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchRelayerInfosAsync(); + } + public componentWillUnmount() { + this._isUnmounted = true; + } + public render() { + const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos); + if (readyToRender) { + return ( + <div style={styles.root}> + <GridList + cellHeight={CELL_HEIGHT} + cols={NUMBER_OF_COLUMNS} + padding={GRID_PADDING} + style={styles.gridList} + > + {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo) => ( + <RelayerGridTile + key={relayerInfo.id} + relayerInfo={relayerInfo} + networkId={this.props.networkId} + /> + ))} + </GridList> + </div> + ); + } else { + // TODO: loading and error states with a scrolling container + return null; + } + } + private async _fetchRelayerInfosAsync(): Promise<void> { + try { + const relayerInfos = await backendClient.getRelayerInfosAsync(); + if (!this._isUnmounted) { + this.setState({ + relayerInfos, + }); + } + } catch (error) { + if (!this._isUnmounted) { + this.setState({ + error, + }); + } + } + } +} 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..2544e6735 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -487,21 +487,33 @@ 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 { - headerUrl: string; +// TODO: Add topTokens and headerUrl properties once available from backend +export interface WebsiteBackendRelayerInfo { id: string; name: string; - marketShare: number; - topTokens: Token[]; + dailyTxnVolume: string; + url: string; +} + +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..fdbb3e03a --- /dev/null +++ b/packages/website/ts/utils/backend_client.ts @@ -0,0 +1,70 @@ +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 { 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 RELAYERS_ENDPOINT = '/relayers'; +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 getRelayerInfosAsync(): Promise<WebsiteBackendRelayerInfo[]> { + const result = await requestAsync(RELAYERS_ENDPOINT); + 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', |