aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--package.json9
-rw-r--r--packages/0x.js/CHANGELOG.md15
-rw-r--r--packages/0x.js/package.json22
-rw-r--r--packages/0x.js/scripts/postpublish.js43
-rwxr-xr-xpackages/0x.js/scripts/test_umd.sh1
-rw-r--r--packages/0x.js/src/index.ts2
-rw-r--r--packages/0x.js/src/order_watcher/event_watcher.ts12
-rw-r--r--packages/0x.js/src/order_watcher/expiration_watcher.ts76
-rw-r--r--packages/0x.js/src/order_watcher/order_state_watcher.ts76
-rw-r--r--packages/0x.js/src/types.ts20
-rw-r--r--packages/0x.js/src/utils/order_validation_utils.ts4
-rw-r--r--packages/0x.js/src/utils/utils.ts7
-rw-r--r--packages/0x.js/src/web3_wrapper.ts38
-rw-r--r--packages/0x.js/test/expiration_watcher_test.ts138
-rw-r--r--packages/0x.js/test/order_state_watcher_test.ts46
-rw-r--r--packages/0x.js/test/token_wrapper_test.ts3
-rw-r--r--packages/0x.js/test/utils/report_callback_errors.ts6
-rw-r--r--packages/assert/package.json6
-rw-r--r--packages/assert/scripts/postpublish.js14
-rw-r--r--packages/connect/CHANGELOG.md3
-rw-r--r--packages/connect/package.json16
-rw-r--r--packages/connect/scripts/postpublish.js39
-rw-r--r--packages/connect/src/http_client.ts8
-rw-r--r--packages/connect/src/index.ts4
-rw-r--r--packages/connect/test/ws_orderbook_channel_test.ts2
-rw-r--r--packages/json-schemas/package.json4
-rw-r--r--packages/json-schemas/scripts/postpublish.js14
-rw-r--r--packages/tslint-config/package.json2
-rw-r--r--packages/tslint-config/scripts/postpublish.js14
-rw-r--r--scripts/postpublish_utils.js51
-rw-r--r--yarn.lock43
32 files changed, 626 insertions, 113 deletions
diff --git a/README.md b/README.md
index 35926ce70..cb15f791c 100644
--- a/README.md
+++ b/README.md
@@ -23,5 +23,6 @@ This repository contains all the 0x developer tools written in TypeScript. Our h
|--------|-------|------------|
| [`0x.js`](/packages/0x.js) | [![npm](https://img.shields.io/npm/v/0x.js.svg?maxAge=2592000)](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
| [`@0xproject/assert`](/packages/assert) | [![npm](https://img.shields.io/npm/v/@0xproject/assert.svg?maxAge=2592000)](https://www.npmjs.com/package/@0xproject/assert) | Standard type and schema assertions |
+| [`@0xproject/connect`](/packages/connect) | [![npm](https://img.shields.io/npm/v/@0xproject/connect.svg?maxAge=2592000)](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the standard relayer api |
| [`@0xproject/json-schemas`](/packages/json-schemas) | [![npm](https://img.shields.io/npm/v/@0xproject/json-schemas.svg?maxAge=2592000)](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
| [`@0xproject/tslint-config`](/packages/tslint-config) | [![npm](https://img.shields.io/npm/v/@0xproject/tslint-config.svg?maxAge=2592000)](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x project TSLint rules |
diff --git a/package.json b/package.json
index 49c97eff6..091ae1069 100644
--- a/package.json
+++ b/package.json
@@ -6,12 +6,17 @@
],
"scripts": {
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
- "lerna:run": "lerna run"
+ "lerna:run": "lerna run",
+ "lerna:publish": "lerna run clean; lerna run build; lerna publish --registry=https://registry.npmjs.org/"
},
"config": {
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
},
"devDependencies": {
- "lerna": "^2.5.1"
+ "lerna": "^2.5.1",
+ "async-child-process": "^1.1.1",
+ "semver-sort": "^0.0.4",
+ "publish-release": "0xproject/publish-release",
+ "es6-promisify": "^5.0.0"
}
}
diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md
index 892a0863e..235a6eedb 100644
--- a/packages/0x.js/CHANGELOG.md
+++ b/packages/0x.js/CHANGELOG.md
@@ -1,14 +1,21 @@
# CHANGELOG
+v0.26.0
+------------------------
+ * Add post-formatter for logs converting `blockNumber`, `logIndex`, `transactionIndex` from hexes to numbers (#231)
+ * Remove support for Async callback types when used in Subscribe functions (#222)
+ * In OrderWatcher subscribe to ZRX Token Transfer and Approval events when maker token is different (#225)
+
v0.25.1 - _November 13, 2017_
------------------------
- * Standardise on Cancelled over Canceled
- * Add missing `DecodedLogEvent` type to exported types
+ * Standardise on Cancelled over Canceled (#217)
+ * Add missing `DecodedLogEvent` type to exported types (#205)
+ * Normalized the transactionReceipt status to be `null|0|1`, 1 meaning transaction execution successful, 0 unsuccessful and `null` if it is a pre-byzantinium transaction. (#200)
v0.23.0 - _November 12, 2017_
------------------------
- * Fixed unhandled promise rejection error in subscribe methods (#209)
- * Subscribe callbacks now receive an error object as their first argument
+ * Fixed unhandled promise rejection error in subscribe methods (#209)
+ * Subscribe callbacks now receive an error object as their first argument
v0.22.6 - _November 10, 2017_
------------------------
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json
index 0204d723f..afca00fe9 100644
--- a/packages/0x.js/package.json
+++ b/packages/0x.js/package.json
@@ -1,6 +1,6 @@
{
"name": "0x.js",
- "version": "0.25.1",
+ "version": "0.26.1",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": [
"0x.js",
@@ -14,20 +14,15 @@
"scripts": {
"prebuild": "npm run clean",
"build": "run-p build:umd:prod build:commonjs; exit 0;",
- "prepublishOnly": "run-p build",
- "postpublish": "run-s release docs:json upload_docs_json",
- "release": "publish-release --assets _bundles/index.js,_bundles/index.min.js --tag $(git describe --tags) --owner 0xProject --repo 0x.js",
- "upload_docs_json": "aws s3 cp docs/index.json s3://0xjs-docs-jsons/$(git describe --tags).json --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
+ "upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
"lint": "tslint src/**/*.ts test/**/*.ts",
- "test:circleci": "run-s test:coverage report_test_coverage; if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi",
+ "test:circleci": "run-s test:coverage report_test_coverage && if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi",
"test": "run-s clean test:commonjs",
"test:umd": "./scripts/test_umd.sh",
"test:coverage": "nyc npm run test --all",
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
"update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
- "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json docs/index.json .",
- "docs:generate": "typedoc --out docs .",
- "docs:open": "opn docs/index.html",
"clean": "shx rm -rf _bundles lib test_temp",
"build:umd:dev": "webpack",
"build:umd:prod": "NODE_ENV=production webpack",
@@ -49,7 +44,8 @@
"node": ">=6.0.0"
},
"devDependencies": {
- "@0xproject/tslint-config": "^0.1.0",
+ "@0xproject/tslint-config": "^0.1.1",
+ "@types/bintrees": "^1.0.2",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.64",
"@types/mocha": "^2.2.41",
@@ -87,9 +83,10 @@
"webpack": "^3.1.0"
},
"dependencies": {
- "@0xproject/assert": "^0.0.4",
- "@0xproject/json-schemas": "^0.6.7",
+ "@0xproject/assert": "^0.0.5",
+ "@0xproject/json-schemas": "^0.6.8",
"bignumber.js": "~4.1.0",
+ "bintrees": "^1.0.2",
"bn.js": "4.11.8",
"compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0",
@@ -99,7 +96,6 @@
"find-versions": "^2.0.0",
"js-sha3": "^0.6.1",
"lodash": "^4.17.4",
- "publish-release": "^1.3.3",
"uuid": "^3.1.0",
"web3": "^0.20.0"
}
diff --git a/packages/0x.js/scripts/postpublish.js b/packages/0x.js/scripts/postpublish.js
new file mode 100644
index 000000000..ffc68afb4
--- /dev/null
+++ b/packages/0x.js/scripts/postpublish.js
@@ -0,0 +1,43 @@
+const execAsync = require('async-child-process').execAsync;
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const cwd = __dirname + '/..';
+const subPackageName = packageJSON.name;
+const S3BucketPath = 's3://0xjs-docs-jsons/';
+
+let tag;
+let version;
+postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ tag = result.tag;
+ version = result.version;
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, version);
+ const assets = [
+ __dirname + '/../_bundles/index.js',
+ __dirname + '/../_bundles/index.min.js',
+ ];
+ return postpublish_utils.publishReleaseNotes(tag, releaseName, assets);
+ })
+ .then(function(release) {
+ console.log('POSTPUBLISH: Release successful, generating docs...');
+ return execAsync(
+ 'JSON_FILE_PATH=' + __dirname + '/../docs/index.json PROJECT_DIR=' + __dirname + '/.. yarn docs:json',
+ {
+ cwd,
+ }
+ );
+ })
+ .then(function(result) {
+ if (result.stderr !== '') {
+ throw new Error(result.stderr);
+ }
+ const fileName = 'v' + version + '.json';
+ console.log('POSTPUBLISH: Doc generation successful, uploading docs... as ', fileName);
+ const s3Url = S3BucketPath + fileName;
+ return execAsync('S3_URL=' + s3Url + ' yarn upload_docs_json', {
+ cwd,
+ });
+ }).catch (function(err) {
+ throw err;
+ });
diff --git a/packages/0x.js/scripts/test_umd.sh b/packages/0x.js/scripts/test_umd.sh
index d200c76d0..e3eba088a 100755
--- a/packages/0x.js/scripts/test_umd.sh
+++ b/packages/0x.js/scripts/test_umd.sh
@@ -3,5 +3,4 @@
# UMD tests should only be run after building the commonjs because they reuse some of the commonjs build artifacts
run-s substitute_umd_bundle run_mocha
return_code=$?
-npm run clean
exit $return_code
diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts
index 1b3e893ba..e529e2858 100644
--- a/packages/0x.js/src/index.ts
+++ b/packages/0x.js/src/index.ts
@@ -6,8 +6,6 @@ export {
ECSignature,
ZeroExError,
EventCallback,
- EventCallbackAsync,
- EventCallbackSync,
ExchangeContractErrs,
ContractEvent,
Token,
diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts
index 81529a98c..ecbab0cd5 100644
--- a/packages/0x.js/src/order_watcher/event_watcher.ts
+++ b/packages/0x.js/src/order_watcher/event_watcher.ts
@@ -12,7 +12,7 @@ import {intervalUtils} from '../utils/interval_utils';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
-const DEFAULT_EVENT_POLLING_INTERVAL = 200;
+const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
enum LogEventState {
Removed,
@@ -28,11 +28,11 @@ export class EventWatcher {
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
- constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number) {
+ constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined|number) {
this._web3Wrapper = web3Wrapper;
- this._pollingIntervalMs = _.isUndefined(pollingIntervalMs) ?
- DEFAULT_EVENT_POLLING_INTERVAL :
- pollingIntervalMs;
+ this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) ?
+ DEFAULT_EVENT_POLLING_INTERVAL_MS :
+ pollingIntervalIfExistsMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
@@ -81,7 +81,7 @@ export class EventWatcher {
...log,
};
if (!_.isUndefined(this._intervalIdIfExists)) {
- await callback(logEvent);
+ callback(logEvent);
}
}
}
diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts
new file mode 100644
index 000000000..717edaad7
--- /dev/null
+++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts
@@ -0,0 +1,76 @@
+import * as _ from 'lodash';
+import {BigNumber} from 'bignumber.js';
+import {RBTree} from 'bintrees';
+import {utils} from '../utils/utils';
+import {intervalUtils} from '../utils/interval_utils';
+import {SignedOrder, ZeroExError} from '../types';
+import {ZeroEx} from '../0x';
+
+const DEFAULT_EXPIRATION_MARGIN_MS = 0;
+const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50;
+
+/**
+ * This class includes the functionality to detect expired orders.
+ * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs`
+ */
+export class ExpirationWatcher {
+ private orderHashByExpirationRBTree: RBTree<string>;
+ private expiration: {[orderHash: string]: BigNumber} = {};
+ private orderExpirationCheckingIntervalMs: number;
+ private expirationMarginMs: number;
+ private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer;
+ constructor(expirationMarginIfExistsMs?: number,
+ orderExpirationCheckingIntervalIfExistsMs?: number) {
+ this.expirationMarginMs = expirationMarginIfExistsMs ||
+ DEFAULT_EXPIRATION_MARGIN_MS;
+ this.orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs ||
+ DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS;
+ const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber();
+ const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs);
+ this.orderHashByExpirationRBTree = new RBTree(comparator);
+ }
+ public subscribe(callbackAsync: (orderHash: string) => Promise<void>): void {
+ if (!_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) {
+ throw new Error(ZeroExError.SubscriptionAlreadyPresent);
+ }
+ this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
+ this.pruneExpiredOrdersAsync.bind(this, callbackAsync), this.orderExpirationCheckingIntervalMs,
+ );
+ }
+ public unsubscribe(): void {
+ if (_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) {
+ throw new Error(ZeroExError.SubscriptionNotFound);
+ }
+ intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists);
+ delete this.orderExpirationCheckingIntervalIdIfExists;
+ }
+ public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void {
+ this.expiration[orderHash] = expirationUnixTimestampMs;
+ this.orderHashByExpirationRBTree.insert(orderHash);
+ }
+ public removeOrder(orderHash: string): void {
+ this.orderHashByExpirationRBTree.remove(orderHash);
+ delete this.expiration[orderHash];
+ }
+ private async pruneExpiredOrdersAsync(callbackAsync: (orderHash: string) => Promise<void>): Promise<void> {
+ const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs();
+ while (true) {
+ const hasTrakedOrders = this.orderHashByExpirationRBTree.size === 0;
+ if (hasTrakedOrders) {
+ break;
+ }
+ const nextOrderHashToExpire = this.orderHashByExpirationRBTree.min();
+ const hasNoExpiredOrders = this.expiration[nextOrderHashToExpire].greaterThan(
+ currentUnixTimestampMs.plus(this.expirationMarginMs),
+ );
+ const isSubscriptionActive = _.isUndefined(this.orderExpirationCheckingIntervalIdIfExists);
+ if (hasNoExpiredOrders || isSubscriptionActive) {
+ break;
+ }
+ const orderHash = this.orderHashByExpirationRBTree.min();
+ this.orderHashByExpirationRBTree.remove(orderHash);
+ delete this.expiration[orderHash];
+ await callbackAsync(orderHash);
+ }
+ }
+}
diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts
index bafd7a994..fd7496699 100644
--- a/packages/0x.js/src/order_watcher/order_state_watcher.ts
+++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts
@@ -6,6 +6,7 @@ import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
import {artifacts} from '../artifacts';
import {AbiDecoder} from '../utils/abi_decoder';
+import {intervalUtils} from '../utils/interval_utils';
import {OrderStateUtils} from '../utils/order_state_utils';
import {
LogEvent,
@@ -24,14 +25,14 @@ import {
ExchangeEvents,
TokenEvents,
ZeroExError,
+ ExchangeContractErrs,
} from '../types';
import {Web3Wrapper} from '../web3_wrapper';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store';
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
-
-const DEFAULT_NUM_CONFIRMATIONS = 0;
+import {ExpirationWatcher} from './expiration_watcher';
interface DependentOrderHashes {
[makerAddress: string]: {
@@ -52,10 +53,11 @@ interface OrderByOrderHash {
export class OrderStateWatcher {
private _orderByOrderHash: OrderByOrderHash = {};
private _dependentOrderHashes: DependentOrderHashes = {};
- private _callbackIfExistsAsync?: OnOrderStateChangeCallback;
+ private _callbackIfExists?: OnOrderStateChangeCallback;
private _eventWatcher: EventWatcher;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
+ private _expirationWatcher: ExpirationWatcher;
private _orderStateUtils: OrderStateUtils;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
@@ -65,38 +67,53 @@ export class OrderStateWatcher {
) {
this._abiDecoder = abiDecoder;
this._web3Wrapper = web3Wrapper;
- const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
- this._eventWatcher = new EventWatcher(web3Wrapper, eventPollingIntervalMs);
+ const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
+ this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderStateUtils = new OrderStateUtils(
this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore,
);
+ const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) ?
+ undefined :
+ config.orderExpirationCheckingIntervalMs;
+ const expirationMarginIfExistsMs = _.isUndefined(config) ?
+ undefined :
+ config.expirationMarginMs;
+ this._expirationWatcher = new ExpirationWatcher(
+ expirationMarginIfExistsMs, orderExpirationCheckingIntervalMsIfExists,
+ );
}
/**
* Add an order to the orderStateWatcher. Before the order is added, it's
* signature is verified.
* @param signedOrder The order you wish to start watching.
*/
- public addOrder(signedOrder: SignedOrder): void {
+ public async addOrderAsync(signedOrder: SignedOrder): Promise<void> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder;
- this.addToDependentOrderHashes(signedOrder, orderHash);
+ await this.addToDependentOrderHashesAsync(signedOrder, orderHash);
+ const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
+ this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
}
/**
* Removes an order from the orderStateWatcher
* @param orderHash The orderHash of the order you wish to stop watching.
*/
- public removeOrder(orderHash: string): void {
+ public async removeOrderAsync(orderHash: string): Promise<void> {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const signedOrder = this._orderByOrderHash[orderHash];
if (_.isUndefined(signedOrder)) {
return; // noop
}
delete this._orderByOrderHash[orderHash];
+ const exchange = (this._orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper;
+ const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
+ this.removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
this.removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
+ this._expirationWatcher.removeOrder(orderHash);
}
/**
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
@@ -106,23 +123,38 @@ export class OrderStateWatcher {
*/
public subscribe(callback: OnOrderStateChangeCallback): void {
assert.isFunction('callback', callback);
- if (!_.isUndefined(this._callbackIfExistsAsync)) {
+ if (!_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
- this._callbackIfExistsAsync = callback;
+ this._callbackIfExists = callback;
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
+ this._expirationWatcher.subscribe(this._onOrderExpiredAsync.bind(this));
}
/**
* Ends an orderStateWatcher subscription.
*/
public unsubscribe(): void {
- if (_.isUndefined(this._callbackIfExistsAsync)) {
+ if (_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
this._balanceAndProxyAllowanceLazyStore.deleteAll();
this._orderFilledCancelledLazyStore.deleteAll();
- delete this._callbackIfExistsAsync;
+ delete this._callbackIfExists;
this._eventWatcher.unsubscribe();
+ this._expirationWatcher.unsubscribe();
+ }
+ private async _onOrderExpiredAsync(orderHash: string): Promise<void> {
+ const orderState: OrderState = {
+ isValid: false,
+ orderHash,
+ error: ExchangeContractErrs.OrderFillExpired,
+ };
+ if (!_.isUndefined(this._orderByOrderHash[orderHash])) {
+ await this.removeOrderAsync(orderHash);
+ if (!_.isUndefined(this._callbackIfExists)) {
+ this._callbackIfExists(orderState);
+ }
+ }
}
private async _onEventWatcherCallbackAsync(log: LogEvent): Promise<void> {
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
@@ -204,13 +236,13 @@ export class OrderStateWatcher {
// Most of these calls will never reach the network because the data is fetched from stores
// and only updated when cache is invalidated
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
- if (_.isUndefined(this._callbackIfExistsAsync)) {
+ if (_.isUndefined(this._callbackIfExists)) {
break; // Unsubscribe was called
}
- await this._callbackIfExistsAsync(orderState);
+ this._callbackIfExists(orderState);
}
}
- private addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string) {
+ private async addToDependentOrderHashesAsync(signedOrder: SignedOrder, orderHash: string): Promise<void> {
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
this._dependentOrderHashes[signedOrder.maker] = {};
}
@@ -218,11 +250,17 @@ export class OrderStateWatcher {
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
}
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
+ const exchange = (this._orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper;
+ const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
+ if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) {
+ this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set();
+ }
+ this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
}
- private removeFromDependentOrderHashes(makerAddress: string, makerTokenAddress: string, orderHash: string) {
- this._dependentOrderHashes[makerAddress][makerTokenAddress].delete(orderHash);
- if (this._dependentOrderHashes[makerAddress][makerTokenAddress].size === 0) {
- delete this._dependentOrderHashes[makerAddress][makerTokenAddress];
+ private removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
+ this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
+ if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
+ delete this._dependentOrderHashes[makerAddress][tokenAddress];
}
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
delete this._dependentOrderHashes[makerAddress];
diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts
index 71089f9a1..c3aabfd86 100644
--- a/packages/0x.js/src/types.ts
+++ b/packages/0x.js/src/types.ts
@@ -42,13 +42,8 @@ export type OrderValues = [BigNumber, BigNumber, BigNumber,
export type LogEvent = Web3.LogEntryEvent;
export type DecodedLogEvent<ArgsType> = Web3.DecodedLogEntryEvent<ArgsType>;
-export type EventCallbackAsync<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => Promise<void>;
-export type EventCallbackSync<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => void;
-export type EventCallback<ArgsType> = EventCallbackSync<ArgsType>|EventCallbackAsync<ArgsType>;
-
-export type EventWatcherCallbackSync = (log: LogEvent) => void;
-export type EventWatcherCallbackAsync = (log: LogEvent) => Promise<void>;
-export type EventWatcherCallback = EventWatcherCallbackSync|EventWatcherCallbackAsync;
+export type EventCallback<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => void;
+export type EventWatcherCallback = (log: LogEvent) => void;
export interface ExchangeContract extends Web3.ContractInstance {
isValidSignature: {
@@ -397,10 +392,15 @@ export interface JSONRPCPayload {
}
/*
- * eventPollingIntervalMs: How often to poll the Ethereum node for new events
+ * orderExpirationCheckingIntervalMs: How often to check for expired orders. Default: 50
+ * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Defaults: 200
+ * expirationMarginMs: Amount of time before order expiry that you'd like to be notified
+ * of an orders expiration. Defaults: 0
*/
export interface OrderStateWatcherConfig {
+ orderExpirationCheckingIntervalMs?: number;
eventPollingIntervalMs?: number;
+ expirationMarginMs?: number;
}
/*
@@ -507,9 +507,7 @@ export interface OrderStateInvalid {
export type OrderState = OrderStateValid|OrderStateInvalid;
-export type OnOrderStateChangeCallbackSync = (orderState: OrderState) => void;
-export type OnOrderStateChangeCallbackAsync = (orderState: OrderState) => Promise<void>;
-export type OnOrderStateChangeCallback = OnOrderStateChangeCallbackAsync|OnOrderStateChangeCallbackSync;
+export type OnOrderStateChangeCallback = (orderState: OrderState) => void;
export interface TransactionReceipt {
blockHash: string;
diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts
index f03703c4e..ed723e3d4 100644
--- a/packages/0x.js/src/utils/order_validation_utils.ts
+++ b/packages/0x.js/src/utils/order_validation_utils.ts
@@ -102,7 +102,7 @@ export class OrderValidationUtils {
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
}
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderCancelExpired);
}
@@ -150,7 +150,7 @@ export class OrderValidationUtils {
}
}
private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts
index 280f3e979..5370c3b4b 100644
--- a/packages/0x.js/src/utils/utils.ts
+++ b/packages/0x.js/src/utils/utils.ts
@@ -49,7 +49,10 @@ export const utils = {
const hashHex = ethUtil.bufferToHex(hashBuff);
return hashHex;
},
- getCurrentUnixTimestamp(): BigNumber {
- return new BigNumber(Date.now() / 1000);
+ getCurrentUnixTimestampSec(): BigNumber {
+ return new BigNumber(Date.now() / 1000).round();
+ },
+ getCurrentUnixTimestampMs(): BigNumber {
+ return new BigNumber(Date.now());
},
};
diff --git a/packages/0x.js/src/web3_wrapper.ts b/packages/0x.js/src/web3_wrapper.ts
index c937f9288..7bd8ea093 100644
--- a/packages/0x.js/src/web3_wrapper.ts
+++ b/packages/0x.js/src/web3_wrapper.ts
@@ -5,6 +5,17 @@ import promisify = require('es6-promisify');
import {ZeroExError, Artifact, TransactionReceipt} from './types';
import {Contract} from './contract';
+interface RawLogEntry {
+ logIndex: string|null;
+ transactionIndex: string|null;
+ transactionHash: string;
+ blockHash: string|null;
+ blockNumber: string|null;
+ address: string;
+ data: string;
+ topics: string[];
+}
+
export class Web3Wrapper {
private web3: Web3;
private defaults: Partial<Web3.TxData>;
@@ -39,7 +50,9 @@ export class Web3Wrapper {
}
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
- transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
+ if (!_.isNull(transactionReceipt)) {
+ transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
+ }
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
@@ -137,8 +150,9 @@ export class Web3Wrapper {
method: 'eth_getLogs',
params: [serializedFilter],
};
- const logs = await this.sendRawPayloadAsync(payload);
- return logs;
+ const rawLogs = await this.sendRawPayloadAsync<RawLogEntry[]>(payload);
+ const formattedLogs = _.map(rawLogs, this.formatLog.bind(this));
+ return formattedLogs;
}
private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A {
const web3ContractInstance = this.web3.eth.contract(abi).at(address);
@@ -149,7 +163,7 @@ export class Web3Wrapper {
const networkId = await promisify(this.web3.version.getNetwork)();
return networkId;
}
- private async sendRawPayloadAsync(payload: Web3.JSONRPCRequestPayload): Promise<any> {
+ private async sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> {
const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider);
const response = await promisify(sendAsync)(payload);
const result = response.result;
@@ -169,4 +183,20 @@ export class Web3Wrapper {
return status;
}
}
+ private formatLog(rawLog: RawLogEntry): Web3.LogEntry {
+ const formattedLog = {
+ ...rawLog,
+ logIndex: this.hexToDecimal(rawLog.logIndex),
+ blockNumber: this.hexToDecimal(rawLog.blockNumber),
+ transactionIndex: this.hexToDecimal(rawLog.transactionIndex),
+ };
+ return formattedLog;
+ }
+ private hexToDecimal(hex: string|null): number|null {
+ if (_.isNull(hex)) {
+ return null;
+ }
+ const decimal = this.web3.toDecimal(hex);
+ return decimal;
+ }
}
diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts
new file mode 100644
index 000000000..0f2470070
--- /dev/null
+++ b/packages/0x.js/test/expiration_watcher_test.ts
@@ -0,0 +1,138 @@
+import 'mocha';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import * as Sinon from 'sinon';
+import * as Web3 from 'web3';
+import BigNumber from 'bignumber.js';
+import {chaiSetup} from './utils/chai_setup';
+import {web3Factory} from './utils/web3_factory';
+import {utils} from '../src/utils/utils';
+import {Web3Wrapper} from '../src/web3_wrapper';
+import {TokenUtils} from './utils/token_utils';
+import {ExpirationWatcher} from '../src/order_watcher/expiration_watcher';
+import {Token, DoneCallback} from '../src/types';
+import {ZeroEx} from '../src/0x';
+import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
+import {FillScenarios} from './utils/fill_scenarios';
+import {reportCallbackErrors} from './utils/report_callback_errors';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle();
+
+describe('ExpirationWatcher', () => {
+ let web3: Web3;
+ let zeroEx: ZeroEx;
+ let tokenUtils: TokenUtils;
+ let tokens: Token[];
+ let userAddresses: string[];
+ let zrxTokenAddress: string;
+ let fillScenarios: FillScenarios;
+ let exchangeContractAddress: string;
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let coinbase: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let feeRecipient: string;
+ const fillableAmount = new BigNumber(5);
+ let currentUnixTimestampSec: BigNumber;
+ let timer: Sinon.SinonFakeTimers;
+ let expirationWatcher: ExpirationWatcher;
+ before(async () => {
+ web3 = web3Factory.create();
+ zeroEx = new ZeroEx(web3.currentProvider);
+ exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
+ userAddresses = await zeroEx.getAvailableAddressesAsync();
+ tokens = await zeroEx.tokenRegistry.getTokensAsync();
+ tokenUtils = new TokenUtils(tokens);
+ zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
+ fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
+ [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
+ tokens = await zeroEx.tokenRegistry.getTokensAsync();
+ const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
+ makerTokenAddress = makerToken.address;
+ takerTokenAddress = takerToken.address;
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ const sinonTimerConfig = {shouldAdvanceTime: true} as any;
+ // This constructor has incorrect types
+ timer = Sinon.useFakeTimers(sinonTimerConfig);
+ currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
+ expirationWatcher = new ExpirationWatcher();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ timer.restore();
+ expirationWatcher.unsubscribe();
+ });
+ it('correctly emits events when order expires', (done: DoneCallback) => {
+ (async () => {
+ const orderLifetimeSec = 60;
+ const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ expirationUnixTimestampSec,
+ );
+ const orderHash = ZeroEx.getOrderHashHex(signedOrder);
+ expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
+ const callbackAsync = reportCallbackErrors(done)(async (hash: string) => {
+ expect(hash).to.be.equal(orderHash);
+ expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
+ done();
+ });
+ expirationWatcher.subscribe(callbackAsync);
+ timer.tick(orderLifetimeSec * 1000);
+ })().catch(done);
+ });
+ it('doesn\'t emit events before order expires', (done: DoneCallback) => {
+ (async () => {
+ const orderLifetimeSec = 60;
+ const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ expirationUnixTimestampSec,
+ );
+ const orderHash = ZeroEx.getOrderHashHex(signedOrder);
+ expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
+ const callbackAsync = reportCallbackErrors(done)(async (hash: string) => {
+ done(new Error('Emitted expiration went before the order actually expired'));
+ });
+ expirationWatcher.subscribe(callbackAsync);
+ const notEnoughTime = orderLifetimeSec - 1;
+ timer.tick(notEnoughTime * 1000);
+ done();
+ })().catch(done);
+ });
+ it('emits events in correct order', (done: DoneCallback) => {
+ (async () => {
+ const order1Lifetime = 60;
+ const order2Lifetime = 120;
+ const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
+ const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
+ const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ order1ExpirationUnixTimestampSec,
+ );
+ const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ order2ExpirationUnixTimestampSec,
+ );
+ const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
+ const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
+ expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
+ expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
+ const expirationOrder = [orderHash1, orderHash2];
+ const callbackAsync = reportCallbackErrors(done)(async (hash: string) => {
+ const orderHash = expirationOrder.shift();
+ expect(hash).to.be.equal(orderHash);
+ if (_.isEmpty(expirationOrder)) {
+ done();
+ }
+ });
+ expirationWatcher.subscribe(callbackAsync);
+ timer.tick(order2Lifetime * 1000);
+ })().catch(done);
+ });
+});
diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts
index e64a69890..ab6174f71 100644
--- a/packages/0x.js/test/order_state_watcher_test.ts
+++ b/packages/0x.js/test/order_state_watcher_test.ts
@@ -75,13 +75,13 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
[orderHash]: signedOrder,
});
let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
- zeroEx.orderStateWatcher.removeOrder(orderHash);
+ await zeroEx.orderStateWatcher.removeOrderAsync(orderHash);
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
[orderHash]: signedOrder,
});
@@ -94,7 +94,7 @@ describe('OrderStateWatcher', () => {
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
const nonExistentOrderHash = `0x${orderHash.substr(2).split('').reverse().join('')}`;
- zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash);
+ await zeroEx.orderStateWatcher.removeOrderAsync(nonExistentOrderHash);
});
});
describe('#subscribe', async () => {
@@ -111,7 +111,7 @@ describe('OrderStateWatcher', () => {
afterEach(async () => {
zeroEx.orderStateWatcher.unsubscribe();
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.removeOrder(orderHash);
+ await zeroEx.orderStateWatcher.removeOrderAsync(orderHash);
});
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
(async () => {
@@ -119,7 +119,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
@@ -137,7 +137,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
throw new Error('OrderState callback fired for irrelevant order');
});
@@ -158,7 +158,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
@@ -178,7 +178,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
@@ -210,7 +210,7 @@ describe('OrderStateWatcher', () => {
const fillAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
@@ -237,6 +237,22 @@ describe('OrderStateWatcher', () => {
);
})().catch(done);
});
+ it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
+ (async () => {
+ const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
+ const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18);
+ signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerToken.address, takerToken.address, makerFee, takerFee, maker, taker, fillableAmount,
+ taker);
+ const orderHash = ZeroEx.getOrderHashHex(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
+ const callback = reportCallbackErrors(done)((orderState: OrderState) => {
+ done();
+ });
+ zeroEx.orderStateWatcher.subscribe(callback);
+ await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0));
+ })().catch(done);
+ });
describe('remainingFillable(M|T)akerTokenAmount', () => {
it('should calculate correct remaining fillable', (done: DoneCallback) => {
(async () => {
@@ -250,7 +266,7 @@ describe('OrderStateWatcher', () => {
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
eventCount++;
@@ -282,7 +298,7 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid;
@@ -307,7 +323,7 @@ describe('OrderStateWatcher', () => {
const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
const transferAmount = makerBalance.sub(remainingAmount);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid;
@@ -412,7 +428,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
@@ -434,7 +450,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
@@ -460,7 +476,7 @@ describe('OrderStateWatcher', () => {
const cancelAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts
index b30762e8c..1a7cb9e40 100644
--- a/packages/0x.js/test/token_wrapper_test.ts
+++ b/packages/0x.js/test/token_wrapper_test.ts
@@ -361,6 +361,9 @@ describe('TokenWrapper', () => {
(async () => {
const callback = (err: Error, logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
+ expect(logEvent.logIndex).to.be.equal(0);
+ expect(logEvent.transactionIndex).to.be.equal(0);
+ expect(logEvent.blockNumber).to.be.a('number');
const args = logEvent.args;
expect(args._from).to.be.equal(coinbase);
expect(args._to).to.be.equal(addressWithoutFunds);
diff --git a/packages/0x.js/test/utils/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts
index d471b2af2..4f9517704 100644
--- a/packages/0x.js/test/utils/report_callback_errors.ts
+++ b/packages/0x.js/test/utils/report_callback_errors.ts
@@ -1,10 +1,10 @@
import { DoneCallback } from '../../src/types';
export const reportCallbackErrors = (done: DoneCallback) => {
- return (f: (...args: any[]) => void) => {
- const wrapped = (...args: any[]) => {
+ return (fAsync: (...args: any[]) => void|Promise<void>) => {
+ const wrapped = async (...args: any[]) => {
try {
- f(...args);
+ await fAsync(...args);
} catch (err) {
done(err);
}
diff --git a/packages/assert/package.json b/packages/assert/package.json
index ed1d2a98b..d0f40c66e 100644
--- a/packages/assert/package.json
+++ b/packages/assert/package.json
@@ -1,6 +1,6 @@
{
"name": "@0xproject/assert",
- "version": "0.0.4",
+ "version": "0.0.5",
"description": "Provides a standard way of performing type and schema validation across 0x projects",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
@@ -23,7 +23,7 @@
},
"homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md",
"devDependencies": {
- "@0xproject/tslint-config": "^0.1.0",
+ "@0xproject/tslint-config": "^0.1.1",
"@types/lodash": "^4.14.78",
"@types/mocha": "^2.2.42",
"@types/valid-url": "^1.0.2",
@@ -37,7 +37,7 @@
"typescript": "^2.4.2"
},
"dependencies": {
- "@0xproject/json-schemas": "^0.6.7",
+ "@0xproject/json-schemas": "^0.6.8",
"bignumber.js": "~4.1.0",
"ethereum-address": "^0.0.4",
"lodash": "^4.17.4",
diff --git a/packages/assert/scripts/postpublish.js b/packages/assert/scripts/postpublish.js
new file mode 100644
index 000000000..7fa452b08
--- /dev/null
+++ b/packages/assert/scripts/postpublish.js
@@ -0,0 +1,14 @@
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const subPackageName = packageJSON.name;
+
+postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
+ const assets = [];
+ return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
+ })
+ .catch (function(err) {
+ throw err;
+ });
diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md
index f3367f4ce..ec6727a12 100644
--- a/packages/connect/CHANGELOG.md
+++ b/packages/connect/CHANGELOG.md
@@ -1,4 +1,5 @@
# CHANGELOG
-v0.0.0 - _Nov. 15, 2017_
+v0.1.0 - _November 22, 2017_
------------------------
+ * Provide a HttpClient class for interacting with standard relayer api compliant HTTP urls
diff --git a/packages/connect/package.json b/packages/connect/package.json
index d26594b5d..9131eef14 100644
--- a/packages/connect/package.json
+++ b/packages/connect/package.json
@@ -1,9 +1,9 @@
{
"name": "@0xproject/connect",
- "version": "0.0.0",
+ "version": "0.1.0",
"description": "A javascript library for interacting with the standard relayer api",
"keywords": [
- "0x-connect",
+ "connect",
"0xproject",
"ethereum",
"tokens",
@@ -14,9 +14,10 @@
"scripts": {
"build": "tsc",
"clean": "shx rm -rf _bundles lib test_temp",
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
+ "upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
"copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures",
"lint": "tslint src/**/*.ts test/**/*.ts",
- "prepublishOnly": "run-p build",
"run_mocha": "mocha lib/test/**/*_test.js",
"test": "run-s clean build copy_test_fixtures run_mocha",
"test:circleci": "yarn test"
@@ -35,9 +36,9 @@
},
"homepage": "https://github.com/0xProject/0x.js/packages/connect/README.md",
"dependencies": {
- "@0xproject/assert": "0.0.4",
- "@0xproject/json-schemas": "0.6.7",
- "0x.js": "~0.25.1",
+ "0x.js": "^0.26.1",
+ "@0xproject/assert": "^0.0.5",
+ "@0xproject/json-schemas": "^0.6.8",
"bignumber.js": "~4.1.0",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
@@ -45,7 +46,7 @@
"websocket": "^1.0.25"
},
"devDependencies": {
- "@0xproject/tslint-config": "0.1.0",
+ "@0xproject/tslint-config": "^0.1.1",
"@types/fetch-mock": "^5.12.1",
"@types/lodash": "^4.14.77",
"@types/mocha": "^2.2.42",
@@ -62,6 +63,7 @@
"npm-run-all": "^4.0.2",
"shx": "^0.2.2",
"tslint": "5.8.0",
+ "typedoc": "~0.8.0",
"typescript": "~2.6.1",
"web3-typescript-typings": "^0.7.1"
}
diff --git a/packages/connect/scripts/postpublish.js b/packages/connect/scripts/postpublish.js
new file mode 100644
index 000000000..ba0f8507d
--- /dev/null
+++ b/packages/connect/scripts/postpublish.js
@@ -0,0 +1,39 @@
+const execAsync = require('async-child-process').execAsync;
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const cwd = __dirname + '/..';
+const subPackageName = packageJSON.name;
+const S3BucketPath = 's3://connect-docs-jsons/';
+
+let tag;
+let version;
+postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ tag = result.tag;
+ version = result.version;
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, version);
+ return postpublish_utils.publishReleaseNotes(tag, releaseName);
+ })
+ .then(function(release) {
+ console.log('POSTPUBLISH: Release successful, generating docs...');
+ return execAsync(
+ 'JSON_FILE_PATH=' + __dirname + '/../docs/index.json PROJECT_DIR=' + __dirname + '/.. yarn docs:json',
+ {
+ cwd,
+ }
+ );
+ })
+ .then(function(result) {
+ if (result.stderr !== '') {
+ throw new Error(result.stderr);
+ }
+ const fileName = 'v' + version + '.json';
+ console.log('POSTPUBLISH: Doc generation successful, uploading docs... as ', fileName);
+ const s3Url = S3BucketPath + fileName;
+ return execAsync('S3_URL=' + s3Url + ' yarn upload_docs_json', {
+ cwd,
+ });
+ }).catch (function(err) {
+ throw err;
+ });
diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts
index ab8c6bfa1..85dc83c61 100644
--- a/packages/connect/src/http_client.ts
+++ b/packages/connect/src/http_client.ts
@@ -18,6 +18,11 @@ import {
import {schemas as clientSchemas} from './schemas/schemas';
import {typeConverters} from './utils/type_converters';
+// TODO: move this and bigNumberConfigs in the 0x.js package into one place
+BigNumber.config({
+ EXPONENTIAL_AT: 1000,
+});
+
interface RequestOptions {
params?: object;
payload?: object;
@@ -157,9 +162,10 @@ export class HttpClient implements Client {
const headers = new Headers({
'content-type': 'application/json',
});
+
const response = await fetch(url, {
method: requestType,
- body: payload,
+ body: JSON.stringify(payload),
headers,
});
if (!response.ok) {
diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts
index 5e97f4f26..c9ebde510 100644
--- a/packages/connect/src/index.ts
+++ b/packages/connect/src/index.ts
@@ -1,12 +1,8 @@
export {HttpClient} from './http_client';
-export {WebSocketOrderbookChannel} from './ws_orderbook_channel';
export {
Client,
FeesRequest,
FeesResponse,
- OrderbookChannel,
- OrderbookChannelHandler,
- OrderbookChannelSubscriptionOpts,
OrderbookRequest,
OrderbookResponse,
OrdersRequest,
diff --git a/packages/connect/test/ws_orderbook_channel_test.ts b/packages/connect/test/ws_orderbook_channel_test.ts
index f3dead9ae..e92c6f44a 100644
--- a/packages/connect/test/ws_orderbook_channel_test.ts
+++ b/packages/connect/test/ws_orderbook_channel_test.ts
@@ -4,7 +4,7 @@ import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import {
WebSocketOrderbookChannel,
-} from '../src/index';
+} from '../src/ws_orderbook_channel';
chai.config.includeStack = true;
chai.use(dirtyChai);
diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json
index 07ed20551..89c0d25f7 100644
--- a/packages/json-schemas/package.json
+++ b/packages/json-schemas/package.json
@@ -1,6 +1,6 @@
{
"name": "@0xproject/json-schemas",
- "version": "0.6.7",
+ "version": "0.6.8",
"description": "0x-related json schemas",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
@@ -28,7 +28,7 @@
"lodash.values": "^4.3.0"
},
"devDependencies": {
- "@0xproject/tslint-config": "^0.1.0",
+ "@0xproject/tslint-config": "^0.1.1",
"@types/lodash.foreach": "^4.5.3",
"@types/lodash.values": "^4.3.3",
"@types/mocha": "^2.2.42",
diff --git a/packages/json-schemas/scripts/postpublish.js b/packages/json-schemas/scripts/postpublish.js
new file mode 100644
index 000000000..7fa452b08
--- /dev/null
+++ b/packages/json-schemas/scripts/postpublish.js
@@ -0,0 +1,14 @@
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const subPackageName = packageJSON.name;
+
+postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
+ const assets = [];
+ return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
+ })
+ .catch (function(err) {
+ throw err;
+ });
diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json
index ca46d63fc..7ee3f1f71 100644
--- a/packages/tslint-config/package.json
+++ b/packages/tslint-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@0xproject/tslint-config",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Lint rules related to 0xProject for TSLint",
"main": "tslint.json",
"files": [
diff --git a/packages/tslint-config/scripts/postpublish.js b/packages/tslint-config/scripts/postpublish.js
new file mode 100644
index 000000000..7fa452b08
--- /dev/null
+++ b/packages/tslint-config/scripts/postpublish.js
@@ -0,0 +1,14 @@
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const subPackageName = packageJSON.name;
+
+postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
+ const assets = [];
+ return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
+ })
+ .catch (function(err) {
+ throw err;
+ });
diff --git a/scripts/postpublish_utils.js b/scripts/postpublish_utils.js
new file mode 100644
index 000000000..4f9798e60
--- /dev/null
+++ b/scripts/postpublish_utils.js
@@ -0,0 +1,51 @@
+const execAsync = require('async-child-process').execAsync;
+const semverSort = require('semver-sort');
+const promisify = require('es6-promisify');
+const publishRelease = require('publish-release');
+
+const publishReleaseAsync = promisify(publishRelease);
+const githubPersonalAccessToken = process.env.GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS;
+
+module.exports = {
+ getLatestTagAndVersionAsync: function(subPackageName) {
+ const subPackagePrefix = subPackageName + '@';
+ const gitTagsCommand = 'git tag -l "' + subPackagePrefix + '*"';
+ return execAsync(gitTagsCommand)
+ .then(function(result) {
+ if (result.stderr !== '') {
+ throw new Error(result.stderr);
+ }
+ const tags = result.stdout.trim().split('\n');
+ const versions = tags.map(function(tag) {
+ return tag.slice(subPackagePrefix.length);
+ });
+ const sortedVersions = semverSort.desc(versions);
+ const latestVersion = sortedVersions[0];
+ const latestTag = subPackagePrefix + latestVersion;
+ return {
+ tag: latestTag,
+ version: latestVersion
+ };
+ });
+ },
+ publishReleaseNotes: function(tag, releaseName, assets) {
+ console.log('POSTPUBLISH: Releasing ', releaseName, '...');
+ return publishReleaseAsync({
+ token: githubPersonalAccessToken,
+ owner: '0xProject',
+ repo: '0x.js',
+ tag: tag,
+ name: releaseName,
+ notes: 'TODO',
+ draft: false,
+ prerelease: false,
+ reuseRelease: true,
+ reuseDraftOnly: false,
+ assets: assets,
+ });
+ },
+ getReleaseName(subPackageName, version) {
+ const releaseName = subPackageName + ' v' + version;
+ return releaseName;
+ },
+};
diff --git a/yarn.lock b/yarn.lock
index d10468fed..db4609ac1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6,9 +6,13 @@
version "5.12.2"
resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2"
+"@types/bintrees@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6"
+
"@types/fs-extra@^4.0.0":
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.4.tgz#72947e108f2cbeda5ab288a927399fdf6d02bd42"
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.5.tgz#8aa6033c0e87c653b09a6711686916864b48ec9e"
dependencies:
"@types/node" "*"
@@ -53,7 +57,11 @@
version "0.0.28"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa"
-"@types/minimatch@*", "@types/minimatch@^2.0.29":
+"@types/minimatch@*":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.1.tgz#b683eb60be358304ef146f5775db4c0e3696a550"
+
+"@types/minimatch@^2.0.29":
version "2.0.29"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a"
@@ -70,8 +78,8 @@
resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-5.0.1.tgz#6cb41c724cb1644d56c2d1dae7c7b204e706b39e"
"@types/shelljs@^0.7.0":
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.5.tgz#5834fb7385d1137bd2be5842f2c278ac36a117f4"
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.6.tgz#4ac7ca01c191ba65b8e2bf50543c5560084d8d27"
dependencies:
"@types/glob" "*"
"@types/node" "*"
@@ -323,6 +331,12 @@ assertion-error@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
+async-child-process@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/async-child-process/-/async-child-process-1.1.1.tgz#27d0a598b5738707f9898c048bd231340583747b"
+ dependencies:
+ babel-runtime "^6.11.6"
+
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -740,7 +754,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@@ -849,6 +863,10 @@ bindings@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
+bintrees@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
+
bip39@^2.2.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.4.0.tgz#a0b8adbf163f53495f00f05d9ede7c25369ccf13"
@@ -4500,9 +4518,9 @@ public-encrypt@^4.0.0:
parse-asn1 "^5.0.0"
randombytes "^2.0.1"
-publish-release@^1.3.3:
+publish-release@0xproject/publish-release:
version "1.3.3"
- resolved "https://registry.yarnpkg.com/publish-release/-/publish-release-1.3.3.tgz#6cd11df835e14c13b0e08a35d3fb992b918bec3c"
+ resolved "https://codeload.github.com/0xproject/publish-release/tar.gz/c67c546726deecabd0cb35f9873afc912f862bd3"
dependencies:
async "^0.9.0"
ghauth "^2.0.0"
@@ -4979,7 +4997,14 @@ semver-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9"
-"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1:
+semver-sort@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/semver-sort/-/semver-sort-0.0.4.tgz#34fdbddc6a6b2b4161398c3c4dba56243bfeaa8b"
+ dependencies:
+ semver "^5.0.3"
+ semver-regex "^1.0.0"
+
+"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"