Merge branch 'v2-prototype' into feature/order-watcher-v2

This commit is contained in:
Leonid Logvinov 2018-07-16 14:38:27 +02:00
commit acff177c54
No known key found for this signature in database
GPG Key ID: 0DD294BFDE8C95D4
66 changed files with 10639 additions and 10416 deletions

View File

@ -105,6 +105,7 @@
"@0xproject/contract-wrappers": "^0.1.0", "@0xproject/contract-wrappers": "^0.1.0",
"@0xproject/order-utils": "^1.0.0", "@0xproject/order-utils": "^1.0.0",
"@0xproject/sol-compiler": "^0.5.3", "@0xproject/sol-compiler": "^0.5.3",
"@0xproject/subproviders": "^0.10.5",
"@0xproject/types": "^1.0.0", "@0xproject/types": "^1.0.0",
"@0xproject/typescript-typings": "^0.4.2", "@0xproject/typescript-typings": "^0.4.2",
"@0xproject/utils": "^0.7.2", "@0xproject/utils": "^0.7.2",

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
export { ZeroEx } from './0x'; export { ZeroEx } from './0x';
export { MessagePrefixType, MessagePrefixOpts } from '@0xproject/order-utils'; export { MessagePrefixType, MessagePrefixOpts } from '@0xproject/order-utils';
export { Web3ProviderEngine, RPCSubprovider } from '@0xproject/subproviders';
export { export {
ExchangeContractErrs, ExchangeContractErrs,

View File

@ -2,7 +2,6 @@ import * as chai from 'chai';
import * as dirtyChai from 'dirty-chai'; import * as dirtyChai from 'dirty-chai';
import * as fs from 'fs'; import * as fs from 'fs';
import 'mocha'; import 'mocha';
import * as sleep from 'sleep';
import * as tmp from 'tmp'; import * as tmp from 'tmp';
import { utils } from '../src/utils'; import { utils } from '../src/utils';
@ -13,8 +12,6 @@ chai.use(dirtyChai);
const expect = chai.expect; const expect = chai.expect;
const SLEEP_MS = 10; // time to wait before re-timestamping a file
describe('makeOutputFileName()', () => { describe('makeOutputFileName()', () => {
it('should handle Metacoin usage', () => { it('should handle Metacoin usage', () => {
expect(utils.makeOutputFileName('Metacoin')).to.equal('metacoin'); expect(utils.makeOutputFileName('Metacoin')).to.equal('metacoin');
@ -65,19 +62,22 @@ describe('isOutputFileUpToDate()', () => {
describe('with an existing output file', () => { describe('with an existing output file', () => {
let outputFile: string; let outputFile: string;
before(() => { before(() => {
sleep.msleep(SLEEP_MS); // to ensure different timestamp
outputFile = tmp.fileSync( outputFile = tmp.fileSync(
{ discardDescriptor: true }, // close file (set timestamp) { discardDescriptor: true }, // close file (set timestamp)
).name; ).name;
const abiFileModTimeMs = fs.statSync(abiFile).mtimeMs;
const outfileModTimeMs = abiFileModTimeMs + 1;
fs.utimesSync(outputFile, outfileModTimeMs, outfileModTimeMs);
}); });
it('should return true when output file and is newer than abi file', async () => { it('should return true when output file is newer than abi file', async () => {
expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.true(); expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.true();
}); });
it('should return false when output file exists but is older than abi file', () => { it('should return false when output file exists but is older than abi file', () => {
sleep.msleep(SLEEP_MS); // to ensure different timestamp const outFileModTimeMs = fs.statSync(outputFile).mtimeMs;
fs.closeSync(fs.openSync(abiFile, 'w')); // touch abi file const abiFileModTimeMs = outFileModTimeMs + 1;
fs.utimesSync(abiFile, abiFileModTimeMs, abiFileModTimeMs);
expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.false(); expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.false();
}); });

View File

@ -56,7 +56,6 @@
"@0xproject/types": "^0.8.2", "@0xproject/types": "^0.8.2",
"@0xproject/typescript-typings": "^0.4.2", "@0xproject/typescript-typings": "^0.4.2",
"@0xproject/utils": "^0.7.2", "@0xproject/utils": "^0.7.2",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"query-string": "^5.0.1", "query-string": "^5.0.1",
"sinon": "^4.0.0", "sinon": "^4.0.0",

View File

@ -1,7 +1,7 @@
import { assert } from '@0xproject/assert'; import { assert } from '@0xproject/assert';
import { schemas } from '@0xproject/json-schemas'; import { schemas } from '@0xproject/json-schemas';
import { SignedOrder } from '@0xproject/types'; import { SignedOrder } from '@0xproject/types';
import 'isomorphic-fetch'; import { fetchAsync } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as queryString from 'query-string'; import * as queryString from 'query-string';
@ -167,7 +167,7 @@ export class HttpClient implements Client {
const headers = new Headers({ const headers = new Headers({
'content-type': 'application/json', 'content-type': 'application/json',
}); });
const response = await fetch(url, { const response = await fetchAsync(url, {
method: requestType, method: requestType,
body: JSON.stringify(payload), body: JSON.stringify(payload),
headers, headers,

View File

@ -1,11 +1,10 @@
import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils';
import { EmptyWalletSubprovider } from '@0xproject/subproviders'; import { EmptyWalletSubprovider, Web3ProviderEngine } from '@0xproject/subproviders';
import { DoneCallback } from '@0xproject/types'; import { DoneCallback } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai'; import * as chai from 'chai';
import { Provider } from 'ethereum-types'; import { Provider } from 'ethereum-types';
import 'mocha'; import 'mocha';
import Web3ProviderEngine = require('web3-provider-engine');
import { import {
BlockParamLiteral, BlockParamLiteral,

View File

@ -1,11 +1,10 @@
import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils';
import { EmptyWalletSubprovider } from '@0xproject/subproviders'; import { EmptyWalletSubprovider, Web3ProviderEngine } from '@0xproject/subproviders';
import { DoneCallback } from '@0xproject/types'; import { DoneCallback } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai'; import * as chai from 'chai';
import { Provider } from 'ethereum-types'; import { Provider } from 'ethereum-types';
import 'mocha'; import 'mocha';
import Web3ProviderEngine = require('web3-provider-engine');
import { import {
BlockParamLiteral, BlockParamLiteral,

View File

@ -51,8 +51,7 @@
"ethereum-types": "^0.0.2", "ethereum-types": "^0.0.2",
"@0xproject/typescript-typings": "^0.4.2", "@0xproject/typescript-typings": "^0.4.2",
"@0xproject/web3-wrapper": "^0.7.2", "@0xproject/web3-wrapper": "^0.7.2",
"lodash": "^4.17.4", "lodash": "^4.17.4"
"web3-provider-engine": "14.0.6"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -1,7 +1,10 @@
import ProviderEngine = require('web3-provider-engine'); import {
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); EmptyWalletSubprovider,
FakeGasEstimateSubprovider,
import { EmptyWalletSubprovider, FakeGasEstimateSubprovider, GanacheSubprovider } from '@0xproject/subproviders'; GanacheSubprovider,
RPCSubprovider,
Web3ProviderEngine,
} from '@0xproject/subproviders';
import * as fs from 'fs'; import * as fs from 'fs';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -16,8 +19,8 @@ export interface Web3Config {
} }
export const web3Factory = { export const web3Factory = {
getRpcProvider(config: Web3Config = {}): ProviderEngine { getRpcProvider(config: Web3Config = {}): Web3ProviderEngine {
const provider = new ProviderEngine(); const provider = new Web3ProviderEngine();
const hasAddresses = _.isUndefined(config.hasAddresses) || config.hasAddresses; const hasAddresses = _.isUndefined(config.hasAddresses) || config.hasAddresses;
config.shouldUseFakeGasEstimate = config.shouldUseFakeGasEstimate =
_.isUndefined(config.shouldUseFakeGasEstimate) || config.shouldUseFakeGasEstimate; _.isUndefined(config.shouldUseFakeGasEstimate) || config.shouldUseFakeGasEstimate;
@ -49,11 +52,7 @@ export const web3Factory = {
}), }),
); );
} else { } else {
provider.addProvider( provider.addProvider(new RPCSubprovider(config.rpcUrl || constants.RPC_URL));
new RpcSubprovider({
rpcUrl: config.rpcUrl || constants.RPC_URL,
}),
);
} }
provider.start(); provider.start();
return provider; return provider;

View File

@ -44,8 +44,7 @@
"ethereum-types": "^0.0.2", "ethereum-types": "^0.0.2",
"ethers": "3.0.22", "ethers": "3.0.22",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"run-s": "^0.0.0", "run-s": "^0.0.0"
"web3-provider-engine": "14.0.6"
}, },
"devDependencies": { "devDependencies": {
"@0xproject/dev-utils": "^0.4.5", "@0xproject/dev-utils": "^0.4.5",

View File

@ -1,10 +1,8 @@
import { env, EnvVars } from '@0xproject/dev-utils'; import { env, EnvVars } from '@0xproject/dev-utils';
import { GanacheSubprovider, prependSubprovider } from '@0xproject/subproviders'; import { GanacheSubprovider, prependSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0xproject/subproviders';
import { errorUtils, logUtils } from '@0xproject/utils'; import { errorUtils, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as fs from 'fs'; import * as fs from 'fs';
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { config } from './config'; import { config } from './config';
import { coverage } from './coverage'; import { coverage } from './coverage';
@ -30,7 +28,7 @@ switch (process.env.TEST_PROVIDER) {
throw errorUtils.spawnSwitchErr('TEST_PROVIDER', process.env.TEST_PROVIDER); throw errorUtils.spawnSwitchErr('TEST_PROVIDER', process.env.TEST_PROVIDER);
} }
export const provider = new ProviderEngine(); export const provider = new Web3ProviderEngine();
if (testProvider === ProviderType.Ganache) { if (testProvider === ProviderType.Ganache) {
provider.addProvider( provider.addProvider(
new GanacheSubprovider({ new GanacheSubprovider({
@ -45,7 +43,7 @@ if (testProvider === ProviderType.Ganache) {
}), }),
); );
} else { } else {
provider.addProvider(new RpcSubprovider({ rpcUrl: 'http://localhost:8501' })); provider.addProvider(new RPCSubprovider('http://localhost:8501'));
} }
provider.start(); provider.start();

View File

@ -60,8 +60,7 @@
"@ledgerhq/hw-app-eth": "^4.3.0", "@ledgerhq/hw-app-eth": "^4.3.0",
"ethereum-types": "^0.0.2", "ethereum-types": "^0.0.2",
"ethers": "3.0.22", "ethers": "3.0.22",
"lodash": "^4.17.4", "lodash": "^4.17.4"
"web3-provider-engine": "14.0.6"
}, },
"optionalDependencies": { "optionalDependencies": {
"@ledgerhq/hw-transport-node-hid": "^4.3.0" "@ledgerhq/hw-transport-node-hid": "^4.3.0"

View File

@ -1,10 +1,8 @@
import { LedgerEthereumClient, LedgerSubprovider } from '@0xproject/subproviders'; import { LedgerEthereumClient, LedgerSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0xproject/subproviders';
import Eth from '@ledgerhq/hw-app-eth'; import Eth from '@ledgerhq/hw-app-eth';
// tslint:disable:no-implicit-dependencies // tslint:disable:no-implicit-dependencies
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; import TransportNodeHid from '@ledgerhq/hw-transport-node-hid';
import { Provider } from 'ethereum-types'; import { Provider } from 'ethereum-types';
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { constants } from './constants'; import { constants } from './constants';
@ -15,18 +13,14 @@ async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumC
} }
export const providerFactory = { export const providerFactory = {
async getLedgerProviderAsync(): Promise<Provider> { async getLedgerProviderAsync(): Promise<Provider> {
const provider = new ProviderEngine(); const provider = new Web3ProviderEngine();
const ledgerWalletConfigs = { const ledgerWalletConfigs = {
networkId: constants.KOVAN_NETWORK_ID, networkId: constants.KOVAN_NETWORK_ID,
ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync,
}; };
const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
provider.addProvider(ledgerSubprovider); provider.addProvider(ledgerSubprovider);
provider.addProvider( provider.addProvider(new RPCSubprovider(constants.RPC_URL));
new RpcSubprovider({
rpcUrl: constants.RPC_URL,
}),
);
provider.start(); provider.start();
return provider; return provider;
}, },

View File

@ -87,7 +87,6 @@
"ethereum-types": "^0.0.2", "ethereum-types": "^0.0.2",
"chalk": "^2.3.0", "chalk": "^2.3.0",
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"require-from-string": "^2.0.1", "require-from-string": "^2.0.1",

View File

@ -8,11 +8,10 @@ import {
Resolver, Resolver,
URLResolver, URLResolver,
} from '@0xproject/sol-resolver'; } from '@0xproject/sol-resolver';
import { logUtils } from '@0xproject/utils'; import { fetchAsync, logUtils } from '@0xproject/utils';
import chalk from 'chalk'; import chalk from 'chalk';
import * as ethUtil from 'ethereumjs-util'; import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs'; import * as fs from 'fs';
import 'isomorphic-fetch';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as path from 'path'; import * as path from 'path';
import * as requireFromString from 'require-from-string'; import * as requireFromString from 'require-from-string';
@ -149,7 +148,7 @@ export class Compiler {
} else { } else {
logUtils.log(`Downloading ${fullSolcVersion}...`); logUtils.log(`Downloading ${fullSolcVersion}...`);
const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`; const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
const response = await fetch(url); const response = await fetchAsync(url);
const SUCCESS_STATUS = 200; const SUCCESS_STATUS = 200;
if (response.status !== SUCCESS_STATUS) { if (response.status !== SUCCESS_STATUS) {
throw new Error(`Failed to load ${fullSolcVersion}`); throw new Error(`Failed to load ${fullSolcVersion}`);

View File

@ -55,6 +55,7 @@
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"ganache-core": "0xProject/ganache-core", "ganache-core": "0xProject/ganache-core",
"hdkey": "^0.7.1", "hdkey": "^0.7.1",
"json-rpc-error": "2.0.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"semaphore-async-await": "^1.5.1", "semaphore-async-await": "^1.5.1",
"web3-provider-engine": "14.0.6" "web3-provider-engine": "14.0.6"

View File

@ -1,5 +1,6 @@
import Eth from '@ledgerhq/hw-app-eth'; import Eth from '@ledgerhq/hw-app-eth';
import TransportU2F from '@ledgerhq/hw-transport-u2f'; import TransportU2F from '@ledgerhq/hw-transport-u2f';
export import Web3ProviderEngine = require('web3-provider-engine');
export { ECSignature } from '@0xproject/types'; export { ECSignature } from '@0xproject/types';
import { LedgerEthereumClient } from './types'; import { LedgerEthereumClient } from './types';
@ -10,6 +11,7 @@ export { FakeGasEstimateSubprovider } from './subproviders/fake_gas_estimate_sub
export { SignerSubprovider } from './subproviders/signer'; export { SignerSubprovider } from './subproviders/signer';
export { RedundantSubprovider } from './subproviders/redundant_subprovider'; export { RedundantSubprovider } from './subproviders/redundant_subprovider';
export { LedgerSubprovider } from './subproviders/ledger'; export { LedgerSubprovider } from './subproviders/ledger';
export { RPCSubprovider } from './subproviders/rpc_subprovider';
export { GanacheSubprovider } from './subproviders/ganache'; export { GanacheSubprovider } from './subproviders/ganache';
export { Subprovider } from './subproviders/subprovider'; export { Subprovider } from './subproviders/subprovider';
export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker';

View File

@ -0,0 +1,90 @@
import { assert } from '@0xproject/assert';
import { StatusCodes } from '@0xproject/types';
import { fetchAsync } from '@0xproject/utils';
import { JSONRPCRequestPayload } from 'ethereum-types';
import JsonRpcError = require('json-rpc-error');
import { Callback, ErrorCallback } from '../types';
import { Subprovider } from './subprovider';
/**
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
* It forwards on JSON RPC requests to the supplied `rpcUrl` endpoint
*/
export class RPCSubprovider extends Subprovider {
private _rpcUrl: string;
private _requestTimeoutMs: number;
constructor(rpcUrl: string, requestTimeoutMs: number = 20000) {
super();
assert.isString('rpcUrl', rpcUrl);
assert.isNumber('requestTimeoutMs', requestTimeoutMs);
this._rpcUrl = rpcUrl;
this._requestTimeoutMs = requestTimeoutMs;
}
/**
* This method conforms to the web3-provider-engine interface.
* It is called internally by the ProviderEngine when it is this subproviders
* turn to handle a JSON RPC request.
* @param payload JSON RPC payload
* @param next Callback to call if this subprovider decides not to handle the request
* @param end Callback to call if subprovider handled the request and wants to pass back the request.
*/
// tslint:disable-next-line:prefer-function-over-method async-suffix
public async handleRequest(payload: JSONRPCRequestPayload, _next: Callback, end: ErrorCallback): Promise<void> {
const finalPayload = Subprovider._createFinalPayload(payload);
const headers = new Headers({
Accept: 'application/json',
'Content-Type': 'application/json',
});
let response;
try {
response = await fetchAsync(
this._rpcUrl,
{
method: 'POST',
headers,
body: JSON.stringify(finalPayload),
},
this._requestTimeoutMs,
);
} catch (err) {
end(new JsonRpcError.InternalError(err));
return;
}
const text = await response.text();
if (!response.ok) {
const statusCode = response.status;
switch (statusCode) {
case StatusCodes.MethodNotAllowed:
end(new JsonRpcError.MethodNotFound());
return;
case StatusCodes.GatewayTimeout:
const errMsg =
'Gateway timeout. The request took too long to process. This can happen when querying logs over too wide a block range.';
const err = new Error(errMsg);
end(new JsonRpcError.InternalError(err));
return;
default:
end(new JsonRpcError.InternalError(text));
return;
}
}
let data;
try {
data = JSON.parse(text);
} catch (err) {
end(new JsonRpcError.InternalError(err));
return;
}
if (data.error) {
end(data.error);
return;
}
end(null, data.result);
}
}

View File

@ -9,18 +9,7 @@ import { Callback, ErrorCallback, JSONRPCRequestPayloadWithMethod } from '../typ
export abstract class Subprovider { export abstract class Subprovider {
// tslint:disable-next-line:underscore-private-and-protected // tslint:disable-next-line:underscore-private-and-protected
private engine!: Provider; private engine!: Provider;
// Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js protected static _createFinalPayload(
private static _getRandomId(): number {
const extraDigits = 3;
const baseTen = 10;
// 13 time digits
const datePart = new Date().getTime() * Math.pow(baseTen, extraDigits);
// 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(baseTen, extraDigits));
// 16 digits
return datePart + extraPart;
}
private static _createFinalPayload(
payload: Partial<JSONRPCRequestPayloadWithMethod>, payload: Partial<JSONRPCRequestPayloadWithMethod>,
): Partial<JSONRPCRequestPayloadWithMethod> { ): Partial<JSONRPCRequestPayloadWithMethod> {
const finalPayload = { const finalPayload = {
@ -32,6 +21,17 @@ export abstract class Subprovider {
}; };
return finalPayload; return finalPayload;
} }
// Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js
private static _getRandomId(): number {
const extraDigits = 3;
const baseTen = 10;
// 13 time digits
const datePart = new Date().getTime() * Math.pow(baseTen, extraDigits);
// 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(baseTen, extraDigits));
// 16 digits
return datePart + extraPart;
}
// tslint:disable-next-line:async-suffix // tslint:disable-next-line:async-suffix
public abstract async handleRequest( public abstract async handleRequest(
payload: JSONRPCRequestPayload, payload: JSONRPCRequestPayload,

View File

@ -1,4 +1,4 @@
import ProviderEngine = require('web3-provider-engine'); import Web3ProviderEngine = require('web3-provider-engine');
import { Subprovider } from '../subproviders/subprovider'; import { Subprovider } from '../subproviders/subprovider';
@ -7,7 +7,7 @@ import { Subprovider } from '../subproviders/subprovider';
* @param provider Given provider * @param provider Given provider
* @param subprovider Subprovider to prepend * @param subprovider Subprovider to prepend
*/ */
export function prependSubprovider(provider: ProviderEngine, subprovider: Subprovider): void { export function prependSubprovider(provider: Web3ProviderEngine, subprovider: Subprovider): void {
subprovider.setEngine(provider); subprovider.setEngine(provider);
// HACK: We use implementation details of provider engine here // HACK: We use implementation details of provider engine here
// https://github.com/MetaMask/provider-engine/blob/master/index.js#L68 // https://github.com/MetaMask/provider-engine/blob/master/index.js#L68

View File

@ -7,10 +7,8 @@ import TransportNodeHid from '@ledgerhq/hw-transport-node-hid';
import * as chai from 'chai'; import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as ethUtils from 'ethereumjs-util'; import * as ethUtils from 'ethereumjs-util';
import Web3ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { LedgerSubprovider } from '../../src'; import { LedgerSubprovider, RPCSubprovider, Web3ProviderEngine } from '../../src';
import { LedgerEthereumClient } from '../../src/types'; import { LedgerEthereumClient } from '../../src/types';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
import { fixtureData } from '../utils/fixture_data'; import { fixtureData } from '../utils/fixture_data';
@ -86,9 +84,7 @@ describe('LedgerSubprovider', () => {
before(() => { before(() => {
ledgerProvider = new Web3ProviderEngine(); ledgerProvider = new Web3ProviderEngine();
ledgerProvider.addProvider(ledgerSubprovider); ledgerProvider.addProvider(ledgerSubprovider);
const httpProvider = new RpcSubprovider({ const httpProvider = new RPCSubprovider('http://localhost:8545');
rpcUrl: 'http://localhost:8545',
});
ledgerProvider.addProvider(httpProvider); ledgerProvider.addProvider(httpProvider);
ledgerProvider.start(); ledgerProvider.start();

View File

@ -2,9 +2,8 @@ import * as chai from 'chai';
import * as lightwallet from 'eth-lightwallet'; import * as lightwallet from 'eth-lightwallet';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as ethUtils from 'ethereumjs-util'; import * as ethUtils from 'ethereumjs-util';
import Web3ProviderEngine = require('web3-provider-engine');
import { EthLightwalletSubprovider } from '../../src'; import { EthLightwalletSubprovider, Web3ProviderEngine } from '../../src';
import { DoneCallback } from '../../src/types'; import { DoneCallback } from '../../src/types';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
import { fixtureData } from '../utils/fixture_data'; import { fixtureData } from '../utils/fixture_data';

View File

@ -2,9 +2,8 @@ import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as ethUtils from 'ethereumjs-util'; import * as ethUtils from 'ethereumjs-util';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Web3ProviderEngine = require('web3-provider-engine');
import { LedgerSubprovider } from '../../src'; import { LedgerSubprovider, Web3ProviderEngine } from '../../src';
import { import {
DoneCallback, DoneCallback,
LedgerCommunicationClient, LedgerCommunicationClient,

View File

@ -1,9 +1,8 @@
import * as chai from 'chai'; import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as ethUtils from 'ethereumjs-util'; import * as ethUtils from 'ethereumjs-util';
import Web3ProviderEngine = require('web3-provider-engine');
import { GanacheSubprovider, MnemonicWalletSubprovider } from '../../src/'; import { GanacheSubprovider, MnemonicWalletSubprovider, Web3ProviderEngine } from '../../src/';
import { DoneCallback, WalletSubproviderErrors } from '../../src/types'; import { DoneCallback, WalletSubproviderErrors } from '../../src/types';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
import { fixtureData } from '../utils/fixture_data'; import { fixtureData } from '../utils/fixture_data';

View File

@ -1,11 +1,10 @@
import * as chai from 'chai'; import * as chai from 'chai';
import Web3ProviderEngine = require('web3-provider-engine');
import FixtureSubprovider = require('web3-provider-engine/subproviders/fixture'); import FixtureSubprovider = require('web3-provider-engine/subproviders/fixture');
import { promisify } from '@0xproject/utils'; import { promisify } from '@0xproject/utils';
import EthereumTx = require('ethereumjs-tx'); import EthereumTx = require('ethereumjs-tx');
import { NonceTrackerSubprovider } from '../../src'; import { NonceTrackerSubprovider, Web3ProviderEngine } from '../../src';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
const expect = chai.expect; const expect = chai.expect;

View File

@ -1,9 +1,8 @@
import * as chai from 'chai'; import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as ethUtils from 'ethereumjs-util'; import * as ethUtils from 'ethereumjs-util';
import Web3ProviderEngine = require('web3-provider-engine');
import { GanacheSubprovider, PrivateKeyWalletSubprovider } from '../../src/'; import { GanacheSubprovider, PrivateKeyWalletSubprovider, Web3ProviderEngine } from '../../src/';
import { DoneCallback, WalletSubproviderErrors } from '../../src/types'; import { DoneCallback, WalletSubproviderErrors } from '../../src/types';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
import { fixtureData } from '../utils/fixture_data'; import { fixtureData } from '../utils/fixture_data';

View File

@ -2,10 +2,8 @@ import { DoneCallback } from '@0xproject/types';
import * as chai from 'chai'; import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types'; import { JSONRPCResponsePayload } from 'ethereum-types';
import * as Sinon from 'sinon'; import * as Sinon from 'sinon';
import Web3ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { RedundantSubprovider } from '../../src'; import { RedundantSubprovider, RPCSubprovider, Web3ProviderEngine } from '../../src';
import { Subprovider } from '../../src/subproviders/subprovider'; import { Subprovider } from '../../src/subproviders/subprovider';
import { chaiSetup } from '../chai_setup'; import { chaiSetup } from '../chai_setup';
import { ganacheSubprovider } from '../utils/ganache_subprovider'; import { ganacheSubprovider } from '../utils/ganache_subprovider';
@ -39,9 +37,7 @@ describe('RedundantSubprovider', () => {
}); });
it('succeeds when supplied at least one healthy endpoint', (done: DoneCallback) => { it('succeeds when supplied at least one healthy endpoint', (done: DoneCallback) => {
provider = new Web3ProviderEngine(); provider = new Web3ProviderEngine();
const nonExistentSubprovider = new RpcSubprovider({ const nonExistentSubprovider = new RPCSubprovider('http://does-not-exist:3000');
rpcUrl: 'http://does-not-exist:3000',
});
const handleRequestStub = Sinon.stub(nonExistentSubprovider, 'handleRequest').throws( const handleRequestStub = Sinon.stub(nonExistentSubprovider, 'handleRequest').throws(
new Error('REQUEST_FAILED'), new Error('REQUEST_FAILED'),
); );

View File

@ -18,7 +18,7 @@
"author": "Fabio Berger", "author": "Fabio Berger",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"0x.js": "^0.38.0", "0x.js": "0.38.4",
"@0xproject/subproviders": "^0.10.5", "@0xproject/subproviders": "^0.10.5",
"@0xproject/web3-wrapper": "^0.7.2", "@0xproject/web3-wrapper": "^0.7.2",
"@0xproject/typescript-typings": "^0.4.2", "@0xproject/typescript-typings": "^0.4.2",
@ -28,8 +28,7 @@
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"express": "^4.15.2", "express": "^4.15.2",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"rollbar": "^0.6.5", "rollbar": "^0.6.5"
"web3-provider-engine": "14.0.6"
}, },
"devDependencies": { "devDependencies": {
"@0xproject/tslint-config": "^0.4.21", "@0xproject/tslint-config": "^0.4.21",

View File

@ -5,9 +5,12 @@ import { Provider } from 'ethereum-types';
import * as express from 'express'; import * as express from 'express';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { NonceTrackerSubprovider, PrivateKeyWalletSubprovider } from '@0xproject/subproviders'; import {
import ProviderEngine = require('web3-provider-engine'); NonceTrackerSubprovider,
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); PrivateKeyWalletSubprovider,
RPCSubprovider,
Web3ProviderEngine,
} from '@0xproject/subproviders';
import { configs } from './configs'; import { configs } from './configs';
import { constants } from './constants'; import { constants } from './constants';
@ -39,14 +42,10 @@ export class Handler {
if (_.isUndefined(configs.DISPENSER_PRIVATE_KEY)) { if (_.isUndefined(configs.DISPENSER_PRIVATE_KEY)) {
throw new Error('Dispenser Private key not found'); throw new Error('Dispenser Private key not found');
} }
const engine = new ProviderEngine(); const engine = new Web3ProviderEngine();
engine.addProvider(new NonceTrackerSubprovider()); engine.addProvider(new NonceTrackerSubprovider());
engine.addProvider(new PrivateKeyWalletSubprovider(configs.DISPENSER_PRIVATE_KEY)); engine.addProvider(new PrivateKeyWalletSubprovider(configs.DISPENSER_PRIVATE_KEY));
engine.addProvider( engine.addProvider(new RPCSubprovider(rpcUrl));
new RpcSubprovider({
rpcUrl,
}),
);
engine.start(); engine.start();
return engine; return engine;
} }

View File

@ -221,3 +221,11 @@ export enum RevertReason {
ValueGreaterThanZero = 'VALUE_GREATER_THAN_ZERO', ValueGreaterThanZero = 'VALUE_GREATER_THAN_ZERO',
InvalidMsgValue = 'INVALID_MSG_VALUE', InvalidMsgValue = 'INVALID_MSG_VALUE',
} }
export enum StatusCodes {
Success = 200,
NotFound = 404,
InternalError = 500,
MethodNotAllowed = 405,
GatewayTimeout = 504,
}

View File

@ -0,0 +1,3 @@
declare module 'detect-node' {
export const isNode: boolean;
}

View File

@ -0,0 +1,8 @@
declare module 'json-rpc-error' {
export class InternalError extends Error {
constructor(err: Error | string);
}
export class MethodNotFound extends Error {
constructor();
}
}

View File

@ -35,14 +35,15 @@
"typescript": "2.7.1" "typescript": "2.7.1"
}, },
"dependencies": { "dependencies": {
"ethereum-types": "^0.0.2", "@0xproject/types": "^1.0.0",
"@0xproject/typescript-typings": "^0.4.2", "@0xproject/typescript-typings": "^0.4.2",
"@types/node": "^8.0.53", "@types/node": "^8.0.53",
"ethereumjs-util": "^5.1.1",
"bignumber.js": "~4.1.0", "bignumber.js": "~4.1.0",
"detect-node": "2.0.3",
"ethereum-types": "^0.0.2", "ethereum-types": "^0.0.2",
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"ethers": "3.0.22", "ethers": "3.0.22",
"isomorphic-fetch": "^2.2.1",
"js-sha3": "^0.7.0", "js-sha3": "^0.7.0",
"lodash": "^4.17.4" "lodash": "^4.17.4"
}, },

View File

@ -0,0 +1,37 @@
import isNode = require('detect-node');
import 'isomorphic-fetch';
export const fetchAsync = async (
endpoint: string,
options: RequestInit = {},
timeoutMs: number = 20000,
): Promise<Response> => {
if (options.signal || (options as any).timeout) {
throw new Error(
'Cannot call fetchAsync with options.signal or options.timeout. To set a timeout, please use the supplied "timeoutMs" parameter.',
);
}
let optionsWithAbortParam;
if (!isNode) {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => {
controller.abort();
}, timeoutMs);
optionsWithAbortParam = {
signal,
...options,
};
} else {
// HACK: the `timeout` param only exists in `node-fetch`, and not on the `isomorphic-fetch`
// `RequestInit` type. Since `isomorphic-fetch` conditionally wraps `node-fetch` when the
// execution environment is `Node.js`, we need to cast it to `any` in that scenario.
optionsWithAbortParam = {
timeout: timeoutMs,
...options,
} as any;
}
const response = await fetch(endpoint, optionsWithAbortParam);
return response;
};

View File

@ -8,3 +8,4 @@ export { logUtils } from './log_utils';
export { abiUtils } from './abi_utils'; export { abiUtils } from './abi_utils';
export { NULL_BYTES } from './constants'; export { NULL_BYTES } from './constants';
export { errorUtils } from './error_utils'; export { errorUtils } from './error_utils';
export { fetchAsync } from './fetchAsync';

View File

@ -16,7 +16,7 @@
"deploy_staging": "deploy_staging":
"npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers",
"deploy_live": "deploy_live":
"npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --exclude *.map.js" "DEPLOY_ROLLBAR_SOURCEMAPS=true npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --exclude *.map.js"
}, },
"author": "Fabio Berger", "author": "Fabio Berger",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -49,7 +49,6 @@
"react-copy-to-clipboard": "^4.2.3", "react-copy-to-clipboard": "^4.2.3",
"react-document-title": "^2.0.3", "react-document-title": "^2.0.3",
"react-dom": "15.6.1", "react-dom": "15.6.1",
"react-ga": "^2.4.1",
"react-popper": "^1.0.0-beta.6", "react-popper": "^1.0.0-beta.6",
"react-redux": "^5.0.3", "react-redux": "^5.0.3",
"react-router-dom": "^4.1.1", "react-router-dom": "^4.1.1",
@ -63,7 +62,6 @@
"thenby": "^1.2.3", "thenby": "^1.2.3",
"truffle-contract": "2.0.1", "truffle-contract": "2.0.1",
"web3-provider-engine": "14.0.6", "web3-provider-engine": "14.0.6",
"whatwg-fetch": "^2.0.3",
"xml-js": "^1.6.4" "xml-js": "^1.6.4"
}, },
"devDependencies": { "devDependencies": {

View File

@ -23,6 +23,12 @@
</head> </head>
<body style="margin: 0px; min-width: 355px;"> <body style="margin: 0px; min-width: 355px;">
<!-- Heap SDK -->
<script type="text/javascript">
window.heap = window.heap || [], heap.load = function (e, t) { window.heap.appid = e, window.heap.config = t = t || {}; var r = t.forceSSL || "https:" === document.location.protocol, a = document.createElement("script"); a.type = "text/javascript", a.async = !0, a.src = (r ? "https:" : "http:") + "//cdn.heapanalytics.com/js/heap-" + e + ".js"; var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(a, n); for (var o = function (e) { return function () { heap.push([e].concat(Array.prototype.slice.call(arguments, 0))) } }, p = ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"], c = 0; c < p.length; c++)heap[p[c]] = o(p[c]) };
heap.load("410099666");
</script>
<!-- End Heap SDK -->
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script>
<script> <script>

View File

@ -15,8 +15,9 @@ import {
ledgerEthereumBrowserClientFactoryAsync, ledgerEthereumBrowserClientFactoryAsync,
LedgerSubprovider, LedgerSubprovider,
RedundantSubprovider, RedundantSubprovider,
RPCSubprovider,
SignerSubprovider, SignerSubprovider,
Subprovider, Web3ProviderEngine,
} from '@0xproject/subproviders'; } from '@0xproject/subproviders';
import { import {
BlockParam, BlockParam,
@ -60,9 +61,7 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter'; import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
import ProviderEngine = require('web3-provider-engine');
import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import * as MintableArtifacts from '../contracts/Mintable.json'; import * as MintableArtifacts from '../contracts/Mintable.json';
@ -148,7 +147,7 @@ export class Blockchain {
if (!isU2FSupported) { if (!isU2FSupported) {
throw new Error('Cannot update providerType to LEDGER without U2F support'); throw new Error('Cannot update providerType to LEDGER without U2F support');
} }
const provider = new ProviderEngine(); const provider = new Web3ProviderEngine();
const ledgerWalletConfigs = { const ledgerWalletConfigs = {
networkId: networkIdIfExists, networkId: networkIdIfExists,
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
@ -157,25 +156,21 @@ export class Blockchain {
provider.addProvider(ledgerSubprovider); provider.addProvider(ledgerSubprovider);
provider.addProvider(new FilterSubprovider()); provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => { const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => {
return new RpcSubprovider({ return new RPCSubprovider(publicNodeUrl);
rpcUrl: publicNodeUrl,
});
}); });
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); provider.addProvider(new RedundantSubprovider(rpcSubproviders));
provider.start(); provider.start();
return [provider, ledgerSubprovider]; return [provider, ledgerSubprovider];
} else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { } else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
// We catch all requests involving a users account and send it to the injectedWeb3 // We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node. // instance. All other requests go to the public hosted node.
const provider = new ProviderEngine(); const provider = new Web3ProviderEngine();
provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider)); provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider));
provider.addProvider(new FilterSubprovider()); provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
return new RpcSubprovider({ return new RPCSubprovider(publicNodeUrl);
rpcUrl: publicNodeUrl,
});
}); });
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); provider.addProvider(new RedundantSubprovider(rpcSubproviders));
provider.start(); provider.start();
return [provider, undefined]; return [provider, undefined];
} else if (doesInjectedWeb3Exist) { } else if (doesInjectedWeb3Exist) {
@ -185,15 +180,13 @@ export class Blockchain {
// If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node
// We do this so that users can still browse the 0x Portal DApp even if they do not have web3 // We do this so that users can still browse the 0x Portal DApp even if they do not have web3
// injected into their browser. // injected into their browser.
const provider = new ProviderEngine(); const provider = new Web3ProviderEngine();
provider.addProvider(new FilterSubprovider()); provider.addProvider(new FilterSubprovider());
const networkId = constants.NETWORK_ID_MAINNET; const networkId = constants.NETWORK_ID_MAINNET;
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => { const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
return new RpcSubprovider({ return new RPCSubprovider(publicNodeUrl);
rpcUrl: publicNodeUrl,
});
}); });
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); provider.addProvider(new RedundantSubprovider(rpcSubproviders));
provider.start(); provider.start();
return [provider, undefined]; return [provider, undefined];
} }
@ -571,7 +564,7 @@ export class Blockchain {
configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, configs.DEFAULT_TRACKED_TOKEN_SYMBOLS,
)}) not found in tokenRegistry: ${JSON.stringify(tokenRegistryTokens)}`, )}) not found in tokenRegistry: ${JSON.stringify(tokenRegistryTokens)}`,
); );
await errorReporter.reportAsync(err); errorReporter.report(err);
return; return;
} }
if (_.isEmpty(trackedTokensByAddress)) { if (_.isEmpty(trackedTokensByAddress)) {
@ -682,8 +675,7 @@ export class Blockchain {
// Note: it's not entirely clear from the documentation which // Note: it's not entirely clear from the documentation which
// errors will be thrown by `watch`. For now, let's log the error // errors will be thrown by `watch`. For now, let's log the error
// to rollbar and stop watching when one occurs // to rollbar and stop watching when one occurs
// tslint:disable-next-line:no-floating-promises errorReporter.report(err); // fire and forget
errorReporter.reportAsync(err); // fire and forget
return; return;
} else { } else {
const decodedLog = decodedLogEvent.log; const decodedLog = decodedLogEvent.log;
@ -795,7 +787,7 @@ export class Blockchain {
return tokenByAddress; return tokenByAddress;
} }
private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { private async _onPageLoadInitFireAndForgetAsync(): Promise<void> {
await utils.onPageLoadAsync(); // wait for page to load await utils.onPageLoadPromise; // wait for page to load
const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET; this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET;
const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
@ -915,7 +907,7 @@ export class Blockchain {
if (_.includes(errMsg, 'not been deployed to detected network')) { if (_.includes(errMsg, 'not been deployed to detected network')) {
throw new Error(BlockchainCallErrs.ContractDoesNotExist); throw new Error(BlockchainCallErrs.ContractDoesNotExist);
} else { } else {
await errorReporter.reportAsync(err); errorReporter.report(err);
throw new Error(BlockchainCallErrs.UnhandledError); throw new Error(BlockchainCallErrs.UnhandledError);
} }
} }

View File

@ -118,7 +118,7 @@ export class EthWethConversionButton extends React.Component<
? 'Failed to wrap your ETH. Please try again.' ? 'Failed to wrap your ETH. Please try again.'
: 'Failed to unwrap your WETH. Please try again.'; : 'Failed to unwrap your WETH. Please try again.';
this.props.dispatcher.showFlashMessage(errorMsg); this.props.dispatcher.showFlashMessage(errorMsg);
await errorReporter.reportAsync(err); errorReporter.report(err);
} }
} }
this.setState({ this.setState({

View File

@ -1,5 +1,5 @@
import { getOrderHashHex, isValidSignature } from '@0xproject/order-utils'; import { getOrderHashHex, isValidSignature } from '@0xproject/order-utils';
import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { colors } from '@0xproject/react-shared';
import { Order as ZeroExOrder } from '@0xproject/types'; import { Order as ZeroExOrder } from '@0xproject/types';
import { BigNumber, logUtils } from '@0xproject/utils'; import { BigNumber, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Web3Wrapper } from '@0xproject/web3-wrapper';
@ -506,6 +506,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
await this._checkForUntrackedTokensAndAskToAddAsync(); await this._checkForUntrackedTokensAndAskToAddAsync();
} }
private _trackOrderEvent(eventName: string): void {
const parsedOrder = this.state.parsedOrder;
analytics.trackOrderEvent(eventName, parsedOrder);
}
private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { private async _onFillOrderClickFireAndForgetAsync(): Promise<void> {
if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
@ -552,14 +556,12 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
}); });
return; return;
} }
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const eventLabel = `${parsedOrder.metadata.takerToken.symbol}-${networkName}`;
try { try {
const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync(
signedOrder, signedOrder,
this.props.orderFillAmount, this.props.orderFillAmount,
); );
analytics.logEvent('Portal', 'Fill Order Success', eventLabel, parsedOrder.signedOrder.takerTokenAmount); this._trackOrderEvent('Fill Order Success');
// After fill completes, let's force fetch the token balances // After fill completes, let's force fetch the token balances
this.props.dispatcher.forceTokenStateRefetch(); this.props.dispatcher.forceTokenStateRefetch();
this.setState({ this.setState({
@ -573,7 +575,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
this.setState({ this.setState({
isFilling: false, isFilling: false,
}); });
analytics.logEvent('Portal', 'Fill Order Failure', eventLabel, parsedOrder.signedOrder.takerTokenAmount); this._trackOrderEvent('Fill Order Failure');
const errMsg = `${err}`; const errMsg = `${err}`;
if (utils.didUserDenyWeb3Request(errMsg)) { if (utils.didUserDenyWeb3Request(errMsg)) {
return; return;
@ -583,7 +585,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
this.setState({ this.setState({
globalErrMsg, globalErrMsg,
}); });
await errorReporter.reportAsync(err); errorReporter.report(err);
return; return;
} }
} }
@ -628,8 +630,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
}); });
return; return;
} }
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const eventLabel = `${parsedOrder.metadata.makerToken.symbol}-${networkName}`;
try { try {
await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount);
this.setState({ this.setState({
@ -638,7 +638,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
globalErrMsg: '', globalErrMsg: '',
unavailableTakerAmount: takerTokenAmount, unavailableTakerAmount: takerTokenAmount,
}); });
analytics.logEvent('Portal', 'Cancel Order Success', eventLabel, parsedOrder.signedOrder.makerTokenAmount); this._trackOrderEvent('Cancel Order Success');
return; return;
} catch (err) { } catch (err) {
this.setState({ this.setState({
@ -648,13 +648,13 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
if (utils.didUserDenyWeb3Request(errMsg)) { if (utils.didUserDenyWeb3Request(errMsg)) {
return; return;
} }
analytics.logEvent('Portal', 'Cancel Order Failure', eventLabel, parsedOrder.signedOrder.makerTokenAmount); this._trackOrderEvent('Cancel Order Failure');
globalErrMsg = 'Failed to cancel order, please refresh and try again'; globalErrMsg = 'Failed to cancel order, please refresh and try again';
logUtils.log(`${err}`); logUtils.log(`${err}`);
this.setState({ this.setState({
globalErrMsg, globalErrMsg,
}); });
await errorReporter.reportAsync(err); errorReporter.report(err);
return; return;
} }
} }

View File

@ -6,6 +6,7 @@ import { Button } from 'ts/components/ui/button';
import { Container } from 'ts/components/ui/container'; import { Container } from 'ts/components/ui/container';
import { Input } from 'ts/components/ui/input'; import { Input } from 'ts/components/ui/input';
import { Text } from 'ts/components/ui/text'; import { Text } from 'ts/components/ui/text';
import { analytics } from 'ts/utils/analytics';
import { backendClient } from 'ts/utils/backend_client'; import { backendClient } from 'ts/utils/backend_client';
export interface SubscribeFormProps {} export interface SubscribeFormProps {}
@ -112,6 +113,9 @@ export class SubscribeForm extends React.Component<SubscribeFormProps, Subscribe
try { try {
const response = await backendClient.subscribeToNewsletterAsync(this.state.emailText); const response = await backendClient.subscribeToNewsletterAsync(this.state.emailText);
const status = response.status === 200 ? SubscribeFormStatus.Success : SubscribeFormStatus.Error; const status = response.status === 200 ? SubscribeFormStatus.Success : SubscribeFormStatus.Error;
if (status === SubscribeFormStatus.Success) {
analytics.identify(this.state.emailText, 'email');
}
this.setState({ status, emailText: '' }); this.setState({ status, emailText: '' });
} catch (error) { } catch (error) {
this._setStatus(SubscribeFormStatus.Error); this._setStatus(SubscribeFormStatus.Error);

View File

@ -1,6 +1,6 @@
import { generatePseudoRandomSalt, getOrderHashHex } from '@0xproject/order-utils'; import { generatePseudoRandomSalt, getOrderHashHex } from '@0xproject/order-utils';
import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { colors } from '@0xproject/react-shared';
import { ECSignature, Order } from '@0xproject/types'; import { ECSignature, Order as ZeroExOrder } from '@0xproject/types';
import { BigNumber, logUtils } from '@0xproject/utils'; import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Dialog from 'material-ui/Dialog'; import Dialog from 'material-ui/Dialog';
@ -20,7 +20,7 @@ import { SwapIcon } from 'ts/components/ui/swap_icon';
import { Dispatcher } from 'ts/redux/dispatcher'; import { Dispatcher } from 'ts/redux/dispatcher';
import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
import { validator } from 'ts/schemas/validator'; import { validator } from 'ts/schemas/validator';
import { AlertTypes, BlockchainErrs, HashData, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; import { AlertTypes, BlockchainErrs, HashData, Order, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types';
import { analytics } from 'ts/utils/analytics'; import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter'; import { errorReporter } from 'ts/utils/error_reporter';
@ -254,7 +254,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
userAddressIfExists, userAddressIfExists,
debitToken.address, debitToken.address,
); );
const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; const receiveToken = this.props.sideToAssetToken[Side.Receive];
const receiveAmount = receiveToken.amount;
if ( if (
!_.isUndefined(debitToken.amount) && !_.isUndefined(debitToken.amount) &&
!_.isUndefined(receiveAmount) && !_.isUndefined(receiveAmount) &&
@ -264,24 +265,28 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
debitBalance.gte(debitToken.amount) && debitBalance.gte(debitToken.amount) &&
debitAllowance.gte(debitToken.amount) debitAllowance.gte(debitToken.amount)
) { ) {
const didSignSuccessfully = await this._signTransactionAsync(); const signedOrder = await this._signTransactionAsync();
if (didSignSuccessfully) { const doesSignedOrderExist = !_.isUndefined(signedOrder);
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; if (doesSignedOrderExist) {
const eventLabel = `${this.props.tokenByAddress[debitToken.address].symbol}-${networkName}`; analytics.trackOrderEvent('Sign Order Success', signedOrder);
analytics.logEvent('Portal', 'Sign Order Success', eventLabel, debitToken.amount.toNumber());
this.setState({ this.setState({
globalErrMsg: '', globalErrMsg: '',
shouldShowIncompleteErrs: false, shouldShowIncompleteErrs: false,
}); });
} }
return didSignSuccessfully; return doesSignedOrderExist;
} else { } else {
let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; let globalErrMsg = 'You must fix the above errors in order to generate a valid order';
if (this.props.userAddress === '') { if (this.props.userAddress === '') {
globalErrMsg = 'You must enable wallet communication'; globalErrMsg = 'You must enable wallet communication';
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
} }
analytics.logEvent('Portal', 'Sign Order Failure', globalErrMsg); analytics.track('Sign Order Failure', {
makerTokenAmount: debitToken.amount.toString(),
makerToken: this.props.tokenByAddress[debitToken.address].symbol,
takerTokenAmount: receiveToken.amount.toString(),
takerToken: this.props.tokenByAddress[receiveToken.address].symbol,
});
this.setState({ this.setState({
globalErrMsg, globalErrMsg,
shouldShowIncompleteErrs: true, shouldShowIncompleteErrs: true,
@ -289,7 +294,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
return false; return false;
} }
} }
private async _signTransactionAsync(): Promise<boolean> { private async _signTransactionAsync(): Promise<Order | undefined> {
this.setState({ this.setState({
signingState: SigningState.SIGNING, signingState: SigningState.SIGNING,
}); });
@ -299,11 +304,11 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
this.setState({ this.setState({
signingState: SigningState.UNSIGNED, signingState: SigningState.UNSIGNED,
}); });
return false; return undefined;
} }
const hashData = this.props.hashData; const hashData = this.props.hashData;
const zeroExOrder: Order = { const zeroExOrder: ZeroExOrder = {
exchangeContractAddress: exchangeContractAddr, exchangeContractAddress: exchangeContractAddr,
expirationUnixTimestampSec: hashData.orderExpiryTimestamp, expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
feeRecipient: hashData.feeRecipientAddress, feeRecipient: hashData.feeRecipientAddress,
@ -320,9 +325,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
const orderHash = getOrderHashHex(zeroExOrder); const orderHash = getOrderHashHex(zeroExOrder);
let globalErrMsg = ''; let globalErrMsg = '';
let order;
try { try {
const ecSignature = await this.props.blockchain.signOrderHashAsync(orderHash); const ecSignature = await this.props.blockchain.signOrderHashAsync(orderHash);
const order = utils.generateOrder( order = utils.generateOrder(
exchangeContractAddr, exchangeContractAddr,
this.props.sideToAssetToken, this.props.sideToAssetToken,
hashData.orderExpiryTimestamp, hashData.orderExpiryTimestamp,
@ -349,14 +355,14 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; globalErrMsg = 'An unexpected error occured. Please try refreshing the page';
logUtils.log(`Unexpected error occured: ${err}`); logUtils.log(`Unexpected error occured: ${err}`);
logUtils.log(err.stack); logUtils.log(err.stack);
await errorReporter.reportAsync(err); errorReporter.report(err);
} }
} }
this.setState({ this.setState({
signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED,
globalErrMsg, globalErrMsg,
}); });
return globalErrMsg === ''; return order;
} }
private _updateOrderAddress(address?: string): void { private _updateOrderAddress(address?: string): void {
if (!_.isUndefined(address)) { if (!_.isUndefined(address)) {

View File

@ -1,4 +1,4 @@
import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils'; import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Toggle from 'material-ui/Toggle'; import Toggle from 'material-ui/Toggle';
@ -111,14 +111,16 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
if (!this._isAllowanceSet()) { if (!this._isAllowanceSet()) {
newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
} }
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const logData = {
const eventLabel = `${this.props.token.symbol}-${networkName}`; tokenSymbol: this.props.token.symbol,
newAllowance: newAllowanceAmountInBaseUnits.toNumber(),
};
try { try {
await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
analytics.logEvent('Portal', 'Set Allowance Success', eventLabel, newAllowanceAmountInBaseUnits.toNumber()); analytics.track('Set Allowances Success', logData);
await this.props.refetchTokenStateAsync(); await this.props.refetchTokenStateAsync();
} catch (err) { } catch (err) {
analytics.logEvent('Portal', 'Set Allowance Failure', eventLabel, newAllowanceAmountInBaseUnits.toNumber()); analytics.track('Set Allowance Failure', logData);
this.setState({ this.setState({
isSpinnerVisible: false, isSpinnerVisible: false,
}); });
@ -129,7 +131,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
logUtils.log(`Unexpected error encountered: ${err}`); logUtils.log(`Unexpected error encountered: ${err}`);
logUtils.log(err.stack); logUtils.log(err.stack);
this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
await errorReporter.reportAsync(err); errorReporter.report(err);
} }
} }
private _isAllowanceSet(): boolean { private _isAllowanceSet(): boolean {

View File

@ -1,4 +1,3 @@
import { constants as sharedConstants } from '@0xproject/react-shared';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router'; import { RouteComponentProps, withRouter } from 'react-router';
@ -225,20 +224,24 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
(this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) || (this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) ||
(!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded) (!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded)
) { ) {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; analytics.track('Onboarding Started', {
analytics.logEvent('Portal', 'Onboarding Started - Automatic', networkName, this.props.stepIndex); reason: 'automatic',
stepIndex: this.props.stepIndex,
});
this.props.updateIsRunning(true); this.props.updateIsRunning(true);
} }
} }
private _updateOnboardingStep(stepIndex: number): void { private _updateOnboardingStep(stepIndex: number): void {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
this.props.updateOnboardingStep(stepIndex); this.props.updateOnboardingStep(stepIndex);
analytics.logEvent('Portal', 'Update Onboarding Step', networkName, stepIndex); analytics.track('Update Onboarding Step', {
stepIndex,
});
} }
private _closeOnboarding(): void { private _closeOnboarding(): void {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
this.props.updateIsRunning(false); this.props.updateIsRunning(false);
analytics.logEvent('Portal', 'Onboarding Closed', networkName, this.props.stepIndex); analytics.track('Onboarding Closed', {
stepIndex: this.props.stepIndex,
});
} }
private _renderZrxAllowanceToggle(): React.ReactNode { private _renderZrxAllowanceToggle(): React.ReactNode {
const zrxToken = utils.getZrxToken(this.props.tokenByAddress); const zrxToken = utils.getZrxToken(this.props.tokenByAddress);

View File

@ -1,5 +1,5 @@
import { ECSignature } from '@0xproject/types'; import { ECSignature } from '@0xproject/types';
import { BigNumber, logUtils } from '@0xproject/utils'; import { BigNumber, fetchAsync, logUtils } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Paper from 'material-ui/Paper'; import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField'; import TextField from 'material-ui/TextField';
@ -148,13 +148,13 @@ You can see and fill it here: ${this.state.shareLink}`);
const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${
configs.BITLY_ACCESS_TOKEN configs.BITLY_ACCESS_TOKEN
}&longUrl=${longUrl}`; }&longUrl=${longUrl}`;
const response = await fetch(bitlyRequestUrl); const response = await fetchAsync(bitlyRequestUrl);
const responseBody = await response.text(); const responseBody = await response.text();
const bodyObj = JSON.parse(responseBody); const bodyObj = JSON.parse(responseBody);
if (response.status !== 200 || bodyObj.status_code !== 200) { if (response.status !== 200 || bodyObj.status_code !== 200) {
// TODO: Show error message in UI // TODO: Show error message in UI
logUtils.log(`Unexpected status code: ${response.status} -> ${responseBody}`); logUtils.log(`Unexpected status code: ${response.status} -> ${responseBody}`);
await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); errorReporter.report(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`));
return ''; return '';
} }
return bodyObj.data.url; return bodyObj.data.url;

View File

@ -1,4 +1,4 @@
import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { colors } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
@ -388,10 +388,11 @@ export class Portal extends React.Component<PortalProps, PortalState> {
startOnboarding startOnboarding
); );
} }
private _startOnboarding(): void { private _startOnboarding(): void {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; analytics.track('Onboarding Started', {
analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep); reason: 'manual',
stepIndex: this.props.portalOnboardingStep,
});
this.props.dispatcher.updatePortalOnboardingShowing(true); this.props.dispatcher.updatePortalOnboardingShowing(true);
} }
private _renderWalletSection(): React.ReactNode { private _renderWalletSection(): React.ReactNode {

View File

@ -1,4 +1,4 @@
import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { GridTile as PlainGridTile } from 'material-ui/GridList'; import { GridTile as PlainGridTile } from 'material-ui/GridList';
import * as React from 'react'; import * as React from 'react';
@ -64,10 +64,10 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
const link = props.relayerInfo.appUrl || props.relayerInfo.url; const link = props.relayerInfo.appUrl || props.relayerInfo.url;
const topTokens = props.relayerInfo.topTokens; const topTokens = props.relayerInfo.topTokens;
const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume; const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId];
const eventLabel = `${props.relayerInfo.name}-${networkName}`;
const onClick = () => { const onClick = () => {
analytics.logEvent('Portal', 'Relayer Click', eventLabel); analytics.track('Relayer Click', {
name: props.relayerInfo.name,
});
utils.openUrl(link); utils.openUrl(link);
}; };
const headerImageUrl = props.relayerInfo.logoImgUrl; const headerImageUrl = props.relayerInfo.logoImgUrl;

View File

@ -1,9 +1,4 @@
import { import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
utils as sharedUtils,
} from '@0xproject/react-shared';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
@ -46,11 +41,11 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
}; };
} }
public render(): React.ReactNode { public render(): React.ReactNode {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
const onClick = (event: React.MouseEvent<HTMLElement>) => { const onClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation(); event.stopPropagation();
analytics.logEvent('Portal', 'Token Click', eventLabel); analytics.track('Token Click', {
tokenSymbol: this.props.tokenInfo.symbol,
});
const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId); const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId);
utils.openUrl(tokenLink); utils.openUrl(tokenLink);
}; };

View File

@ -80,7 +80,7 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState
logUtils.log(`Unexpected error encountered: ${err}`); logUtils.log(`Unexpected error encountered: ${err}`);
logUtils.log(err.stack); logUtils.log(err.stack);
this.props.onError(); this.props.onError();
await errorReporter.reportAsync(err); errorReporter.report(err);
} }
} }
this.setState({ this.setState({

View File

@ -5,7 +5,7 @@ import {
Styles, Styles,
utils as sharedUtils, utils as sharedUtils,
} from '@0xproject/react-shared'; } from '@0xproject/react-shared';
import { BigNumber, errorUtils, logUtils } from '@0xproject/utils'; import { BigNumber, errorUtils, fetchAsync, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Dialog from 'material-ui/Dialog'; import Dialog from 'material-ui/Dialog';
@ -526,7 +526,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
this.setState({ this.setState({
errorType: BalanceErrs.mintingFailed, errorType: BalanceErrs.mintingFailed,
}); });
await errorReporter.reportAsync(err); errorReporter.report(err);
return false; return false;
} }
} }
@ -548,7 +548,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY);
const segment = isEtherRequest ? 'ether' : 'zrx'; const segment = isEtherRequest ? 'ether' : 'zrx';
const response = await fetch( const response = await fetchAsync(
`${constants.URL_TESTNET_FAUCET}/${segment}/${this.props.userAddress}?networkId=${this.props.networkId}`, `${constants.URL_TESTNET_FAUCET}/${segment}/${this.props.userAddress}?networkId=${this.props.networkId}`,
); );
const responseBody = await response.text(); const responseBody = await response.text();
@ -561,7 +561,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
this.setState({ this.setState({
errorType, errorType,
}); });
await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); errorReporter.report(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`));
return false; return false;
} }

View File

@ -1,4 +1,4 @@
import { constants as sharedConstants, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
import { BigNumber, errorUtils } from '@0xproject/utils'; import { BigNumber, errorUtils } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -488,19 +488,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
); );
} }
private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void { private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const action = const action =
wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened'; wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened';
analytics.logEvent('Portal', action, networkName); analytics.track(action);
this.setState({ this.setState({
wrappedEtherDirection, wrappedEtherDirection,
}); });
} }
private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void { private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const action = const action =
wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed'; wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed';
analytics.logEvent('Portal', action, networkName); analytics.track(action);
this.setState({ this.setState({
wrappedEtherDirection: undefined, wrappedEtherDirection: undefined,
}); });

View File

@ -1,4 +1,4 @@
import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils'; import { BigNumber, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -188,20 +188,23 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
this.setState({ this.setState({
isEthConversionHappening: true, isEthConversionHappening: true,
}); });
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const etherToken = this.props.etherToken;
const amountToConvert = this.state.currentInputAmount;
const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH).toString();
const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals).toString();
try { try {
const etherToken = this.props.etherToken;
const amountToConvert = this.state.currentInputAmount;
if (this.props.direction === Side.Deposit) { if (this.props.direction === Side.Deposit) {
await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert); await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert);
const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH); this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount} ETH to WETH`);
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); analytics.track('Wrap ETH Success', {
analytics.logEvent('Portal', 'Wrap ETH Successfully', networkName); amount: ethAmount,
});
} else { } else {
await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert); await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert);
const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals); this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount} WETH to ETH`);
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); analytics.track('Unwrap WETH Success', {
analytics.logEvent('Portal', 'Unwrap WETH Successfully', networkName); amount: tokenAmount,
});
} }
await this.props.refetchEthTokenStateAsync(); await this.props.refetchEthTokenStateAsync();
this.props.onConversionSuccessful(); this.props.onConversionSuccessful();
@ -214,12 +217,16 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
logUtils.log(err.stack); logUtils.log(err.stack);
if (this.props.direction === Side.Deposit) { if (this.props.direction === Side.Deposit) {
this.props.dispatcher.showFlashMessage('Failed to wrap your ETH. Please try again.'); this.props.dispatcher.showFlashMessage('Failed to wrap your ETH. Please try again.');
analytics.logEvent('Portal', 'Wrap ETH Failed', networkName); analytics.track('Wrap ETH Failure', {
amount: ethAmount,
});
} else { } else {
this.props.dispatcher.showFlashMessage('Failed to unwrap your WETH. Please try again.'); this.props.dispatcher.showFlashMessage('Failed to unwrap your WETH. Please try again.');
analytics.logEvent('Portal', 'Unwrap WETH Failed', networkName); analytics.track('Unwrap WETH Failed', {
amount: tokenAmount,
});
} }
await errorReporter.reportAsync(err); errorReporter.report(err);
} }
} }
this.setState({ this.setState({

View File

@ -16,11 +16,9 @@ import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage';
import { store } from 'ts/redux/store'; import { store } from 'ts/redux/store';
import { WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; import { WebsiteLegacyPaths, WebsitePaths } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { muiTheme } from 'ts/utils/mui_theme'; import { muiTheme } from 'ts/utils/mui_theme';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
// Polyfills // Polyfills
import 'whatwg-fetch';
injectTapEventPlugin(); injectTapEventPlugin();
// Check if we've introduced an update that requires us to clear the tradeHistory local storage entries // Check if we've introduced an update that requires us to clear the tradeHistory local storage entries
@ -69,10 +67,6 @@ const LazyEthereumTypesDocumentation = createLazyComponent('Documentation', asyn
System.import<any>(/* webpackChunkName: "ethereumTypesDocs" */ 'ts/containers/ethereum_types_documentation'), System.import<any>(/* webpackChunkName: "ethereumTypesDocs" */ 'ts/containers/ethereum_types_documentation'),
); );
analytics.init();
// tslint:disable-next-line:no-floating-promises
analytics.logProviderAsync((window as any).web3);
render( render(
<Router> <Router>
<div> <div>

View File

@ -205,7 +205,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
articlesBySection, articlesBySection,
}, },
async () => { async () => {
await utils.onPageLoadAsync(); await utils.onPageLoadPromise;
const hash = this.props.location.hash.slice(1); const hash = this.props.location.hash.slice(1);
sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
}, },

View File

@ -0,0 +1,36 @@
import { Middleware } from 'redux';
import { State } from 'ts/redux/reducer';
import { ActionTypes } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
export const analyticsMiddleware: Middleware = store => next => action => {
const nextAction = next(action);
const nextState = (store.getState() as any) as State;
switch (action.type) {
case ActionTypes.UpdateInjectedProviderName:
analytics.addEventProperties({
injectedProviderName: nextState.injectedProviderName,
});
break;
case ActionTypes.UpdateNetworkId:
analytics.addEventProperties({
networkId: nextState.networkId,
});
break;
case ActionTypes.UpdateUserAddress:
analytics.addUserProperties({
ethAddress: nextState.userAddress,
});
break;
case ActionTypes.UpdateUserEtherBalance:
if (nextState.userEtherBalanceInWei) {
analytics.addUserProperties({
ethBalance: nextState.userEtherBalanceInWei.toString(),
});
}
break;
default:
break;
}
return nextAction;
};

View File

@ -1,7 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { createStore, Store as ReduxStore } from 'redux'; import { applyMiddleware, createStore, Store as ReduxStore } from 'redux';
import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
import { stateStorage } from 'ts/local_storage/state_storage'; import { stateStorage } from 'ts/local_storage/state_storage';
import { analyticsMiddleware } from 'ts/redux/analyticsMiddleware';
import { reducer, State } from 'ts/redux/reducer'; import { reducer, State } from 'ts/redux/reducer';
const ONE_SECOND = 1000; const ONE_SECOND = 1000;
@ -9,7 +10,7 @@ const ONE_SECOND = 1000;
export const store: ReduxStore<State> = createStore( export const store: ReduxStore<State> = createStore(
reducer, reducer,
stateStorage.getPersistedDefaultState(), stateStorage.getPersistedDefaultState(),
devToolsEnhancer({ name: '0x Website Redux Store' }), composeWithDevTools(applyMiddleware(analyticsMiddleware)),
); );
store.subscribe( store.subscribe(
_.throttle(() => { _.throttle(() => {

View File

@ -244,7 +244,10 @@ export enum BlockchainCallErrs {
export enum Environments { export enum Environments {
DEVELOPMENT = 'DEVELOPMENT', DEVELOPMENT = 'DEVELOPMENT',
DOGFOOD = 'DOGFOOD',
STAGING = 'STAGING',
PRODUCTION = 'PRODUCTION', PRODUCTION = 'PRODUCTION',
UNKNOWN = 'UNKNOWN',
} }
export type ContractInstance = any; // TODO: add type definition for Contract export type ContractInstance = any; // TODO: add type definition for Contract
@ -516,8 +519,10 @@ export interface OutdatedWrappedEtherByNetworkId {
}; };
} }
export interface ItemByAddress<T> { export type ItemByAddress<T> = ObjectMap<T>;
[address: string]: T;
export interface ObjectMap<T> {
[key: string]: T;
} }
export type TokenStateByAddress = ItemByAddress<TokenState>; export type TokenStateByAddress = ItemByAddress<TokenState>;

View File

@ -1,27 +1,83 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as ReactGA from 'react-ga'; import { ObjectMap, Order } from 'ts/types';
import { InjectedWeb3 } from 'ts/types';
import { configs } from 'ts/utils/configs';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
export const analytics = { export interface HeapAnalytics {
init(): void { loaded: boolean;
ReactGA.initialize(configs.GOOGLE_ANALYTICS_ID); identify(id: string, idType: string): void;
}, track(eventName: string, eventProperties?: ObjectMap<string | number>): void;
logEvent(category: string, action: string, label: string, value?: any): void { resetIdentity(): void;
ReactGA.event({ addUserProperties(properties: ObjectMap<string | number>): void;
category, addEventProperties(properties: ObjectMap<string | number>): void;
action, removeEventProperty(property: string): void;
label, clearEventProperties(): void;
value, }
}); export class Analytics {
}, private _heap: HeapAnalytics;
async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> { public static init(): Analytics {
await utils.onPageLoadAsync(); return new Analytics(Analytics.getHeap());
const providerType = }
!_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider) public static getHeap(): HeapAnalytics {
? utils.getProviderType(web3IfExists.currentProvider) const heap = (window as any).heap;
: 'NONE'; if (!_.isUndefined(heap)) {
ReactGA.ga('set', 'dimension1', providerType); return heap;
}, } else {
}; throw new Error('Could not find the Heap SDK on the page.');
}
}
constructor(heap: HeapAnalytics) {
this._heap = heap;
}
// tslint:disable:no-floating-promises
// HeapAnalytics Wrappers
public identify(id: string, idType: string): void {
this._heapLoadedGuardAsync(() => this._heap.identify(id, idType));
}
public track(eventName: string, eventProperties?: ObjectMap<string | number>): void {
this._heapLoadedGuardAsync(() => this._heap.track(eventName, eventProperties));
}
public resetIdentity(): void {
this._heapLoadedGuardAsync(() => this._heap.resetIdentity());
}
public addUserProperties(properties: ObjectMap<string | number>): void {
this._heapLoadedGuardAsync(() => this._heap.addUserProperties(properties));
}
public addEventProperties(properties: ObjectMap<string | number>): void {
this._heapLoadedGuardAsync(() => this._heap.addEventProperties(properties));
}
public removeEventProperty(property: string): void {
this._heapLoadedGuardAsync(() => this._heap.removeEventProperty(property));
}
public clearEventProperties(): void {
this._heapLoadedGuardAsync(() => this._heap.clearEventProperties());
}
// tslint:enable:no-floating-promises
// Custom methods
public trackOrderEvent(eventName: string, order: Order): void {
const orderLoggingData = {
takerTokenAmount: order.signedOrder.takerTokenAmount,
makeTokenAmount: order.signedOrder.makerTokenAmount,
takerToken: order.metadata.takerToken.symbol,
makerToken: order.metadata.makerToken.symbol,
};
this.track(eventName, orderLoggingData);
}
/**
* Heap is not available as a UMD module, and additionally has the strange property of replacing itself with
* a different object once it's loaded.
* Instead of having an await call before every analytics use, we opt to have the awaiting logic in here by
* guarding every API call with the guard below.
*/
private async _heapLoadedGuardAsync(callback: () => void): Promise<void> {
if (this._heap.loaded) {
callback();
return undefined;
}
await utils.onPageLoadPromise;
// HACK: Reset heap to loaded heap
this._heap = (window as any).heap;
callback();
}
}
export const analytics = Analytics.init();

View File

@ -1,11 +1,6 @@
import * as _ from 'lodash'; import { OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types';
import { Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types';
const BASE_URL = window.location.origin; const BASE_URL = window.location.origin;
const isDevelopment = _.includes(
['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'],
BASE_URL,
);
const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
export const configs = { export const configs = {
@ -19,9 +14,8 @@ export const configs = {
DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'],
DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com',
DOMAIN_DOGFOOD: 'dogfood.0xproject.com', DOMAIN_DOGFOOD: 'dogfood.0xproject.com',
DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', DOMAINS_DEVELOPMENT: ['0xproject.localhost:3572', 'localhost:3572', '127.0.0.1'],
DOMAIN_PRODUCTION: '0xproject.com', DOMAIN_PRODUCTION: '0xproject.com',
ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION,
GOOGLE_ANALYTICS_ID: 'UA-98720122-1', GOOGLE_ANALYTICS_ID: 'UA-98720122-1',
LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22',
LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-7-5', LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-7-5',

View File

@ -1,5 +1,5 @@
import { DoxityDocObj, TypeDocNode } from '@0xproject/react-docs'; import { DoxityDocObj, TypeDocNode } from '@0xproject/react-docs';
import { logUtils } from '@0xproject/utils'; import { fetchAsync, logUtils } from '@0xproject/utils';
import findVersions = require('find-versions'); import findVersions = require('find-versions');
import * as _ from 'lodash'; import * as _ from 'lodash';
import { S3FileObject, VersionToFilePath } from 'ts/types'; import { S3FileObject, VersionToFilePath } from 'ts/types';
@ -16,7 +16,7 @@ export const docUtils = {
return versionToFilePath; return versionToFilePath;
}, },
async getVersionFileNamesAsync(s3DocJsonRoot: string, folderName: string): Promise<string[]> { async getVersionFileNamesAsync(s3DocJsonRoot: string, folderName: string): Promise<string[]> {
const response = await fetch(s3DocJsonRoot); const response = await fetchAsync(s3DocJsonRoot);
if (response.status !== 200) { if (response.status !== 200) {
// TODO: Show the user an error message when the docs fail to load // TODO: Show the user an error message when the docs fail to load
const errMsg = await response.text(); const errMsg = await response.text();
@ -73,7 +73,7 @@ export const docUtils = {
}, },
async getJSONDocFileAsync(filePath: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { async getJSONDocFileAsync(filePath: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> {
const endpoint = `${s3DocJsonRoot}/${filePath}`; const endpoint = `${s3DocJsonRoot}/${filePath}`;
const response = await fetch(endpoint); const response = await fetchAsync(endpoint);
if (response.status !== 200) { if (response.status !== 200) {
// TODO: Show the user an error message when the docs fail to load // TODO: Show the user an error message when the docs fail to load
const errMsg = await response.text(); const errMsg = await response.text();

View File

@ -1,7 +1,7 @@
import { logUtils } from '@0xproject/utils'; import { logUtils } from '@0xproject/utils';
import { Environments } from 'ts/types';
import { configs } from 'ts/utils/configs'; import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
// Suggested way to include Rollbar with Webpack // Suggested way to include Rollbar with Webpack
// https://github.com/rollbar/rollbar.js/tree/master/examples/webpack // https://github.com/rollbar/rollbar.js/tree/master/examples/webpack
@ -12,7 +12,7 @@ const rollbarConfig = {
itemsPerMinute: 10, itemsPerMinute: 10,
maxItems: 500, maxItems: 500,
payload: { payload: {
environment: configs.ENVIRONMENT, environment: utils.getEnvironment(),
client: { client: {
javascript: { javascript: {
source_map_enabled: true, source_map_enabled: true,
@ -40,21 +40,14 @@ import Rollbar = require('../../public/js/rollbar.umd.min.js');
const rollbar = Rollbar.init(rollbarConfig); const rollbar = Rollbar.init(rollbarConfig);
export const errorReporter = { export const errorReporter = {
async reportAsync(err: Error): Promise<any> { report(err: Error): void {
if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { if (utils.isDevelopment()) {
return; // Let's not log development errors to rollbar return; // Let's not log development errors to rollbar
} }
rollbar.error(err, (rollbarErr: Error) => {
return new Promise((resolve, _reject) => { if (rollbarErr) {
rollbar.error(err, (rollbarErr: Error) => { logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
if (rollbarErr) { }
logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
// We never want to reject and cause the app to throw because of rollbar
resolve();
} else {
resolve();
}
});
}); });
}, },
}; };

View File

@ -1,4 +1,4 @@
import { logUtils } from '@0xproject/utils'; import { fetchAsync, logUtils } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as queryString from 'query-string'; import * as queryString from 'query-string';
@ -9,8 +9,7 @@ const logErrorIfPresent = (response: Response, requestedURL: string) => {
const errorText = `Error requesting url: ${requestedURL}, ${response.status}: ${response.statusText}`; const errorText = `Error requesting url: ${requestedURL}, ${response.status}: ${response.statusText}`;
logUtils.log(errorText); logUtils.log(errorText);
const error = Error(errorText); const error = Error(errorText);
// tslint:disable-next-line:no-floating-promises errorReporter.report(error);
errorReporter.reportAsync(error);
throw error; throw error;
} }
}; };
@ -19,14 +18,14 @@ export const fetchUtils = {
async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> { async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> {
const query = queryStringFromQueryParams(queryParams); const query = queryStringFromQueryParams(queryParams);
const url = `${baseUrl}${path}${query}`; const url = `${baseUrl}${path}${query}`;
const response = await fetch(url); const response = await fetchAsync(url);
logErrorIfPresent(response, url); logErrorIfPresent(response, url);
const result = await response.json(); const result = await response.json();
return result; return result;
}, },
async postAsync(baseUrl: string, path: string, body: object): Promise<Response> { async postAsync(baseUrl: string, path: string, body: object): Promise<Response> {
const url = `${baseUrl}${path}`; const url = `${baseUrl}${path}`;
const response = await fetch(url, { const response = await fetchAsync(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -30,8 +30,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import * as u2f from 'ts/vendor/u2f_api'; import * as u2f from 'ts/vendor/u2f_api';
const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD);
export const utils = { export const utils = {
assert(condition: boolean, message: string): void { assert(condition: boolean, message: string): void {
if (!condition) { if (!condition) {
@ -177,18 +175,6 @@ export const utils = {
_.includes(errMsg, ledgerDenialErrMsg); _.includes(errMsg, ledgerDenialErrMsg);
return isUserDeniedErrMsg; return isUserDeniedErrMsg;
}, },
getCurrentEnvironment(): string {
switch (location.host) {
case configs.DOMAIN_DEVELOPMENT:
return 'development';
case configs.DOMAIN_STAGING:
return 'staging';
case configs.DOMAIN_PRODUCTION:
return 'production';
default:
return 'production';
}
},
getAddressBeginAndEnd(address: string): string { getAddressBeginAndEnd(address: string): string {
const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
return truncatedAddress; return truncatedAddress;
@ -313,14 +299,13 @@ export const utils = {
const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`;
return baseUrl; return baseUrl;
}, },
async onPageLoadAsync(): Promise<void> { onPageLoadPromise: new Promise((resolve, _reject) => {
if (document.readyState === 'complete') { if (document.readyState === 'complete') {
return; // Already loaded resolve();
return;
} }
return new Promise<void>((resolve, _reject) => { window.onload = resolve;
window.onload = () => resolve(); }),
});
},
getProviderType(provider: Provider): Providers | string { getProviderType(provider: Provider): Providers | string {
const constructorName = provider.constructor.name; const constructorName = provider.constructor.name;
let parsedProviderName = constructorName; let parsedProviderName = constructorName;
@ -346,10 +331,10 @@ export const utils = {
return parsedProviderName; return parsedProviderName;
}, },
getBackendBaseUrl(): string { getBackendBaseUrl(): string {
return isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL; return utils.isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL;
}, },
isDevelopment(): boolean { isDevelopment(): boolean {
return configs.ENVIRONMENT === Environments.DEVELOPMENT; return _.includes(configs.DOMAINS_DEVELOPMENT, window.location.origin);
}, },
isStaging(): boolean { isStaging(): boolean {
return _.includes(window.location.href, configs.DOMAIN_STAGING); return _.includes(window.location.href, configs.DOMAIN_STAGING);
@ -357,7 +342,27 @@ export const utils = {
isExternallyInjected(providerType: ProviderType, injectedProviderName: string): boolean { isExternallyInjected(providerType: ProviderType, injectedProviderName: string): boolean {
return providerType === ProviderType.Injected && injectedProviderName !== constants.PROVIDER_NAME_PUBLIC; return providerType === ProviderType.Injected && injectedProviderName !== constants.PROVIDER_NAME_PUBLIC;
}, },
isDogfood, isDogfood(): boolean {
return _.includes(window.location.href, configs.DOMAIN_DOGFOOD);
},
isProduction(): boolean {
return _.includes(window.location.href, configs.DOMAIN_PRODUCTION);
},
getEnvironment(): Environments {
if (utils.isDogfood()) {
return Environments.DOGFOOD;
}
if (utils.isDevelopment()) {
return Environments.DEVELOPMENT;
}
if (utils.isStaging()) {
return Environments.STAGING;
}
if (utils.isProduction()) {
return Environments.PRODUCTION;
}
return Environments.UNKNOWN;
},
shouldShowJobsPage(): boolean { shouldShowJobsPage(): boolean {
return this.isDevelopment() || this.isStaging() || this.isDogfood(); return this.isDevelopment() || this.isStaging() || this.isDogfood();
}, },
@ -380,21 +385,28 @@ export const utils = {
getFormattedAmountFromToken(token: Token, tokenState: TokenState): string { getFormattedAmountFromToken(token: Token, tokenState: TokenState): string {
return utils.getFormattedAmount(tokenState.balance, token.decimals); return utils.getFormattedAmount(tokenState.balance, token.decimals);
}, },
format(value: BigNumber, format: string): string {
const formattedAmount = numeral(value).format(format);
if (_.isNaN(formattedAmount)) {
// https://github.com/adamwdraper/Numeral-js/issues/596
return numeral(new BigNumber(0)).format(format);
}
return formattedAmount;
},
getFormattedAmount(amount: BigNumber, decimals: number): string { getFormattedAmount(amount: BigNumber, decimals: number): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
// if the unit amount is less than 1, show the natural number of decimal places with a max of 4 // if the unit amount is less than 1, show the natural number of decimal places with a max of 4
// if the unit amount is greater than or equal to 1, show only 2 decimal places // if the unit amount is greater than or equal to 1, show only 2 decimal places
const precision = unitAmount.lt(1) const lessThanOnePrecision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces());
? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()) const greaterThanOnePrecision = 2;
: 2; const precision = unitAmount.lt(1) ? lessThanOnePrecision : greaterThanOnePrecision;
const format = `0,0.${_.repeat('0', precision)}`; const format = `0,0.${_.repeat('0', precision)}`;
const formattedAmount = numeral(unitAmount).format(format); return utils.format(unitAmount, format);
return formattedAmount;
}, },
getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string { getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
const value = unitAmount.mul(price); const value = unitAmount.mul(price);
return numeral(value).format(constants.NUMERAL_USD_FORMAT); return utils.format(value, constants.NUMERAL_USD_FORMAT);
}, },
openUrl(url: string): void { openUrl(url: string): void {
window.open(url, '_blank'); window.open(url, '_blank');

View File

@ -9,6 +9,43 @@ const GIT_SHA = childProcess
.toString() .toString()
.trim(); .trim();
const generatePlugins = () => {
let plugins = [];
if (process.env.NODE_ENV === 'production') {
plugins = plugins.concat([
// Since we do not use moment's locale feature, we exclude them from the bundle.
// This reduces the bundle size by 0.4MB.
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
GIT_SHA: JSON.stringify(GIT_SHA),
},
}),
// TODO: Revert to webpack bundled version with webpack v4.
// The v3 series bundled version does not support ES6 and
// fails to build.
new UglifyJsPlugin({
sourceMap: true,
uglifyOptions: {
mangle: {
reserved: ['BigNumber'],
},
},
}),
]);
if (process.env.DEPLOY_ROLLBAR_SOURCEMAPS === 'true') {
plugins = plugins.concat([
new RollbarSourceMapPlugin({
accessToken: '32c39bfa4bb6440faedc1612a9c13d28',
version: GIT_SHA,
publicPath: 'https://0xproject.com/',
}),
]);
}
}
return plugins;
};
module.exports = { module.exports = {
entry: ['./ts/index.tsx'], entry: ['./ts/index.tsx'],
output: { output: {
@ -78,34 +115,5 @@ module.exports = {
}, },
disableHostCheck: true, disableHostCheck: true,
}, },
plugins: plugins: generatePlugins(),
process.env.NODE_ENV === 'production'
? [
// Since we do not use moment's locale feature, we exclude them from the bundle.
// This reduces the bundle size by 0.4MB.
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
GIT_SHA: JSON.stringify(GIT_SHA),
},
}),
// TODO: Revert to webpack bundled version with webpack v4.
// The v3 series bundled version does not support ES6 and
// fails to build.
new UglifyJsPlugin({
sourceMap: true,
uglifyOptions: {
mangle: {
reserved: ['BigNumber'],
},
},
}),
new RollbarSourceMapPlugin({
accessToken: '32c39bfa4bb6440faedc1612a9c13d28',
version: GIT_SHA,
publicPath: 'https://0xproject.com/',
}),
]
: [],
}; };

View File

@ -2,6 +2,23 @@
# yarn lockfile v1 # yarn lockfile v1
"0x.js@0.38.4":
version "0.38.4"
resolved "https://registry.yarnpkg.com/0x.js/-/0x.js-0.38.4.tgz#735c12d3b8d68945caab87990e6cdd24bf5bc070"
dependencies:
"@0xproject/assert" "^0.2.12"
"@0xproject/base-contract" "^0.3.4"
"@0xproject/contract-wrappers" "^0.0.5"
"@0xproject/order-utils" "^0.0.7"
"@0xproject/order-watcher" "^0.0.6"
"@0xproject/sol-compiler" "^0.5.2"
"@0xproject/types" "^0.8.1"
"@0xproject/typescript-typings" "^0.4.1"
"@0xproject/utils" "^0.7.1"
"@0xproject/web3-wrapper" "^0.7.1"
ethers "3.0.22"
lodash "4.17.10"
"8fold-marked@0.3.9": "8fold-marked@0.3.9":
version "0.3.9" version "0.3.9"
resolved "https://registry.yarnpkg.com/8fold-marked/-/8fold-marked-0.3.9.tgz#bb89c645612f8ccfaffac1ca6e3c11f168c9cf59" resolved "https://registry.yarnpkg.com/8fold-marked/-/8fold-marked-0.3.9.tgz#bb89c645612f8ccfaffac1ca6e3c11f168c9cf59"
@ -122,6 +139,24 @@
ethereumjs-util "5.1.5" ethereumjs-util "5.1.5"
lodash "4.17.10" lodash "4.17.10"
"@0xproject/order-watcher@^0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@0xproject/order-watcher/-/order-watcher-0.0.6.tgz#85a8fb21e5755bb555f427b12d64d10b89b332e6"
dependencies:
"@0xproject/assert" "^0.2.12"
"@0xproject/base-contract" "^0.3.4"
"@0xproject/contract-wrappers" "^0.0.5"
"@0xproject/fill-scenarios" "^0.0.4"
"@0xproject/json-schemas" "^0.8.1"
"@0xproject/order-utils" "^0.0.7"
"@0xproject/types" "^0.8.1"
"@0xproject/typescript-typings" "^0.4.1"
"@0xproject/utils" "^0.7.1"
"@0xproject/web3-wrapper" "^0.7.1"
bintrees "1.0.2"
ethers "3.0.22"
lodash "4.17.10"
"@0xproject/types@^0.5.0": "@0xproject/types@^0.5.0":
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/@0xproject/types/-/types-0.5.0.tgz#ba3cfbc11a8c6344b57c9680aa7df2ea84b9bf05" resolved "https://registry.yarnpkg.com/@0xproject/types/-/types-0.5.0.tgz#ba3cfbc11a8c6344b57c9680aa7df2ea84b9bf05"
@ -1834,7 +1869,7 @@ bindings@^1.2.1, bindings@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
bintrees@^1.0.2: bintrees@1.0.2, bintrees@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
@ -3696,7 +3731,7 @@ detect-libc@^1.0.2, detect-libc@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
detect-node@^2.0.3: detect-node@2.0.3, detect-node@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
@ -4481,9 +4516,9 @@ ethereumjs-wallet@~0.6.0:
utf8 "^2.1.1" utf8 "^2.1.1"
uuid "^2.0.1" uuid "^2.0.1"
ethers@3.0.22: ethers@0xproject/ethers.js#eip-838-reasons, ethers@3.0.22:
version "3.0.22" version "3.0.18"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-3.0.22.tgz#7fab1ea16521705837aa43c15831877b2716b436" resolved "https://codeload.github.com/0xproject/ethers.js/tar.gz/b91342bd200d142af0165d6befddf783c8ae8447"
dependencies: dependencies:
aes-js "3.0.0" aes-js "3.0.0"
bn.js "^4.4.0" bn.js "^4.4.0"
@ -6899,7 +6934,7 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0:
json-rpc-error "^2.0.0" json-rpc-error "^2.0.0"
promise-to-callback "^1.0.0" promise-to-callback "^1.0.0"
json-rpc-error@^2.0.0: json-rpc-error@2.0.0, json-rpc-error@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02"
dependencies: dependencies:
@ -9891,13 +9926,6 @@ react-event-listener@^0.4.5:
prop-types "^15.5.4" prop-types "^15.5.4"
warning "^3.0.0" warning "^3.0.0"
react-ga@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.4.1.tgz#dfbd5f028ed39a07067f7a8bf57dc0d240000767"
optionalDependencies:
prop-types "^15.6.0"
react "^15.6.2 || ^16.0"
react-highlight@0xproject/react-highlight: react-highlight@0xproject/react-highlight:
version "0.10.0" version "0.10.0"
resolved "https://codeload.github.com/0xproject/react-highlight/tar.gz/83bbb4a09801abd341e2b9041cd884885a4a2098" resolved "https://codeload.github.com/0xproject/react-highlight/tar.gz/83bbb4a09801abd341e2b9041cd884885a4a2098"
@ -10023,15 +10051,6 @@ react@^15.5.4:
object-assign "^4.1.0" object-assign "^4.1.0"
prop-types "^15.5.10" prop-types "^15.5.10"
"react@^15.6.2 || ^16.0":
version "16.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
read-chunk@^2.1.0: read-chunk@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655" resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
@ -13271,7 +13290,7 @@ websocket@^1.0.24, websocket@^1.0.25:
typedarray-to-buffer "^3.1.2" typedarray-to-buffer "^3.1.2"
yaeti "^0.0.6" yaeti "^0.0.6"
whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3: whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"