aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/utils
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
committerFabio Berger <me@fabioberger.com>2017-11-22 04:03:08 +0800
commit3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b (patch)
treef101656799da807489253e17bea7abfaea90b62d /packages/website/ts/utils
parent037f466e1f80f635b48f3235258402e2ce75fb7b (diff)
downloaddexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.gz
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.bz2
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.lz
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.xz
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.tar.zst
dexon-sol-tools-3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b.zip
Add website to mono repo, update packages to align with existing sub-packages, use new subscribeAsync 0x.js method
Diffstat (limited to 'packages/website/ts/utils')
-rw-r--r--packages/website/ts/utils/configs.ts18
-rw-r--r--packages/website/ts/utils/constants.ts270
-rw-r--r--packages/website/ts/utils/doc_utils.ts55
-rw-r--r--packages/website/ts/utils/doxity_utils.ts162
-rw-r--r--packages/website/ts/utils/error_reporter.ts52
-rw-r--r--packages/website/ts/utils/typedoc_utils.ts356
-rw-r--r--packages/website/ts/utils/utils.ts215
7 files changed, 1128 insertions, 0 deletions
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
new file mode 100644
index 000000000..49ac4b5e0
--- /dev/null
+++ b/packages/website/ts/utils/configs.ts
@@ -0,0 +1,18 @@
+import * as _ from 'lodash';
+import {Environments} from 'ts/types';
+
+const BASE_URL = window.location.origin;
+const isDevelopment = _.includes(BASE_URL, 'https://0xproject.dev:3572') ||
+ _.includes(BASE_URL, 'https://localhost:3572') ||
+ _.includes(BASE_URL, 'https://127.0.0.1');
+
+export const configs = {
+ BASE_URL,
+ ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION,
+ BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com',
+ symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'],
+ // WARNING: ZRX & WETH MUST always be default trackedTokens
+ defaultTrackedTokenSymbols: ['WETH', 'ZRX'],
+ lastLocalStorageFillClearanceDate: '2017-09-09',
+ isMainnetEnabled: true,
+};
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
new file mode 100644
index 000000000..42b80795e
--- /dev/null
+++ b/packages/website/ts/utils/constants.ts
@@ -0,0 +1,270 @@
+import {
+ ExchangeContractErrs,
+ PublicNodeUrlsByNetworkId,
+ ZeroExJsDocSections,
+ SmartContractsDocSections,
+ Docs,
+ ContractAddresses,
+ Networks,
+ WebsitePaths,
+} from 'ts/types';
+import BigNumber from 'bignumber.js';
+
+const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
+
+export const constants = {
+ ANGELLIST_URL: 'https://angel.co/0xproject/jobs',
+ STAGING_DOMAIN: 'staging-0xproject.s3-website-us-east-1.amazonaws.com',
+ PRODUCTION_DOMAIN: '0xproject.com',
+ DEVELOPMENT_DOMAIN: '0xproject.dev:3572',
+ BIGNUMBERJS_GITHUB_URL: 'http://mikemcl.github.io/bignumber.js',
+ BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
+ BITLY_ENDPOINT: 'https://api-ssl.bitly.com',
+ BLOG_URL: 'https://blog.0xproject.com/latest',
+ CUSTOM_BLUE: '#60a4f4',
+ DEFAULT_DERIVATION_PATH: `44'/60'/0'`,
+ ETHER_FAUCET_ENDPOINT: 'https://faucet.0xproject.com',
+ FEE_RECIPIENT_ADDRESS: '0x0000000000000000000000000000000000000000',
+ FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/',
+ GITHUB_URL: 'https://github.com/0xProject',
+ GITHUB_0X_JS_URL: 'https://github.com/0xProject/0x.js',
+ GITHUB_CONTRACTS_URL: 'https://github.com/0xProject/contracts',
+ GITHUB_WIKI_URL: 'https://github.com/0xProject/wiki',
+ HTTP_NO_CONTENT_STATUS_CODE: 204,
+ ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY: 'didAcceptPortalDisclaimer',
+ LINKEDIN_0X_URL: 'https://www.linkedin.com/company/0x',
+ LEDGER_PROVIDER_NAME: 'Ledger',
+ METAMASK_PROVIDER_NAME: 'Metamask',
+ GENESIS_ORDER_BLOCK_BY_NETWORK_ID: {
+ 1: 4145578,
+ 42: 3117574,
+ 50: 0,
+ } as {[networkId: number]: number},
+ PUBLIC_PROVIDER_NAME: '0x Public',
+ // The order matters. We first try first node and only then fall back to others.
+ PUBLIC_NODE_URLS_BY_NETWORK_ID: {
+ [1]: [
+ `https://mainnet.infura.io/${INFURA_API_KEY}`,
+ ],
+ [42]: [
+ `https://kovan.infura.io/${INFURA_API_KEY}`,
+ ],
+ } as PublicNodeUrlsByNetworkId,
+ PARITY_SIGNER_PROVIDER_NAME: 'Parity Signer',
+ GENERIC_PROVIDER_NAME: 'Injected Web3',
+ MAKER_FEE: new BigNumber(0),
+ MAINNET_NAME: 'Main network',
+ MAINNET_NETWORK_ID: 1,
+ METAMASK_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+ // tslint:disable-next-line:max-line-length
+ PARITY_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig',
+ MIST_DOWNLOAD_URL: 'https://github.com/ethereum/mist/releases',
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65',
+ DOCS_SCROLL_DURATION_MS: 0,
+ DOCS_CONTAINER_ID: 'documentation',
+ HOME_SCROLL_DURATION_MS: 500,
+ REDDIT_URL: 'https://reddit.com/r/0xproject',
+ STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md',
+ SUCCESS_STATUS: 200,
+ S3_0XJS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/0xjs-docs-jsons',
+ S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/smart-contracts-docs-json',
+ UNAVAILABLE_STATUS: 503,
+ TAKER_FEE: new BigNumber(0),
+ TESTNET_NAME: 'Kovan',
+ TESTNET_NETWORK_ID: 42,
+ TESTRPC_NETWORK_ID: 50,
+ TWITTER_URL: 'https://twitter.com/0xproject',
+ ETH_DECIMAL_PLACES: 18,
+ MINT_AMOUNT: new BigNumber('100000000000000000000'),
+ WEB3_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API',
+ WEB3_PROVIDER_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API#example-7',
+ ZEROEX_CHAT_URL: 'https://chat.0xproject.com',
+ // Projects
+ ETHFINEX_URL: 'https://www.bitfinex.com/ethfinex',
+ RADAR_RELAY_URL: 'https://radarrelay.com',
+ PARADEX_URL: 'https://paradex.io',
+ DYDX_URL: 'https://dydx.exchange',
+ MELONPORT_URL: 'https://melonport.com',
+ DISTRICT_0X_URL: 'https://district0x.io',
+ DHARMA_URL: 'https://dharma.io',
+ LENDROID_URL: 'https://lendroid.com',
+ MAKER_URL: 'https://makerdao.com',
+ ARAGON_URL: 'https://aragon.one',
+ BLOCKNET_URL: 'https://blocknet.co',
+ OCEAN_URL: 'http://the0cean.com',
+ STATUS_URL: 'https://status.im',
+ AUGUR_URL: 'https://augur.net',
+ AUCTUS_URL: 'https://auctus.org',
+ OPEN_ANX_URL: 'https://www.openanx.org',
+
+ iconUrlBySymbol: {
+ 'REP': '/images/token_icons/augur.png',
+ 'DGD': '/images/token_icons/digixdao.png',
+ 'WETH': '/images/token_icons/ether_erc20.png',
+ 'MLN': '/images/token_icons/melon.png',
+ 'GNT': '/images/token_icons/golem.png',
+ 'MKR': '/images/token_icons/makerdao.png',
+ 'ZRX': '/images/token_icons/zero_ex.png',
+ 'ANT': '/images/token_icons/aragon.png',
+ 'BNT': '/images/token_icons/bancor.png',
+ 'BAT': '/images/token_icons/basicattentiontoken.png',
+ 'CVC': '/images/token_icons/civic.png',
+ 'EOS': '/images/token_icons/eos.png',
+ 'FUN': '/images/token_icons/funfair.png',
+ 'GNO': '/images/token_icons/gnosis.png',
+ 'ICN': '/images/token_icons/iconomi.png',
+ 'OMG': '/images/token_icons/omisego.png',
+ 'SNT': '/images/token_icons/status.png',
+ 'STORJ': '/images/token_icons/storjcoinx.png',
+ 'PAY': '/images/token_icons/tenx.png',
+ 'QTUM': '/images/token_icons/qtum.png',
+ 'DNT': '/images/token_icons/district0x.png',
+ 'SNGLS': '/images/token_icons/singularity.png',
+ 'EDG': '/images/token_icons/edgeless.png',
+ '1ST': '/images/token_icons/firstblood.jpg',
+ 'WINGS': '/images/token_icons/wings.png',
+ 'BQX': '/images/token_icons/bitquence.png',
+ 'LUN': '/images/token_icons/lunyr.png',
+ 'RLC': '/images/token_icons/iexec.png',
+ 'MCO': '/images/token_icons/monaco.png',
+ 'ADT': '/images/token_icons/adtoken.png',
+ 'CFI': '/images/token_icons/cofound-it.png',
+ 'ROL': '/images/token_icons/etheroll.png',
+ 'WGNT': '/images/token_icons/golem.png',
+ 'MTL': '/images/token_icons/metal.png',
+ 'NMR': '/images/token_icons/numeraire.png',
+ 'SAN': '/images/token_icons/santiment.png',
+ 'TAAS': '/images/token_icons/taas.png',
+ 'TKN': '/images/token_icons/tokencard.png',
+ 'TRST': '/images/token_icons/trust.png',
+ } as {[symbol: string]: string},
+ networkNameById: {
+ 1: Networks.mainnet,
+ 3: Networks.ropsten,
+ 4: Networks.rinkeby,
+ 42: Networks.kovan,
+ } as {[symbol: number]: string},
+ networkIdByName: {
+ [Networks.mainnet]: 1,
+ [Networks.ropsten]: 3,
+ [Networks.rinkeby]: 4,
+ [Networks.kovan]: 42,
+ } as {[networkName: string]: number},
+ // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is
+ // currently no way to extract the re-exported types from index.ts via TypeDoc :(
+ public0xjsTypes: [
+ 'Order',
+ 'SignedOrder',
+ 'ECSignature',
+ 'ZeroExError',
+ 'EventCallback',
+ 'EventCallbackAsync',
+ 'EventCallbackSync',
+ 'ExchangeContractErrs',
+ 'ContractEvent',
+ 'Token',
+ 'ExchangeEvents',
+ 'IndexedFilterValues',
+ 'SubscriptionOpts',
+ 'BlockParam',
+ 'OrderFillOrKillRequest',
+ 'OrderCancellationRequest',
+ 'OrderFillRequest',
+ 'ContractEventEmitter',
+ 'Web3Provider',
+ 'ContractEventArgs',
+ 'LogCancelArgs',
+ 'LogFillArgs',
+ 'LogErrorContractEventArgs',
+ 'LogFillContractEventArgs',
+ 'LogCancelContractEventArgs',
+ 'TokenEvents',
+ 'ExchangeContractEventArgs',
+ 'TransferContractEventArgs',
+ 'ApprovalContractEventArgs',
+ 'TokenContractEventArgs',
+ 'ZeroExConfig',
+ 'TransactionReceiptWithDecodedLogs',
+ 'LogWithDecodedArgs',
+ 'DecodedLogArgs',
+ 'MethodOpts',
+ 'ValidateOrderFillableOpts',
+ 'OrderTransactionOpts',
+ 'ContractEventArg',
+ 'LogEvent',
+ 'LogEntry',
+ 'DecodedLogEvent',
+ ],
+ menuSmartContracts: {
+ introduction: [
+ SmartContractsDocSections.Introduction,
+ ],
+ contracts: [
+ SmartContractsDocSections.Exchange,
+ SmartContractsDocSections.TokenRegistry,
+ SmartContractsDocSections.ZRXToken,
+ SmartContractsDocSections.EtherToken,
+ SmartContractsDocSections.TokenTransferProxy,
+ ],
+ },
+ menu0xjs: {
+ introduction: [
+ ZeroExJsDocSections.introduction,
+ ],
+ install: [
+ ZeroExJsDocSections.installation,
+ ],
+ topics: [
+ ZeroExJsDocSections.async,
+ ZeroExJsDocSections.errors,
+ ZeroExJsDocSections.versioning,
+ ],
+ zeroEx: [
+ ZeroExJsDocSections.zeroEx,
+ ],
+ contracts: [
+ ZeroExJsDocSections.exchange,
+ ZeroExJsDocSections.token,
+ ZeroExJsDocSections.tokenRegistry,
+ ZeroExJsDocSections.etherToken,
+ ZeroExJsDocSections.proxy,
+ ],
+ types: [
+ ZeroExJsDocSections.types,
+ ],
+ },
+ menuSubsectionToVersionWhenIntroduced: {
+ [ZeroExJsDocSections.etherToken]: '0.7.1',
+ [ZeroExJsDocSections.proxy]: '0.8.0',
+ },
+ docToPath: {
+ [Docs.ZeroExJs]: WebsitePaths.ZeroExJs,
+ [Docs.SmartContracts]: WebsitePaths.SmartContracts,
+ },
+ contractAddresses: {
+ '1.0.0': {
+ [Networks.mainnet]: {
+ [SmartContractsDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
+ [SmartContractsDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
+ [SmartContractsDocSections.EtherToken]: '0x2956356cd2a2bf3202f771f50d3d14a367b48070',
+ [SmartContractsDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
+ },
+ [Networks.ropsten]: {
+ [SmartContractsDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
+ [SmartContractsDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
+ [SmartContractsDocSections.EtherToken]: '0xc00fd9820cd2898cc4c054b7bf142de637ad129a',
+ [SmartContractsDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
+ },
+ [Networks.kovan]: {
+ [SmartContractsDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
+ [SmartContractsDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
+ [SmartContractsDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
+ [SmartContractsDocSections.EtherToken]: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c',
+ [SmartContractsDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f',
+ },
+ },
+ } as ContractAddresses,
+};
diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts
new file mode 100644
index 000000000..ca6e0dc52
--- /dev/null
+++ b/packages/website/ts/utils/doc_utils.ts
@@ -0,0 +1,55 @@
+import * as _ from 'lodash';
+import findVersions = require('find-versions');
+import convert = require('xml-js');
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {VersionToFileName, S3FileObject, TypeDocNode, DoxityDocObj} from 'ts/types';
+
+export const docUtils = {
+ async getVersionToFileNameAsync(s3DocJsonRoot: string):
+ Promise<VersionToFileName> {
+ const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot);
+ const versionToFileName: VersionToFileName = {};
+ _.each(versionFileNames, fileName => {
+ const [version] = findVersions(fileName);
+ versionToFileName[version] = fileName;
+ });
+ return versionToFileName;
+ },
+ async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> {
+ const response = await fetch(s3DocJsonRoot);
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the docs fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`);
+ return;
+ }
+ const responseXML = await response.text();
+ const responseJSONString = convert.xml2json(responseXML, {
+ compact: true,
+ });
+ const responseObj = JSON.parse(responseJSONString);
+ let fileObjs: S3FileObject[];
+ if (_.isArray(responseObj.ListBucketResult.Contents)) {
+ fileObjs = responseObj.ListBucketResult.Contents as S3FileObject[];
+ } else {
+ fileObjs = [responseObj.ListBucketResult.Contents];
+ }
+ const versionFileNames = _.map(fileObjs, fileObj => {
+ return fileObj.Key._text;
+ });
+ return versionFileNames;
+ },
+ async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode|DoxityDocObj> {
+ const endpoint = `${s3DocJsonRoot}/${fileName}`;
+ const response = await fetch(endpoint);
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the docs fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`);
+ return;
+ }
+ const jsonDocObj = await response.json();
+ return jsonDocObj;
+ },
+};
diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts
new file mode 100644
index 000000000..3bab0a69d
--- /dev/null
+++ b/packages/website/ts/utils/doxity_utils.ts
@@ -0,0 +1,162 @@
+import * as _ from 'lodash';
+import {
+ DoxityDocObj,
+ DoxityContractObj,
+ DoxityAbiDoc,
+ DoxityInput,
+ DocAgnosticFormat,
+ DocSection,
+ Parameter,
+ Property,
+ Type,
+ TypeDocTypes,
+ EventArg,
+ AbiTypes,
+ SolidityMethod,
+} from 'ts/types';
+
+export const doxityUtils = {
+ convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat {
+ const docAgnosticFormat: DocAgnosticFormat = {};
+ _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => {
+ const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return abiDoc.type === AbiTypes.Constructor;
+ });
+ const constructors = [];
+ if (!_.isUndefined(doxityConstructor)) {
+ const constructor = {
+ isConstructor: true,
+ name: doxityContractObj.name,
+ comment: doxityConstructor.details,
+ returnComment: doxityConstructor.return,
+ callPath: '',
+ parameters: this._convertParameters(doxityConstructor.inputs),
+ returnType: this._convertType(doxityContractObj.name),
+ };
+ constructors.push(constructor);
+ }
+
+ const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>
+ (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return this._isMethod(abiDoc);
+ });
+ const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>(doxityMethods,
+ (doxityMethod: DoxityAbiDoc) => {
+ // We assume that none of our functions returns more then a single value
+ const outputIfExists = !_.isUndefined(doxityMethod.outputs) ?
+ doxityMethod.outputs[0] :
+ undefined;
+ const returnTypeIfExists = !_.isUndefined(outputIfExists) ?
+ this._convertType(outputIfExists.type) :
+ undefined;
+ // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken
+ const callPath = contractName !== 'ZRXToken' ?
+ `${contractName[0].toLowerCase()}${contractName.slice(1)}.` :
+ `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`;
+ const method = {
+ isConstructor: false,
+ isConstant: doxityMethod.constant,
+ isPayable: doxityMethod.payable,
+ name: doxityMethod.name,
+ comment: doxityMethod.details,
+ returnComment: doxityMethod.return,
+ callPath,
+ parameters: this._convertParameters(doxityMethod.inputs),
+ returnType: returnTypeIfExists,
+ };
+ return method;
+ });
+
+ const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>
+ (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
+ return this._isProperty(abiDoc);
+ });
+ const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => {
+ // We assume that none of our functions return more then a single return value
+ let typeName = doxityProperty.outputs[0].type;
+ if (!_.isEmpty(doxityProperty.inputs)) {
+ // Properties never have more then a single input
+ typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`;
+ }
+ const property = {
+ name: doxityProperty.name,
+ type: this._convertType(typeName),
+ comment: doxityProperty.details,
+ };
+ return property;
+ });
+
+ const doxityEvents = _.filter(
+ doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event,
+ );
+ const events = _.map(doxityEvents, doxityEvent => {
+ const event = {
+ name: doxityEvent.name,
+ eventArgs: this._convertEventArgs(doxityEvent.inputs),
+ };
+ return event;
+ });
+
+ const docSection: DocSection = {
+ comment: doxityContractObj.title,
+ constructors,
+ methods,
+ properties,
+ types: [],
+ events,
+ };
+ docAgnosticFormat[contractName] = docSection;
+ });
+ return docAgnosticFormat;
+ },
+ _convertParameters(inputs: DoxityInput[]): Parameter[] {
+ const parameters = _.map(inputs, input => {
+ const parameter = {
+ name: input.name,
+ comment: input.description,
+ isOptional: false,
+ type: this._convertType(input.type),
+ };
+ return parameter;
+ });
+ return parameters;
+ },
+ _convertType(typeName: string): Type {
+ const type = {
+ name: typeName,
+ typeDocType: TypeDocTypes.Intrinsic,
+ };
+ return type;
+ },
+ _isMethod(abiDoc: DoxityAbiDoc) {
+ if (abiDoc.type !== AbiTypes.Function) {
+ return false;
+ }
+ const hasInputs = !_.isEmpty(abiDoc.inputs);
+ const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
+ const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
+ const isMethod = hasNamedOutputIfExists && !isNameAllCaps;
+ return isMethod;
+ },
+ _isProperty(abiDoc: DoxityAbiDoc) {
+ if (abiDoc.type !== AbiTypes.Function) {
+ return false;
+ }
+ const hasInputs = !_.isEmpty(abiDoc.inputs);
+ const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
+ const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
+ const isProperty = !hasNamedOutputIfExists || isNameAllCaps;
+ return isProperty;
+ },
+ _convertEventArgs(inputs: DoxityInput[]): EventArg[] {
+ const eventArgs = _.map(inputs, input => {
+ const eventArg = {
+ isIndexed: input.indexed,
+ name: input.name,
+ type: this._convertType(input.type),
+ };
+ return eventArg;
+ });
+ return eventArgs;
+ },
+};
diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts
new file mode 100644
index 000000000..a9731c4d4
--- /dev/null
+++ b/packages/website/ts/utils/error_reporter.ts
@@ -0,0 +1,52 @@
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {Environments} from 'ts/types';
+
+// Suggested way to include Rollbar with Webpack
+// https://github.com/rollbar/rollbar.js/tree/master/examples/webpack
+const rollbarConfig = {
+ accessToken: constants.ROLLBAR_ACCESS_TOKEN,
+ captureUncaught: true,
+ captureUnhandledRejections: true,
+ itemsPerMinute: 10,
+ maxItems: 500,
+ payload: {
+ environment: configs.ENVIRONMENT,
+ },
+ uncaughtErrorLevel: 'error',
+ hostWhiteList: [constants.PRODUCTION_DOMAIN, constants.STAGING_DOMAIN],
+ ignoredMessages: [
+ // Errors from the third-party scripts
+ 'Script error',
+ // Network errors or ad-blockers
+ 'TypeError: Failed to fetch',
+ 'Exchange has not been deployed to detected network (network/artifact mismatch)',
+ // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE
+ 'undefined is not an object (evaluating \'__gCrWeb.autofill.extractForms\')',
+ // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging
+ 'SecurityError (DOM Exception 18)',
+ ],
+};
+import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js');
+const rollbar = Rollbar.init(rollbarConfig);
+
+export const errorReporter = {
+ reportAsync(err: Error): Promise<any> {
+ if (configs.ENVIRONMENT === Environments.DEVELOPMENT) {
+ return; // Let's not log development errors to rollbar
+ }
+
+ return new Promise((resolve, reject) => {
+ rollbar.error(err, (rollbarErr: Error) => {
+ if (rollbarErr) {
+ utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
+ // We never want to reject and cause the app to throw because of rollbar
+ resolve();
+ } else {
+ resolve();
+ }
+ });
+ });
+ },
+};
diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts
new file mode 100644
index 000000000..b3d0f7d90
--- /dev/null
+++ b/packages/website/ts/utils/typedoc_utils.ts
@@ -0,0 +1,356 @@
+import * as _ from 'lodash';
+import compareVersions = require('compare-versions');
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {
+ TypeDocNode,
+ KindString,
+ ZeroExJsDocSections,
+ MenuSubsectionsBySection,
+ TypeDocType,
+ Type,
+ DocAgnosticFormat,
+ DocSection,
+ TypescriptMethod,
+ Parameter,
+ Property,
+ CustomType,
+ IndexSignature,
+ CustomTypeChild,
+ TypeParameter,
+ TypeDocTypes,
+} from 'ts/types';
+
+const TYPES_MODULE_PATH = '"src/types"';
+
+export const sectionNameToPossibleModulePaths: {[name: string]: string[]} = {
+ [ZeroExJsDocSections.zeroEx]: ['"src/0x"'],
+ [ZeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'],
+ [ZeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'],
+ [ZeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'],
+ [ZeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'],
+ [ZeroExJsDocSections.proxy]: [
+ '"src/contract_wrappers/proxy_wrapper"',
+ '"src/contract_wrappers/token_transfer_proxy_wrapper"',
+ ],
+ [ZeroExJsDocSections.types]: [TYPES_MODULE_PATH],
+};
+
+export const typeDocUtils = {
+ isType(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Interface ||
+ entity.kindString === KindString.Function ||
+ entity.kindString === KindString['Type alias'] ||
+ entity.kindString === KindString.Variable ||
+ entity.kindString === KindString.Enumeration;
+ },
+ isMethod(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Method;
+ },
+ isConstructor(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Constructor;
+ },
+ isProperty(entity: TypeDocNode): boolean {
+ return entity.kindString === KindString.Property;
+ },
+ isPrivateOrProtectedProperty(propertyName: string): boolean {
+ return _.startsWith(propertyName, '_');
+ },
+ isPublicType(typeName: string): boolean {
+ return _.includes(constants.public0xjsTypes, typeName);
+ },
+ getModuleDefinitionBySectionNameIfExists(versionDocObj: TypeDocNode, sectionName: string):
+ TypeDocNode|undefined {
+ const possibleModulePathNames = sectionNameToPossibleModulePaths[sectionName];
+ const modules = versionDocObj.children;
+ for (const mod of modules) {
+ if (_.includes(possibleModulePathNames, mod.name)) {
+ const moduleWithName = mod;
+ return moduleWithName;
+ }
+ }
+ return undefined;
+ },
+ getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
+ const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
+ if (_.isUndefined(docAgnosticFormat)) {
+ return menuSubsectionsBySection;
+ }
+
+ const docSections = _.keys(ZeroExJsDocSections);
+ _.each(docSections, sectionName => {
+ const docSection = docAgnosticFormat[sectionName];
+ if (_.isUndefined(docSection)) {
+ return; // no-op
+ }
+
+ if (sectionName === ZeroExJsDocSections.types) {
+ const typeNames = _.map(docSection.types, t => t.name);
+ menuSubsectionsBySection[sectionName] = typeNames;
+ } else {
+ const methodNames = _.map(docSection.methods, m => m.name);
+ menuSubsectionsBySection[sectionName] = methodNames;
+ }
+ });
+ return menuSubsectionsBySection;
+ },
+ getFinal0xjsMenu(selectedVersion: string): {[section: string]: string[]} {
+ const finalMenu = _.cloneDeep(constants.menu0xjs);
+ finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => {
+ const versionIntroducedIfExists = constants.menuSubsectionToVersionWhenIntroduced[contractName];
+ if (!_.isUndefined(versionIntroducedIfExists)) {
+ const existsInSelectedVersion = compareVersions(selectedVersion,
+ versionIntroducedIfExists) >= 0;
+ return existsInSelectedVersion;
+ } else {
+ return true;
+ }
+ });
+ return finalMenu;
+ },
+ convertToDocAgnosticFormat(typeDocJson: TypeDocNode): DocAgnosticFormat {
+ const subMenus = _.values(constants.menu0xjs);
+ const orderedSectionNames = _.flatten(subMenus);
+ const docAgnosticFormat: DocAgnosticFormat = {};
+ _.each(orderedSectionNames, sectionName => {
+ const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists(
+ typeDocJson, sectionName,
+ );
+ if (_.isUndefined(packageDefinitionIfExists)) {
+ return; // no-op
+ }
+
+ // Since the `types.ts` file is the only file that does not export a module/class but
+ // instead has each type export itself, we do not need to go down two levels of nesting
+ // for it.
+ let entities;
+ let packageComment = '';
+ if (sectionName === ZeroExJsDocSections.types) {
+ entities = packageDefinitionIfExists.children;
+ } else {
+ entities = packageDefinitionIfExists.children[0].children;
+ const commentObj = packageDefinitionIfExists.children[0].comment;
+ packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
+ }
+
+ const docSection = typeDocUtils._convertEntitiesToDocSection(entities, sectionName);
+ docSection.comment = packageComment;
+ docAgnosticFormat[sectionName] = docSection;
+ });
+ return docAgnosticFormat;
+ },
+ _convertEntitiesToDocSection(entities: TypeDocNode[], sectionName: string) {
+ const docSection: DocSection = {
+ comment: '',
+ constructors: [],
+ methods: [],
+ properties: [],
+ types: [],
+ };
+
+ let isConstructor;
+ _.each(entities, entity => {
+ switch (entity.kindString) {
+ case KindString.Constructor:
+ isConstructor = true;
+ const constructor = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
+ docSection.constructors.push(constructor);
+ break;
+
+ case KindString.Method:
+ if (entity.flags.isPublic) {
+ isConstructor = false;
+ const method = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
+ docSection.methods.push(method);
+ }
+ break;
+
+ case KindString.Property:
+ if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) {
+ const property = typeDocUtils._convertProperty(entity, sectionName);
+ docSection.properties.push(property);
+ }
+ break;
+
+ case KindString.Interface:
+ case KindString.Function:
+ case KindString.Variable:
+ case KindString.Enumeration:
+ case KindString['Type alias']:
+ if (typeDocUtils.isPublicType(entity.name)) {
+ const customType = typeDocUtils._convertCustomType(entity, sectionName);
+ docSection.types.push(customType);
+ }
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('kindString', entity.kindString);
+ }
+ });
+ return docSection;
+ },
+ _convertCustomType(entity: TypeDocNode, sectionName: string): CustomType {
+ const typeIfExists = !_.isUndefined(entity.type) ?
+ typeDocUtils._convertType(entity.type, sectionName) :
+ undefined;
+ const isConstructor = false;
+ const methodIfExists = !_.isUndefined(entity.declaration) ?
+ typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
+ undefined;
+ const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) ?
+ typeDocUtils._convertIndexSignature(entity.indexSignature[0], sectionName) :
+ undefined;
+ const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ?
+ entity.comment.shortText :
+ undefined;
+
+ const childrenIfExist = !_.isUndefined(entity.children) ?
+ _.map(entity.children, (child: TypeDocNode) => {
+ const childTypeIfExists = !_.isUndefined(child.type) ?
+ typeDocUtils._convertType(child.type, sectionName) :
+ undefined;
+ const c: CustomTypeChild = {
+ name: child.name,
+ type: childTypeIfExists,
+ defaultValue: child.defaultValue,
+ };
+ return c;
+ }) :
+ undefined;
+
+ const customType = {
+ name: entity.name,
+ kindString: entity.kindString,
+ type: typeIfExists,
+ method: methodIfExists,
+ indexSignature: indexSignatureIfExists,
+ defaultValue: entity.defaultValue,
+ comment: commentIfExists,
+ children: childrenIfExist,
+ };
+ return customType;
+ },
+ _convertIndexSignature(entity: TypeDocNode, sectionName: string): IndexSignature {
+ const key = entity.parameters[0];
+ const indexSignature = {
+ keyName: key.name,
+ keyType: typeDocUtils._convertType(key.type, sectionName),
+ valueName: entity.type.name,
+ };
+ return indexSignature;
+ },
+ _convertProperty(entity: TypeDocNode, sectionName: string): Property {
+ const source = entity.sources[0];
+ const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined;
+ const property = {
+ name: entity.name,
+ type: typeDocUtils._convertType(entity.type, sectionName),
+ source: {
+ fileName: source.fileName,
+ line: source.line,
+ },
+ comment: commentIfExists,
+ };
+ return property;
+ },
+ _convertMethod(entity: TypeDocNode, isConstructor: boolean, sectionName: string): TypescriptMethod {
+ const signature = entity.signatures[0];
+ const source = entity.sources[0];
+ const hasComment = !_.isUndefined(signature.comment);
+ const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic;
+
+ const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.';
+ // HACK: we use the fact that the sectionName is the same as the property name at the top-level
+ // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON.
+ let callPath = (sectionName !== ZeroExJsDocSections.zeroEx) ?
+ `${topLevelInterface}${sectionName}.` :
+ topLevelInterface;
+ callPath = isConstructor ? '' : callPath;
+
+ const parameters = _.map(signature.parameters, param => {
+ return typeDocUtils._convertParameter(param, sectionName);
+ });
+ const returnType = typeDocUtils._convertType(signature.type, sectionName);
+ const typeParameter = _.isUndefined(signature.typeParameter) ?
+ undefined :
+ typeDocUtils._convertTypeParameter(signature.typeParameter[0], sectionName);
+
+ const method = {
+ isConstructor,
+ isStatic,
+ name: signature.name,
+ comment: hasComment ? signature.comment.shortText : undefined,
+ returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined,
+ source: {
+ fileName: source.fileName,
+ line: source.line,
+ },
+ callPath,
+ parameters,
+ returnType,
+ typeParameter,
+ };
+ return method;
+ },
+ _convertTypeParameter(entity: TypeDocNode, sectionName: string): TypeParameter {
+ const type = typeDocUtils._convertType(entity.type, sectionName);
+ const parameter = {
+ name: entity.name,
+ type,
+ };
+ return parameter;
+ },
+ _convertParameter(entity: TypeDocNode, sectionName: string): Parameter {
+ let comment = '<No comment>';
+ if (entity.comment && entity.comment.shortText) {
+ comment = entity.comment.shortText;
+ } else if (entity.comment && entity.comment.text) {
+ comment = entity.comment.text;
+ }
+
+ const isOptional = !_.isUndefined(entity.flags.isOptional) ?
+ entity.flags.isOptional :
+ false;
+
+ const type = typeDocUtils._convertType(entity.type, sectionName);
+
+ const parameter = {
+ name: entity.name,
+ comment,
+ isOptional,
+ type,
+ };
+ return parameter;
+ },
+ _convertType(entity: TypeDocType, sectionName: string): Type {
+ const typeArguments = _.map(entity.typeArguments, typeArgument => {
+ return typeDocUtils._convertType(typeArgument, sectionName);
+ });
+ const types = _.map(entity.types, t => {
+ return typeDocUtils._convertType(t, sectionName);
+ });
+
+ const isConstructor = false;
+ const methodIfExists = !_.isUndefined(entity.declaration) ?
+ typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
+ undefined;
+
+ const elementTypeIfExists = !_.isUndefined(entity.elementType) ?
+ {
+ name: entity.elementType.name,
+ typeDocType: entity.elementType.type,
+ } :
+ undefined;
+
+ const type = {
+ name: entity.name,
+ value: entity.value,
+ typeDocType: entity.type,
+ typeArguments,
+ elementType: elementTypeIfExists,
+ types,
+ method: methodIfExists,
+ };
+ return type;
+ },
+};
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
new file mode 100644
index 000000000..eb4c5be3a
--- /dev/null
+++ b/packages/website/ts/utils/utils.ts
@@ -0,0 +1,215 @@
+import * as _ from 'lodash';
+import {
+ SideToAssetToken,
+ SignatureData,
+ Order,
+ Side,
+ TokenByAddress,
+ OrderParty,
+ ScreenWidths,
+ EtherscanLinkSuffixes,
+ Token,
+ Networks,
+} from 'ts/types';
+import * as moment from 'moment';
+import isMobile = require('is-mobile');
+import * as u2f from 'ts/vendor/u2f_api';
+import deepEqual = require('deep-equal');
+import ethUtil = require('ethereumjs-util');
+import BigNumber from 'bignumber.js';
+import {constants} from 'ts/utils/constants';
+
+const LG_MIN_EM = 64;
+const MD_MIN_EM = 52;
+
+export const utils = {
+ assert(condition: boolean, message: string) {
+ if (!condition) {
+ throw new Error(message);
+ }
+ },
+ spawnSwitchErr(name: string, value: any) {
+ return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
+ },
+ isNumeric(n: string) {
+ return !isNaN(parseFloat(n)) && isFinite(Number(n));
+ },
+ // This default unix timestamp is used for orders where the user does not specify an expiry date.
+ // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for
+ // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide
+ // with actual values a user would select.
+ initialOrderExpiryUnixTimestampSec(): BigNumber {
+ const m = moment('2050-01-01');
+ return new BigNumber(m.unix());
+ },
+ convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber {
+ const finalMoment = date;
+ if (!_.isUndefined(time)) {
+ finalMoment.hours(time.hours());
+ finalMoment.minutes(time.minutes());
+ }
+ return new BigNumber(finalMoment.unix());
+ },
+ convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment {
+ return moment.unix(unixTimestampSec.toNumber());
+ },
+ convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string {
+ const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec);
+ const formattedDate: string = m.format('h:MMa MMMM D YYYY');
+ return formattedDate;
+ },
+ generateOrder(networkId: number, exchangeContract: string, sideToAssetToken: SideToAssetToken,
+ orderExpiryTimestamp: BigNumber, orderTakerAddress: string, orderMakerAddress: string,
+ makerFee: BigNumber, takerFee: BigNumber, feeRecipient: string,
+ signatureData: SignatureData, tokenByAddress: TokenByAddress, orderSalt: BigNumber): Order {
+ const makerToken = tokenByAddress[sideToAssetToken[Side.deposit].address];
+ const takerToken = tokenByAddress[sideToAssetToken[Side.receive].address];
+ const order = {
+ maker: {
+ address: orderMakerAddress,
+ token: {
+ name: makerToken.name,
+ symbol: makerToken.symbol,
+ decimals: makerToken.decimals,
+ address: makerToken.address,
+ },
+ amount: sideToAssetToken[Side.deposit].amount.toString(),
+ feeAmount: makerFee.toString(),
+ },
+ taker: {
+ address: orderTakerAddress,
+ token: {
+ name: takerToken.name,
+ symbol: takerToken.symbol,
+ decimals: takerToken.decimals,
+ address: takerToken.address,
+ },
+ amount: sideToAssetToken[Side.receive].amount.toString(),
+ feeAmount: takerFee.toString(),
+ },
+ expiration: orderExpiryTimestamp.toString(),
+ feeRecipient,
+ salt: orderSalt.toString(),
+ signature: signatureData,
+ exchangeContract,
+ networkId,
+ };
+ return order;
+ },
+ consoleLog(message: string) {
+ /* tslint:disable */
+ console.log(message);
+ /* tslint:enable */
+ },
+ sleepAsync(ms: number) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ },
+ deepEqual(actual: any, expected: any, opts?: {strict: boolean}) {
+ return deepEqual(actual, expected, opts);
+ },
+ getColSize(items: number) {
+ const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid
+ const colSize = 12 / items;
+ if (!_.isInteger(colSize)) {
+ throw new Error('Number of cols must be divisible by 12');
+ }
+ return colSize;
+ },
+ getScreenWidth() {
+ const documentEl = document.documentElement;
+ const body = document.getElementsByTagName('body')[0];
+ const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth;
+ const bodyStyles: any = window.getComputedStyle(document.querySelector('body'));
+ const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']);
+
+ // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS
+ // class prefixes. Do not edit these.
+ if (widthInEm > LG_MIN_EM) {
+ return ScreenWidths.LG;
+ } else if (widthInEm > MD_MIN_EM) {
+ return ScreenWidths.MD;
+ } else {
+ return ScreenWidths.SM;
+ }
+ },
+ isUserOnMobile(): boolean {
+ const isUserOnMobile = isMobile();
+ return isUserOnMobile;
+ },
+ getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string {
+ const networkName = constants.networkNameById[networkId];
+ if (_.isUndefined(networkName)) {
+ return undefined;
+ }
+ const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`;
+ return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`;
+ },
+ setUrlHash(anchorId: string) {
+ window.location.hash = anchorId;
+ },
+ async isU2FSupportedAsync(): Promise<boolean> {
+ const w = (window as any);
+ return new Promise((resolve: (isSupported: boolean) => void) => {
+ if (w.u2f && !w.u2f.getApiVersion) {
+ // u2f object was found (Firefox with extension)
+ resolve(true);
+ } else {
+ // u2f object was not found. Using Google polyfill
+ // HACK: u2f.getApiVersion will simply not return a version if the
+ // U2F call fails for any reason. Because of this, we set a hard 3sec
+ // timeout to the request on our end.
+ const getApiVersionTimeoutMs = 3000;
+ const intervalId = setTimeout(() => {
+ resolve(false);
+ }, getApiVersionTimeoutMs);
+ u2f.getApiVersion((version: number) => {
+ clearTimeout(intervalId);
+ resolve(true);
+ });
+ }
+ });
+ },
+ // This checks the error message returned from an injected Web3 instance on the page
+ // after a user was prompted to sign a message or send a transaction and decided to
+ // reject the request.
+ didUserDenyWeb3Request(errMsg: string) {
+ const metamaskDenialErrMsg = 'User denied message';
+ const paritySignerDenialErrMsg = 'Request has been rejected';
+ const ledgerDenialErrMsg = 'Invalid status 6985';
+ const isUserDeniedErrMsg = _.includes(errMsg, metamaskDenialErrMsg) ||
+ _.includes(errMsg, paritySignerDenialErrMsg) ||
+ _.includes(errMsg, ledgerDenialErrMsg);
+ return isUserDeniedErrMsg;
+ },
+ getCurrentEnvironment() {
+ switch (location.host) {
+ case constants.DEVELOPMENT_DOMAIN:
+ return 'development';
+ case constants.STAGING_DOMAIN:
+ return 'staging';
+ case constants.PRODUCTION_DOMAIN:
+ return 'production';
+ default:
+ return 'production';
+ }
+ },
+ getIdFromName(name: string) {
+ const id = name.replace(/ /g, '-');
+ return id;
+ },
+ getAddressBeginAndEnd(address: string): string {
+ const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
+ return truncatedAddress;
+ },
+ hasUniqueNameAndSymbol(tokens: Token[], token: Token) {
+ if (token.isRegistered) {
+ return true; // Since it's registered, it is the canonical token
+ }
+ const registeredTokens = _.filter(tokens, t => t.isRegistered);
+ const tokenWithSameNameIfExists = _.find(registeredTokens, {name: token.name});
+ const isUniqueName = _.isUndefined(tokenWithSameNameIfExists);
+ const tokenWithSameSymbolIfExists = _.find(registeredTokens, {name: token.symbol});
+ const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists);
+ return isUniqueName && isUniqueSymbol;
+ },
+};