Merge branch 'development' into feature/orderExpired

This commit is contained in:
Leonid 2017-11-20 14:46:53 -06:00 committed by GitHub
commit d39c0bee39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 3342 additions and 210 deletions

View File

@ -6,33 +6,22 @@
This repository contains all the 0x developer tools written in TypeScript. Our hope is that these tools make it easy to build Relayers and other DApps that use the 0x protocol. This repository contains all the 0x developer tools written in TypeScript. Our hope is that these tools make it easy to build Relayers and other DApps that use the 0x protocol.
[website-url]: https://0xproject.com/
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
[![CircleCI](https://circleci.com/gh/0xProject/0x.js.svg?style=svg&circle-token=61bf7cd8c9b4e11b132089dfcffdd1be277d1e0c)](https://circleci.com/gh/0xProject/0x.js) [![CircleCI](https://circleci.com/gh/0xProject/0x.js.svg?style=svg&circle-token=61bf7cd8c9b4e11b132089dfcffdd1be277d1e0c)](https://circleci.com/gh/0xProject/0x.js)
[![npm version](https://badge.fury.io/js/0x.js.svg)](https://badge.fury.io/js/0x.js)
[![Coverage Status](https://coveralls.io/repos/github/0xProject/0x.js/badge.svg?branch=master&t=fp0cXD)](https://coveralls.io/github/0xProject/0x.js?branch=master) [![Coverage Status](https://coveralls.io/repos/github/0xProject/0x.js/badge.svg?branch=master&t=fp0cXD)](https://coveralls.io/github/0xProject/0x.js?branch=master)
[![Slack Status](http://slack.0xProject.com/badge.svg)](http://slack.0xProject.com) [![Discord](https://img.shields.io/badge/chat-rocket.chat-yellow.svg?style=flat
)](https://chat.0xproject.com)
[![Join the chat at https://gitter.im/0xProject/Lobby](https://badges.gitter.im/0xProject/Lobby.svg)](https://gitter.im/0xProject/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/0xProject/Lobby](https://badges.gitter.im/0xProject/Lobby.svg)](https://gitter.im/0xProject/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Greenkeeper badge](https://badges.greenkeeper.io/0xProject/0x.js.svg?token=7c22e5c72acf39d3ead8d29c5d9bb38f9096df3e643024dcedd53ab732847be1&ts=1496426342666)](https://greenkeeper.io/) [![Greenkeeper badge](https://badges.greenkeeper.io/0xProject/0x.js.svg?token=7c22e5c72acf39d3ead8d29c5d9bb38f9096df3e643024dcedd53ab732847be1&ts=1496426342666)](https://greenkeeper.io/)
Instructions ### Core Packages
------------
Make sure you have `yarn@1.x` installed locally. | Package | Version | Description |
|--------|-------|------------|
### Creating a new sub-package | [`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 |
1. Make sure the `name` field in the sub-package's `package.json` starts with `@0xproject/` and has a unique name (e.g `@0xproject/assert`). | [`@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 |
2. Run `yarn install` to install all it's dependencies.
### How to add a sub-package as a dependency to another sub-package:
1. Add the sub-packages name (declared in it's `package.json`) to your sub-packages `package.json` under `dependencies` or `devDependencies`.
2. Run `yarn install` from anywhere in the mono repo.
3. Import the sub-package as:
```
import {myPkg} from '@0xproject/myPkg';
```

View File

@ -6,12 +6,17 @@
], ],
"scripts": { "scripts": {
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"", "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"
}, },
"config": { "config": {
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic" "mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
}, },
"devDependencies": { "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"
} }
} }

View File

@ -1,8 +1,15 @@
# CHANGELOG # CHANGELOG
v0.24.0 - _November 13, 2017_ vx.x.x
------------------------
* Remove support for Async callback types when used in Subscribe functions
* 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 * Standardise on Cancelled over Canceled
* Add missing `DecodedLogEvent` type to exported types
* 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.
v0.23.0 - _November 12, 2017_ v0.23.0 - _November 12, 2017_
------------------------ ------------------------

View File

@ -1,3 +1,6 @@
0x.js
-----
## Installation ## Installation
0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package. 0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package.

View File

@ -1,6 +1,6 @@
{ {
"name": "0x.js", "name": "0x.js",
"version": "0.23.0", "version": "0.25.1",
"description": "A javascript library for interacting with the 0x protocol", "description": "A javascript library for interacting with the 0x protocol",
"keywords": [ "keywords": [
"0x.js", "0x.js",
@ -13,11 +13,9 @@
"types": "lib/src/index.d.ts", "types": "lib/src/index.d.ts",
"scripts": { "scripts": {
"prebuild": "npm run clean", "prebuild": "npm run clean",
"build": "run-p build:umd:prod build:commonjs", "build": "run-p build:umd:prod build:commonjs; exit 0;",
"prepublishOnly": "run-p build", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
"postpublish": "run-s release docs:json upload_docs_json", "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",
"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",
"lint": "tslint src/**/*.ts test/**/*.ts", "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": "run-s clean test:commonjs",
@ -25,9 +23,6 @@
"test:coverage": "nyc npm run test --all", "test:coverage": "nyc npm run test --all",
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls", "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;", "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", "clean": "shx rm -rf _bundles lib test_temp",
"build:umd:dev": "webpack", "build:umd:dev": "webpack",
"build:umd:prod": "NODE_ENV=production webpack", "build:umd:prod": "NODE_ENV=production webpack",
@ -49,6 +44,7 @@
"node": ">=6.0.0" "node": ">=6.0.0"
}, },
"devDependencies": { "devDependencies": {
"@0xproject/tslint-config": "^0.1.0",
"@types/bintrees": "^1.0.2", "@types/bintrees": "^1.0.2",
"@types/jsonschema": "^1.1.1", "@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.64", "@types/lodash": "^4.14.64",
@ -77,8 +73,7 @@
"sinon": "^4.0.0", "sinon": "^4.0.0",
"source-map-support": "^0.5.0", "source-map-support": "^0.5.0",
"truffle-hdwallet-provider": "^0.0.3", "truffle-hdwallet-provider": "^0.0.3",
"tslint": "~5.5.0", "tslint": "5.8.0",
"tslint-config-0xproject": "^0.0.2",
"typedoc": "~0.8.0", "typedoc": "~0.8.0",
"types-bn": "^0.0.1", "types-bn": "^0.0.1",
"types-ethereumjs-util": "0xProject/types-ethereumjs-util", "types-ethereumjs-util": "0xProject/types-ethereumjs-util",
@ -88,10 +83,11 @@
"webpack": "^3.1.0" "webpack": "^3.1.0"
}, },
"dependencies": { "dependencies": {
"0x-json-schemas": "^0.6.1", "@0xproject/assert": "^0.0.4",
"@0xproject/assert": "0.0.3", "@0xproject/json-schemas": "^0.6.7",
"bignumber.js": "~4.1.0", "bignumber.js": "~4.1.0",
"bintrees": "^1.0.2", "bintrees": "^1.0.2",
"bn.js": "4.11.8",
"compare-versions": "^3.0.1", "compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0", "es6-promisify": "^5.0.0",
"ethereumjs-abi": "^0.6.4", "ethereumjs-abi": "^0.6.4",
@ -100,7 +96,6 @@
"find-versions": "^2.0.0", "find-versions": "^2.0.0",
"js-sha3": "^0.6.1", "js-sha3": "^0.6.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"publish-release": "^1.3.3",
"uuid": "^3.1.0", "uuid": "^3.1.0",
"web3": "^0.20.0" "web3": "^0.20.0"
} }

View File

@ -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;
});

View File

@ -3,5 +3,4 @@
# UMD tests should only be run after building the commonjs because they reuse some of the commonjs build artifacts # 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 run-s substitute_umd_bundle run_mocha
return_code=$? return_code=$?
npm run clean
exit $return_code exit $return_code

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import {SchemaValidator, schemas} from '0x-json-schemas'; import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {bigNumberConfigs} from './bignumber_config'; import {bigNumberConfigs} from './bignumber_config';
import * as ethUtil from 'ethereumjs-util'; import * as ethUtil from 'ethereumjs-util';
import {Web3Wrapper} from './web3_wrapper'; import {Web3Wrapper} from './web3_wrapper';

View File

@ -1,7 +1,7 @@
import * as Web3 from 'web3'; import * as Web3 from 'web3';
import * as _ from 'lodash'; import * as _ from 'lodash';
import promisify = require('es6-promisify'); import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '0x-json-schemas'; import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {AbiType} from './types'; import {AbiType} from './types';
export class Contract implements Web3.ContractInstance { export class Contract implements Web3.ContractInstance {

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as Web3 from 'web3'; import * as Web3 from 'web3';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import {schemas} from '0x-json-schemas'; import {schemas} from '@0xproject/json-schemas';
import {Web3Wrapper} from '../web3_wrapper'; import {Web3Wrapper} from '../web3_wrapper';
import { import {
ECSignature, ECSignature,

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import {schemas} from '0x-json-schemas'; import {schemas} from '@0xproject/json-schemas';
import {Web3Wrapper} from '../web3_wrapper'; import {Web3Wrapper} from '../web3_wrapper';
import {assert} from '../utils/assert'; import {assert} from '../utils/assert';
import {constants} from '../utils/constants'; import {constants} from '../utils/constants';

View File

@ -6,8 +6,6 @@ export {
ECSignature, ECSignature,
ZeroExError, ZeroExError,
EventCallback, EventCallback,
EventCallbackAsync,
EventCallbackSync,
ExchangeContractErrs, ExchangeContractErrs,
ContractEvent, ContractEvent,
Token, Token,

View File

@ -81,7 +81,7 @@ export class EventWatcher {
...log, ...log,
}; };
if (!_.isUndefined(this._intervalIdIfExists)) { if (!_.isUndefined(this._intervalIdIfExists)) {
await callback(logEvent); callback(logEvent);
} }
} }
} }

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import {schemas} from '0x-json-schemas'; import {schemas} from '@0xproject/json-schemas';
import {ZeroEx} from '../0x'; import {ZeroEx} from '../0x';
import {EventWatcher} from './event_watcher'; import {EventWatcher} from './event_watcher';
import {assert} from '../utils/assert'; import {assert} from '../utils/assert';
@ -53,7 +53,7 @@ interface OrderByOrderHash {
export class OrderStateWatcher { export class OrderStateWatcher {
private _orderByOrderHash: OrderByOrderHash = {}; private _orderByOrderHash: OrderByOrderHash = {};
private _dependentOrderHashes: DependentOrderHashes = {}; private _dependentOrderHashes: DependentOrderHashes = {};
private _callbackIfExistsAsync?: OnOrderStateChangeCallback; private _callbackIfExists?: OnOrderStateChangeCallback;
private _eventWatcher: EventWatcher; private _eventWatcher: EventWatcher;
private _web3Wrapper: Web3Wrapper; private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder; private _abiDecoder: AbiDecoder;
@ -89,12 +89,12 @@ export class OrderStateWatcher {
* signature is verified. * signature is verified.
* @param signedOrder The order you wish to start watching. * @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); assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder; this._orderByOrderHash[orderHash] = signedOrder;
this.addToDependentOrderHashes(signedOrder, orderHash); await this.addToDependentOrderHashesAsync(signedOrder, orderHash);
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
} }
@ -102,13 +102,16 @@ export class OrderStateWatcher {
* Removes an order from the orderStateWatcher * Removes an order from the orderStateWatcher
* @param orderHash The orderHash of the order you wish to stop watching. * @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); assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const signedOrder = this._orderByOrderHash[orderHash]; const signedOrder = this._orderByOrderHash[orderHash];
if (_.isUndefined(signedOrder)) { if (_.isUndefined(signedOrder)) {
return; // noop return; // noop
} }
delete this._orderByOrderHash[orderHash]; 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.removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
this._expirationWatcher.removeOrder(orderHash); this._expirationWatcher.removeOrder(orderHash);
} }
@ -120,10 +123,10 @@ export class OrderStateWatcher {
*/ */
public subscribe(callback: OnOrderStateChangeCallback): void { public subscribe(callback: OnOrderStateChangeCallback): void {
assert.isFunction('callback', callback); assert.isFunction('callback', callback);
if (!_.isUndefined(this._callbackIfExistsAsync)) { if (!_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent); throw new Error(ZeroExError.SubscriptionAlreadyPresent);
} }
this._callbackIfExistsAsync = callback; this._callbackIfExists = callback;
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); this._expirationWatcher.subscribe(this._onOrderExpired.bind(this));
} }
@ -131,12 +134,12 @@ export class OrderStateWatcher {
* Ends an orderStateWatcher subscription. * Ends an orderStateWatcher subscription.
*/ */
public unsubscribe(): void { public unsubscribe(): void {
if (_.isUndefined(this._callbackIfExistsAsync)) { if (_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound); throw new Error(ZeroExError.SubscriptionNotFound);
} }
this._balanceAndProxyAllowanceLazyStore.deleteAll(); this._balanceAndProxyAllowanceLazyStore.deleteAll();
this._orderFilledCancelledLazyStore.deleteAll(); this._orderFilledCancelledLazyStore.deleteAll();
delete this._callbackIfExistsAsync; delete this._callbackIfExists;
this._eventWatcher.unsubscribe(); this._eventWatcher.unsubscribe();
this._expirationWatcher.unsubscribe(); this._expirationWatcher.unsubscribe();
} }
@ -232,13 +235,13 @@ export class OrderStateWatcher {
// Most of these calls will never reach the network because the data is fetched from stores // Most of these calls will never reach the network because the data is fetched from stores
// and only updated when cache is invalidated // and only updated when cache is invalidated
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
if (_.isUndefined(this._callbackIfExistsAsync)) { if (_.isUndefined(this._callbackIfExists)) {
break; // Unsubscribe was called 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])) { if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
this._dependentOrderHashes[signedOrder.maker] = {}; this._dependentOrderHashes[signedOrder.maker] = {};
} }
@ -246,11 +249,17 @@ export class OrderStateWatcher {
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
} }
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); 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();
} }
private removeFromDependentOrderHashes(makerAddress: string, makerTokenAddress: string, orderHash: string) { this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
this._dependentOrderHashes[makerAddress][makerTokenAddress].delete(orderHash); }
if (this._dependentOrderHashes[makerAddress][makerTokenAddress].size === 0) { private removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
delete this._dependentOrderHashes[makerAddress][makerTokenAddress]; this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
delete this._dependentOrderHashes[makerAddress][tokenAddress];
} }
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
delete this._dependentOrderHashes[makerAddress]; delete this._dependentOrderHashes[makerAddress];

View File

@ -42,13 +42,8 @@ export type OrderValues = [BigNumber, BigNumber, BigNumber,
export type LogEvent = Web3.LogEntryEvent; export type LogEvent = Web3.LogEntryEvent;
export type DecodedLogEvent<ArgsType> = Web3.DecodedLogEntryEvent<ArgsType>; export type DecodedLogEvent<ArgsType> = Web3.DecodedLogEntryEvent<ArgsType>;
export type EventCallbackAsync<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => Promise<void>; export type EventCallback<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => void;
export type EventCallbackSync<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => void; export type EventWatcherCallback = (log: LogEvent) => 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 interface ExchangeContract extends Web3.ContractInstance { export interface ExchangeContract extends Web3.ContractInstance {
isValidSignature: { isValidSignature: {
@ -496,6 +491,7 @@ export interface OrderRelevantState {
filledTakerTokenAmount: BigNumber; filledTakerTokenAmount: BigNumber;
cancelledTakerTokenAmount: BigNumber; cancelledTakerTokenAmount: BigNumber;
remainingFillableMakerTokenAmount: BigNumber; remainingFillableMakerTokenAmount: BigNumber;
remainingFillableTakerTokenAmount: BigNumber;
} }
export interface OrderStateValid { export interface OrderStateValid {
@ -512,9 +508,7 @@ export interface OrderStateInvalid {
export type OrderState = OrderStateValid|OrderStateInvalid; export type OrderState = OrderStateValid|OrderStateInvalid;
export type OnOrderStateChangeCallbackSync = (orderState: OrderState) => void; export type OnOrderStateChangeCallback = (orderState: OrderState) => void;
export type OnOrderStateChangeCallbackAsync = (orderState: OrderState) => Promise<void>;
export type OnOrderStateChangeCallback = OnOrderStateChangeCallbackAsync|OnOrderStateChangeCallbackSync;
export interface TransactionReceipt { export interface TransactionReceipt {
blockHash: string; blockHash: string;

View File

@ -34,7 +34,7 @@ export class AbiDecoder {
value = this.padZeros(new BigNumber(value).toString(16)); value = this.padZeros(new BigNumber(value).toString(16));
} else if (param.type === SolidityTypes.Uint256 || } else if (param.type === SolidityTypes.Uint256 ||
param.type === SolidityTypes.Uint8 || param.type === SolidityTypes.Uint8 ||
param.type === SolidityTypes.Uint ) { param.type === SolidityTypes.Uint) {
value = new BigNumber(value); value = new BigNumber(value);
} }
decodedParams[param.name] = value; decodedParams[param.name] = value;

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as Web3 from 'web3'; import * as Web3 from 'web3';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import {SchemaValidator, Schema} from '0x-json-schemas'; import {SchemaValidator, Schema} from '@0xproject/json-schemas';
import {assert as sharedAssert} from '@0xproject/assert'; import {assert as sharedAssert} from '@0xproject/assert';
import {Web3Wrapper} from '../web3_wrapper'; import {Web3Wrapper} from '../web3_wrapper';
import {signatureUtils} from '../utils/signature_utils'; import {signatureUtils} from '../utils/signature_utils';
@ -9,7 +9,8 @@ import {ECSignature} from '../types';
const HEX_REGEX = /^0x[0-9A-F]*$/i; const HEX_REGEX = /^0x[0-9A-F]*$/i;
export const assert = _.extend({}, sharedAssert, { export const assert = {
...sharedAssert,
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress); const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`); this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
@ -26,4 +27,4 @@ export const assert = _.extend({}, sharedAssert, {
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider'); this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
}, },
}); };

View File

@ -18,6 +18,8 @@ import {constants} from '../utils/constants';
import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store'; import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store';
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store'; import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
export class OrderStateUtils { export class OrderStateUtils {
private balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; private balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; private orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
@ -78,6 +80,9 @@ export class OrderStateUtils {
.dividedToIntegerBy(totalTakerTokenAmount); .dividedToIntegerBy(totalTakerTokenAmount);
const fillableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); const fillableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
const remainingFillableMakerTokenAmount = BigNumber.min(fillableMakerTokenAmount, remainingMakerTokenAmount); const remainingFillableMakerTokenAmount = BigNumber.min(fillableMakerTokenAmount, remainingMakerTokenAmount);
const remainingFillableTakerTokenAmount = remainingFillableMakerTokenAmount
.times(totalTakerTokenAmount)
.dividedToIntegerBy(totalMakerTokenAmount);
// TODO: Handle edge case where maker token is ZRX with fee // TODO: Handle edge case where maker token is ZRX with fee
const orderRelevantState = { const orderRelevantState = {
makerBalance, makerBalance,
@ -87,6 +92,7 @@ export class OrderStateUtils {
filledTakerTokenAmount, filledTakerTokenAmount,
cancelledTakerTokenAmount, cancelledTakerTokenAmount,
remainingFillableMakerTokenAmount, remainingFillableMakerTokenAmount,
remainingFillableTakerTokenAmount,
}; };
return orderRelevantState; return orderRelevantState;
} }
@ -113,6 +119,13 @@ export class OrderStateUtils {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
} }
} }
const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
.dividedBy(signedOrder.makerTokenAmount);
if (orderRelevantState.remainingFillableTakerTokenAmount
.lessThan(minFillableTakerTokenAmountWithinNoRoundingErrorRange)) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
// TODO Add linear function solver when maker token is ZRX #badass // TODO Add linear function solver when maker token is ZRX #badass
// Return the max amount that's fillable // Return the max amount that's fillable
} }

View File

@ -47,7 +47,7 @@ describe('OrderStateWatcher', () => {
let taker: string; let taker: string;
let web3Wrapper: Web3Wrapper; let web3Wrapper: Web3Wrapper;
let signedOrder: SignedOrder; let signedOrder: SignedOrder;
const fillableAmount = new BigNumber(5); const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), 18);
before(async () => { before(async () => {
web3 = web3Factory.create(); web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider); zeroEx = new ZeroEx(web3.currentProvider);
@ -61,19 +61,25 @@ describe('OrderStateWatcher', () => {
[makerToken, takerToken] = tokenUtils.getNonProtocolTokens(); [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
web3Wrapper = (zeroEx as any)._web3Wrapper; web3Wrapper = (zeroEx as any)._web3Wrapper;
}); });
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#removeOrder', async () => { describe('#removeOrder', async () => {
it('should successfully remove existing order', async () => { it('should successfully remove existing order', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync( signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
[orderHash]: signedOrder, [orderHash]: signedOrder,
}); });
let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); 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({ expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
[orderHash]: signedOrder, [orderHash]: signedOrder,
}); });
@ -86,7 +92,7 @@ describe('OrderStateWatcher', () => {
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
const nonExistentOrderHash = `0x${orderHash.substr(2).split('').reverse().join('')}`; const nonExistentOrderHash = `0x${orderHash.substr(2).split('').reverse().join('')}`;
zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); await zeroEx.orderStateWatcher.removeOrderAsync(nonExistentOrderHash);
}); });
}); });
describe('#subscribe', async () => { describe('#subscribe', async () => {
@ -103,7 +109,7 @@ describe('OrderStateWatcher', () => {
afterEach(async () => { afterEach(async () => {
zeroEx.orderStateWatcher.unsubscribe(); zeroEx.orderStateWatcher.unsubscribe();
const orderHash = ZeroEx.getOrderHashHex(signedOrder); 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) => { it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
(async () => { (async () => {
@ -111,7 +117,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false(); expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid; const invalidOrderState = orderState as OrderStateInvalid;
@ -129,7 +135,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
throw new Error('OrderState callback fired for irrelevant order'); throw new Error('OrderState callback fired for irrelevant order');
}); });
@ -150,7 +156,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false(); expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid; const invalidOrderState = orderState as OrderStateInvalid;
@ -170,7 +176,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0; let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
@ -202,7 +208,7 @@ describe('OrderStateWatcher', () => {
const fillAmountInBaseUnits = new BigNumber(2); const fillAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0; let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
@ -215,6 +221,8 @@ describe('OrderStateWatcher', () => {
const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
remainingFillable); remainingFillable);
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
remainingFillable);
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
if (eventCount === 2) { if (eventCount === 2) {
done(); done();
@ -227,18 +235,36 @@ describe('OrderStateWatcher', () => {
); );
})().catch(done); })().catch(done);
}); });
describe('remainingFillableMakerTokenAmount', () => { 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) => { it('should calculate correct remaining fillable', (done: DoneCallback) => {
(async () => { (async () => {
const takerFillableAmount = new BigNumber(10); const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), 18);
const makerFillableAmount = new BigNumber(20); const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), 18);
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerToken.address, takerToken.address, maker, taker, makerFillableAmount, takerFillableAmount); makerToken.address, takerToken.address, maker, taker, makerFillableAmount,
takerFillableAmount,
);
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker); const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
const fillAmountInBaseUnits = new BigNumber(2); const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0; let eventCount = 0;
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
eventCount++; eventCount++;
@ -247,7 +273,9 @@ describe('OrderStateWatcher', () => {
expect(validOrderState.orderHash).to.be.equal(orderHash); expect(validOrderState.orderHash).to.be.equal(orderHash);
const orderRelevantState = validOrderState.orderRelevantState; const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
new BigNumber(16)); ZeroEx.toBaseUnitAmount(new BigNumber(16), 18));
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
ZeroEx.toBaseUnitAmount(new BigNumber(8), 18));
if (eventCount === 2) { if (eventCount === 2) {
done(); done();
} }
@ -267,14 +295,16 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const changedMakerApprovalAmount = new BigNumber(3); const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), 18);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid; const validOrderState = orderState as OrderStateValid;
const orderRelevantState = validOrderState.orderRelevantState; const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
changedMakerApprovalAmount); changedMakerApprovalAmount);
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
changedMakerApprovalAmount);
done(); done();
}); });
zeroEx.orderStateWatcher.subscribe(callback); zeroEx.orderStateWatcher.subscribe(callback);
@ -289,15 +319,17 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const remainingAmount = new BigNumber(1); const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
const transferAmount = makerBalance.sub(remainingAmount); const transferAmount = makerBalance.sub(remainingAmount);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid; const validOrderState = orderState as OrderStateValid;
const orderRelevantState = validOrderState.orderRelevantState; const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
remainingAmount); remainingAmount);
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
remainingAmount);
done(); done();
}); });
zeroEx.orderStateWatcher.subscribe(callback); zeroEx.orderStateWatcher.subscribe(callback);
@ -312,7 +344,7 @@ describe('OrderStateWatcher', () => {
makerToken.address, takerToken.address, maker, taker, fillableAmount, makerToken.address, takerToken.address, maker, taker, fillableAmount,
); );
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false(); expect(orderState.isValid).to.be.false();
@ -327,6 +359,28 @@ describe('OrderStateWatcher', () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
})().catch(done); })().catch(done);
}); });
it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => {
(async () => {
const remainingFillableAmountInBaseUnits = new BigNumber(100);
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerToken.address, takerToken.address, maker, taker, fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
done();
});
zeroEx.orderStateWatcher.subscribe(callback);
await zeroEx.exchange.cancelOrderAsync(
signedOrder, fillableAmount.minus(remainingFillableAmountInBaseUnits),
);
})().catch(done);
});
it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => {
(async () => { (async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync( signedOrder = await fillScenarios.createFillableSignedOrderAsync(
@ -338,7 +392,7 @@ describe('OrderStateWatcher', () => {
const cancelAmountInBaseUnits = new BigNumber(2); const cancelAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder); const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => { const callback = reportCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true(); expect(orderState.isValid).to.be.true();

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import 'mocha'; import 'mocha';
import * as chai from 'chai'; import * as chai from 'chai';
import {SchemaValidator, schemas} from '0x-json-schemas'; import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {chaiSetup} from './utils/chai_setup'; import {chaiSetup} from './utils/chai_setup';
import {web3Factory} from './utils/web3_factory'; import {web3Factory} from './utils/web3_factory';
import {ZeroEx, Token} from '../src'; import {ZeroEx, Token} from '../src';

View File

@ -1,5 +1,5 @@
{ {
"extends": [ "extends": [
"tslint-config-0xproject" "@0xproject/tslint-config"
] ]
} }

View File

@ -0,0 +1,6 @@
# CHANGELOG
v0.0.4 - _Nov. 14, 2017_
------------------------
* Re-publish Assert previously published under NPM package @0xproject/0x-assert
* Added assertion isValidBaseUnitAmount which checks both that the value is a valid bigNumber and that it does not contain decimals.

View File

@ -1 +1,10 @@
assert
------
Standard type and schema assertions to be used across all 0x projects and packages Standard type and schema assertions to be used across all 0x projects and packages
## Install
```bash
npm install @0xproject/assert --save
```

View File

@ -1,7 +1,6 @@
{ {
"private": true,
"name": "@0xproject/assert", "name": "@0xproject/assert",
"version": "0.0.3", "version": "0.0.4",
"description": "Provides a standard way of performing type and schema validation across 0x projects", "description": "Provides a standard way of performing type and schema validation across 0x projects",
"main": "lib/src/index.js", "main": "lib/src/index.js",
"types": "lib/src/index.d.ts", "types": "lib/src/index.d.ts",
@ -24,6 +23,7 @@
}, },
"homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md", "homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md",
"devDependencies": { "devDependencies": {
"@0xproject/tslint-config": "^0.1.0",
"@types/lodash": "^4.14.78", "@types/lodash": "^4.14.78",
"@types/mocha": "^2.2.42", "@types/mocha": "^2.2.42",
"@types/valid-url": "^1.0.2", "@types/valid-url": "^1.0.2",
@ -33,12 +33,11 @@
"mocha": "^4.0.1", "mocha": "^4.0.1",
"npm-run-all": "^4.1.1", "npm-run-all": "^4.1.1",
"shx": "^0.2.2", "shx": "^0.2.2",
"tslint": "~5.5.0", "tslint": "5.8.0",
"tslint-config-0xproject": "^0.0.2",
"typescript": "^2.4.2" "typescript": "^2.4.2"
}, },
"dependencies": { "dependencies": {
"0x-json-schemas": "^0.6.5", "@0xproject/json-schemas": "^0.6.7",
"bignumber.js": "~4.1.0", "bignumber.js": "~4.1.0",
"ethereum-address": "^0.0.4", "ethereum-address": "^0.0.4",
"lodash": "^4.17.4", "lodash": "^4.17.4",

View File

@ -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;
});

View File

@ -2,7 +2,10 @@ import BigNumber from 'bignumber.js';
import * as ethereum_address from 'ethereum-address'; import * as ethereum_address from 'ethereum-address';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as validUrl from 'valid-url'; import * as validUrl from 'valid-url';
import {SchemaValidator, Schema} from '0x-json-schemas'; import {
SchemaValidator,
Schema,
} from '@0xproject/json-schemas';
const HEX_REGEX = /^0x[0-9A-F]*$/i; const HEX_REGEX = /^0x[0-9A-F]*$/i;

View File

@ -2,7 +2,7 @@ import 'mocha';
import * as dirtyChai from 'dirty-chai'; import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai'; import * as chai from 'chai';
import {BigNumber} from 'bignumber.js'; import {BigNumber} from 'bignumber.js';
import {schemas} from '0x-json-schemas'; import {schemas} from '@0xproject/json-schemas';
import {assert} from '../src/index'; import {assert} from '../src/index';
chai.config.includeStack = true; chai.config.includeStack = true;

View File

@ -1,5 +1,5 @@
{ {
"extends": [ "extends": [
"tslint-config-0xproject" "@0xproject/tslint-config"
] ]
} }

View File

@ -0,0 +1,4 @@
# CHANGELOG
v0.0.0 - _Nov. 15, 2017_
------------------------

View File

@ -0,0 +1 @@
This repository contains a Javascript library that makes it easy to interact with Relayers that conform to the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api)

View File

@ -0,0 +1,68 @@
{
"name": "@0xproject/connect",
"version": "0.0.0",
"description": "A javascript library for interacting with the standard relayer api",
"keywords": [
"0x-connect",
"0xproject",
"ethereum",
"tokens",
"exchange"
],
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "shx rm -rf _bundles lib test_temp",
"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"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js.git"
},
"author": "Brandon Millman",
"license": "Apache-2.0",
"engines": {
"node": ">=6.0.0"
},
"bugs": {
"url": "https://github.com/0xProject/0x.js/issues"
},
"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",
"bignumber.js": "~4.1.0",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
"query-string": "^5.0.1",
"websocket": "^1.0.25"
},
"devDependencies": {
"@0xproject/tslint-config": "0.1.0",
"@types/fetch-mock": "^5.12.1",
"@types/lodash": "^4.14.77",
"@types/mocha": "^2.2.42",
"@types/query-string": "^5.0.1",
"@types/websocket": "^0.0.34",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-as-promised-typescript-typings": "0.0.3",
"chai-typescript-typings": "^0.0.1",
"copyfiles": "^1.2.0",
"dirty-chai": "^2.0.1",
"fetch-mock": "^5.13.1",
"mocha": "^4.0.0",
"npm-run-all": "^4.0.2",
"shx": "^0.2.2",
"tslint": "5.8.0",
"typescript": "~2.6.1",
"web3-typescript-typings": "^0.7.1"
}
}

View File

@ -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;
});

6
packages/connect/src/globals.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module 'dirty-chai';
declare module '*.json' {
const value: any;
export default value;
}

View File

@ -0,0 +1,171 @@
import 'isomorphic-fetch';
import * as _ from 'lodash';
import {BigNumber} from 'bignumber.js';
import * as queryString from 'query-string';
import {assert} from '@0xproject/assert';
import {schemas} from '@0xproject/json-schemas';
import {SignedOrder} from '0x.js';
import {
Client,
FeesRequest,
FeesResponse,
OrderbookRequest,
OrderbookResponse,
OrdersRequest,
TokenPairsItem,
TokenPairsRequest,
} from './types';
import {schemas as clientSchemas} from './schemas/schemas';
import {typeConverters} from './utils/type_converters';
interface RequestOptions {
params?: object;
payload?: object;
}
enum RequestType {
Get = 'GET',
Post = 'POST',
}
/**
* This class includes all the functionality related to interacting with a set of HTTP endpoints
* that implement the standard relayer API v0
*/
export class HttpClient implements Client {
private apiEndpointUrl: string;
/**
* Instantiates a new HttpClient instance
* @param url The base url for making API calls
* @return An instance of HttpClient
*/
constructor(url: string) {
assert.isHttpUrl('url', url);
this.apiEndpointUrl = url;
}
/**
* Retrieve token pair info from the API
* @param request A TokenPairsRequest instance describing specific token information
* to retrieve
* @return The resulting TokenPairsItems that match the request
*/
public async getTokenPairsAsync(request?: TokenPairsRequest): Promise<TokenPairsItem[]> {
if (!_.isUndefined(request)) {
assert.doesConformToSchema('request', request, clientSchemas.relayerTokenPairsRequestSchema);
}
const requestOpts = {
params: request,
};
const tokenPairs = await this._requestAsync('/token_pairs', RequestType.Get, requestOpts);
assert.doesConformToSchema(
'tokenPairs', tokenPairs, schemas.relayerApiTokenPairsResponseSchema);
_.each(tokenPairs, (tokenPair: object) => {
typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [
'tokenA.minAmount',
'tokenA.maxAmount',
'tokenB.minAmount',
'tokenB.maxAmount',
]);
});
return tokenPairs;
}
/**
* Retrieve orders from the API
* @param request An OrdersRequest instance describing specific orders to retrieve
* @return The resulting SignedOrders that match the request
*/
public async getOrdersAsync(request?: OrdersRequest): Promise<SignedOrder[]> {
if (!_.isUndefined(request)) {
assert.doesConformToSchema('request', request, clientSchemas.relayerOrdersRequestSchema);
}
const requestOpts = {
params: request,
};
const orders = await this._requestAsync(`/orders`, RequestType.Get, requestOpts);
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
_.each(orders, (order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order));
return orders;
}
/**
* Retrieve a specific order from the API
* @param orderHash An orderHash generated from the desired order
* @return The SignedOrder that matches the supplied orderHash
*/
public async getOrderAsync(orderHash: string): Promise<SignedOrder> {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const order = await this._requestAsync(`/order/${orderHash}`, RequestType.Get);
assert.doesConformToSchema('order', order, schemas.signedOrderSchema);
typeConverters.convertOrderStringFieldsToBigNumber(order);
return order;
}
/**
* Retrieve an orderbook from the API
* @param request An OrderbookRequest instance describing the specific orderbook to retrieve
* @return The resulting OrderbookResponse that matches the request
*/
public async getOrderbookAsync(request: OrderbookRequest): Promise<OrderbookResponse> {
assert.doesConformToSchema('request', request, clientSchemas.relayerOrderBookRequestSchema);
const requestOpts = {
params: request,
};
const orderBook = await this._requestAsync('/orderbook', RequestType.Get, requestOpts);
assert.doesConformToSchema('orderBook', orderBook, schemas.relayerApiOrderBookResponseSchema);
typeConverters.convertOrderbookStringFieldsToBigNumber(orderBook);
return orderBook;
}
/**
* Retrieve fee information from the API
* @param request A FeesRequest instance describing the specific fees to retrieve
* @return The resulting FeesResponse that matches the request
*/
public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> {
assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema);
typeConverters.convertBigNumberFieldsToStrings(request, [
'makerTokenAmount',
'takerTokenAmount',
'expirationUnixTimestampSec',
'salt',
]);
const requestOpts = {
payload: request,
};
const fees = await this._requestAsync('/fees', RequestType.Post, requestOpts);
assert.doesConformToSchema('fees', fees, schemas.relayerApiFeesResponseSchema);
typeConverters.convertStringsFieldsToBigNumbers(fees, ['makerFee', 'takerFee']);
return fees;
}
/**
* Submit a signed order to the API
* @param signedOrder A SignedOrder instance to submit
*/
public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const requestOpts = {
payload: signedOrder,
};
await this._requestAsync('/order', RequestType.Post, requestOpts);
}
private async _requestAsync(path: string, requestType: RequestType, requestOptions?: RequestOptions): Promise<any> {
const params = _.get(requestOptions, 'params');
const payload = _.get(requestOptions, 'payload');
let query = '';
if (!_.isUndefined(params) && !_.isEmpty(params)) {
const stringifiedParams = queryString.stringify(params);
query = `?${stringifiedParams}`;
}
const url = `${this.apiEndpointUrl}/v0${path}${query}`;
const headers = new Headers({
'content-type': 'application/json',
});
const response = await fetch(url, {
method: requestType,
body: payload,
headers,
});
if (!response.ok) {
throw Error(response.statusText);
}
const json = await response.json();
return json;
}
}

View File

@ -0,0 +1,15 @@
export {HttpClient} from './http_client';
export {WebSocketOrderbookChannel} from './ws_orderbook_channel';
export {
Client,
FeesRequest,
FeesResponse,
OrderbookChannel,
OrderbookChannelHandler,
OrderbookChannelSubscriptionOpts,
OrderbookRequest,
OrderbookResponse,
OrdersRequest,
TokenPairsItem,
TokenPairsRequest,
} from './types';

View File

@ -0,0 +1,8 @@
export const relayerOrderBookRequestSchema = {
id: '/RelayerOrderBookRequest',
type: 'object',
properties: {
baseTokenAddress: {$ref: '/Address'},
quoteTokenAddress: {$ref: '/Address'},
},
};

View File

@ -0,0 +1,8 @@
export const relayerOrderBookRequestSchema = {
id: '/RelayerOrderBookRequest',
type: 'object',
properties: {
baseTokenAddress: {$ref: '/Address'},
quoteTokenAddress: {$ref: '/Address'},
},
};

View File

@ -0,0 +1,16 @@
export const relayerOrdersRequestSchema = {
id: '/RelayerOrdersRequest',
type: 'object',
properties: {
exchangeContractAddress: {$ref: '/Address'},
tokenAddress: {$ref: '/Address'},
makerTokenAddress: {$ref: '/Address'},
takerTokenAddress: {$ref: '/Address'},
tokenA: {$ref: '/Address'},
tokenB: {$ref: '/Address'},
maker: {$ref: '/Address'},
taker: {$ref: '/Address'},
trader: {$ref: '/Address'},
feeRecipient: {$ref: '/Address'},
},
};

View File

@ -0,0 +1,8 @@
export const relayerTokenPairsRequestSchema = {
id: '/RelayerTokenPairsRequest',
type: 'object',
properties: {
tokenA: {$ref: '/Address'},
tokenB: {$ref: '/Address'},
},
};

View File

@ -0,0 +1,15 @@
import {
relayerOrderBookRequestSchema,
} from './relayer_orderbook_request_schema';
import {
relayerOrdersRequestSchema,
} from './relayer_orders_request_schema';
import {
relayerTokenPairsRequestSchema,
} from './relayer_token_pairs_request_schema';
export const schemas = {
relayerOrderBookRequestSchema,
relayerOrdersRequestSchema,
relayerTokenPairsRequestSchema,
};

View File

@ -0,0 +1,120 @@
import {SignedOrder} from '0x.js';
import {BigNumber} from 'bignumber.js';
export interface Client {
getTokenPairsAsync: (request?: TokenPairsRequest) => Promise<TokenPairsItem[]>;
getOrdersAsync: (request?: OrdersRequest) => Promise<SignedOrder[]>;
getOrderAsync: (orderHash: string) => Promise<SignedOrder>;
getOrderbookAsync: (request: OrderbookRequest) => Promise<OrderbookResponse>;
getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>;
submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>;
}
export interface OrderbookChannel {
subscribe: (subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler) => void;
close: () => void;
}
export interface OrderbookChannelHandler {
onSnapshot: (channel: OrderbookChannel, snapshot: OrderbookResponse) => void;
onUpdate: (channel: OrderbookChannel, order: SignedOrder) => void;
onError: (channel: OrderbookChannel, err: Error) => void;
onClose: (channel: OrderbookChannel) => void;
}
export type OrderbookChannelMessage =
SnapshotOrderbookChannelMessage |
UpdateOrderbookChannelMessage |
UnknownOrderbookChannelMessage;
export enum OrderbookChannelMessageTypes {
Snapshot = 'snapshot',
Update = 'update',
Unknown = 'unknown',
}
export interface SnapshotOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Snapshot;
payload: OrderbookResponse;
}
export interface UpdateOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Update;
payload: SignedOrder;
}
export interface UnknownOrderbookChannelMessage {
type: OrderbookChannelMessageTypes.Unknown;
payload: undefined;
}
/*
* baseTokenAddress: The address of token designated as the baseToken in the currency pair calculation of price
* quoteTokenAddress: The address of token designated as the quoteToken in the currency pair calculation of price
* snapshot: If true, a snapshot of the orderbook will be sent before the updates to the orderbook
* limit: Maximum number of bids and asks in orderbook snapshot
*/
export interface OrderbookChannelSubscriptionOpts {
baseTokenAddress: string;
quoteTokenAddress: string;
snapshot: boolean;
limit: number;
}
export interface TokenPairsRequest {
tokenA?: string;
tokenB?: string;
}
export interface TokenPairsItem {
tokenA: TokenTradeInfo;
tokenB: TokenTradeInfo;
}
export interface TokenTradeInfo {
address: string;
minAmount: BigNumber;
maxAmount: BigNumber;
precision: number;
}
export interface OrdersRequest {
exchangeContractAddress?: string;
tokenAddress?: string;
makerTokenAddress?: string;
takerTokenAddress?: string;
tokenA?: string;
tokenB?: string;
maker?: string;
taker?: string;
trader?: string;
feeRecipient?: string;
}
export interface OrderbookRequest {
baseTokenAddress: string;
quoteTokenAddress: string;
}
export interface OrderbookResponse {
bids: SignedOrder[];
asks: SignedOrder[];
}
export interface FeesRequest {
exchangeContractAddress: string;
maker: string;
taker: string;
makerTokenAddress: string;
takerTokenAddress: string;
makerTokenAmount: BigNumber;
takerTokenAmount: BigNumber;
expirationUnixTimestampSec: BigNumber;
salt: BigNumber;
}
export interface FeesResponse {
feeRecipient: string;
makerFee: BigNumber;
takerFee: BigNumber;
}

View File

@ -0,0 +1,43 @@
import * as _ from 'lodash';
import {SignedOrder} from '0x.js';
import {assert} from '@0xproject/assert';
import {schemas} from '@0xproject/json-schemas';
import {
OrderbookChannelMessage,
OrderbookChannelMessageTypes,
} from '../types';
import {typeConverters} from './type_converters';
export const orderbookChannelMessageParsers = {
parser(utf8Data: string): OrderbookChannelMessage {
const messageObj = JSON.parse(utf8Data);
const type: string = _.get(messageObj, 'type');
assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`);
switch (type) {
case (OrderbookChannelMessageTypes.Snapshot): {
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema);
const orderbook = messageObj.payload;
typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook);
return {
type,
payload: orderbook,
};
}
case (OrderbookChannelMessageTypes.Update): {
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema);
const order = messageObj.payload;
typeConverters.convertOrderStringFieldsToBigNumber(order);
return {
type,
payload: order,
};
}
default: {
return {
type: OrderbookChannelMessageTypes.Unknown,
payload: undefined,
};
}
}
},
};

View File

@ -0,0 +1,31 @@
import * as _ from 'lodash';
import {BigNumber} from 'bignumber.js';
// TODO: convert all of these to non-mutating, pure functions
export const typeConverters = {
convertOrderbookStringFieldsToBigNumber(orderbook: object): void {
_.each(orderbook, (orders: object[]) => {
_.each(orders, (order: object) => this.convertOrderStringFieldsToBigNumber(order));
});
},
convertOrderStringFieldsToBigNumber(order: object): void {
this.convertStringsFieldsToBigNumbers(order, [
'makerTokenAmount',
'takerTokenAmount',
'makerFee',
'takerFee',
'expirationUnixTimestampSec',
'salt',
]);
},
convertBigNumberFieldsToStrings(obj: object, fields: string[]): void {
_.each(fields, field => {
_.update(obj, field, (value: BigNumber) => value.toString());
});
},
convertStringsFieldsToBigNumbers(obj: object, fields: string[]): void {
_.each(fields, field => {
_.update(obj, field, (value: string) => new BigNumber(value));
});
},
};

View File

@ -0,0 +1,127 @@
import * as _ from 'lodash';
import * as WebSocket from 'websocket';
import {assert} from '@0xproject/assert';
import {schemas} from '@0xproject/json-schemas';
import {SignedOrder} from '0x.js';
import {
OrderbookChannel,
OrderbookChannelHandler,
OrderbookChannelMessageTypes,
OrderbookChannelSubscriptionOpts,
} from './types';
import {orderbookChannelMessageParsers} from './utils/orderbook_channel_message_parsers';
enum ConnectionEventType {
Close = 'close',
Error = 'error',
Message = 'message',
}
enum ClientEventType {
Connect = 'connect',
ConnectFailed = 'connectFailed',
}
/**
* This class includes all the functionality related to interacting with a websocket endpoint
* that implements the standard relayer API v0
*/
export class WebSocketOrderbookChannel implements OrderbookChannel {
private apiEndpointUrl: string;
private client: WebSocket.client;
private connectionIfExists?: WebSocket.connection;
/**
* Instantiates a new WebSocketOrderbookChannel instance
* @param url The base url for making API calls
* @return An instance of WebSocketOrderbookChannel
*/
constructor(url: string) {
assert.isUri('url', url);
this.apiEndpointUrl = url;
this.client = new WebSocket.client();
}
/**
* Subscribe to orderbook snapshots and updates from the websocket
* @param subscriptionOpts An OrderbookChannelSubscriptionOpts instance describing which
* token pair to subscribe to
* @param handler An OrderbookChannelHandler instance that responds to various
* channel updates
*/
public subscribe(subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler): void {
assert.doesConformToSchema(
'subscriptionOpts', subscriptionOpts, schemas.relayerApiOrderbookChannelSubscribePayload);
assert.isFunction('handler.onSnapshot', _.get(handler, 'onSnapshot'));
assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate'));
assert.isFunction('handler.onError', _.get(handler, 'onError'));
assert.isFunction('handler.onClose', _.get(handler, 'onClose'));
const subscribeMessage = {
type: 'subscribe',
channel: 'orderbook',
payload: subscriptionOpts,
};
this._getConnection((error, connection) => {
if (!_.isUndefined(error)) {
handler.onError(this, error);
} else if (!_.isUndefined(connection) && connection.connected) {
connection.on(ConnectionEventType.Error, wsError => {
handler.onError(this, wsError);
});
connection.on(ConnectionEventType.Close, () => {
handler.onClose(this);
});
connection.on(ConnectionEventType.Message, message => {
this._handleWebSocketMessage(message, handler);
});
connection.sendUTF(JSON.stringify(subscribeMessage));
}
});
}
/**
* Close the websocket and stop receiving updates
*/
public close() {
if (!_.isUndefined(this.connectionIfExists)) {
this.connectionIfExists.close();
}
}
private _getConnection(callback: (error?: Error, connection?: WebSocket.connection) => void) {
if (!_.isUndefined(this.connectionIfExists) && this.connectionIfExists.connected) {
callback(undefined, this.connectionIfExists);
} else {
this.client.on(ClientEventType.Connect, connection => {
this.connectionIfExists = connection;
callback(undefined, this.connectionIfExists);
});
this.client.on(ClientEventType.ConnectFailed, error => {
callback(error, undefined);
});
this.client.connect(this.apiEndpointUrl);
}
}
private _handleWebSocketMessage(message: WebSocket.IMessage, handler: OrderbookChannelHandler): void {
if (!_.isUndefined(message.utf8Data)) {
try {
const utf8Data = message.utf8Data;
const parserResult = orderbookChannelMessageParsers.parser(utf8Data);
const type = parserResult.type;
switch (parserResult.type) {
case (OrderbookChannelMessageTypes.Snapshot): {
handler.onSnapshot(this, parserResult.payload);
break;
}
case (OrderbookChannelMessageTypes.Update): {
handler.onUpdate(this, parserResult.payload);
break;
}
default: {
handler.onError(this, new Error(`Message has missing a type parameter: ${utf8Data}`));
}
}
} catch (error) {
handler.onError(this, error);
}
} else {
handler.onError(this, new Error(`Message does not contain utf8Data`));
}
}
}

View File

@ -0,0 +1,5 @@
{
"feeRecipient": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"makerFee": "10000000000000000",
"takerFee": "30000000000000000"
}

View File

@ -0,0 +1,8 @@
import {BigNumber} from 'bignumber.js';
import {FeesResponse} from '../../../src/types';
export const feesResponse: FeesResponse = {
feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
makerFee: new BigNumber('10000000000000000'),
takerFee: new BigNumber('30000000000000000'),
};

View File

@ -0,0 +1,19 @@
{
"maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
"makerFee": "100000000000000",
"takerFee": "200000000000000",
"makerTokenAmount": "10000000000000000",
"takerTokenAmount": "20000000000000000",
"makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990",
"salt": "256",
"feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da",
"exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093",
"expirationUnixTimestampSec": "42",
"ecSignature": {
"v": 27,
"r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33",
"s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254"
}
}

View File

@ -0,0 +1,21 @@
import {BigNumber} from 'bignumber.js';
export const orderResponse = {
maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
makerFee: new BigNumber('100000000000000'),
takerFee: new BigNumber('200000000000000'),
makerTokenAmount: new BigNumber('10000000000000000'),
takerTokenAmount: new BigNumber('20000000000000000'),
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
salt: new BigNumber('256'),
feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da',
exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093',
expirationUnixTimestampSec: new BigNumber('42'),
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
};

View File

@ -0,0 +1,44 @@
{
"bids": [
{
"maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
"makerFee": "100000000000000",
"takerFee": "200000000000000",
"makerTokenAmount": "10000000000000000",
"takerTokenAmount": "20000000000000000",
"makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990",
"salt": "256",
"feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da",
"exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093",
"expirationUnixTimestampSec": "42",
"ecSignature": {
"v": 27,
"r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33",
"s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254"
}
}
],
"asks": [
{
"maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
"makerFee": "100000000000000",
"takerFee": "200000000000000",
"makerTokenAmount": "10000000000000000",
"takerTokenAmount": "20000000000000000",
"makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990",
"salt": "256",
"feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da",
"exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093",
"expirationUnixTimestampSec": "42",
"ecSignature": {
"v": 27,
"r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33",
"s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254"
}
}
]
}

View File

@ -0,0 +1,46 @@
import {BigNumber} from 'bignumber.js';
export const orderbookResponse = {
bids: [
{
maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
makerFee: new BigNumber('100000000000000'),
takerFee: new BigNumber('200000000000000'),
makerTokenAmount: new BigNumber('10000000000000000'),
takerTokenAmount: new BigNumber('20000000000000000'),
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
salt: new BigNumber('256'),
feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da',
exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093',
expirationUnixTimestampSec: new BigNumber('42'),
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
},
],
asks: [
{
maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
makerFee: new BigNumber('100000000000000'),
takerFee: new BigNumber('200000000000000'),
makerTokenAmount: new BigNumber('10000000000000000'),
takerTokenAmount: new BigNumber('20000000000000000'),
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
salt: new BigNumber('256'),
feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da',
exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093',
expirationUnixTimestampSec: new BigNumber('42'),
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
},
],
};

View File

@ -0,0 +1,21 @@
[
{
"maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
"makerFee": "100000000000000",
"takerFee": "200000000000000",
"makerTokenAmount": "10000000000000000",
"takerTokenAmount": "20000000000000000",
"makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990",
"salt": "256",
"feeRecipient": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"exchangeContractAddress": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
"expirationUnixTimestampSec": "42",
"ecSignature": {
"v": 27,
"r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33",
"s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254"
}
}
]

View File

@ -0,0 +1,23 @@
import {BigNumber} from 'bignumber.js';
export const ordersResponse = [
{
maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
makerFee: new BigNumber('100000000000000'),
takerFee: new BigNumber('200000000000000'),
makerTokenAmount: new BigNumber('10000000000000000'),
takerTokenAmount: new BigNumber('20000000000000000'),
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
salt: new BigNumber('256'),
feeRecipient: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
exchangeContractAddress: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
expirationUnixTimestampSec: new BigNumber('42'),
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
},
];

View File

@ -0,0 +1,17 @@
import * as orderbookJSON from './orderbook.json';
const orderbookJsonString = JSON.stringify(orderbookJSON);
export const snapshotOrderbookChannelMessage = `{
"type": "snapshot",
"channel": "orderbook",
"channelId": 1,
"payload": ${orderbookJsonString}
}`;
export const malformedSnapshotOrderbookChannelMessage = `{
"type": "snapshot",
"channel": "orderbook",
"channelId": 1,
"payload": {}
}`;

View File

@ -0,0 +1,16 @@
[
{
"tokenA": {
"address": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
"minAmount": "0",
"maxAmount": "10000000000000000000",
"precision": 5
},
"tokenB": {
"address": "0xef7fff64389b814a946f3e92105513705ca6b990",
"minAmount": "0",
"maxAmount": "50000000000000000000",
"precision": 5
}
}
]

View File

@ -0,0 +1,19 @@
import {BigNumber} from 'bignumber.js';
import {TokenPairsItem} from '../../../src/types';
export const tokenPairsResponse: TokenPairsItem[] = [
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
minAmount: new BigNumber(0),
maxAmount: new BigNumber('10000000000000000000'),
precision: 5,
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
minAmount: new BigNumber(0),
maxAmount: new BigNumber('50000000000000000000'),
precision: 5,
},
},
];

View File

@ -0,0 +1,10 @@
import * as orderResponseJSON from './order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json';
const orderJSONString = JSON.stringify(orderResponseJSON);
export const unknownOrderbookChannelMessage = `{
"type": "superGoodUpdate",
"channel": "orderbook",
"channelId": 1,
"payload": ${orderJSONString}
}`;

View File

@ -0,0 +1,17 @@
import * as orderResponseJSON from './order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json';
const orderJSONString = JSON.stringify(orderResponseJSON);
export const updateOrderbookChannelMessage = `{
"type": "update",
"channel": "orderbook",
"channelId": 1,
"payload": ${orderJSONString}
}`;
export const malformedUpdateOrderbookChannelMessage = `{
"type": "update",
"channel": "orderbook",
"channelId": 1,
"payload": {}
}`;

View File

@ -0,0 +1,130 @@
import 'mocha';
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as fetchMock from 'fetch-mock';
import {BigNumber} from 'bignumber.js';
import {HttpClient} from '../src/index';
import {feesResponse} from './fixtures/standard_relayer_api/fees';
import {
orderResponse,
} from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f';
import {ordersResponse} from './fixtures/standard_relayer_api/orders';
import {tokenPairsResponse} from './fixtures/standard_relayer_api/token_pairs';
import {orderbookResponse} from './fixtures/standard_relayer_api/orderbook';
import * as feesResponseJSON from './fixtures/standard_relayer_api/fees.json';
// tslint:disable-next-line:max-line-length
import * as orderResponseJSON from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json';
import * as ordersResponseJSON from './fixtures/standard_relayer_api/orders.json';
import * as tokenPairsResponseJSON from './fixtures/standard_relayer_api/token_pairs.json';
import * as orderbookJSON from './fixtures/standard_relayer_api/orderbook.json';
chai.config.includeStack = true;
chai.use(dirtyChai);
chai.use(chaiAsPromised);
const expect = chai.expect;
describe('HttpClient', () => {
const relayUrl = 'https://example.com';
const relayerClient = new HttpClient(relayUrl);
afterEach(() => {
fetchMock.restore();
});
describe('#getTokenPairsAsync', () => {
const url = `${relayUrl}/v0/token_pairs`;
it('gets token pairs', async () => {
fetchMock.get(url, tokenPairsResponseJSON);
const tokenPairs = await relayerClient.getTokenPairsAsync();
expect(tokenPairs).to.be.deep.equal(tokenPairsResponse);
});
it('gets specfic token pairs for request', async () => {
const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d';
const tokenPairsRequest = {
tokenA: tokenAddress,
};
const urlWithQuery = `${url}?tokenA=${tokenAddress}`;
fetchMock.get(urlWithQuery, tokenPairsResponseJSON);
const tokenPairs = await relayerClient.getTokenPairsAsync(tokenPairsRequest);
expect(tokenPairs).to.be.deep.equal(tokenPairsResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.get(url, {test: 'dummy'});
expect(relayerClient.getTokenPairsAsync()).to.be.rejected();
});
});
describe('#getOrdersAsync', () => {
const url = `${relayUrl}/v0/orders`;
it('gets orders', async () => {
fetchMock.get(url, ordersResponseJSON);
const orders = await relayerClient.getOrdersAsync();
expect(orders).to.be.deep.equal(ordersResponse);
});
it('gets specfic orders for request', async () => {
const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d';
const ordersRequest = {
tokenA: tokenAddress,
};
const urlWithQuery = `${url}?tokenA=${tokenAddress}`;
fetchMock.get(urlWithQuery, ordersResponseJSON);
const orders = await relayerClient.getOrdersAsync(ordersRequest);
expect(orders).to.be.deep.equal(ordersResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.get(url, {test: 'dummy'});
expect(relayerClient.getOrdersAsync()).to.be.rejected();
});
});
describe('#getOrderAsync', () => {
const orderHash = '0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f';
const url = `${relayUrl}/v0/order/${orderHash}`;
it('gets order', async () => {
fetchMock.get(url, orderResponseJSON);
const order = await relayerClient.getOrderAsync(orderHash);
expect(order).to.be.deep.equal(orderResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.get(url, {test: 'dummy'});
expect(relayerClient.getOrderAsync(orderHash)).to.be.rejected();
});
});
describe('#getOrderBookAsync', () => {
const request = {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
};
// tslint:disable-next-line:max-line-length
const url = `${relayUrl}/v0/orderbook?baseTokenAddress=${request.baseTokenAddress}&quoteTokenAddress=${request.quoteTokenAddress}`;
it('gets order book', async () => {
fetchMock.get(url, orderbookJSON);
const orderbook = await relayerClient.getOrderbookAsync(request);
expect(orderbook).to.be.deep.equal(orderbookResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.get(url, {test: 'dummy'});
expect(relayerClient.getOrderbookAsync(request)).to.be.rejected();
});
});
describe('#getFeesAsync', () => {
const request = {
exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093',
maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b',
taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: new BigNumber('10000000000000000000'),
takerTokenAmount: new BigNumber('30000000000000000000'),
salt: new BigNumber('256'),
expirationUnixTimestampSec: new BigNumber('42'),
};
const url = `${relayUrl}/v0/fees`;
it('gets fees', async () => {
fetchMock.post(url, feesResponseJSON);
const fees = await relayerClient.getFeesAsync(request);
expect(fees).to.be.deep.equal(feesResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.post(url, {test: 'dummy'});
expect(relayerClient.getFeesAsync(request)).to.be.rejected();
});
});
});

View File

@ -0,0 +1,66 @@
import 'mocha';
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import {orderbookChannelMessageParsers} from '../src/utils/orderbook_channel_message_parsers';
import {
snapshotOrderbookChannelMessage,
malformedSnapshotOrderbookChannelMessage,
} from './fixtures/standard_relayer_api/snapshot_orderbook_channel_message';
import {
updateOrderbookChannelMessage,
malformedUpdateOrderbookChannelMessage,
} from './fixtures/standard_relayer_api/update_orderbook_channel_message';
import {unknownOrderbookChannelMessage} from './fixtures/standard_relayer_api/unknown_orderbook_channel_message';
import {orderbookResponse} from './fixtures/standard_relayer_api/orderbook';
// tslint:disable-next-line:max-line-length
import {orderResponse} from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f';
chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
describe('orderbookChannelMessageParsers', () => {
describe('#parser', () => {
it('parses snapshot messages', () => {
const snapshotMessage = orderbookChannelMessageParsers.parser(snapshotOrderbookChannelMessage);
expect(snapshotMessage.type).to.be.equal('snapshot');
expect(snapshotMessage.payload).to.be.deep.equal(orderbookResponse);
});
it('parses update messages', () => {
const updateMessage = orderbookChannelMessageParsers.parser(updateOrderbookChannelMessage);
expect(updateMessage.type).to.be.equal('update');
expect(updateMessage.payload).to.be.deep.equal(orderResponse);
});
it('returns unknown message for messages with unsupported types', () => {
const unknownMessage = orderbookChannelMessageParsers.parser(unknownOrderbookChannelMessage);
expect(unknownMessage.type).to.be.equal('unknown');
expect(unknownMessage.payload).to.be.undefined();
});
it('throws when message does not include a type', () => {
const typelessMessage = `{
"channel": "orderbook",
"channelId": 1,
"payload": {}
}`;
const badCall = () => orderbookChannelMessageParsers.parser(typelessMessage);
expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`);
});
it('throws when snapshot message has malformed payload', () => {
const badCall = () =>
orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage);
// tslint:disable-next-line:max-line-length
const errMsg = 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"';
expect(badCall).throws(errMsg);
});
it('throws when update message has malformed payload', () => {
const badCall = () =>
orderbookChannelMessageParsers.parser(malformedUpdateOrderbookChannelMessage);
expect(badCall).throws(/^Expected message to conform to schema/);
});
it('throws when input message is not valid JSON', () => {
const nonJsonString = 'h93b{sdfs9fsd f';
const badCall = () => orderbookChannelMessageParsers.parser(nonJsonString);
expect(badCall).throws('Unexpected token h in JSON at position 0');
});
});
});

View File

@ -0,0 +1,46 @@
import 'mocha';
import * as _ from 'lodash';
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import {
WebSocketOrderbookChannel,
} from '../src/index';
chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
describe('WebSocketOrderbookChannel', () => {
const websocketUrl = 'ws://localhost:8080';
const orderbookChannel = new WebSocketOrderbookChannel(websocketUrl);
const subscriptionOpts = {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
snapshot: true,
limit: 100,
};
const emptyOrderbookChannelHandler = {
onSnapshot: () => { _.noop(); },
onUpdate: () => { _.noop(); },
onError: () => { _.noop(); },
onClose: () => { _.noop(); },
};
describe('#subscribe', () => {
it('throws when subscriptionOpts does not conform to schema', () => {
const badSubscribeCall = orderbookChannel.subscribe.bind(
orderbookChannel, {}, emptyOrderbookChannelHandler);
// tslint:disable-next-line:max-line-length
expect(badSubscribeCall).throws('Expected subscriptionOpts to conform to schema /RelayerApiOrderbookChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseTokenAddress", instance requires property "quoteTokenAddress"');
});
it('throws when handler has the incorrect members', () => {
const badSubscribeCall = orderbookChannel.subscribe.bind(orderbookChannel, subscriptionOpts, {});
expect(badSubscribeCall)
.throws('Expected handler.onSnapshot to be of type function, encountered: undefined');
});
it('does not throw when inputs are of correct types', () => {
const goodSubscribeCall = orderbookChannel.subscribe.bind(
orderbookChannel, subscriptionOpts, emptyOrderbookChannelHandler);
expect(goodSubscribeCall).to.not.throw();
});
});
});

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [ "es2015", "dom" ],
"outDir": "lib",
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"strictNullChecks": true
},
"include": [
"./src/**/*",
"./test/**/*",
"../../node_modules/chai-as-promised-typescript-typings/index.d.ts",
"../../node_modules/chai-typescript-typings/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts"
]
}

View File

@ -0,0 +1,5 @@
{
"extends": [
"@0xproject/tslint-config"
]
}

View File

@ -0,0 +1,5 @@
# CHANGELOG
v0.6.7 - _Nov. 14, 2017_
------------------------
* Re-publish JSON-schema previously published under NPM package 0x-json-schemas

View File

@ -0,0 +1,24 @@
json-schemas
------------
Contains 0x-related json schemas
## Install:
```bash
npm install @0xproject/json-schemas --save
```
## Usage:
```
import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas';
const {orderSchema} = schemas;
const validator = new SchemaValidator();
const order = {
...
};
const validatorResult: ValidatorResult = validator.validate(order, orderSchema); // Contains all errors
const isValid: boolean = validator.isValid(order, orderSchema); // Only returns boolean
```

View File

@ -0,0 +1,46 @@
{
"name": "@0xproject/json-schemas",
"version": "0.6.7",
"description": "0x-related json schemas",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"lint": "tslint src/*.ts test/*.ts",
"test": "run-s clean build run_mocha",
"test:circleci": "yarn test",
"run_mocha": "mocha lib/test/**/*_test.js",
"clean": "shx rm -rf _bundles lib test_temp",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js.git"
},
"author": "",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x.js/issues"
},
"homepage": "https://github.com/0xProject/0x.js/packages/json-schemas/README.md",
"dependencies": {
"es6-promisify": "^5.0.0",
"jsonschema": "^1.2.0",
"lodash.values": "^4.3.0"
},
"devDependencies": {
"@0xproject/tslint-config": "^0.1.0",
"@types/lodash.foreach": "^4.5.3",
"@types/lodash.values": "^4.3.3",
"@types/mocha": "^2.2.42",
"bignumber.js": "^4.0.2",
"chai": "^4.1.1",
"chai-typescript-typings": "^0.0.1",
"dirty-chai": "^2.0.1",
"lodash.foreach": "^4.5.0",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.1",
"shx": "^0.2.2",
"tslint": "5.8.0",
"typescript": "~2.6.1"
}
}

View File

@ -0,0 +1,11 @@
export const addressSchema = {
id: '/Address',
type: 'string',
pattern: '^0x[0-9a-f]{40}$',
};
export const numberSchema = {
id: '/Number',
type: 'string',
pattern: '^\\d+(\\.\\d+)?$',
};

View File

@ -0,0 +1,20 @@
export const ecSignatureParameterSchema = {
id: '/ECSignatureParameter',
type: 'string',
pattern: '^0[xX][0-9A-Fa-f]{64}$',
};
export const ecSignatureSchema = {
id: '/ECSignature',
properties: {
v: {
type: 'number',
minimum: 27,
maximum: 28,
},
r: {$ref: '/ECSignatureParameter'},
s: {$ref: '/ECSignatureParameter'},
},
required: ['v', 'r', 's'],
type: 'object',
};

View File

@ -0,0 +1,11 @@
export const indexFilterValuesSchema = {
id: '/IndexFilterValues',
additionalProperties: {
oneOf: [
{$ref: '/Number'},
{$ref: '/Address'},
{$ref: '/OrderHashSchema'},
],
},
type: 'object',
};

View File

@ -0,0 +1,12 @@
export const orderCancellationRequestsSchema = {
id: '/OrderCancellationRequests',
type: 'array',
items: {
properties: {
order: {$ref: '/Order'},
takerTokenCancelAmount: {$ref: '/Number'},
},
required: ['order', 'takerTokenCancelAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,12 @@
export const orderFillOrKillRequestsSchema = {
id: '/OrderFillOrKillRequests',
type: 'array',
items: {
properties: {
signedOrder: {$ref: '/SignedOrder'},
fillTakerAmount: {$ref: '/Number'},
},
required: ['signedOrder', 'fillTakerAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,12 @@
export const orderFillRequestsSchema = {
id: '/OrderFillRequests',
type: 'array',
items: {
properties: {
signedOrder: {$ref: '/SignedOrder'},
takerTokenFillAmount: {$ref: '/Number'},
},
required: ['signedOrder', 'takerTokenFillAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,5 @@
export const orderHashSchema = {
id: '/OrderHashSchema',
type: 'string',
pattern: '^0x[0-9a-fA-F]{64}$',
};

View File

@ -0,0 +1,35 @@
export const orderSchema = {
id: '/Order',
properties: {
maker: {$ref: '/Address'},
taker: {$ref: '/Address'},
makerFee: {$ref: '/Number'},
takerFee: {$ref: '/Number'},
makerTokenAmount: {$ref: '/Number'},
takerTokenAmount: {$ref: '/Number'},
makerTokenAddress: {$ref: '/Address'},
takerTokenAddress: {$ref: '/Address'},
salt: {$ref: '/Number'},
feeRecipient: {$ref: '/Address'},
expirationUnixTimestampSec: {$ref: '/Number'},
exchangeContractAddress: {$ref: '/Address'},
},
required: [
'maker', 'taker', 'makerFee', 'takerFee', 'makerTokenAmount', 'takerTokenAmount',
'salt', 'feeRecipient', 'expirationUnixTimestampSec', 'exchangeContractAddress',
],
type: 'object',
};
export const signedOrderSchema = {
id: '/SignedOrder',
allOf: [
{ $ref: '/Order' },
{
properties: {
ecSignature: {$ref: '/ECSignature'},
},
required: ['ecSignature'],
},
],
};

View File

@ -0,0 +1,21 @@
export const relayerApiErrorResponseSchema = {
id: '/RelayerApiErrorResponse',
type: 'object',
properties: {
code: {type: 'number'},
reason: {type: 'string'},
validationErrors: {
type: 'array',
items: {
type: 'object',
properties: {
field: {type: 'string'},
code: {type: 'number'},
reason: {type: 'string'},
},
required: ['field', 'code', 'reason'],
},
},
},
required: ['code', 'reason'],
};

View File

@ -0,0 +1,19 @@
export const relayerApiFeesPayloadSchema = {
id: '/RelayerApiFeesPayload',
type: 'object',
properties: {
exchangeContractAddress: {$ref: '/Address'},
maker: {$ref: '/Address'},
taker: {$ref: '/Address'},
makerTokenAddress: {$ref: '/Address'},
takerTokenAddress: {$ref: '/Address'},
makerTokenAmount: {$ref: '/Number'},
takerTokenAmount: {$ref: '/Number'},
expirationUnixTimestampSec: {$ref: '/Number'},
salt: {$ref: '/Number'},
},
required: [
'exchangeContractAddress', 'maker', 'taker', 'makerTokenAddress', 'takerTokenAddress',
'expirationUnixTimestampSec', 'salt',
],
};

View File

@ -0,0 +1,10 @@
export const relayerApiFeesResponseSchema = {
id: '/RelayerApiFeesResponse',
type: 'object',
properties: {
makerFee: {$ref: '/Number'},
takerFee: {$ref: '/Number'},
feeRecipient: {$ref: '/Address'},
},
required: ['makerFee', 'takerFee', 'feeRecipient'],
};

View File

@ -0,0 +1,22 @@
export const relayerApiOrderbookChannelSubscribeSchema = {
id: '/RelayerApiOrderbookChannelSubscribe',
type: 'object',
properties: {
type: {enum: ['subscribe']},
channel: {enum: ['orderbook']},
payload: {$ref: '/RelayerApiOrderbookChannelSubscribePayload'},
},
required: ['type', 'channel', 'payload'],
};
export const relayerApiOrderbookChannelSubscribePayload = {
id: '/RelayerApiOrderbookChannelSubscribePayload',
type: 'object',
properties: {
baseTokenAddress: {$ref: '/Address'},
quoteTokenAddress: {$ref: '/Address'},
snapshot: {type: 'boolean'},
limit: {type: 'number'},
},
required: ['baseTokenAddress', 'quoteTokenAddress'],
};

View File

@ -0,0 +1,21 @@
export const relayerApiOrderbookChannelSnapshotSchema = {
id: '/RelayerApiOrderbookChannelSnapshot',
type: 'object',
properties: {
type: {enum: ['snapshot']},
channel: {enum: ['orderbook']},
channelId: {type: 'number'},
payload: {$ref: '/RelayerApiOrderbookChannelSnapshotPayload'},
},
required: ['type', 'channel', 'channelId', 'payload'],
};
export const relayerApiOrderbookChannelSnapshotPayload = {
id: '/RelayerApiOrderbookChannelSnapshotPayload',
type: 'object',
properties: {
bids: {$ref: '/signedOrdersSchema'},
asks: {$ref: '/signedOrdersSchema'},
},
required: ['bids', 'asks'],
};

View File

@ -0,0 +1,11 @@
export const relayerApiOrderbookChannelUpdateSchema = {
id: '/RelayerApiOrderbookChannelUpdate',
type: 'object',
properties: {
type: {enum: ['update']},
channel: {enum: ['orderbook']},
channelId: {type: 'number'},
payload: {$ref: '/SignedOrder'},
},
required: ['type', 'channel', 'channelId', 'payload'],
};

View File

@ -0,0 +1,9 @@
export const relayerApiOrderBookResponseSchema = {
id: '/RelayerApiOrderBookResponse',
type: 'object',
properties: {
bids: {$ref: '/signedOrdersSchema'},
asks: {$ref: '/signedOrdersSchema'},
},
required: ['bids', 'asks'],
};

View File

@ -0,0 +1,24 @@
export const relayerApiTokenPairsResponseSchema = {
id: '/RelayerApiTokenPairsResponse',
type: 'array',
items: {
properties: {
tokenA: {$ref: '/RelayerApiTokenTradeInfo'},
tokenB: {$ref: '/RelayerApiTokenTradeInfo'},
},
required: ['tokenA', 'tokenB'],
type: 'object',
},
};
export const relayerApiTokenTradeInfoSchema = {
id: '/RelayerApiTokenTradeInfo',
type: 'object',
properties: {
address: {$ref: '/Address'},
minAmount: {$ref: '/Number'},
maxAmount: {$ref: '/Number'},
precision: {type: 'number'},
},
required: ['address'],
};

View File

@ -0,0 +1,5 @@
export const signedOrdersSchema = {
id: '/signedOrdersSchema',
type: 'array',
items: {$ref: '/SignedOrder'},
};

View File

@ -0,0 +1,20 @@
export const blockParamSchema = {
id: '/BlockParam',
oneOf: [
{
type: 'number',
},
{
enum: ['latest', 'earliest', 'pending'],
},
],
};
export const subscriptionOptsSchema = {
id: '/SubscriptionOpts',
properties: {
fromBlock: {$ref: '/BlockParam'},
toBlock: {$ref: '/BlockParam'},
},
type: 'object',
};

View File

@ -0,0 +1,11 @@
export const tokenSchema = {
id: '/Token',
properties: {
name: {type: 'string'},
symbol: {type: 'string'},
decimals: {type: 'number'},
address: {$ref: '/Address'},
},
required: ['name', 'symbol', 'decimals', 'address'],
type: 'object',
};

View File

@ -0,0 +1,42 @@
export const jsNumber = {
id: '/JsNumber',
type: 'number',
minimum: 0,
};
export const txDataSchema = {
id: '/TxData',
properties: {
from: {$ref: '/Address'},
to: {$ref: '/Address'},
value: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
gas: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
gasPrice: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
data: {
type: 'string',
pattern: '^0x[0-9a-f]*$',
},
nonce: {
type: 'number',
minimum: 0,
},
},
required: ['from'],
type: 'object',
additionalProperties: false,
};

View File

@ -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;
});

View File

@ -0,0 +1,7 @@
declare module 'dirty-chai';
// es6-promisify declarations
declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
declare module 'es6-promisify' {
export = promisify;
}

View File

@ -0,0 +1,4 @@
export {ValidatorResult, Schema} from 'jsonschema';
export {SchemaValidator} from './schema_validator';
export {schemas} from './schemas';

View File

@ -0,0 +1,28 @@
import values = require('lodash.values');
import {Validator, ValidatorResult, Schema} from 'jsonschema';
import {schemas} from './schemas';
export class SchemaValidator {
private validator: Validator;
constructor() {
this.validator = new Validator();
for (const schema of values(schemas)) {
this.validator.addSchema(schema, schema.id);
}
}
public addSchema(schema: Schema) {
this.validator.addSchema(schema, schema.id);
}
// In order to validate a complex JS object using jsonschema, we must replace any complex
// sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other
// complex types implement the `toString` method, we can stringify the object and
// then parse it. The resultant object can then be checked using jsonschema.
public validate(instance: any, schema: Schema): ValidatorResult {
const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance));
return this.validator.validate(jsonSchemaCompatibleObject, schema);
}
public isValid(instance: any, schema: Schema): boolean {
const isValid = this.validate(instance, schema).errors.length === 0;
return isValid;
}
}

View File

@ -0,0 +1,99 @@
import {
numberSchema,
addressSchema,
} from '../schemas/basic_type_schemas';
import {
ecSignatureSchema,
ecSignatureParameterSchema,
} from '../schemas/ec_signature_schema';
import {
indexFilterValuesSchema,
} from '../schemas/index_filter_values_schema';
import {
orderCancellationRequestsSchema,
} from '../schemas/order_cancel_schema';
import {
orderFillOrKillRequestsSchema,
} from '../schemas/order_fill_or_kill_requests_schema';
import {
orderFillRequestsSchema,
} from '../schemas/order_fill_requests_schema';
import {
orderHashSchema,
} from '../schemas/order_hash_schema';
import {
orderSchema,
signedOrderSchema,
} from '../schemas/order_schemas';
import {
blockParamSchema,
subscriptionOptsSchema,
} from '../schemas/subscription_opts_schema';
import {
tokenSchema,
} from '../schemas/token_schema';
import {
signedOrdersSchema,
} from '../schemas/signed_orders_schema';
import {
relayerApiErrorResponseSchema,
} from '../schemas/relayer_api_error_response_schema';
import {
relayerApiFeesResponseSchema,
} from '../schemas/relayer_api_fees_response_schema';
import {
relayerApiFeesPayloadSchema,
} from '../schemas/relayer_api_fees_payload_schema';
import {
relayerApiOrderBookResponseSchema,
} from '../schemas/relayer_api_orderbook_response_schema';
import {
relayerApiTokenPairsResponseSchema,
relayerApiTokenTradeInfoSchema,
} from '../schemas/relayer_api_token_pairs_response_schema';
import {
jsNumber,
txDataSchema,
} from '../schemas/tx_data_schema';
import {
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelSubscribePayload,
} from '../schemas/relayer_api_orberbook_channel_subscribe_schema';
import {
relayerApiOrderbookChannelUpdateSchema,
} from '../schemas/relayer_api_orderbook_channel_update_response_schema';
import {
relayerApiOrderbookChannelSnapshotSchema,
relayerApiOrderbookChannelSnapshotPayload,
} from '../schemas/relayer_api_orderbook_channel_snapshot_schema';
export const schemas = {
numberSchema,
addressSchema,
ecSignatureSchema,
ecSignatureParameterSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
orderFillRequestsSchema,
orderHashSchema,
orderSchema,
signedOrderSchema,
signedOrdersSchema,
blockParamSchema,
subscriptionOptsSchema,
tokenSchema,
jsNumber,
txDataSchema,
relayerApiErrorResponseSchema,
relayerApiFeesPayloadSchema,
relayerApiFeesResponseSchema,
relayerApiOrderBookResponseSchema,
relayerApiTokenPairsResponseSchema,
relayerApiTokenTradeInfoSchema,
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelSubscribePayload,
relayerApiOrderbookChannelUpdateSchema,
relayerApiOrderbookChannelSnapshotSchema,
relayerApiOrderbookChannelSnapshotPayload,
};

View File

@ -0,0 +1,972 @@
import 'mocha';
import forEach = require('lodash.foreach');
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '../src/index';
chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const {
numberSchema,
addressSchema,
ecSignatureSchema,
ecSignatureParameterSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
orderFillRequestsSchema,
orderHashSchema,
orderSchema,
signedOrderSchema,
signedOrdersSchema,
blockParamSchema,
subscriptionOptsSchema,
tokenSchema,
jsNumber,
txDataSchema,
relayerApiErrorResponseSchema,
relayerApiOrderBookResponseSchema,
relayerApiTokenPairsResponseSchema,
relayerApiFeesPayloadSchema,
relayerApiFeesResponseSchema,
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelUpdateSchema,
relayerApiOrderbookChannelSnapshotSchema,
} = schemas;
describe('Schema', () => {
const validator = new SchemaValidator();
const validateAgainstSchema = (testCases: any[], schema: any, shouldFail = false) => {
forEach(testCases, (testCase: any) => {
const validationResult = validator.validate(testCase, schema);
const hasErrors = validationResult.errors.length !== 0;
if (shouldFail) {
if (!hasErrors) {
throw new Error(
`Expected testCase: ${JSON.stringify(testCase, null, '\t')} to fail and it didn't.`,
);
}
} else {
if (hasErrors) {
throw new Error(JSON.stringify(validationResult.errors, null, '\t'));
}
}
});
};
describe('#numberSchema', () => {
it('should validate valid numbers', () => {
const testCases = ['42', '0', '1.3', '0.2', '00.00'];
validateAgainstSchema(testCases, numberSchema);
});
it('should fail for invalid numbers', () => {
const testCases = ['.3', '1.', 'abacaba', 'и', '1..0'];
const shouldFail = true;
validateAgainstSchema(testCases, numberSchema, shouldFail);
});
});
describe('#addressSchema', () => {
it('should validate valid addresses', () => {
const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS];
validateAgainstSchema(testCases, addressSchema);
});
it('should fail for invalid addresses', () => {
const testCases = [
'0x',
'0',
'0x00',
'0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42',
'0x8b0292B11a196601eD2ce54B665CaFEca0347D42',
];
const shouldFail = true;
validateAgainstSchema(testCases, addressSchema, shouldFail);
});
});
describe('#ecSignatureParameterSchema', () => {
it('should validate valid parameters', () => {
const testCases = [
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
'0X40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
];
validateAgainstSchema(testCases, ecSignatureParameterSchema);
});
it('should fail for invalid parameters', () => {
const testCases = [
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3', // shorter
'0xzzzz9190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // invalid characters
'40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // no 0x
];
const shouldFail = true;
validateAgainstSchema(testCases, ecSignatureParameterSchema, shouldFail);
});
});
describe('#ecSignatureSchema', () => {
it('should validate valid signature', () => {
const signature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const testCases = [
signature,
{
...signature,
v: 28,
},
];
validateAgainstSchema(testCases, ecSignatureSchema);
});
it('should fail for invalid signature', () => {
const v = 27;
const r = '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33';
const s = '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254';
const testCases = [
{},
{v},
{r, s, v: 31},
];
const shouldFail = true;
validateAgainstSchema(testCases, ecSignatureSchema, shouldFail);
});
});
describe('#orderHashSchema', () => {
it('should validate valid order hash', () => {
const testCases = [
'0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
'0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
];
validateAgainstSchema(testCases, orderHashSchema);
});
it('should fail for invalid order hash', () => {
const testCases = [
{},
'0x',
'0x8b0292B11a196601eD2ce54B665CaFEca0347D42',
'61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
];
const shouldFail = true;
validateAgainstSchema(testCases, orderHashSchema, shouldFail);
});
});
describe('#blockParamSchema', () => {
it('should validate valid block param', () => {
const testCases = [
42,
'latest',
'pending',
'earliest',
];
validateAgainstSchema(testCases, blockParamSchema);
});
it('should fail for invalid block param', () => {
const testCases = [
{},
'42',
'pemding',
];
const shouldFail = true;
validateAgainstSchema(testCases, blockParamSchema, shouldFail);
});
});
describe('#subscriptionOptsSchema', () => {
it('should validate valid subscription opts', () => {
const testCases = [
{fromBlock: 42, toBlock: 'latest'},
{fromBlock: 42},
{},
];
validateAgainstSchema(testCases, subscriptionOptsSchema);
});
it('should fail for invalid subscription opts', () => {
const testCases = [
{fromBlock: '42'},
];
const shouldFail = true;
validateAgainstSchema(testCases, subscriptionOptsSchema, shouldFail);
});
});
describe('#tokenSchema', () => {
const token = {
name: 'Zero Ex',
symbol: 'ZRX',
decimals: 100500,
address: '0x8b0292b11a196601ed2ce54b665cafeca0347d42',
url: 'https://0xproject.com',
};
it('should validate valid token', () => {
const testCases = [
token,
];
validateAgainstSchema(testCases, tokenSchema);
});
it('should fail for invalid token', () => {
const testCases = [
{
...token,
address: null,
},
{
...token,
decimals: undefined,
},
[],
4,
];
const shouldFail = true;
validateAgainstSchema(testCases, tokenSchema, shouldFail);
});
});
describe('order including schemas', () => {
const order = {
maker: NULL_ADDRESS,
taker: NULL_ADDRESS,
makerFee: '1',
takerFee: '2',
makerTokenAmount: '1',
takerTokenAmount: '2',
makerTokenAddress: NULL_ADDRESS,
takerTokenAddress: NULL_ADDRESS,
salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500',
feeRecipient: NULL_ADDRESS,
exchangeContractAddress: NULL_ADDRESS,
expirationUnixTimestampSec: '42',
};
describe('#orderSchema', () => {
it('should validate valid order', () => {
const testCases = [
order,
];
validateAgainstSchema(testCases, orderSchema);
});
it('should fail for invalid order', () => {
const testCases = [
{
...order,
salt: undefined,
},
{
...order,
salt: 'salt',
},
'order',
];
const shouldFail = true;
validateAgainstSchema(testCases, orderSchema, shouldFail);
});
});
describe('signed order including schemas', () => {
const signedOrder = {
...order,
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
};
describe('#signedOrdersSchema', () => {
it('should validate valid signed orders', () => {
const testCases = [
[signedOrder],
[],
];
validateAgainstSchema(testCases, signedOrdersSchema);
});
it('should fail for invalid signed orders', () => {
const testCases = [
[
signedOrder,
1,
],
];
const shouldFail = true;
validateAgainstSchema(testCases, signedOrdersSchema, shouldFail);
});
});
describe('#signedOrderSchema', () => {
it('should validate valid signed order', () => {
const testCases = [
signedOrder,
];
validateAgainstSchema(testCases, signedOrderSchema);
});
it('should fail for invalid signed order', () => {
const testCases = [
{
...signedOrder,
ecSignature: undefined,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, signedOrderSchema, shouldFail);
});
});
describe('#orderFillOrKillRequestsSchema', () => {
const orderFillOrKillRequests = [
{
signedOrder,
fillTakerAmount: '5',
},
];
it('should validate valid order fill or kill requests', () => {
const testCases = [
orderFillOrKillRequests,
];
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema);
});
it('should fail for invalid order fill or kill requests', () => {
const testCases = [
[
{
...orderFillOrKillRequests[0],
fillTakerAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema, shouldFail);
});
});
describe('#orderCancellationRequestsSchema', () => {
const orderCancellationRequests = [
{
order,
takerTokenCancelAmount: '5',
},
];
it('should validate valid order cancellation requests', () => {
const testCases = [
orderCancellationRequests,
];
validateAgainstSchema(testCases, orderCancellationRequestsSchema);
});
it('should fail for invalid order cancellation requests', () => {
const testCases = [
[
{
...orderCancellationRequests[0],
takerTokenCancelAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderCancellationRequestsSchema, shouldFail);
});
});
describe('#orderFillRequestsSchema', () => {
const orderFillRequests = [
{
signedOrder,
takerTokenFillAmount: '5',
},
];
it('should validate valid order fill requests', () => {
const testCases = [
orderFillRequests,
];
validateAgainstSchema(testCases, orderFillRequestsSchema);
});
it('should fail for invalid order fill requests', () => {
const testCases = [
[
{
...orderFillRequests[0],
takerTokenFillAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderFillRequestsSchema, shouldFail);
});
});
describe('#relayerApiOrderBookResponseSchema', () => {
it('should validate valid order book responses', () => {
const testCases = [
{
bids: [],
asks: [],
},
{
bids: [signedOrder, signedOrder],
asks: [],
},
{
bids: [],
asks: [signedOrder, signedOrder],
},
{
bids: [signedOrder],
asks: [signedOrder, signedOrder],
},
];
validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema);
});
it('should fail for invalid order fill requests', () => {
const testCases = [
{},
{
bids: [signedOrder, signedOrder],
},
{
asks: [signedOrder, signedOrder],
},
{
bids: signedOrder,
asks: [signedOrder, signedOrder],
},
{
bids: [signedOrder],
asks: signedOrder,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelSubscribeSchema', () => {
it('should validate valid orderbook channel websocket subscribe message', () => {
const testCases = [
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true,
limit: 100,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema);
});
it('should fail for invalid orderbook channel websocket subscribe message', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{
type: 'foo',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'bar',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: checksummedAddress,
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: checksummedAddress,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: 'true',
limit: 100,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true,
limit: '100',
},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelSnapshotSchema', () => {
it('should validate valid orderbook channel websocket snapshot message', () => {
const testCases = [
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [],
asks: [],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema);
});
it('should fail for invalid orderbook channel websocket snapshot message', () => {
const testCases = [
{
type: 'foo',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'bar',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: '2',
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
{},
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
{},
],
asks: [
signedOrder,
],
},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelUpdateSchema', () => {
it('should validate valid orderbook channel websocket update message', () => {
const testCases = [
{
type: 'update',
channel: 'orderbook',
channelId: 2,
payload: signedOrder,
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema);
});
it('should fail for invalid orderbook channel websocket update message', () => {
const testCases = [
{
type: 'foo',
channel: 'orderbook',
payload: signedOrder,
},
{
type: 'update',
channel: 'bar',
payload: signedOrder,
},
{
type: 'update',
channel: 'orderbook',
payload: {},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema, shouldFail);
});
});
});
});
describe('BigNumber serialization', () => {
it('should correctly serialize BigNumbers', () => {
const testCases = {
'42': '42',
'0': '0',
'1.3': '1.3',
'0.2': '0.2',
'00.00': '0',
'.3': '0.3',
};
forEach(testCases, (serialized: string, input: string) => {
expect(JSON.parse(JSON.stringify(new BigNumber(input)))).to.be.equal(serialized);
});
});
});
describe('#relayerApiErrorResponseSchema', () => {
it('should validate valid errorResponse', () => {
const testCases = [
{
code: 102,
reason: 'Order submission disabled',
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
code: 1002,
reason: 'Invalid address',
},
],
},
];
validateAgainstSchema(testCases, relayerApiErrorResponseSchema);
});
it('should fail for invalid error responses', () => {
const testCases = [
{},
{
code: 102,
},
{
code: '102',
reason: 'Order submission disabled',
},
{
reason: 'Order submission disabled',
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
reason: 'Invalid address',
},
],
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
code: '1002',
reason: 'Invalid address',
},
],
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiErrorResponseSchema, shouldFail);
});
});
describe('#relayerApiFeesPayloadSchema', () => {
it('should validate valid fees payloads', () => {
const testCases = [
{
exchangeContractAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
maker: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
taker: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
expirationUnixTimestampSec: '42',
salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500',
},
];
validateAgainstSchema(testCases, relayerApiFeesPayloadSchema);
});
it('should fail for invalid fees payloads', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{},
{
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
},
{
taker: checksummedAddress,
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
},
{
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: 10000000000000000000,
takerTokenAmount: 30000000000000000000,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiFeesPayloadSchema, shouldFail);
});
});
describe('#relayerApiFeesResponseSchema', () => {
it('should validate valid fees responses', () => {
const testCases = [
{
makerFee: '10000000000000000',
takerFee: '30000000000000000',
feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
];
validateAgainstSchema(testCases, relayerApiFeesResponseSchema);
});
it('should fail for invalid fees responses', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{},
{
makerFee: 10000000000000000,
takerFee: 30000000000000000,
},
{
feeRecipient: checksummedAddress,
takerToSpecify: checksummedAddress,
makerFee: '10000000000000000',
takerFee: '30000000000000000',
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiFeesResponseSchema, shouldFail);
});
});
describe('#relayerApiTokenPairsResponseSchema', () => {
it('should validate valid tokenPairs response', () => {
const testCases = [
[],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
minAmount: '0',
maxAmount: '10000000000000000000',
precision: 5,
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
minAmount: '0',
maxAmount: '50000000000000000000',
precision: 5,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
},
},
],
];
validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema);
});
it('should fail for invalid tokenPairs responses', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
[
{
tokenA: {
address: checksummedAddress,
},
tokenB: {
address: checksummedAddress,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
minAmount: 0,
maxAmount: 10000000000000000000,
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
minAmount: 0,
maxAmount: 50000000000000000000,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
precision: '5',
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
precision: '5',
},
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema, shouldFail);
});
});
describe('#jsNumberSchema', () => {
it('should validate valid js number', () => {
const testCases = [
1,
42,
];
validateAgainstSchema(testCases, jsNumber);
});
it('should fail for invalid js number', () => {
const testCases = [
NaN,
-1,
new BigNumber(1),
];
const shouldFail = true;
validateAgainstSchema(testCases, jsNumber, shouldFail);
});
});
describe('#txDataSchema', () => {
it('should validate valid txData', () => {
const testCases = [
{
from: NULL_ADDRESS,
},
{
from: NULL_ADDRESS,
gas: new BigNumber(42),
},
{
from: NULL_ADDRESS,
gas: 42,
},
];
validateAgainstSchema(testCases, txDataSchema);
});
it('should fail for invalid txData', () => {
const testCases = [
{
gas: new BigNumber(42),
},
{
from: NULL_ADDRESS,
unknownProp: 'here',
},
{},
[],
new BigNumber(1),
];
const shouldFail = true;
validateAgainstSchema(testCases, txDataSchema, shouldFail);
});
});
});

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [ "es2017", "dom"],
"outDir": "lib",
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"strictNullChecks": true
},
"include": [
"./src/**/*",
"./test/**/*",
"../../node_modules/chai-typescript-typings/index.d.ts"
]
}

View File

@ -0,0 +1,5 @@
{
"extends": [
"@0xproject/tslint-config"
]
}

View File

@ -0,0 +1,6 @@
# CHANGELOG
v0.1.0 - _Nov. 14, 2017_
------------------------
* Re-published TsLintConfig previously published under NPM package `tslint-config-0xproject`
* Updated to TSLint v5.8.0, requiring several rule additions to keep our conventions aligned.

View File

@ -0,0 +1,10 @@
tslint-config
-------------
Lint rules related to 0xProject for TSLint.
## Install:
```bash
npm install @0xproject/tslint-config --save-dev
```

View File

@ -0,0 +1,38 @@
{
"name": "@0xproject/tslint-config",
"version": "0.1.0",
"description": "Lint rules related to 0xProject for TSLint",
"main": "tslint.json",
"files": [
"tslint.js",
"README.md",
"LICENSE"
],
"repository": {
"type": "git",
"url": "git://github.com/0xProject/0x.js.git"
},
"keywords": [
"tslint",
"config",
"0xProject",
"typescript",
"ts"
],
"author": {
"name": "Fabio Berger",
"email": "fabio@0xproject.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x.js/issues"
},
"homepage": "https://github.com/0xProject/0x.js/packages/tslint-config/README.md",
"devDependencies": {
"tslint": "5.8.0",
"typescript": "2.6.1"
},
"dependencies": {
"tslint-react": "^3.2.0"
}
}

View File

@ -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;
});

View File

@ -0,0 +1,53 @@
{
"extends": [
"tslint:latest",
"tslint-react"
],
"rules": {
"arrow-parens": [true, "ban-single-arg-parens"],
"ordered-imports": false,
"quotemark": [true, "single", "avoid-escape", "jsx-double"],
"callable-types": true,
"interface-name": false,
"interface-over-type-literal": true,
"object-literal-sort-keys": false,
"max-classes-per-file": false,
"max-line-length": [true, 120],
"member-ordering": [true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-angle-bracket-type-assertion": true,
"no-default-export": true,
"no-empty-interface": false,
"no-string-throw": true,
"no-submodule-imports": false,
"no-implicit-dependencies": [true, "dev"],
"prefer-const": true,
"variable-name": [true,
"ban-keywords",
"allow-pascal-case"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-rest-spread",
"check-type",
"check-typecast",
"check-preblock"
],
"jsx-alignment": true,
"jsx-boolean-value": true,
"jsx-curly-spacing": [true, "never"],
"jsx-no-lambda": true,
"jsx-no-multiline-js": false,
"jsx-no-string-ref": true,
"jsx-self-close": true,
"jsx-wrap-multiline": false,
"jsx-no-bind": false
}
}

View File

@ -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 tags -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;
},
};

152
yarn.lock
View File

@ -2,20 +2,17 @@
# yarn lockfile v1 # yarn lockfile v1
"0x-json-schemas@^0.6.1", "0x-json-schemas@^0.6.5": "@types/fetch-mock@^5.12.1":
version "0.6.6" version "5.12.2"
resolved "https://registry.yarnpkg.com/0x-json-schemas/-/0x-json-schemas-0.6.6.tgz#3852e639245474a14daa2f8c454ba83ca5df8a9c" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2"
dependencies:
jsonschema "^1.2.0"
lodash.values "^4.3.0"
"@types/bintrees@^1.0.2": "@types/bintrees@^1.0.2":
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6" resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6"
"@types/fs-extra@^4.0.0": "@types/fs-extra@^4.0.0":
version "4.0.4" version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.4.tgz#72947e108f2cbeda5ab288a927399fdf6d02bd42" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.5.tgz#8aa6033c0e87c653b09a6711686916864b48ec9e"
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@ -40,7 +37,19 @@
dependencies: dependencies:
jsonschema "*" jsonschema "*"
"@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.78": "@types/lodash.foreach@^4.5.3":
version "4.5.3"
resolved "https://registry.yarnpkg.com/@types/lodash.foreach/-/lodash.foreach-4.5.3.tgz#87c01a0c5d9d17eec936ca3c28897af79440cdfc"
dependencies:
"@types/lodash" "*"
"@types/lodash.values@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@types/lodash.values/-/lodash.values-4.3.3.tgz#910edc65b391782d65dc4b4d8804a0dabc5370e6"
dependencies:
"@types/lodash" "*"
"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.77", "@types/lodash@^4.14.78":
version "4.14.85" version "4.14.85"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b"
@ -48,7 +57,11 @@
version "0.0.28" version "0.0.28"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" 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" version "2.0.29"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a"
@ -60,9 +73,13 @@
version "8.0.51" version "8.0.51"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb"
"@types/query-string@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-5.0.1.tgz#6cb41c724cb1644d56c2d1dae7c7b204e706b39e"
"@types/shelljs@^0.7.0": "@types/shelljs@^0.7.0":
version "0.7.5" version "0.7.6"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.5.tgz#5834fb7385d1137bd2be5842f2c278ac36a117f4" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.6.tgz#4ac7ca01c191ba65b8e2bf50543c5560084d8d27"
dependencies: dependencies:
"@types/glob" "*" "@types/glob" "*"
"@types/node" "*" "@types/node" "*"
@ -81,6 +98,12 @@
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/valid-url/-/valid-url-1.0.2.tgz#60fa435ce24bfd5ba107b8d2a80796aeaf3a8f45" resolved "https://registry.yarnpkg.com/@types/valid-url/-/valid-url-1.0.2.tgz#60fa435ce24bfd5ba107b8d2a80796aeaf3a8f45"
"@types/websocket@^0.0.34":
version "0.0.34"
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-0.0.34.tgz#25596764cec885eda070fdb6d19cd76fe582747c"
dependencies:
"@types/node" "*"
JSONStream@^1.0.4: JSONStream@^1.0.4:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
@ -308,6 +331,12 @@ assertion-error@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 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: async-each@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@ -725,7 +754,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1" mkdirp "^0.5.1"
source-map-support "^0.4.15" 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" version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies: dependencies:
@ -876,7 +905,7 @@ bn.js@4.11.7:
version "4.11.7" version "4.11.7"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.3, bn.js@^4.11.7, bn.js@^4.4.0, bn.js@^4.8.0: bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.3, bn.js@^4.11.7, bn.js@^4.4.0, bn.js@^4.8.0:
version "4.11.8" version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@ -1031,7 +1060,7 @@ buffer@^5.0.6:
base64-js "^1.0.2" base64-js "^1.0.2"
ieee754 "^1.1.4" ieee754 "^1.1.4"
builtin-modules@^1.0.0: builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -1127,7 +1156,7 @@ chai-typescript-typings@^0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/chai-typescript-typings/-/chai-typescript-typings-0.0.1.tgz#433dee303b0b2978ad0dd03129df0a5afb791274" resolved "https://registry.yarnpkg.com/chai-typescript-typings/-/chai-typescript-typings-0.0.1.tgz#433dee303b0b2978ad0dd03129df0a5afb791274"
chai@^4.0.1: chai@^4.0.1, chai@^4.1.1:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"
dependencies: dependencies:
@ -2284,6 +2313,14 @@ fast-json-stable-stringify@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
fetch-mock@^5.13.1:
version "5.13.1"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7"
dependencies:
glob-to-regexp "^0.3.0"
node-fetch "^1.3.3"
path-to-regexp "^1.7.0"
fetch-ponyfill@^4.0.0: fetch-ponyfill@^4.0.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz#ae3ce5f732c645eab87e4ae8793414709b239893" resolved "https://registry.yarnpkg.com/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz#ae3ce5f732c645eab87e4ae8793414709b239893"
@ -2602,6 +2639,10 @@ glob-parent@^3.1.0:
is-glob "^3.1.0" is-glob "^3.1.0"
path-dirname "^1.0.0" path-dirname "^1.0.0"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.2: glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.2:
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@ -3143,7 +3184,7 @@ is-text-path@^1.0.0:
dependencies: dependencies:
text-extensions "^1.0.0" text-extensions "^1.0.0"
is-typedarray@~1.0.0: is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@ -3177,7 +3218,7 @@ isobject@^3.0.0, isobject@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
isomorphic-fetch@^2.2.0: isomorphic-fetch@^2.2.0, isomorphic-fetch@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
dependencies: dependencies:
@ -3555,6 +3596,10 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
lodash.foreach@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
lodash.get@^4.4.2: lodash.get@^4.4.2:
version "4.4.2" version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@ -3880,7 +3925,7 @@ mute-stream@0.0.7, mute-stream@~0.0.4:
version "0.0.7" version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
nan@^2.0.5, nan@^2.0.8, nan@^2.2.1, nan@^2.3.0: nan@^2.0.5, nan@^2.0.8, nan@^2.2.1, nan@^2.3.0, nan@^2.3.3:
version "2.7.0" version "2.7.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
@ -3916,7 +3961,7 @@ node-abi@^2.1.1:
dependencies: dependencies:
semver "^5.4.1" semver "^5.4.1"
node-fetch@^1.0.1, node-fetch@~1.7.1: node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@~1.7.1:
version "1.7.3" version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
dependencies: dependencies:
@ -4473,9 +4518,9 @@ public-encrypt@^4.0.0:
parse-asn1 "^5.0.0" parse-asn1 "^5.0.0"
randombytes "^2.0.1" randombytes "^2.0.1"
publish-release@^1.3.3: publish-release@0xproject/publish-release:
version "1.3.3" 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: dependencies:
async "^0.9.0" async "^0.9.0"
ghauth "^2.0.0" ghauth "^2.0.0"
@ -4518,6 +4563,14 @@ qs@~6.5.1:
version "6.5.1" version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
query-string@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88"
dependencies:
decode-uri-component "^0.2.0"
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
querystring-es3@^0.2.0: querystring-es3@^0.2.0:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@ -4944,7 +4997,14 @@ semver-regex@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" 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" version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
@ -5278,6 +5338,10 @@ stream-http@^2.3.1:
to-arraybuffer "^1.0.0" to-arraybuffer "^1.0.0"
xtend "^4.0.0" xtend "^4.0.0"
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
string-editor@^0.1.0: string-editor@^0.1.0:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/string-editor/-/string-editor-0.1.2.tgz#f5ff1b5ac4aed7ac6c2fb8de236d1551b20f61d0" resolved "https://registry.yarnpkg.com/string-editor/-/string-editor-0.1.2.tgz#f5ff1b5ac4aed7ac6c2fb8de236d1551b20f61d0"
@ -5620,24 +5684,19 @@ tslib@^1.7.1:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6"
tslint-config-0xproject@^0.0.2: tslint-react@^3.2.0:
version "0.0.2"
resolved "https://registry.yarnpkg.com/tslint-config-0xproject/-/tslint-config-0xproject-0.0.2.tgz#39901e0c0b3e9388f00092a28b90c015395d5bba"
dependencies:
tslint-react "^3.0.0"
tslint-react@^3.0.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.2.0.tgz#851fb505201c63d0343c51726e6364f7e9ad2e99" resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.2.0.tgz#851fb505201c63d0343c51726e6364f7e9ad2e99"
dependencies: dependencies:
tsutils "^2.8.0" tsutils "^2.8.0"
tslint@~5.5.0: tslint@5.8.0:
version "5.5.0" version "5.8.0"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.5.0.tgz#10e8dab3e3061fa61e9442e8cee3982acf20a6aa" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.8.0.tgz#1f49ad5b2e77c76c3af4ddcae552ae4e3612eb13"
dependencies: dependencies:
babel-code-frame "^6.22.0" babel-code-frame "^6.22.0"
colors "^1.1.2" builtin-modules "^1.1.1"
chalk "^2.1.0"
commander "^2.9.0" commander "^2.9.0"
diff "^3.2.0" diff "^3.2.0"
glob "^7.1.1" glob "^7.1.1"
@ -5645,9 +5704,9 @@ tslint@~5.5.0:
resolve "^1.3.2" resolve "^1.3.2"
semver "^5.3.0" semver "^5.3.0"
tslib "^1.7.1" tslib "^1.7.1"
tsutils "^2.5.1" tsutils "^2.12.1"
tsutils@^2.5.1, tsutils@^2.8.0: tsutils@^2.12.1, tsutils@^2.8.0:
version "2.12.2" version "2.12.2"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.12.2.tgz#ad58a4865d17ec3ddb6631b6ca53be14a5656ff3" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.12.2.tgz#ad58a4865d17ec3ddb6631b6ca53be14a5656ff3"
dependencies: dependencies:
@ -5671,6 +5730,12 @@ type-detect@^4.0.0:
version "4.0.5" version "4.0.5"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2"
typedarray-to-buffer@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz#1017b32d984ff556eba100f501589aba1ace2e04"
dependencies:
is-typedarray "^1.0.0"
typedarray@^0.0.6: typedarray@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@ -5719,7 +5784,7 @@ typescript@2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
typescript@^2.4.2, typescript@~2.6.1: typescript@2.6.1, typescript@^2.4.2, typescript@~2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631"
@ -5982,6 +6047,15 @@ webpack@^3.0.0, webpack@^3.1.0:
webpack-sources "^1.0.1" webpack-sources "^1.0.1"
yargs "^8.0.2" yargs "^8.0.2"
websocket@^1.0.25:
version "1.0.25"
resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.25.tgz#998ec790f0a3eacb8b08b50a4350026692a11958"
dependencies:
debug "^2.2.0"
nan "^2.3.3"
typedarray-to-buffer "^3.1.2"
yaeti "^0.0.6"
whatwg-fetch@>=0.10.0: 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"
@ -6098,6 +6172,10 @@ y18n@^3.2.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
yallist@^2.1.2: yallist@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"