From 42ecc087cbd3558258107fbce645441955416497 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 22 Feb 2018 00:30:35 -0800 Subject: [PATCH 01/94] Implement initial sra-report command line tool --- packages/sra-report/.npmignore | 5 + packages/sra-report/CHANGELOG.md | 1 + packages/sra-report/README.md | 53 ++ packages/sra-report/package.json | 42 ++ .../sra_report.postman_collection.json | 384 ++++++++++++++ .../kovan_tokens.postman_environment.json | 34 ++ .../mainnet_tokens.postman_environment.json | 28 ++ packages/sra-report/scripts/postpublish.js | 5 + packages/sra-report/src/globals.d.ts | 6 + packages/sra-report/src/index.ts | 108 ++++ packages/sra-report/src/types.ts | 3 + packages/sra-report/src/utils.ts | 5 + packages/sra-report/tsconfig.json | 7 + packages/sra-report/tslint.json | 3 + yarn.lock | 472 ++++++++++++++++-- 15 files changed, 1121 insertions(+), 35 deletions(-) create mode 100644 packages/sra-report/.npmignore create mode 100644 packages/sra-report/CHANGELOG.md create mode 100644 packages/sra-report/README.md create mode 100644 packages/sra-report/package.json create mode 100644 packages/sra-report/postman_configs/collections/sra_report.postman_collection.json create mode 100644 packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json create mode 100644 packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json create mode 100644 packages/sra-report/scripts/postpublish.js create mode 100644 packages/sra-report/src/globals.d.ts create mode 100644 packages/sra-report/src/index.ts create mode 100644 packages/sra-report/src/types.ts create mode 100644 packages/sra-report/src/utils.ts create mode 100644 packages/sra-report/tsconfig.json create mode 100644 packages/sra-report/tslint.json diff --git a/packages/sra-report/.npmignore b/packages/sra-report/.npmignore new file mode 100644 index 0000000000..87bc304368 --- /dev/null +++ b/packages/sra-report/.npmignore @@ -0,0 +1,5 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json diff --git a/packages/sra-report/CHANGELOG.md b/packages/sra-report/CHANGELOG.md new file mode 100644 index 0000000000..a0cf709bc0 --- /dev/null +++ b/packages/sra-report/CHANGELOG.md @@ -0,0 +1 @@ +# CHANGELOG diff --git a/packages/sra-report/README.md b/packages/sra-report/README.md new file mode 100644 index 0000000000..18802f06af --- /dev/null +++ b/packages/sra-report/README.md @@ -0,0 +1,53 @@ +# SRA Report + +This package allows you to generate reports detailing an endpoint's [standard relayer API HTTP specification](https://github.com/0xProject/standard-relayer-api/blob/master/http/v0.md) compliance. + +## Installation + +`yarn add -g @0xproject/sra-report` + +## Usage + +``` +abi-gen +Options: + --help Show help [boolean] + --version Show version number [boolean] + --url, -u API endpoint to test for standard relayer API compliance + [string] [required] + --output, -o, --out Folder where to write the reports [string] + --network-id, -n ID of the network that the API is serving orders from + [number] [default: 1] +``` + +## Contributing + +We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +```bash +yarn build +``` + +### Lint + +```bash +yarn lint +``` diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json new file mode 100644 index 0000000000..0eaa5277ec --- /dev/null +++ b/packages/sra-report/package.json @@ -0,0 +1,42 @@ +{ + "name": "@0xproject/sra-report", + "version": "0.0.1", + "description": "Generate reports for standard relayer API compliance", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build:watch": "tsc -w", + "lint": "tslint --project . 'src/**/*.ts'", + "clean": "shx rm -rf lib", + "build": "tsc" + }, + "bin": { + "sra-report": "lib/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", + "dependencies": { + "@0xproject/assert": "^0.0.20", + "@0xproject/json-schemas": "^0.7.12", + "chalk": "^2.3.0", + "lodash": "^4.17.4", + "newman": "^3.9.3", + "yargs": "^10.0.3" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.9", + "@types/lodash": "^4.14.104", + "@types/node": "^8.0.53", + "@types/yargs": "^10.0.0", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "2.7.1" + } +} diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json new file mode 100644 index 0000000000..861f040f7b --- /dev/null +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -0,0 +1,384 @@ +{ + "info": { + "name": "0x Standard Relayer API", + "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", + "description": + "[Standard Relayer Api](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /token_pairs", + "description": "", + "item": [ + { + "name": "Default", + "event": [ + { + "listen": "test", + "script": { + "id": "e25cd907-44f8-4d1e-823a-429d6244e6b4", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/token_pairs", + "host": ["{{url}}"], + "path": ["v0", "token_pairs"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "DAI pairs", + "event": [ + { + "listen": "test", + "script": { + "id": "cb6e73f9-f5a7-4d75-807a-c9db94dda4f3", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/token_pairs?tokenA={{DAI_address}}", + "host": ["{{url}}"], + "path": ["v0", "token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{DAI_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "WETH pairs", + "event": [ + { + "listen": "test", + "script": { + "id": "cb6e73f9-f5a7-4d75-807a-c9db94dda4f3", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/token_pairs?tokenA={{WETH_address}}", + "host": ["{{url}}"], + "path": ["v0", "token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ] + }, + { + "name": "GET /orders", + "description": "", + "item": [ + { + "name": "Default", + "event": [ + { + "listen": "test", + "script": { + "id": "742679e1-5c16-4fe3-8056-dc7d34257564", + "type": "text/javascript", + "exec": [ + "const orders = JSON.parse(responseBody);", + "const firstOrder = orders[0];", + "const firstOrderHash = _.get(firstOrder, \"orderHash\");", + "pm.globals.set(\"_orderHash\", firstOrderHash);" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "DAI orders", + "event": [ + { + "listen": "test", + "script": { + "id": "a1eabd96-fee3-4328-ae50-4a992f0401ba", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders?tokenAddress={{DAI_address}}", + "host": ["{{url}}"], + "path": ["v0", "orders"], + "query": [ + { + "key": "tokenAddress", + "value": "{{DAI_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "WETH orders", + "event": [ + { + "listen": "test", + "script": { + "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders?tokenAddress={{WETH_address}}", + "host": ["{{url}}"], + "path": ["v0", "orders"], + "query": [ + { + "key": "tokenAddress", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", + "type": "text/javascript", + "exec": [""] + } + } + ] + }, + { + "name": "GET /orderbook", + "description": "", + "item": [ + { + "name": "ZRX/WETH orderbook", + "event": [ + { + "listen": "test", + "script": { + "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": + "{{url}}/v0/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", + "host": ["{{url}}"], + "path": ["v0", "orderbook"], + "query": [ + { + "key": "baseTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + }, + { + "key": "quoteTokenAddress", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "ZRX/DAI orderbook", + "event": [ + { + "listen": "test", + "script": { + "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": + "{{url}}/v0/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{DAI_address}}", + "host": ["{{url}}"], + "path": ["v0", "orderbook"], + "query": [ + { + "key": "baseTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + }, + { + "key": "quoteTokenAddress", + "value": "{{DAI_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ] + }, + { + "name": "GET /order", + "description": "", + "item": [ + { + "name": "order by order hash", + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/order/{{_orderHash}}", + "host": ["{{url}}"], + "path": ["v0", "order", "{{_orderHash}}"] + }, + "description": "Retrieves a specific order by orderHash." + }, + "response": [] + } + ] + }, + { + "name": "POST /fees", + "description": "", + "item": [ + { + "name": "Calculate Fees", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": + "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" + }, + "url": { + "raw": "{{url}}/v0/fees", + "host": ["{{url}}"], + "path": ["v0", "fees"] + }, + "description": + "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "da60f639-df79-4f4d-9861-79219f5fc341", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", + "type": "text/javascript", + "exec": [""] + } + } + ] +} diff --git a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json new file mode 100644 index 0000000000..fc2b1a9ff2 --- /dev/null +++ b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json @@ -0,0 +1,34 @@ +{ + "id": "8dab7221-c8b8-2931-92c8-605f6f4072e1", + "name": "Kovan Tokens", + "values": [ + { + "enabled": true, + "key": "url", + "value": "https://api.radarrelay.com/0x", + "type": "text" + }, + { + "enabled": true, + "key": "DAI_address", + "value": "0xb18845c260f680d5b9d84649638813e342e4f8c9", + "type": "text" + }, + { + "enabled": true, + "key": "WETH_address", + "value": "0xd0a1e359811322d97991e03f863a0c30c2cf029c", + "type": "text" + }, + { + "enabled": true, + "key": "ZRX_address", + "value": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", + "type": "text" + } + ], + "timestamp": 1518639440766, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2018-02-14T22:49:13.162Z", + "_postman_exported_using": "Postman/5.5.2" +} diff --git a/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json new file mode 100644 index 0000000000..0040dda954 --- /dev/null +++ b/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json @@ -0,0 +1,28 @@ +{ + "id": "e1087711-9cff-fd54-ba20-990b17a15f80", + "name": "Mainnet tokens", + "values": [ + { + "enabled": true, + "key": "DAI_address", + "value": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", + "type": "text" + }, + { + "enabled": true, + "key": "WETH_address", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "type": "text" + }, + { + "enabled": true, + "key": "ZRX_address", + "value": "0xe41d2489571d322189246dafa5ebde1f4699f498", + "type": "text" + } + ], + "timestamp": 1518561760642, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2018-02-14T22:48:11.259Z", + "_postman_exported_using": "Postman/5.5.2" +} diff --git a/packages/sra-report/scripts/postpublish.js b/packages/sra-report/scripts/postpublish.js new file mode 100644 index 0000000000..b3e5407c81 --- /dev/null +++ b/packages/sra-report/scripts/postpublish.js @@ -0,0 +1,5 @@ +const postpublish_utils = require('../../../scripts/postpublish_utils'); +const packageJSON = require('../package.json'); + +const subPackageName = packageJSON.name; +postpublish_utils.standardPostPublishAsync(subPackageName); \ No newline at end of file diff --git a/packages/sra-report/src/globals.d.ts b/packages/sra-report/src/globals.d.ts new file mode 100644 index 0000000000..eb09d3fcfc --- /dev/null +++ b/packages/sra-report/src/globals.d.ts @@ -0,0 +1,6 @@ +declare module 'newman'; + +declare module '*.json' { + const value: any; + export default value; +} diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts new file mode 100644 index 0000000000..fe100bd42c --- /dev/null +++ b/packages/sra-report/src/index.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +import { assert } from '@0xproject/assert'; +import { schemas } from '@0xproject/json-schemas'; +import chalk from 'chalk'; +import * as _ from 'lodash'; +import * as newman from 'newman'; +import * as yargs from 'yargs'; + +import * as sraReportCollectionJSON from '../postman_configs/collections/sra_report.postman_collection.json'; +import * as kovanTokensEnvironmentJSON from '../postman_configs/environments/kovan_tokens.postman_environment.json'; +import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/mainnet_tokens.postman_environment.json'; + +import { Schema } from './types'; +import { utils } from './utils'; + +const DEFAULT_NETWORK_ID = 1; +const SUPPORTED_NETWORK_IDS = [1, 42]; + +// extract command line arguments +const args = yargs + .option('url', { + alias: ['u'], + describe: 'API endpoint to test for standard relayer API compliance', + type: 'string', + demandOption: true, + }) + .option('output', { + alias: ['o', 'out'], + describe: 'Folder where to write the reports', + type: 'string', + normalize: true, + demandOption: false, + }) + .option('network-id', { + alias: ['n'], + describe: 'ID of the network that the API is serving orders from', + type: 'number', + default: DEFAULT_NETWORK_ID, + }) + .example("$0 --url 'http://api.example.com' --out 'src/contracts/generated/' --network-id 42", 'Full usage example') + .argv; +// perform extra validation on command line arguments +try { + assert.isHttpUrl('args', args.url); +} catch (err) { + utils.log(`${chalk.red(`Invalid url format:`)} ${args.url}`); + process.exit(1); +} +if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { + utils.log(`${chalk.red(`Unsupported network id:`)} ${args.networkId}`); + utils.log(`${chalk.bold(`Supported network ids:`)} ${SUPPORTED_NETWORK_IDS}`); + process.exit(1); +} +// run newman +newman.run( + { + collection: sraReportCollectionJSON, + reporters: 'cli', + globals: createGlobals(args.url, _.values(schemas)), + environment: getEnvironment(args.networkId), + }, + (err: Error) => { + if (err) { + throw err; + } + utils.log('collection run complete!'); + }, +); +function createGlobals(url: string, schemaList: Schema[]) { + const urlGlobalsValue = { + key: 'url', + value: args.url, + enabled: true, + type: 'text', + }; + const schemaGlobalsValues = _.map(schemaList, (schema: Schema) => { + return { + key: convertSchemaIdToKey(schema.id), + value: JSON.stringify(schema), + enabled: true, + type: 'text', + }; + }); + const globalsValues = _.concat(schemaGlobalsValues, urlGlobalsValue); + const globals = { + values: globalsValues, + }; + return globals; +} +function convertSchemaIdToKey(schemaId: string) { + let result = schemaId; + if (_.startsWith(result, '/')) { + result = result.substr(1); + } + result = `${result}Schema`; + return result; +} +function getEnvironment(networkId: number) { + switch (networkId) { + case 1: + return mainnetTokensEnvironmentJSON; + case 42: + return kovanTokensEnvironmentJSON; + default: + return {}; + } +} diff --git a/packages/sra-report/src/types.ts b/packages/sra-report/src/types.ts new file mode 100644 index 0000000000..f08ef40424 --- /dev/null +++ b/packages/sra-report/src/types.ts @@ -0,0 +1,3 @@ +export interface Schema { + id: string; +} diff --git a/packages/sra-report/src/utils.ts b/packages/sra-report/src/utils.ts new file mode 100644 index 0000000000..5423cabd98 --- /dev/null +++ b/packages/sra-report/src/utils.ts @@ -0,0 +1,5 @@ +export const utils = { + log(...args: any[]): void { + console.log(...args); // tslint:disable-line:no-console + }, +}; diff --git a/packages/sra-report/tsconfig.json b/packages/sra-report/tsconfig.json new file mode 100644 index 0000000000..3d967d05f0 --- /dev/null +++ b/packages/sra-report/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] +} diff --git a/packages/sra-report/tslint.json b/packages/sra-report/tslint.json new file mode 100644 index 0000000000..ffaefe83a6 --- /dev/null +++ b/packages/sra-report/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} diff --git a/yarn.lock b/yarn.lock index 7c7dcd76b1..3a458565ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,10 @@ # yarn lockfile v1 +"8fold-marked@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/8fold-marked/-/8fold-marked-0.3.9.tgz#bb89c645612f8ccfaffac1ca6e3c11f168c9cf59" + "@types/accounting@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/accounting/-/accounting-0.4.1.tgz#865d9f5694fd7c438fba34eb4bc82eec6f34cdd5" @@ -109,6 +113,10 @@ version "4.14.92" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.92.tgz#6e3cb0b71a1e12180a47a42a744e856c3ae99a57" +"@types/lodash@^4.14.104": + version "4.14.104" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" + "@types/marked@0.0.28": version "0.0.28" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" @@ -611,6 +619,12 @@ async@2.1.4: dependencies: lodash "^4.14.0" +async@2.6.0, async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + dependencies: + lodash "^4.14.0" + async@^0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -619,12 +633,6 @@ async@^1.4.0, async@^1.4.2, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" - dependencies: - lodash "^4.14.0" - async@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" @@ -669,7 +677,7 @@ aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.2.1, aws4@^1.6.0: +aws4@1.6.0, aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" @@ -1343,6 +1351,10 @@ blockies@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/blockies/-/blockies-0.0.2.tgz#22ad58da4f6b382bc79bf4386c5820c70047e4ed" +bluebird@^2.6.2: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + bluebird@^3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -1540,6 +1552,10 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" +btoa@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0" + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1705,7 +1721,7 @@ chain-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1731,6 +1747,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +charset@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" + check-error@^1.0.1, check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -1788,6 +1808,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-json@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + clap@^1.0.9: version "1.2.3" resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" @@ -1817,6 +1841,21 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-progress@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-1.7.0.tgz#401cdfeacc1a34afc281b4bdf88829236724c92f" + dependencies: + colors "^1.1.2" + +cli-table2@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97" + dependencies: + lodash "^3.10.1" + string-width "^1.0.1" + optionalDependencies: + colors "^1.1.2" + cli-width@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d" @@ -1936,7 +1975,7 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@^1.1.2, colors@~1.1.2: +colors@1.1.2, colors@^1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1961,7 +2000,11 @@ commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" -commander@^2.9.0: +commander@2.12.2: + version "2.12.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" + +commander@2.13.0, commander@^2.9.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -2420,14 +2463,14 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@3.1.9-1, crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + crypto-js@^3.1.4: version "3.1.8" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" -crypto-js@^3.1.9-1: - version "3.1.9-1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" - crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -2528,6 +2571,10 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" +csv-parse@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-1.2.4.tgz#cbf676e355226625888c6432400b83f07e75cc2e" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2571,6 +2618,10 @@ dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" +dbug@~0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/dbug/-/dbug-0.4.2.tgz#32b4b3105e8861043a6f9ac755d80e542d365b31" + debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" @@ -2797,6 +2848,13 @@ dom-helpers@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" +dom-serializer@0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -2805,6 +2863,27 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" +domelementtype@1, domelementtype@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domhandler@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + dependencies: + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + dependencies: + dom-serializer "0" + domelementtype "1" + dot-prop@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" @@ -2932,7 +3011,7 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" -"entities@~ 1.1.1": +entities@^1.1.1, "entities@~ 1.1.1", entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -3032,7 +3111,7 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.1" es6-symbol "^3.1.1" -escape-html@~1.0.3: +escape-html@1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3272,6 +3351,10 @@ eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" +eventemitter3@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.0.0.tgz#fc29ecf233bd19fbd527bb4089bbf665dc90c1e3" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3518,7 +3601,7 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-type@^3.6.0: +file-type@3.9.0, file-type@^3.6.0: version "3.9.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" @@ -3526,6 +3609,10 @@ filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" +filesize@3.5.11: + version "3.5.11" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" + fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -4134,7 +4221,7 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" -handlebars@^4.0.11, handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6: +handlebars@4.0.11, handlebars@^4.0.11, handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: @@ -4159,7 +4246,7 @@ har-validator@~4.2.1: ajv "^4.9.1" har-schema "^1.0.5" -har-validator@~5.0.3: +har-validator@~5.0.2, har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" dependencies: @@ -4344,6 +4431,17 @@ html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" +htmlparser2@^3.9.0: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -4377,6 +4475,10 @@ http-proxy@^1.16.2: eventemitter3 "1.x.x" requires-port "1.x.x" +http-reasons@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/http-reasons/-/http-reasons-0.1.0.tgz#a953ca670078669dde142ce899401b9d6e85d3b4" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -4393,6 +4495,13 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpntlm@1.7.5: + version "1.7.5" + resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.7.5.tgz#cd1558ed93125418ece5bf824c1335675feedecc" + dependencies: + httpreq ">=0.4.22" + underscore "~1.7.0" + httpplease@^0.16: version "0.16.4" resolved "https://registry.yarnpkg.com/httpplease/-/httpplease-0.16.4.tgz#d382ebe230ef5079080b4e9ffebf316a9e75c0da" @@ -4401,6 +4510,10 @@ httpplease@^0.16: xmlhttprequest "*" xtend "~3.0.0" +httpreq@>=0.4.22: + version "0.4.24" + resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -4550,6 +4663,17 @@ inquirer@^3.2.2: strip-ansi "^4.0.0" through "^2.3.6" +intel@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/intel/-/intel-1.2.0.tgz#11d1147eb6b3f4582bdf5337b37d541584e9e41e" + dependencies: + chalk "^1.1.0" + dbug "~0.4.2" + stack-trace "~0.0.9" + strftime "~0.10.0" + symbol "~0.3.1" + utcstring "~0.1.0" + internal-ip@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" @@ -4578,6 +4702,10 @@ ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" +irregular-plurals@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" + is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -5304,6 +5432,10 @@ liftoff@^2.1.0: rechoir "^0.6.2" resolve "^1.1.7" +liquid-json@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/liquid-json/-/liquid-json-0.3.1.tgz#9155a18136d8a6b2615e5f16f9a2448ab6b50eea" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5438,6 +5570,10 @@ lodash.escape@^3.0.0: dependencies: lodash._root "^3.0.0" +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" @@ -5530,14 +5666,18 @@ lodash.words@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@^3.3.1, lodash@^3.6.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" +lodash@4.17.2: + version "4.17.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: +lodash@4.17.4, lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^3.10.1, lodash@^3.3.1, lodash@^3.6.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -5807,7 +5947,13 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: +mime-format@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mime-format/-/mime-format-2.0.0.tgz#e29f8891e284d78270246f0050d6834bdbbe1332" + dependencies: + charset "^1.0.0" + +mime-types@2.1.17, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: @@ -6014,6 +6160,33 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +newman@^3.9.3: + version "3.9.3" + resolved "https://registry.yarnpkg.com/newman/-/newman-3.9.3.tgz#939356026942474ba15482bd37a15c60bb200ca0" + dependencies: + async "2.6.0" + cli-progress "1.7.0" + cli-table2 "0.2.0" + colors "1.1.2" + commander "2.13.0" + csv-parse "1.2.4" + eventemitter3 "3.0.0" + filesize "3.5.11" + handlebars "4.0.11" + lodash "4.17.2" + mkdirp "0.5.1" + parse-json "3.0.0" + postman-collection "3.0.7" + postman-collection-transformer "2.5.4" + postman-request "2.81.1-postman.4" + postman-runtime "7.1.3" + pretty-ms "3.1.0" + semver "5.5.0" + serialised-error "1.1.2" + shelljs "0.8.0" + word-wrap "1.2.3" + xmlbuilder "9.0.4" + nise@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" @@ -6070,6 +6243,12 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-oauth1@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/node-oauth1/-/node-oauth1-1.2.2.tgz#fffb2813a88c2770711332ad0e5487b4927644a4" + dependencies: + crypto-js "3.1.9-1" + node-pre-gyp@0.6.31: version "0.6.31" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.31.tgz#d8a00ddaa301a940615dbcc8caad4024d58f6017" @@ -6100,6 +6279,10 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" +node-uuid@^1.4.7: + version "1.4.8" + resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" + nodemon@^1.11.0: version "1.14.11" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.14.11.tgz#cc0009dd8d82f126f3aba50ace7e753827a8cebc" @@ -6264,6 +6447,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" + object-inspect@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-0.4.0.tgz#f5157c116c1455b243b06ee97703392c5ad89fec" @@ -6511,18 +6698,18 @@ parse-headers@^2.0.0: for-each "^0.3.2" trim "0.0.1" +parse-json@3.0.0, parse-json@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" + dependencies: + error-ex "^1.3.1" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: error-ex "^1.2.0" -parse-json@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" - dependencies: - error-ex "^1.3.1" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -6530,6 +6717,10 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-ms@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -6682,6 +6873,12 @@ pkginfo@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" +plur@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + dependencies: + irregular-plurals "^1.0.0" + portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -6940,6 +7137,117 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.1.0" +postman-collection-transformer@2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/postman-collection-transformer/-/postman-collection-transformer-2.5.4.tgz#4e715a9913340621a9fc0e9a5a0373b22e9a9d9a" + dependencies: + commander "2.12.2" + inherits "2.0.3" + intel "1.2.0" + lodash "4.17.4" + semver "5.4.1" + strip-json-comments "2.0.1" + +postman-collection@3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/postman-collection/-/postman-collection-3.0.6.tgz#3bc2307158a783964a473cac82f20d26ffdf5500" + dependencies: + "8fold-marked" "0.3.9" + escape-html "1.0.3" + file-type "3.9.0" + http-reasons "0.1.0" + iconv-lite "0.4.19" + liquid-json "0.3.1" + lodash "4.17.4" + mime-format "2.0.0" + mime-types "2.1.17" + postman-url-encoder "1.0.1" + sanitize-html "1.15.0" + semver "5.4.1" + uuid "3.1.0" + +postman-collection@3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/postman-collection/-/postman-collection-3.0.7.tgz#699496583ff61443da8a4e0314e58ee6d10442a3" + dependencies: + "8fold-marked" "0.3.9" + escape-html "1.0.3" + file-type "3.9.0" + http-reasons "0.1.0" + iconv-lite "0.4.19" + liquid-json "0.3.1" + lodash "4.17.4" + mime-format "2.0.0" + mime-types "2.1.17" + postman-url-encoder "1.0.1" + sanitize-html "1.15.0" + semver "5.4.1" + uuid "3.1.0" + +postman-request@2.81.1-postman.4: + version "2.81.1-postman.4" + resolved "https://registry.yarnpkg.com/postman-request/-/postman-request-2.81.1-postman.4.tgz#be8f60541539da13739d77b670b9fca6f1aecf83" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~5.0.2" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + postman-url-encoder "1.0.1" + qs "~6.4.0" + safe-buffer "^5.0.1" + stream-length "^1.0.2" + stringstream "~0.0.4" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +postman-runtime@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/postman-runtime/-/postman-runtime-7.1.3.tgz#e0c5dfc40dd7d39a2f71519705c97afde35d6f8c" + dependencies: + async "2.6.0" + aws4 "1.6.0" + btoa "1.1.2" + crypto-js "3.1.9-1" + eventemitter3 "3.0.0" + hawk "3.1.3" + http-reasons "0.1.0" + httpntlm "1.7.5" + inherits "2.0.3" + lodash "4.17.4" + node-oauth1 "1.2.2" + postman-collection "3.0.6" + postman-request "2.81.1-postman.4" + postman-sandbox "3.0.4" + resolve-from "4.0.0" + serialised-error "1.1.2" + uuid "3.1.0" + +postman-sandbox@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postman-sandbox/-/postman-sandbox-3.0.4.tgz#d011b50d3ec8a8075405d219e1659dbdec354997" + dependencies: + inherits "2.0.3" + lodash "4.17.4" + uuid "3.1.0" + uvm "1.7.0" + +postman-url-encoder@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postman-url-encoder/-/postman-url-encoder-1.0.1.tgz#a094a42e9415ff0bbfdce0eaa8e6011d449ee83c" + prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -6963,6 +7271,13 @@ pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" +pretty-ms@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-3.1.0.tgz#e9cac9c76bf6ee52fe942dd9c6c4213153b12881" + dependencies: + parse-ms "^1.0.0" + plur "^2.1.2" + private@^0.1.6, private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -7785,6 +8100,10 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" +resolve-from@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" @@ -7906,6 +8225,15 @@ samsam@1.x: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" +sanitize-html@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.15.0.tgz#d101a62c9fe0347486badc6cd6ed72daa0a82ced" + dependencies: + htmlparser2 "^3.9.0" + lodash.escaperegexp "^4.1.2" + srcset "^1.0.0" + xtend "^4.0.0" + sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -7990,10 +8318,14 @@ semver-sort@0.0.4, semver-sort@^0.0.4: 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: +"semver@2 || 3 || 4 || 5", semver@5.4.1, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +semver@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + semver@^4.1.0: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" @@ -8024,6 +8356,14 @@ sequencify@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" +serialised-error@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/serialised-error/-/serialised-error-1.1.2.tgz#b5c3822196f873feb0c76587e1d6dfa6790ade97" + dependencies: + node-uuid "^1.4.7" + object-hash "^1.1.2" + stack-trace "0.0.9" + serialize-javascript@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" @@ -8139,6 +8479,14 @@ shell-quote@^1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" +shelljs@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.0.tgz#12f561c52ec5d0d3315af15616c011a18ff80d59" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shelljs@^0.7.0, shelljs@^0.7.3: version "0.7.8" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" @@ -8430,6 +8778,13 @@ sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" +srcset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef" + dependencies: + array-uniq "^1.0.2" + number-is-nan "^1.0.0" + sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -8450,6 +8805,14 @@ ssri@^5.0.0: dependencies: safe-buffer "^5.1.0" +stack-trace@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" + +stack-trace@~0.0.9: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -8503,10 +8866,20 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-length/-/stream-length-1.0.2.tgz#8277f3cbee49a4daabcfdb4e2f4a9b5e9f2c9f00" + dependencies: + bluebird "^2.6.2" + stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" +strftime@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/strftime/-/strftime-0.10.0.tgz#b3f0fa419295202a5a289f6d6be9f4909a617193" + 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" @@ -8617,7 +8990,7 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@~2.0.1: +strip-json-comments@2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -8693,6 +9066,10 @@ symbol-observable@^1.0.3, symbol-observable@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" +symbol@~0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/symbol/-/symbol-0.3.1.tgz#b6f9a900d496a57f02408f22198c109dda063041" + tapable@^0.2.5, tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" @@ -9183,6 +9560,10 @@ underscore@1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -9328,6 +9709,10 @@ user-home@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" +utcstring@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/utcstring/-/utcstring-0.1.0.tgz#430fd510ab7fc95b5d5910c902d79880c208436b" + utf8@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.1.tgz#2e01db02f7d8d0944f77104f1609eb0c304cf768" @@ -9354,13 +9739,22 @@ uuid@3.0.1, uuid@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +uuid@3.1.0, uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uvm@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/uvm/-/uvm-1.7.0.tgz#685d3a149ec7118fb73a73dfdc158ab46b0f0634" + dependencies: + circular-json "0.3.1" + inherits "2.0.3" + lodash "4.17.4" + uuid "3.0.1" v8flags@^2.0.2: version "2.1.1" @@ -9709,6 +10103,10 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" +word-wrap@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" @@ -9811,6 +10209,10 @@ xml-js@^1.3.2: dependencies: sax "^1.2.4" +xmlbuilder@9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + xmlhttprequest@*: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" From 63f8f469b0967ef606572ba2a92d051030e41c6c Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 22 Feb 2018 13:54:39 -0800 Subject: [PATCH 02/94] Fix @types/lodash version --- packages/sra-report/package.json | 2 +- yarn.lock | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 0eaa5277ec..8265ceb280 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "@0xproject/tslint-config": "^0.4.9", - "@types/lodash": "^4.14.104", + "@types/lodash": "^4.14.86", "@types/node": "^8.0.53", "@types/yargs": "^10.0.0", "shx": "^0.2.2", diff --git a/yarn.lock b/yarn.lock index 3a458565ed..c9a5942b44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,10 +113,6 @@ version "4.14.92" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.92.tgz#6e3cb0b71a1e12180a47a42a744e856c3ae99a57" -"@types/lodash@^4.14.104": - version "4.14.104" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" - "@types/marked@0.0.28": version "0.0.28" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" From 87b9caa7dc20947f818c9546033e7314e7e78535 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 23 Feb 2018 11:47:42 -0800 Subject: [PATCH 03/94] Fix README typo --- packages/sra-report/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sra-report/README.md b/packages/sra-report/README.md index 18802f06af..49609aeb26 100644 --- a/packages/sra-report/README.md +++ b/packages/sra-report/README.md @@ -9,7 +9,7 @@ This package allows you to generate reports detailing an endpoint's [standard re ## Usage ``` -abi-gen +sra-report Options: --help Show help [boolean] --version Show version number [boolean] From 7afe00f06a0cf0bf4f079dcc86e2241cc3dc9832 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 23 Feb 2018 11:56:50 -0800 Subject: [PATCH 04/94] Use Schema interface from @0xproject/json-schemas --- packages/sra-report/src/index.ts | 23 +++++++++++++---------- packages/sra-report/src/types.ts | 3 --- 2 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 packages/sra-report/src/types.ts diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index fe100bd42c..a24de377af 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { assert } from '@0xproject/assert'; -import { schemas } from '@0xproject/json-schemas'; +import { Schema, schemas } from '@0xproject/json-schemas'; import chalk from 'chalk'; import * as _ from 'lodash'; import * as newman from 'newman'; @@ -11,7 +11,6 @@ import * as sraReportCollectionJSON from '../postman_configs/collections/sra_rep import * as kovanTokensEnvironmentJSON from '../postman_configs/environments/kovan_tokens.postman_environment.json'; import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/mainnet_tokens.postman_environment.json'; -import { Schema } from './types'; import { utils } from './utils'; const DEFAULT_NETWORK_ID = 1; @@ -74,14 +73,18 @@ function createGlobals(url: string, schemaList: Schema[]) { enabled: true, type: 'text', }; - const schemaGlobalsValues = _.map(schemaList, (schema: Schema) => { - return { - key: convertSchemaIdToKey(schema.id), - value: JSON.stringify(schema), - enabled: true, - type: 'text', - }; - }); + const schemaGlobalsValues = _.compact(_.map(schemaList, (schema: Schema) => { + if (_.isUndefined(schema.id)) { + return undefined; + } else { + return { + key: convertSchemaIdToKey(schema.id), + value: JSON.stringify(schema), + enabled: true, + type: 'text', + }; + } + })); const globalsValues = _.concat(schemaGlobalsValues, urlGlobalsValue); const globals = { values: globalsValues, diff --git a/packages/sra-report/src/types.ts b/packages/sra-report/src/types.ts deleted file mode 100644 index f08ef40424..0000000000 --- a/packages/sra-report/src/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface Schema { - id: string; -} From e13924cd2dbe14c426ddadd4ab21a872712ae0fd Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 23 Feb 2018 11:57:33 -0800 Subject: [PATCH 05/94] Remove url from kovan environment json --- .../environments/kovan_tokens.postman_environment.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json index fc2b1a9ff2..f5b28aa0f1 100644 --- a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json +++ b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json @@ -2,12 +2,6 @@ "id": "8dab7221-c8b8-2931-92c8-605f6f4072e1", "name": "Kovan Tokens", "values": [ - { - "enabled": true, - "key": "url", - "value": "https://api.radarrelay.com/0x", - "type": "text" - }, { "enabled": true, "key": "DAI_address", From 55f38b9c35bb8b988eb22ee47d4d671475f05a0e Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 23 Feb 2018 12:58:36 -0800 Subject: [PATCH 06/94] Add an array of schemaKeys to the globals --- packages/sra-report/src/index.ts | 44 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index a24de377af..58734182eb 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -13,6 +13,13 @@ import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/m import { utils } from './utils'; +interface GlobalsValue { + key: string; + value: string; + enabled: boolean; + type: string; +} + const DEFAULT_NETWORK_ID = 1; const SUPPORTED_NETWORK_IDS = [1, 42]; @@ -73,19 +80,30 @@ function createGlobals(url: string, schemaList: Schema[]) { enabled: true, type: 'text', }; - const schemaGlobalsValues = _.compact(_.map(schemaList, (schema: Schema) => { - if (_.isUndefined(schema.id)) { - return undefined; - } else { - return { - key: convertSchemaIdToKey(schema.id), - value: JSON.stringify(schema), - enabled: true, - type: 'text', - }; - } - })); - const globalsValues = _.concat(schemaGlobalsValues, urlGlobalsValue); + const schemaGlobalsValues = _.compact( + _.map(schemaList, (schema: Schema) => { + if (_.isUndefined(schema.id)) { + return undefined; + } else { + return { + key: convertSchemaIdToKey(schema.id), + value: JSON.stringify(schema), + enabled: true, + type: 'text', + }; + } + }), + ); + const schemaKeys = _.map(schemaGlobalsValues, (globalsValue: GlobalsValue) => { + return globalsValue.key; + }); + const schemaKeysGlobalsValue = { + key: 'schemaKeys', + value: JSON.stringify(schemaKeys), + enabled: true, + type: 'text', + }; + const globalsValues = _.concat(schemaGlobalsValues, urlGlobalsValue, schemaKeysGlobalsValue); const globals = { values: globalsValues, }; From 0b9646136a79f16b4aaaebd99cf27b79f2cec3a4 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 23 Feb 2018 14:26:24 -0800 Subject: [PATCH 07/94] Update postman collection to load json schemas into tv4 before every test run --- .../sra_report.postman_collection.json | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index 861f040f7b..8ba1ba8943 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "name": "0x Standard Relayer API", + "name": "sra_report", "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", "description": "[Standard Relayer Api](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", @@ -226,6 +226,27 @@ } ] }, + { + "name": "GET /order", + "description": "", + "item": [ + { + "name": "order by order hash", + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/order/{{_orderHash}}", + "host": ["{{url}}"], + "path": ["v0", "order", "{{_orderHash}}"] + }, + "description": "Retrieves a specific order by orderHash." + }, + "response": [] + } + ] + }, { "name": "GET /orderbook", "description": "", @@ -311,32 +332,42 @@ ] }, { - "name": "GET /order", + "name": "POST /fees", "description": "", "item": [ { - "name": "order by order hash", + "name": "Calculate Fees", "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/v0/order/{{_orderHash}}", - "host": ["{{url}}"], - "path": ["v0", "order", "{{_orderHash}}"] + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": + "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" }, - "description": "Retrieves a specific order by orderHash." + "url": { + "raw": "{{url}}/v0/fees", + "host": ["{{url}}"], + "path": ["v0", "fees"] + }, + "description": + "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." }, "response": [] } ] }, { - "name": "POST /fees", + "name": "POST /order", "description": "", "item": [ { - "name": "Calculate Fees", + "name": "Submit order", "request": { "method": "POST", "header": [ @@ -369,7 +400,17 @@ "script": { "id": "da60f639-df79-4f4d-9861-79219f5fc341", "type": "text/javascript", - "exec": [""] + "exec": [ + "const schemaKeysString = pm.globals.get('schemaKeys');", + "const schemaKeys = JSON.parse(schemaKeysString);", + "", + "_.forEach(schemaKeys, function(schemaKey) {", + " const schemaString = pm.globals.get(schemaKey);", + " const schema = JSON.parse(schemaString);", + " tv4.addSchema(schema);", + "});", + "" + ] } }, { From 4b325676f72fce5bd1205a3cca1e38f41580a7f6 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 26 Feb 2018 13:32:53 -0800 Subject: [PATCH 08/94] Add schema validation to each collection folder and flesh out params --- .../sra_report.postman_collection.json | 523 +++++++++++++----- 1 file changed, 381 insertions(+), 142 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index 8ba1ba8943..91faf0c764 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -3,7 +3,7 @@ "name": "sra_report", "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", "description": - "[Standard Relayer Api](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", + "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ @@ -12,12 +12,12 @@ "description": "", "item": [ { - "name": "Default", + "name": "default request", "event": [ { "listen": "test", "script": { - "id": "e25cd907-44f8-4d1e-823a-429d6244e6b4", + "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", "type": "text/javascript", "exec": [""] } @@ -32,51 +32,17 @@ "host": ["{{url}}"], "path": ["v0", "token_pairs"] }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + "description": "" }, "response": [] }, { - "name": "DAI pairs", + "name": "tokenA param", "event": [ { "listen": "test", "script": { - "id": "cb6e73f9-f5a7-4d75-807a-c9db94dda4f3", - "type": "text/javascript", - "exec": [""] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/v0/token_pairs?tokenA={{DAI_address}}", - "host": ["{{url}}"], - "path": ["v0", "token_pairs"], - "query": [ - { - "key": "tokenA", - "value": "{{DAI_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "WETH pairs", - "event": [ - { - "listen": "test", - "script": { - "id": "cb6e73f9-f5a7-4d75-807a-c9db94dda4f3", + "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", "type": "text/javascript", "exec": [""] } @@ -98,10 +64,104 @@ } ] }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + "description": "" }, "response": [] + }, + { + "name": "tokenB param", + "event": [ + { + "listen": "test", + "script": { + "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/token_pairs?tokenB={{WETH_address}}", + "host": ["{{url}}"], + "path": ["v0", "token_pairs"], + "query": [ + { + "key": "tokenB", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA and tokenB params", + "event": [ + { + "listen": "test", + "script": { + "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", + "host": ["{{url}}"], + "path": ["v0", "token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + }, + { + "key": "tokenB", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } } ] }, @@ -110,19 +170,14 @@ "description": "", "item": [ { - "name": "Default", + "name": "default request", "event": [ { "listen": "test", "script": { - "id": "742679e1-5c16-4fe3-8056-dc7d34257564", + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", "type": "text/javascript", - "exec": [ - "const orders = JSON.parse(responseBody);", - "const firstOrder = orders[0];", - "const firstOrderHash = _.get(firstOrder, \"orderHash\");", - "pm.globals.set(\"_orderHash\", firstOrderHash);" - ] + "exec": [""] } } ], @@ -141,12 +196,12 @@ "response": [] }, { - "name": "DAI orders", + "name": "exchangeContract param", "event": [ { "listen": "test", "script": { - "id": "a1eabd96-fee3-4328-ae50-4a992f0401ba", + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", "type": "text/javascript", "exec": [""] } @@ -157,16 +212,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders?tokenAddress={{DAI_address}}", + "raw": "{{url}}/v0/orders", "host": ["{{url}}"], - "path": ["v0", "orders"], - "query": [ - { - "key": "tokenAddress", - "value": "{{DAI_address}}", - "equals": true - } - ] + "path": ["v0", "orders"] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -174,12 +222,12 @@ "response": [] }, { - "name": "WETH orders", + "name": "tokenAddress param", "event": [ { "listen": "test", "script": { - "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", "type": "text/javascript", "exec": [""] } @@ -190,16 +238,191 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders?tokenAddress={{WETH_address}}", + "raw": "{{url}}/v0/orders", "host": ["{{url}}"], - "path": ["v0", "orders"], - "query": [ - { - "key": "tokenAddress", - "value": "{{WETH_address}}", - "equals": true - } - ] + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "makerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "takerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "maker param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "taker param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "trader param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "feeRecipient param", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "makerTokenAddress and takerTokenAddress params", + "event": [ + { + "listen": "test", + "script": { + "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/v0/orders", + "host": ["{{url}}"], + "path": ["v0", "orders"] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -221,7 +444,15 @@ "script": { "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", "type": "text/javascript", - "exec": [""] + "exec": [ + "const schema = tv4.getSchema('/signedOrdersSchema');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] } } ] @@ -231,7 +462,7 @@ "description": "", "item": [ { - "name": "order by order hash", + "name": "orderHash param", "request": { "method": "GET", "header": [], @@ -245,6 +476,32 @@ }, "response": [] } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/SignedOrder');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } ] }, { @@ -252,7 +509,7 @@ "description": "", "item": [ { - "name": "ZRX/WETH orderbook", + "name": "baseTokenAddress and quoteTokenAddress params", "event": [ { "listen": "test", @@ -289,45 +546,32 @@ "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." }, "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", + "type": "text/javascript", + "exec": [""] + } }, { - "name": "ZRX/DAI orderbook", - "event": [ - { - "listen": "test", - "script": { - "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", - "type": "text/javascript", - "exec": [""] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": - "{{url}}/v0/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{DAI_address}}", - "host": ["{{url}}"], - "path": ["v0", "orderbook"], - "query": [ - { - "key": "baseTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - }, - { - "key": "quoteTokenAddress", - "value": "{{DAI_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] + "listen": "test", + "script": { + "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } } ] }, @@ -336,7 +580,7 @@ "description": "", "item": [ { - "name": "Calculate Fees", + "name": "default request", "request": { "method": "POST", "header": [ @@ -360,36 +604,31 @@ }, "response": [] } - ] - }, - { - "name": "POST /order", - "description": "", - "item": [ + ], + "event": [ { - "name": "Submit order", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": - "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" - }, - "url": { - "raw": "{{url}}/v0/fees", - "host": ["{{url}}"], - "path": ["v0", "fees"] - }, - "description": - "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." - }, - "response": [] + "listen": "prerequest", + "script": { + "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiFeesResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } } ] } From bb157eefc622148d7ab3f49b3b94df274deccedb Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 26 Feb 2018 17:13:14 -0800 Subject: [PATCH 09/94] Add exchange contract address to environment files --- .../environments/kovan_tokens.postman_environment.json | 6 ++++++ .../environments/mainnet_tokens.postman_environment.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json index f5b28aa0f1..480c1231b8 100644 --- a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json +++ b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json @@ -19,6 +19,12 @@ "key": "ZRX_address", "value": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", "type": "text" + }, + { + "enabled": true, + "key": "EXCHANGE_CONTRACT_address", + "value": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364", + "type": "text" } ], "timestamp": 1518639440766, diff --git a/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json index 0040dda954..f18c38f46d 100644 --- a/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json +++ b/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json @@ -19,6 +19,12 @@ "key": "ZRX_address", "value": "0xe41d2489571d322189246dafa5ebde1f4699f498", "type": "text" + }, + { + "enabled": true, + "key": "EXCHANGE_CONTRACT_address", + "value": "0x12459c951127e0c374ff9105dda097662a027093", + "type": "text" } ], "timestamp": 1518561760642, From 9c8501a84e92faf3ef015ae8ab4d1da9354f9a9d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 27 Feb 2018 11:51:05 -0800 Subject: [PATCH 10/94] Refactor environment factory and promisify newman --- packages/sra-report/package.json | 3 + packages/sra-report/src/index.ts | 87 +++---------------- .../src/postman_environment_factory.ts | 71 +++++++++++++++ 3 files changed, 87 insertions(+), 74 deletions(-) create mode 100644 packages/sra-report/src/postman_environment_factory.ts diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 8265ceb280..6168c128ed 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -23,8 +23,11 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", "dependencies": { + "0x.js": "^0.32.4", "@0xproject/assert": "^0.0.20", + "@0xproject/connect": "^0.6.1", "@0xproject/json-schemas": "^0.7.12", + "@0xproject/utils": "^0.3.4", "chalk": "^2.3.0", "lodash": "^4.17.4", "newman": "^3.9.3", diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index 58734182eb..d273d74a46 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -2,23 +2,18 @@ import { assert } from '@0xproject/assert'; import { Schema, schemas } from '@0xproject/json-schemas'; +import { promisify } from '@0xproject/utils'; import chalk from 'chalk'; import * as _ from 'lodash'; import * as newman from 'newman'; import * as yargs from 'yargs'; import * as sraReportCollectionJSON from '../postman_configs/collections/sra_report.postman_collection.json'; -import * as kovanTokensEnvironmentJSON from '../postman_configs/environments/kovan_tokens.postman_environment.json'; -import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/mainnet_tokens.postman_environment.json'; +import { postmanEnvironmentFactory } from './postman_environment_factory'; import { utils } from './utils'; -interface GlobalsValue { - key: string; - value: string; - enabled: boolean; - type: string; -} +const newmanRunAsync = promisify(newman.run); const DEFAULT_NETWORK_ID = 1; const SUPPORTED_NETWORK_IDS = [1, 42]; @@ -58,72 +53,16 @@ if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { utils.log(`${chalk.bold(`Supported network ids:`)} ${SUPPORTED_NETWORK_IDS}`); process.exit(1); } -// run newman -newman.run( - { + +const mainAsync = async () => { + const newmanRunOptions = { collection: sraReportCollectionJSON, reporters: 'cli', - globals: createGlobals(args.url, _.values(schemas)), - environment: getEnvironment(args.networkId), - }, - (err: Error) => { - if (err) { - throw err; - } - utils.log('collection run complete!'); - }, -); -function createGlobals(url: string, schemaList: Schema[]) { - const urlGlobalsValue = { - key: 'url', - value: args.url, - enabled: true, - type: 'text', + globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url), + environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), }; - const schemaGlobalsValues = _.compact( - _.map(schemaList, (schema: Schema) => { - if (_.isUndefined(schema.id)) { - return undefined; - } else { - return { - key: convertSchemaIdToKey(schema.id), - value: JSON.stringify(schema), - enabled: true, - type: 'text', - }; - } - }), - ); - const schemaKeys = _.map(schemaGlobalsValues, (globalsValue: GlobalsValue) => { - return globalsValue.key; - }); - const schemaKeysGlobalsValue = { - key: 'schemaKeys', - value: JSON.stringify(schemaKeys), - enabled: true, - type: 'text', - }; - const globalsValues = _.concat(schemaGlobalsValues, urlGlobalsValue, schemaKeysGlobalsValue); - const globals = { - values: globalsValues, - }; - return globals; -} -function convertSchemaIdToKey(schemaId: string) { - let result = schemaId; - if (_.startsWith(result, '/')) { - result = result.substr(1); - } - result = `${result}Schema`; - return result; -} -function getEnvironment(networkId: number) { - switch (networkId) { - case 1: - return mainnetTokensEnvironmentJSON; - case 42: - return kovanTokensEnvironmentJSON; - default: - return {}; - } -} + await newmanRunAsync(newmanRunOptions); +}; + +mainAsync() + .catch(err => utils.log); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts new file mode 100644 index 0000000000..697578a2dd --- /dev/null +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -0,0 +1,71 @@ +import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; +import * as _ from 'lodash'; + +import * as kovanTokensEnvironmentJSON from '../postman_configs/environments/kovan_tokens.postman_environment.json'; +import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/mainnet_tokens.postman_environment.json'; + +interface EnvironmentValue { + key: string; + value: string; + enabled: boolean; + type: string; +} + +export const postmanEnvironmentFactory = { + createGlobalEnvironment(url: string) { + const urlEnvironmentValue = { + key: 'url', + value: url, + enabled: true, + type: 'text', + }; + const schemas: Schema[] = _.values(schemasByName); + const schemaEnvironmentValues = _.compact( + _.map(schemas, (schema: Schema) => { + if (_.isUndefined(schema.id)) { + return undefined; + } else { + return { + key: convertSchemaIdToKey(schema.id), + value: JSON.stringify(schema), + enabled: true, + type: 'text', + }; + } + }), + ); + const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { + return environmentValue.key; + }); + const schemaKeysEnvironmentValue = { + key: 'schemaKeys', + value: JSON.stringify(schemaKeys), + enabled: true, + type: 'text', + }; + const environmentValues = _.concat(schemaEnvironmentValues, urlEnvironmentValue, schemaKeysEnvironmentValue); + const environment = { + values: environmentValues, + }; + return environment; + }, + createNetworkEnvironment(networkId: number) { + switch (networkId) { + case 1: + return mainnetTokensEnvironmentJSON; + case 42: + return kovanTokensEnvironmentJSON; + default: + return {}; + } + }, +}; + +function convertSchemaIdToKey(schemaId: string) { + let result = schemaId; + if (_.startsWith(result, '/')) { + result = result.substr(1); + } + result = `${result}Schema`; + return result; +} From e48a3edacba891a18e641e01708e3d73fd6588ac Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 27 Feb 2018 14:06:33 -0800 Subject: [PATCH 11/94] Get orderHash via 0x connect --- packages/sra-report/src/index.ts | 15 +++++-- .../src/postman_environment_factory.ts | 43 ++++++++++--------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index d273d74a46..bd0615480b 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -1,6 +1,8 @@ #!/usr/bin/env node +import { ZeroEx } from '0x.js'; import { assert } from '@0xproject/assert'; +import { HttpClient } from '@0xproject/connect'; import { Schema, schemas } from '@0xproject/json-schemas'; import { promisify } from '@0xproject/utils'; import chalk from 'chalk'; @@ -14,7 +16,6 @@ import { postmanEnvironmentFactory } from './postman_environment_factory'; import { utils } from './utils'; const newmanRunAsync = promisify(newman.run); - const DEFAULT_NETWORK_ID = 1; const SUPPORTED_NETWORK_IDS = [1, 42]; @@ -55,14 +56,20 @@ if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { } const mainAsync = async () => { + const httpClient = new HttpClient(args.url); + const orders = await httpClient.getOrdersAsync(); + const firstOrder = _.head(orders); + if (_.isUndefined(firstOrder)) { + throw new Error('Could not get any orders from /orders endpoint'); + } + const orderHash = ZeroEx.getOrderHashHex(firstOrder); const newmanRunOptions = { collection: sraReportCollectionJSON, reporters: 'cli', - globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url), + globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url, orderHash), environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), }; await newmanRunAsync(newmanRunOptions); }; -mainAsync() - .catch(err => utils.log); +mainAsync().catch(utils.log); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 697578a2dd..b0b69ddade 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -12,38 +12,32 @@ interface EnvironmentValue { } export const postmanEnvironmentFactory = { - createGlobalEnvironment(url: string) { - const urlEnvironmentValue = { - key: 'url', - value: url, - enabled: true, - type: 'text', - }; + createGlobalEnvironment(url: string, orderHash: string) { + const urlEnvironmentValue = createEnvironmentValue('url', url); + const orderHashEnvironmentValue = createEnvironmentValue('orderHash', orderHash); const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( _.map(schemas, (schema: Schema) => { if (_.isUndefined(schema.id)) { return undefined; } else { - return { - key: convertSchemaIdToKey(schema.id), - value: JSON.stringify(schema), - enabled: true, - type: 'text', - }; + const schemaKey = convertSchemaIdToKey(schema.id); + const stringifiedSchema = JSON.stringify(schema); + const schemaEnvironmentValue = createEnvironmentValue(schemaKey, stringifiedSchema); + return schemaEnvironmentValue; } }), ); const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { return environmentValue.key; }); - const schemaKeysEnvironmentValue = { - key: 'schemaKeys', - value: JSON.stringify(schemaKeys), - enabled: true, - type: 'text', - }; - const environmentValues = _.concat(schemaEnvironmentValues, urlEnvironmentValue, schemaKeysEnvironmentValue); + const schemaKeysEnvironmentValue = createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)); + const environmentValues = _.concat( + schemaEnvironmentValues, + urlEnvironmentValue, + schemaKeysEnvironmentValue, + orderHashEnvironmentValue, + ); const environment = { values: environmentValues, }; @@ -60,7 +54,6 @@ export const postmanEnvironmentFactory = { } }, }; - function convertSchemaIdToKey(schemaId: string) { let result = schemaId; if (_.startsWith(result, '/')) { @@ -69,3 +62,11 @@ function convertSchemaIdToKey(schemaId: string) { result = `${result}Schema`; return result; } +function createEnvironmentValue(key: string, value: string) { + return { + key, + value, + enabled: true, + type: 'text', + }; +} From 0987c9a7cfb1936469062b4cb6222158b5e110b7 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 27 Feb 2018 17:12:45 -0800 Subject: [PATCH 12/94] Write to provided output directory --- packages/sra-report/src/index.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index bd0615480b..fade07095a 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -63,13 +63,27 @@ const mainAsync = async () => { throw new Error('Could not get any orders from /orders endpoint'); } const orderHash = ZeroEx.getOrderHashHex(firstOrder); - const newmanRunOptions = { + const newmanEnvironmentOptions = { collection: sraReportCollectionJSON, - reporters: 'cli', globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url, orderHash), environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), }; + const newmanReporterOptions = !_.isUndefined(args.output) + ? { + reporters: 'json', + reporter: { + json: { + export: args.output, + }, + }, + } + : { + reporters: 'cli', + }; + const newmanRunOptions = { + ...newmanEnvironmentOptions, + ...newmanReporterOptions, + }; await newmanRunAsync(newmanRunOptions); }; - mainAsync().catch(utils.log); From a3c7af95e17dda91efaff83824c43f8ffb28f078 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 27 Feb 2018 17:25:42 -0800 Subject: [PATCH 13/94] Add stringified order object into globals --- packages/sra-report/src/index.ts | 5 +---- .../sra-report/src/postman_environment_factory.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index fade07095a..bee9db192a 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -1,6 +1,4 @@ #!/usr/bin/env node - -import { ZeroEx } from '0x.js'; import { assert } from '@0xproject/assert'; import { HttpClient } from '@0xproject/connect'; import { Schema, schemas } from '@0xproject/json-schemas'; @@ -62,10 +60,9 @@ const mainAsync = async () => { if (_.isUndefined(firstOrder)) { throw new Error('Could not get any orders from /orders endpoint'); } - const orderHash = ZeroEx.getOrderHashHex(firstOrder); const newmanEnvironmentOptions = { collection: sraReportCollectionJSON, - globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url, orderHash), + globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url, firstOrder), environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), }; const newmanReporterOptions = !_.isUndefined(args.output) diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index b0b69ddade..bea805ca64 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -1,3 +1,7 @@ +import { + SignedOrder, + ZeroEx, + } from '0x.js'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; import * as _ from 'lodash'; @@ -12,9 +16,11 @@ interface EnvironmentValue { } export const postmanEnvironmentFactory = { - createGlobalEnvironment(url: string, orderHash: string) { - const urlEnvironmentValue = createEnvironmentValue('url', url); + createGlobalEnvironment(url: string, order: SignedOrder) { + const orderHash = ZeroEx.getOrderHashHex(order); + const orderEnvironmentValue = createEnvironmentValue('order', JSON.stringify(order)); const orderHashEnvironmentValue = createEnvironmentValue('orderHash', orderHash); + const urlEnvironmentValue = createEnvironmentValue('url', url); const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( _.map(schemas, (schema: Schema) => { @@ -36,6 +42,7 @@ export const postmanEnvironmentFactory = { schemaEnvironmentValues, urlEnvironmentValue, schemaKeysEnvironmentValue, + orderEnvironmentValue, orderHashEnvironmentValue, ); const environment = { From 17328bce53d67da4f99ca8e803b209350f1d2e0d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 28 Feb 2018 16:13:03 -0800 Subject: [PATCH 14/94] Add more globals --- .../src/postman_environment_factory.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index bea805ca64..a8753d9ff0 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -1,7 +1,4 @@ -import { - SignedOrder, - ZeroEx, - } from '0x.js'; +import { SignedOrder, ZeroEx } from '0x.js'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; import * as _ from 'lodash'; @@ -17,10 +14,6 @@ interface EnvironmentValue { export const postmanEnvironmentFactory = { createGlobalEnvironment(url: string, order: SignedOrder) { - const orderHash = ZeroEx.getOrderHashHex(order); - const orderEnvironmentValue = createEnvironmentValue('order', JSON.stringify(order)); - const orderHashEnvironmentValue = createEnvironmentValue('orderHash', orderHash); - const urlEnvironmentValue = createEnvironmentValue('url', url); const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( _.map(schemas, (schema: Schema) => { @@ -37,16 +30,18 @@ export const postmanEnvironmentFactory = { const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { return environmentValue.key; }); - const schemaKeysEnvironmentValue = createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)); - const environmentValues = _.concat( + const allEnvironmentValues = _.concat( schemaEnvironmentValues, - urlEnvironmentValue, - schemaKeysEnvironmentValue, - orderEnvironmentValue, - orderHashEnvironmentValue, + createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)), + createEnvironmentValue('url', url), + createEnvironmentValue('order', JSON.stringify(order)), + createEnvironmentValue('orderMaker', order.maker), + createEnvironmentValue('orderTaker', order.taker), + createEnvironmentValue('orderFeeRecipient', order.feeRecipient), + createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(order)), ); const environment = { - values: environmentValues, + values: allEnvironmentValues, }; return environment; }, From 003d43b03ae373ca24d6d728ffa6b0f2dfcda79a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 28 Feb 2018 16:59:40 -0800 Subject: [PATCH 15/94] Flesh out postman test cases --- .../sra_report.postman_collection.json | 453 ++++++++++++++---- 1 file changed, 363 insertions(+), 90 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index 91faf0c764..569596576c 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -17,7 +17,7 @@ { "listen": "test", "script": { - "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", "type": "text/javascript", "exec": [""] } @@ -28,9 +28,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/token_pairs", + "raw": "{{url}}/token_pairs", "host": ["{{url}}"], - "path": ["v0", "token_pairs"] + "path": ["token_pairs"] }, "description": "" }, @@ -42,9 +42,24 @@ { "listen": "test", "script": { - "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "id": "53ad6be0-4196-47a1-bbf5-fa96e3c5d47e", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "", + "", + "" + ] } } ], @@ -53,9 +68,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/token_pairs?tokenA={{WETH_address}}", + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", "host": ["{{url}}"], - "path": ["v0", "token_pairs"], + "path": ["token_pairs"], "query": [ { "key": "tokenA", @@ -74,9 +89,27 @@ { "listen": "test", "script": { - "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "id": "7224b56e-0000-4d77-b355-f9bfd7d6320f", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "", + "", + "", + "", + "", + "" + ] } } ], @@ -85,9 +118,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/token_pairs?tokenB={{WETH_address}}", + "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", "host": ["{{url}}"], - "path": ["v0", "token_pairs"], + "path": ["token_pairs"], "query": [ { "key": "tokenB", @@ -106,9 +139,26 @@ { "listen": "test", "script": { - "id": "e6a00c7a-acc8-4f51-b0c9-11d005ee1dac", + "id": "32a19952-90c7-4f11-b95c-815b0746ef8b", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenAEnvKey = 'WETH_address';", + "const filterTokenBEnvKey = 'ZRX_address';", + "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", + "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA') === filterTokenAAddress;", + " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA') === filterTokenBAddress;", + " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB') === filterTokenAAddress;", + " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB') === filterTokenBAddress;", + " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] } } ], @@ -117,9 +167,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", "host": ["{{url}}"], - "path": ["v0", "token_pairs"], + "path": ["token_pairs"], "query": [ { "key": "tokenA", @@ -157,7 +207,7 @@ "const responseJsonData = pm.response.json();", "", "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", "});", "" ] @@ -175,7 +225,7 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "118f47dd-1d93-4288-841f-de88783eff3b", "type": "text/javascript", "exec": [""] } @@ -186,9 +236,9 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -201,9 +251,22 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "f10e0a91-3401-4023-afef-5bff93a25232", "type": "text/javascript", - "exec": [""] + "exec": [ + "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", + "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", + " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", + " });", + "});", + "", + "", + "" + ] } } ], @@ -212,9 +275,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "exchangeContractAddress", + "value": "{{EXCHANGE_CONTRACT_address}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -227,9 +297,26 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "d915624e-9d71-4499-a133-cc8268199895", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", + " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", + " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", + " pm.expect(condition).to.be.true;", + " });", + "});", + "", + "", + "" + ] } } ], @@ -238,9 +325,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "tokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -253,9 +347,22 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "5599a829-b127-4b21-8690-12cc764972a9", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "", + "", + "" + ] } } ], @@ -264,9 +371,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "makerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -279,9 +393,22 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "d5ac5604-b13b-40c2-ba31-3033f52136a2", "type": "text/javascript", - "exec": [""] + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "", + "", + "" + ] } } ], @@ -290,9 +417,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "takerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -305,9 +439,20 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "efc85eb8-7791-4b14-9dee-c5e80adec4b8", "type": "text/javascript", - "exec": [""] + "exec": [ + "const orderMakerEnvKey = 'orderMaker';", + "const referenceOrderMakerAddress = pm.globals.get(orderMakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", + " });", + "});", + "" + ] } } ], @@ -316,9 +461,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?maker={{orderMaker}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "maker", + "value": "{{orderMaker}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -331,9 +483,22 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "a299a290-58b9-434c-b5e8-98ef9e5f70da", "type": "text/javascript", - "exec": [""] + "exec": [ + "const orderTakerEnvKey = 'orderTaker';", + "const referenceOrderTakerAddress = pm.globals.get(orderTakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedTakerAddress = _.get(order, 'taker');", + " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", + " });", + "});", + "", + "", + "" + ] } } ], @@ -342,9 +507,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?taker={{orderTaker}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "taker", + "value": "{{orderTaker}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -357,9 +529,25 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "e80aa474-3352-45dd-87ef-2980bab961fb", "type": "text/javascript", - "exec": [""] + "exec": [ + "const orderTraderEnvKey = 'orderMaker';", + "const referenceOrderTraderAddress = pm.globals.get(orderTraderEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " const returnedTakerAddress = _.get(order, 'taker');", + " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", + " pm.expect(condition).to.be.true;", + " });", + "});", + "", + "", + "", + "" + ] } } ], @@ -368,9 +556,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?trader={{orderMaker}}", "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "trader", + "value": "{{orderMaker}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -383,9 +578,23 @@ { "listen": "test", "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", + "id": "fb34198e-221b-406d-8da4-4e7eed400cbe", "type": "text/javascript", - "exec": [""] + "exec": [ + "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", + "const referenceOrderFeeRecipientAddress = pm.globals.get(orderFeeRecipientEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", + " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", + " });", + "});", + "", + "", + "", + "" + ] } } ], @@ -394,35 +603,16 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/orders", + "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", "host": ["{{url}}"], - "path": ["v0", "orders"] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "makerTokenAddress and takerTokenAddress params", - "event": [ - { - "listen": "test", - "script": { - "id": "bd42f688-23d2-4a9c-941c-d5bf34f85519", - "type": "text/javascript", - "exec": [""] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/v0/orders", - "host": ["{{url}}"], - "path": ["v0", "orders"] + "path": ["orders"], + "query": [ + { + "key": "feeRecipient", + "value": "{{orderFeeRecipient}}", + "equals": true + } + ] }, "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." @@ -449,7 +639,7 @@ "const responseJsonData = pm.response.json();", "", "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", "});", "" ] @@ -463,14 +653,65 @@ "item": [ { "name": "orderHash param", + "event": [ + { + "listen": "test", + "script": { + "id": "e847f317-6346-4ee5-bd94-7554eb3073b3", + "type": "text/javascript", + "exec": [ + "const orderEnvKey = 'order';", + "const referenceOrderString = pm.globals.get(orderEnvKey);", + "const referenceOrderJson = JSON.parse(referenceOrderString);", + "const orderPropertyNames = [", + " 'maker',", + " 'taker',", + " 'makerFee',", + " 'takerFee',", + " 'makerTokenAmount',", + " 'takerTokenAmount',", + " 'makerTokenAddress',", + " 'takerTokenAddress',", + " 'salt',", + " 'feeRecipient',", + " 'expirationUnixTimestampSec',", + " 'exchangeContractAddress'", + "];", + "const signaturePropertyNames = [", + " 'v',", + " 'r',", + " 's'", + "];", + "const returnedOrderJson = pm.response.json();", + "pm.test('Order is properly retreived', function() {", + " _.forEach(orderPropertyNames, function(propertyName) {", + " const returnedProperty = _.get(returnedOrderJson, propertyName);", + " const referenceProperty = _.get(referenceOrderJson, propertyName);", + " pm.expect(returnedProperty).to.be.equal(referenceProperty);", + " });", + " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", + " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", + " _.forEach(signaturePropertyNames, function(propertyName) {", + " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", + " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", + " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", + " });", + "});", + "", + "", + "" + ] + } + } + ], "request": { "method": "GET", "header": [], "body": {}, "url": { - "raw": "{{url}}/v0/order/{{_orderHash}}", + "raw": "{{url}}/order/{{orderHash}}", "host": ["{{url}}"], - "path": ["v0", "order", "{{_orderHash}}"] + "path": ["order", "{{orderHash}}"] }, "description": "Retrieves a specific order by orderHash." }, @@ -496,7 +737,7 @@ "const responseJsonData = pm.response.json();", "", "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", "});", "" ] @@ -514,9 +755,35 @@ { "listen": "test", "script": { - "id": "1a0acd0e-5e4a-4e08-b4e9-291a4e9c433a", + "id": "67d95152-3561-4db2-aaba-d26ceb44b4fd", "type": "text/javascript", - "exec": [""] + "exec": [ + "const baseTokenEnvKey = 'ZRX_address';", + "const quoteTokenEnvKey = 'WETH_address';", + "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", + "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orderbook is properly filtered', function() {", + " const bids = _.get(responseJsonData, 'bids');", + " const asks = _.get(responseJsonData, 'asks');", + " _.forEach(bids, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", + " });", + " _.forEach(asks, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", + " });", + "});", + "", + "", + "", + "" + ] } } ], @@ -526,9 +793,9 @@ "body": {}, "url": { "raw": - "{{url}}/v0/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", + "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", "host": ["{{url}}"], - "path": ["v0", "orderbook"], + "path": ["orderbook"], "query": [ { "key": "baseTokenAddress", @@ -567,7 +834,7 @@ "const responseJsonData = pm.response.json();", "", "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", "});", "" ] @@ -595,9 +862,9 @@ "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" }, "url": { - "raw": "{{url}}/v0/fees", + "raw": "{{url}}/fees", "host": ["{{url}}"], - "path": ["v0", "fees"] + "path": ["fees"] }, "description": "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." @@ -642,7 +909,6 @@ "exec": [ "const schemaKeysString = pm.globals.get('schemaKeys');", "const schemaKeys = JSON.parse(schemaKeysString);", - "", "_.forEach(schemaKeys, function(schemaKey) {", " const schemaString = pm.globals.get(schemaKey);", " const schema = JSON.parse(schemaString);", @@ -657,7 +923,14 @@ "script": { "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", "type": "text/javascript", - "exec": [""] + "exec": [ + "pm.test('Has Content-Type header with value application/json', function () {", + " pm.response.to.have.header('Content-Type');", + " const contentType = postman.getResponseHeader('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "" + ] } } ] From a74a04c5d50f836a042ff7222f9311e1eec71e40 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 1 Mar 2018 13:06:08 -0800 Subject: [PATCH 16/94] Fix yarn.lock --- yarn.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yarn.lock b/yarn.lock index 5ac18dc230..4081bbd786 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10224,6 +10224,10 @@ xml-js@^1.3.2: dependencies: sax "^1.2.4" +xmlbuilder@9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + xmlhttprequest@*, xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" From 89bfcafb80ddc96659ebeff365a9247c053330fd Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 1 Mar 2018 13:17:49 -0800 Subject: [PATCH 17/94] Add ethers types --- packages/sra-report/tsconfig.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/sra-report/tsconfig.json b/packages/sra-report/tsconfig.json index 3d967d05f0..8114d99cd0 100644 --- a/packages/sra-report/tsconfig.json +++ b/packages/sra-report/tsconfig.json @@ -3,5 +3,9 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "include": [ + "./src/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts" + ] } From 92b9dbd706768fd0edfc8f72a90f197abd8b3d66 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 1 Mar 2018 14:41:16 -0800 Subject: [PATCH 18/94] Fix up some postman tests --- .../sra_report.postman_collection.json | 70 ++++++------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index 569596576c..d1d384163c 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -42,7 +42,7 @@ { "listen": "test", "script": { - "id": "53ad6be0-4196-47a1-bbf5-fa96e3c5d47e", + "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", "type": "text/javascript", "exec": [ "const filterTokenEnvKey = 'WETH_address';", @@ -50,14 +50,12 @@ "const responseJsonData = pm.response.json();", "pm.test('Token pairs are properly filtered', function() {", " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB') === filterTokenAddress;", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", " const condition = tokenAIsFilterToken || tokenBIsFilterToken", " pm.expect(condition).to.be.true;", " });", "});", - "", - "", "" ] } @@ -89,7 +87,7 @@ { "listen": "test", "script": { - "id": "7224b56e-0000-4d77-b355-f9bfd7d6320f", + "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", "type": "text/javascript", "exec": [ "const filterTokenEnvKey = 'WETH_address';", @@ -97,17 +95,12 @@ "const responseJsonData = pm.response.json();", "pm.test('Token pairs are properly filtered', function() {", " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB') === filterTokenAddress;", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", " const condition = tokenAIsFilterToken || tokenBIsFilterToken", " pm.expect(condition).to.be.true;", " });", "});", - "", - "", - "", - "", - "", "" ] } @@ -139,7 +132,7 @@ { "listen": "test", "script": { - "id": "32a19952-90c7-4f11-b95c-815b0746ef8b", + "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", "type": "text/javascript", "exec": [ "const filterTokenAEnvKey = 'WETH_address';", @@ -149,10 +142,10 @@ "const responseJsonData = pm.response.json();", "pm.test('Token pairs are properly filtered', function() {", " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA') === filterTokenAAddress;", - " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA') === filterTokenBAddress;", - " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB') === filterTokenAAddress;", - " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB') === filterTokenBAddress;", + " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", + " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", + " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", + " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", " pm.expect(condition).to.be.true;", " });", @@ -251,7 +244,7 @@ { "listen": "test", "script": { - "id": "f10e0a91-3401-4023-afef-5bff93a25232", + "id": "e782ae77-66bf-4096-9190-ef9e7501b316", "type": "text/javascript", "exec": [ "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", @@ -263,8 +256,6 @@ " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", " });", "});", - "", - "", "" ] } @@ -297,7 +288,7 @@ { "listen": "test", "script": { - "id": "d915624e-9d71-4499-a133-cc8268199895", + "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", "type": "text/javascript", "exec": [ "const filterTokenEnvKey = 'ZRX_address';", @@ -313,8 +304,6 @@ " pm.expect(condition).to.be.true;", " });", "});", - "", - "", "" ] } @@ -347,7 +336,7 @@ { "listen": "test", "script": { - "id": "5599a829-b127-4b21-8690-12cc764972a9", + "id": "01eb5865-edec-4216-8a90-c36d790366fc", "type": "text/javascript", "exec": [ "const filterTokenEnvKey = 'ZRX_address';", @@ -359,8 +348,6 @@ " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", " });", "});", - "", - "", "" ] } @@ -393,7 +380,7 @@ { "listen": "test", "script": { - "id": "d5ac5604-b13b-40c2-ba31-3033f52136a2", + "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", "type": "text/javascript", "exec": [ "const filterTokenEnvKey = 'ZRX_address';", @@ -405,8 +392,6 @@ " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", " });", "});", - "", - "", "" ] } @@ -483,7 +468,7 @@ { "listen": "test", "script": { - "id": "a299a290-58b9-434c-b5e8-98ef9e5f70da", + "id": "c9c18715-c7cc-4064-8241-68a362a233fe", "type": "text/javascript", "exec": [ "const orderTakerEnvKey = 'orderTaker';", @@ -495,8 +480,6 @@ " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", " });", "});", - "", - "", "" ] } @@ -529,7 +512,7 @@ { "listen": "test", "script": { - "id": "e80aa474-3352-45dd-87ef-2980bab961fb", + "id": "2c9cdb6a-267c-48b3-9ad7-df7abb75ddf4", "type": "text/javascript", "exec": [ "const orderTraderEnvKey = 'orderMaker';", @@ -543,9 +526,6 @@ " pm.expect(condition).to.be.true;", " });", "});", - "", - "", - "", "" ] } @@ -578,7 +558,7 @@ { "listen": "test", "script": { - "id": "fb34198e-221b-406d-8da4-4e7eed400cbe", + "id": "06ea36eb-6a35-4fc7-8017-59876b1a1cb4", "type": "text/javascript", "exec": [ "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", @@ -590,9 +570,6 @@ " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", " });", "});", - "", - "", - "", "" ] } @@ -657,7 +634,7 @@ { "listen": "test", "script": { - "id": "e847f317-6346-4ee5-bd94-7554eb3073b3", + "id": "3beca3a2-e0ad-45ff-b4d1-eace859af0bf", "type": "text/javascript", "exec": [ "const orderEnvKey = 'order';", @@ -697,8 +674,6 @@ " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", " });", "});", - "", - "", "" ] } @@ -755,7 +730,7 @@ { "listen": "test", "script": { - "id": "67d95152-3561-4db2-aaba-d26ceb44b4fd", + "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", "type": "text/javascript", "exec": [ "const baseTokenEnvKey = 'ZRX_address';", @@ -772,16 +747,13 @@ " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", " });", - " _.forEach(asks, function(order) {", + " _.forEach(asks, function(order) {", " const makerTokenAddress = _.get(order, 'makerTokenAddress');", " const takerTokenAddress = _.get(order, 'takerTokenAddress');", " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", " });", "});", - "", - "", - "", "" ] } From 3592ebef0800fe3c32c9f1e4870b667a07a322bb Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 19:55:40 +0100 Subject: [PATCH 19/94] Move over AnchorTitle and associated elements --- .../ts/components/inputs/hash_input.tsx | 3 +- .../website/ts/components/token_balances.tsx | 2 +- .../website/ts/components/top_bar/top_bar.tsx | 3 +- .../ts/pages/documentation/documentation.tsx | 2 +- .../pages/documentation/event_definition.tsx | 4 +- .../ts/pages/documentation/method_block.tsx | 4 +- .../website/ts/pages/documentation/type.tsx | 9 +- .../pages/documentation/type_definition.tsx | 4 +- packages/website/ts/pages/faq/faq.tsx | 3 +- .../website/ts/pages/shared/anchor_title.tsx | 86 ------------------- .../ts/pages/shared/markdown_link_block.tsx | 3 +- .../ts/pages/shared/markdown_section.tsx | 3 +- .../ts/pages/shared/nested_sidebar_menu.tsx | 6 +- .../ts/pages/shared/section_header.tsx | 3 +- packages/website/ts/pages/wiki/wiki.tsx | 3 +- packages/website/ts/types.ts | 10 --- packages/website/ts/utils/constants.ts | 2 - packages/website/ts/utils/utils.ts | 3 - 18 files changed, 28 insertions(+), 125 deletions(-) delete mode 100644 packages/website/ts/pages/shared/anchor_title.tsx diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx index 5a3d34fe6f..28305637da 100644 --- a/packages/website/ts/components/inputs/hash_input.tsx +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -1,10 +1,11 @@ import { Order, ZeroEx } from '0x.js'; +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; import { FakeTextField } from 'ts/components/ui/fake_text_field'; -import { HashData, Styles } from 'ts/types'; +import { HashData } from 'ts/types'; import { constants } from 'ts/utils/constants'; const styles: Styles = { diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 894edf0a88..b569a9165e 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -1,4 +1,5 @@ import { ZeroEx } from '0x.js'; +import { styles } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import DharmaLoanFrame from 'dharma-loan-frame'; import * as _ from 'lodash'; @@ -29,7 +30,6 @@ import { EtherscanLinkSuffixes, Networks, ScreenWidths, - Styles, Token, TokenByAddress, TokenVisibility, diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index b2b2d2ebd4..8292254407 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -1,3 +1,4 @@ +import { styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Drawer from 'material-ui/Drawer'; import Menu from 'material-ui/Menu'; @@ -14,7 +15,7 @@ import { Identicon } from 'ts/components/ui/identicon'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Deco, DocsMenu, Key, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types'; +import { Deco, DocsMenu, Key, MenuSubsectionsBySection, ProviderType, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 699bef7a81..ce5f7eb6ce 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -1,3 +1,4 @@ +import { styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; @@ -23,7 +24,6 @@ import { Networks, Property, SolidityMethod, - Styles, SupportedDocJson, TypeDefinitionByName, TypescriptMethod, diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx index e62c9ecbda..7ac17e608d 100644 --- a/packages/website/ts/pages/documentation/event_definition.tsx +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -1,9 +1,9 @@ +import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared/anchor_title'; import * as _ from 'lodash'; import * as React from 'react'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Type } from 'ts/pages/documentation/type'; -import { AnchorTitle } from 'ts/pages/shared/anchor_title'; -import { Event, EventArg, HeaderSizes } from 'ts/types'; +import { Event, EventArg } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EventDefinitionProps { diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index d2c96bf8cf..e1f09346ba 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -1,11 +1,11 @@ +import { AnchorTitle, HeaderSizes, Styles } from '@0xproject/react-shared/anchor_title'; import * as _ from 'lodash'; import * as React from 'react'; import { Comment } from 'ts/pages/documentation/comment'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { MethodSignature } from 'ts/pages/documentation/method_signature'; import { SourceLink } from 'ts/pages/documentation/source_link'; -import { AnchorTitle } from 'ts/pages/shared/anchor_title'; -import { HeaderSizes, Parameter, SolidityMethod, Styles, TypeDefinitionByName, TypescriptMethod } from 'ts/types'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx index b306fa0532..2fec4c95f6 100644 --- a/packages/website/ts/pages/documentation/type.tsx +++ b/packages/website/ts/pages/documentation/type.tsx @@ -1,3 +1,4 @@ +import { constants as sharedConstants, utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Link as ScrollLink } from 'react-scroll'; @@ -178,12 +179,12 @@ export function Type(props: TypeProps): any { {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( {typeName} @@ -192,7 +193,7 @@ export function Type(props: TypeProps): any { { - constructor(props: AnchorTitleProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - let opacity = 0; - if (this.props.shouldShowAnchor) { - opacity = this.state.isHovering ? 0.6 : 1; - } - return ( -
-
- {this.props.title} -
- - - -
- ); - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } -} diff --git a/packages/website/ts/pages/shared/markdown_link_block.tsx b/packages/website/ts/pages/shared/markdown_link_block.tsx index e4553c87f3..b1f68c9150 100644 --- a/packages/website/ts/pages/shared/markdown_link_block.tsx +++ b/packages/website/ts/pages/shared/markdown_link_block.tsx @@ -1,3 +1,4 @@ +import { utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { configs } from 'ts/utils/configs'; @@ -41,6 +42,6 @@ export class MarkdownLinkBlock extends React.Component { const w = window as any; return new Promise((resolve: (isSupported: boolean) => void) => { From b7d001da88ab604762ddf9df8abcf956d4c2de7f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 19:58:30 +0100 Subject: [PATCH 20/94] Setup initial react-shared sub-package --- packages/react-shared/.gitignore | 4 + packages/react-shared/.npmignore | 5 ++ packages/react-shared/.prettierignore | 2 + packages/react-shared/.prettierrc | 6 ++ packages/react-shared/package.json | 32 +++++++ packages/react-shared/scripts/postpublish.js | 5 ++ .../src/ts/components/anchor_title.tsx | 87 +++++++++++++++++++ packages/react-shared/src/ts/constants.ts | 4 + packages/react-shared/src/ts/globals.d.ts | 0 packages/react-shared/src/ts/index.ts | 6 ++ packages/react-shared/src/ts/types.ts | 9 ++ packages/react-shared/src/ts/utils.ts | 5 ++ packages/react-shared/tsconfig.json | 23 +++++ packages/react-shared/tslint.json | 9 ++ 14 files changed, 197 insertions(+) create mode 100644 packages/react-shared/.gitignore create mode 100644 packages/react-shared/.npmignore create mode 100644 packages/react-shared/.prettierignore create mode 100644 packages/react-shared/.prettierrc create mode 100644 packages/react-shared/package.json create mode 100644 packages/react-shared/scripts/postpublish.js create mode 100644 packages/react-shared/src/ts/components/anchor_title.tsx create mode 100644 packages/react-shared/src/ts/constants.ts create mode 100644 packages/react-shared/src/ts/globals.d.ts create mode 100644 packages/react-shared/src/ts/index.ts create mode 100644 packages/react-shared/src/ts/types.ts create mode 100644 packages/react-shared/src/ts/utils.ts create mode 100644 packages/react-shared/tsconfig.json create mode 100644 packages/react-shared/tslint.json diff --git a/packages/react-shared/.gitignore b/packages/react-shared/.gitignore new file mode 100644 index 0000000000..380bff9bb0 --- /dev/null +++ b/packages/react-shared/.gitignore @@ -0,0 +1,4 @@ +node_modules +yarn.error +lib +src/public/bundle* diff --git a/packages/react-shared/.npmignore b/packages/react-shared/.npmignore new file mode 100644 index 0000000000..87bc304368 --- /dev/null +++ b/packages/react-shared/.npmignore @@ -0,0 +1,5 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json diff --git a/packages/react-shared/.prettierignore b/packages/react-shared/.prettierignore new file mode 100644 index 0000000000..7349ffb703 --- /dev/null +++ b/packages/react-shared/.prettierignore @@ -0,0 +1,2 @@ +lib +package.json diff --git a/packages/react-shared/.prettierrc b/packages/react-shared/.prettierrc new file mode 100644 index 0000000000..58a17fac22 --- /dev/null +++ b/packages/react-shared/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "printWidth": 120, + "trailingComma": all, + "singleQuote": true +} diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json new file mode 100644 index 0000000000..69d298559c --- /dev/null +++ b/packages/react-shared/package.json @@ -0,0 +1,32 @@ +{ + "name": "@0xproject/react-shared", + "version": "0.0.1", + "description": "0x shared react components", + "main": "lib/index.js", + "scripts": { + "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", + "build": "tsc" + }, + "author": "Fabio Berger", + "license": "MIT", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.9", + "@types/lodash": "^4.14.86", + "@types/node": "^8.0.53", + "@types/material-ui": "0.18.0", + "@types/react": "^15.0.15", + "@types/react-dom": "^0.14.23", + "shx": "^0.2.2", + "tslint": "^5.9.1", + "typescript": "2.7.1" + }, + "dependencies": { + "basscss": "^8.0.3", + "material-ui": "^0.17.1", + "react": "15.6.1", + "react-dom": "15.6.1", + "lodash": "^4.17.4", + "react-scroll": "^1.5.2", + "react-tap-event-plugin": "^2.0.1" + } +} diff --git a/packages/react-shared/scripts/postpublish.js b/packages/react-shared/scripts/postpublish.js new file mode 100644 index 0000000000..639656c7ee --- /dev/null +++ b/packages/react-shared/scripts/postpublish.js @@ -0,0 +1,5 @@ +const postpublish_utils = require('../../../scripts/postpublish_utils'); +const packageJSON = require('../package.json'); + +const subPackageName = packageJSON.name; +postpublish_utils.standardPostPublishAsync(subPackageName); diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx new file mode 100644 index 0000000000..9b8e6854f9 --- /dev/null +++ b/packages/react-shared/src/ts/components/anchor_title.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { Link as ScrollLink } from 'react-scroll'; + +import { constants } from '../constants'; +import { HeaderSizes, Styles } from '../types'; +import { utils } from '../utils'; + +const headerSizeToScrollOffset: { [headerSize: string]: number } = { + h2: -20, + h3: 0, +}; + +interface AnchorTitleProps { + title: string | React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; +} + +interface AnchorTitleState { + isHovering: boolean; +} + +const styles: Styles = { + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + }, + h2: { + fontSize: '1.5em', + fontWeight: 400, + }, + h3: { + fontSize: '1.17em', + }, +}; + +export class AnchorTitle extends React.Component { + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + opacity = this.state.isHovering ? 0.6 : 1; + } + return ( +
+
+ {this.props.title} +
+ + + +
+ ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/react-shared/src/ts/constants.ts b/packages/react-shared/src/ts/constants.ts new file mode 100644 index 0000000000..20d0c6e921 --- /dev/null +++ b/packages/react-shared/src/ts/constants.ts @@ -0,0 +1,4 @@ +export const constants = { + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', +}; diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts new file mode 100644 index 0000000000..d33638e620 --- /dev/null +++ b/packages/react-shared/src/ts/index.ts @@ -0,0 +1,6 @@ +export { AnchorTitle } from './components/anchor_title'; + +export { HeaderSizes, Styles } from './types'; + +export { utils } from './utils'; +export { constants } from './constants'; diff --git a/packages/react-shared/src/ts/types.ts b/packages/react-shared/src/ts/types.ts new file mode 100644 index 0000000000..e4fe7bff9c --- /dev/null +++ b/packages/react-shared/src/ts/types.ts @@ -0,0 +1,9 @@ +export interface Styles { + [name: string]: React.CSSProperties; +} + +export enum HeaderSizes { + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', +} diff --git a/packages/react-shared/src/ts/utils.ts b/packages/react-shared/src/ts/utils.ts new file mode 100644 index 0000000000..822b674961 --- /dev/null +++ b/packages/react-shared/src/ts/utils.ts @@ -0,0 +1,5 @@ +export const utils = { + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, +}; diff --git a/packages/react-shared/tsconfig.json b/packages/react-shared/tsconfig.json new file mode 100644 index 0000000000..69e8cdc20a --- /dev/null +++ b/packages/react-shared/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": ["es2017", "dom"], + "sourceMap": true, + "noImplicitReturns": true, + "allowSyntheticDefaultImports": true, + "outDir": "./lib/", + "jsx": "react", + "baseUrl": "./", + "allowJs": true, + "strictNullChecks": false, + "noImplicitThis": false, + "declaration": false, + "paths": { + "*": ["node_modules/@types/*", "*"] + }, + "pretty": true, + "strict": true + }, + "include": ["./src/ts/**/*"] +} diff --git a/packages/react-shared/tslint.json b/packages/react-shared/tslint.json new file mode 100644 index 0000000000..d6a5f5031e --- /dev/null +++ b/packages/react-shared/tslint.json @@ -0,0 +1,9 @@ +{ + "extends": ["@0xproject/tslint-config"], + "rules": { + "no-implicit-dependencies": false, + "no-object-literal-type-assertion": false, + "completed-docs": false, + "prefer-function-over-method": false + } +} From 223ddc0f68ea67570fe971745522a0e4b9036f2f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 20:15:48 +0100 Subject: [PATCH 21/94] Add react-shared as dep of website --- packages/website/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/website/package.json b/packages/website/package.json index 07122cc7ce..da08ea0326 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -20,6 +20,7 @@ "dependencies": { "0x.js": "^0.32.4", "@0xproject/subproviders": "^0.5.0", + "@0xproject/react-shared": "^0.0.1", "@0xproject/utils": "^0.3.4", "accounting": "^0.4.1", "basscss": "^8.0.3", From 8782559c33b5b57f643c62569b83bee6cfb7e48b Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 20:29:07 +0100 Subject: [PATCH 22/94] remove top-level configs --- packages/react-shared/.gitignore | 4 ---- packages/react-shared/.prettierignore | 2 -- packages/react-shared/.prettierrc | 6 ------ 3 files changed, 12 deletions(-) delete mode 100644 packages/react-shared/.gitignore delete mode 100644 packages/react-shared/.prettierignore delete mode 100644 packages/react-shared/.prettierrc diff --git a/packages/react-shared/.gitignore b/packages/react-shared/.gitignore deleted file mode 100644 index 380bff9bb0..0000000000 --- a/packages/react-shared/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -yarn.error -lib -src/public/bundle* diff --git a/packages/react-shared/.prettierignore b/packages/react-shared/.prettierignore deleted file mode 100644 index 7349ffb703..0000000000 --- a/packages/react-shared/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -lib -package.json diff --git a/packages/react-shared/.prettierrc b/packages/react-shared/.prettierrc deleted file mode 100644 index 58a17fac22..0000000000 --- a/packages/react-shared/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "tabWidth": 4, - "printWidth": 120, - "trailingComma": all, - "singleQuote": true -} From bee90abbc47e099d66cb51c684f9f98cbdf6af3f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 20:29:17 +0100 Subject: [PATCH 23/94] Add changelog and readme --- packages/react-shared/CHANGELOG.md | 3 ++ packages/react-shared/README.md | 47 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/react-shared/CHANGELOG.md create mode 100644 packages/react-shared/README.md diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md new file mode 100644 index 0000000000..7c3ac2adfd --- /dev/null +++ b/packages/react-shared/CHANGELOG.md @@ -0,0 +1,3 @@ +# CHANGELOG + +## vX.X.X - _TBD, 2018_ diff --git a/packages/react-shared/README.md b/packages/react-shared/README.md new file mode 100644 index 0000000000..da7ff83af8 --- /dev/null +++ b/packages/react-shared/README.md @@ -0,0 +1,47 @@ +## @0xproject/react-shared + +Contains React components & frontend types/utils shared between 0x projects. + +## Installation + +```bash +yarn add @0xproject/react-shared +``` + +## Contributing + +We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +```bash +yarn build +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` From e11e26a35274a96fd2d4b7f37e60b89220115994 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 3 Mar 2018 20:29:24 +0100 Subject: [PATCH 24/94] Add clean command --- packages/react-shared/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 69d298559c..8359158427 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -5,7 +5,8 @@ "main": "lib/index.js", "scripts": { "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", - "build": "tsc" + "build": "tsc", + "clean": "shx rm -rf lib" }, "author": "Fabio Berger", "license": "MIT", From 874e6678491d25aa7db300d68bdcb73863685c62 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 5 Mar 2018 07:10:55 +0100 Subject: [PATCH 25/94] Bug fixes --- packages/react-shared/package.json | 1 + .../src/ts/components/anchor_title.tsx | 4 +-- packages/react-shared/tsconfig.json | 13 ++------- .../website/ts/components/token_balances.tsx | 2 +- .../website/ts/components/top_bar/top_bar.tsx | 2 +- packages/website/ts/components/ui/badge.tsx | 2 +- .../ts/components/ui/fake_text_field.tsx | 2 +- packages/website/ts/pages/about/about.tsx | 3 ++- packages/website/ts/pages/about/profile.tsx | 3 ++- .../ts/pages/documentation/documentation.tsx | 2 +- .../pages/documentation/event_definition.tsx | 2 +- .../ts/pages/documentation/method_block.tsx | 2 +- .../pages/documentation/type_definition.tsx | 2 +- packages/website/ts/pages/faq/faq.tsx | 2 +- packages/website/ts/pages/not_found.tsx | 2 +- .../ts/pages/shared/markdown_section.tsx | 2 +- .../ts/pages/shared/section_header.tsx | 2 +- packages/website/ts/pages/wiki/wiki.tsx | 2 +- yarn.lock | 27 ++++++++++++++++++- 19 files changed, 48 insertions(+), 29 deletions(-) diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 8359158427..e1cc2ad8b6 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "description": "0x shared react components", "main": "lib/index.js", + "types": "lib/index.d.ts", "scripts": { "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", "build": "tsc", diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx index 9b8e6854f9..aa839cac9a 100644 --- a/packages/react-shared/src/ts/components/anchor_title.tsx +++ b/packages/react-shared/src/ts/components/anchor_title.tsx @@ -10,14 +10,14 @@ const headerSizeToScrollOffset: { [headerSize: string]: number } = { h3: 0, }; -interface AnchorTitleProps { +export interface AnchorTitleProps { title: string | React.ReactNode; id: string; headerSize: HeaderSizes; shouldShowAnchor: boolean; } -interface AnchorTitleState { +export interface AnchorTitleState { isHovering: boolean; } diff --git a/packages/react-shared/tsconfig.json b/packages/react-shared/tsconfig.json index 69e8cdc20a..6e71fb2e47 100644 --- a/packages/react-shared/tsconfig.json +++ b/packages/react-shared/tsconfig.json @@ -1,23 +1,14 @@ { + "extends": "../../tsconfig", "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": ["es2017", "dom"], - "sourceMap": true, - "noImplicitReturns": true, - "allowSyntheticDefaultImports": true, "outDir": "./lib/", "jsx": "react", "baseUrl": "./", - "allowJs": true, "strictNullChecks": false, "noImplicitThis": false, - "declaration": false, "paths": { "*": ["node_modules/@types/*", "*"] - }, - "pretty": true, - "strict": true + } }, "include": ["./src/ts/**/*"] } diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index b569a9165e..e321b247b1 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -1,5 +1,5 @@ import { ZeroEx } from '0x.js'; -import { styles } from '@0xproject/react-shared'; +import { Styles } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import DharmaLoanFrame from 'dharma-loan-frame'; import * as _ from 'lodash'; diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 8292254407..df6110de33 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -1,4 +1,4 @@ -import { styles } from '@0xproject/react-shared'; +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Drawer from 'material-ui/Drawer'; import Menu from 'material-ui/Menu'; diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx index 056d741e04..3e1c545be8 100644 --- a/packages/website/ts/components/ui/badge.tsx +++ b/packages/website/ts/components/ui/badge.tsx @@ -1,6 +1,6 @@ +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { Styles } from 'ts/types'; const styles: Styles = { badge: { diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx index f3d9410f65..646ae98f62 100644 --- a/packages/website/ts/components/ui/fake_text_field.tsx +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -1,6 +1,6 @@ +import { Styles } from '@0xproject/react-shared'; import * as React from 'react'; import { InputLabel } from 'ts/components/ui/input_label'; -import { Styles } from 'ts/types'; const styles: Styles = { hr: { diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index b99dc34ab6..65dcdfa7b6 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -1,3 +1,4 @@ +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; @@ -5,7 +6,7 @@ import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Profile } from 'ts/pages/about/profile'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProfileInfo, Styles } from 'ts/types'; +import { ProfileInfo } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index 18b4e0d5a2..6d9d10272b 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -1,6 +1,7 @@ +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { ProfileInfo, Styles } from 'ts/types'; +import { ProfileInfo } from 'ts/types'; import { colors } from 'ts/utils/colors'; const IMAGE_DIMENSION = 149; diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index ce5f7eb6ce..88331d1a7f 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -1,4 +1,4 @@ -import { styles } from '@0xproject/react-shared'; +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx index 7ac17e608d..aead27d789 100644 --- a/packages/website/ts/pages/documentation/event_definition.tsx +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared/anchor_title'; +import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index e1f09346ba..a1f0b38abf 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes, Styles } from '@0xproject/react-shared/anchor_title'; +import { AnchorTitle, HeaderSizes, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Comment } from 'ts/pages/documentation/comment'; diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx index 32c388acec..dd83d120d9 100644 --- a/packages/website/ts/pages/documentation/type_definition.tsx +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared/anchor_title'; +import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Comment } from 'ts/pages/documentation/comment'; diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index bcbc628f1c..70d1166227 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -1,4 +1,4 @@ -import { styles } from '@0xproject/react-shared'; +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index ad37f62420..ff25a35e96 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -1,9 +1,9 @@ +import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Styles } from 'ts/types'; import { Translate } from 'ts/utils/translate'; export interface NotFoundProps { diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx index 18cbf70c04..d03b30f4be 100644 --- a/packages/website/ts/pages/shared/markdown_section.tsx +++ b/packages/website/ts/pages/shared/markdown_section.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared/anchor_title'; +import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx index 758aec574d..3f661a1e57 100644 --- a/packages/website/ts/pages/shared/section_header.tsx +++ b/packages/website/ts/pages/shared/section_header.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared/anchor_title'; +import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; import * as React from 'react'; import { Element as ScrollElement } from 'react-scroll'; import { colors } from 'ts/utils/colors'; diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index d65834a9aa..b22ce58265 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -1,4 +1,4 @@ -import { HeaderSizes, styles } from '@0xproject/react-shared'; +import { HeaderSizes, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import RaisedButton from 'material-ui/RaisedButton'; diff --git a/yarn.lock b/yarn.lock index a1e35e3f1f..e3b20ba339 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1995,6 +1995,10 @@ commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" +commander@^2.12.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + commander@^2.9.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -5053,7 +5057,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.6.1: +js-yaml@^3.6.1, js-yaml@^3.7.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -9129,6 +9133,10 @@ tslib@^1.0.0, tslib@^1.7.1, tslib@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" +tslib@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" + tslint-config-0xproject@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/tslint-config-0xproject/-/tslint-config-0xproject-0.0.2.tgz#39901e0c0b3e9388f00092a28b90c015395d5bba" @@ -9165,6 +9173,23 @@ tslint@5.8.0: tslib "^1.7.1" tsutils "^2.12.1" +tslint@^5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.12.1" + tsutils@^1.4.0: version "1.9.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" From 5a90fece8020f9be5c0f52f6ccf65dacb824b1cd Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 5 Mar 2018 13:53:13 +0100 Subject: [PATCH 26/94] Moved over all pages/shared components and dependencies to react-shared --- packages/react-shared/package.json | 4 ++- .../src/ts/components/anchor_title.tsx | 4 +-- .../ts/components}/markdown_code_block.tsx | 4 +-- .../ts/components}/markdown_link_block.tsx | 14 ++++---- .../src/ts/components}/markdown_section.tsx | 17 ++++++---- .../ts/components}/nested_sidebar_menu.tsx | 18 ++++++----- .../src/ts/components}/section_header.tsx | 13 +++++--- .../src/ts/components}/version_drop_down.tsx | 7 ++-- packages/react-shared/src/ts/globals.d.ts | 1 + packages/react-shared/src/ts/index.ts | 12 +++++-- packages/react-shared/src/ts/types.ts | 4 +++ packages/react-shared/src/ts/utils.ts | 5 --- .../src}/ts/utils/colors.ts | 0 .../src/ts/{ => utils}/constants.ts | 2 ++ packages/react-shared/src/ts/utils/utils.ts | 32 +++++++++++++++++++ packages/website/package.json | 1 - .../dialogs/blockchain_err_dialog.tsx | 2 +- .../dialogs/eth_weth_conversion_dialog.tsx | 2 +- .../dialogs/ledger_config_dialog.tsx | 2 +- .../dialogs/portal_disclaimer_dialog.tsx | 2 +- .../dialogs/u2f_not_supported_dialog.tsx | 2 +- .../website/ts/components/eth_wrappers.tsx | 2 +- packages/website/ts/components/fill_order.tsx | 2 +- .../ts/components/fill_warning_dialog.tsx | 2 +- .../flash_messages/token_send_completed.tsx | 2 +- .../flash_messages/transaction_submitted.tsx | 2 +- packages/website/ts/components/footer.tsx | 2 +- .../generate_order/generate_order_form.tsx | 2 +- .../generate_order/new_token_form.tsx | 2 +- .../ts/components/inputs/address_input.tsx | 2 +- .../inputs/balance_bounded_input.tsx | 2 +- .../components/inputs/token_amount_input.tsx | 2 +- .../ts/components/inputs/token_input.tsx | 2 +- packages/website/ts/components/portal.tsx | 2 +- .../website/ts/components/token_balances.tsx | 3 +- .../components/top_bar/provider_display.tsx | 2 +- .../ts/components/top_bar/provider_picker.tsx | 2 +- .../website/ts/components/top_bar/top_bar.tsx | 6 ++-- .../components/top_bar/top_bar_menu_item.tsx | 2 +- .../components/track_token_confirmation.tsx | 2 +- .../trade_history/trade_history_item.tsx | 2 +- packages/website/ts/components/ui/alert.tsx | 2 +- .../website/ts/components/ui/copy_icon.tsx | 2 +- .../ts/components/ui/etherscan_icon.tsx | 2 +- .../website/ts/components/ui/input_label.tsx | 2 +- .../components/ui/lifecycle_raised_button.tsx | 2 +- packages/website/ts/components/ui/party.tsx | 2 +- .../ts/components/ui/required_label.tsx | 2 +- .../website/ts/components/ui/swap_icon.tsx | 2 +- packages/website/ts/globals.d.ts | 1 - packages/website/ts/pages/about/about.tsx | 3 +- packages/website/ts/pages/about/profile.tsx | 3 +- .../ts/pages/documentation/comment.tsx | 2 +- .../ts/pages/documentation/doc_page.tsx | 3 +- .../ts/pages/documentation/docs_info.ts | 2 +- .../ts/pages/documentation/documentation.tsx | 23 +++++++------ .../pages/documentation/event_definition.tsx | 3 +- .../ts/pages/documentation/method_block.tsx | 3 +- .../ts/pages/documentation/source_link.tsx | 2 +- .../website/ts/pages/documentation/type.tsx | 3 +- .../pages/documentation/type_definition.tsx | 3 +- packages/website/ts/pages/faq/faq.tsx | 3 +- packages/website/ts/pages/faq/question.tsx | 2 +- packages/website/ts/pages/landing/landing.tsx | 2 +- packages/website/ts/pages/wiki/wiki.tsx | 21 +++++++----- packages/website/ts/types.ts | 4 --- packages/website/ts/utils/configs.ts | 2 -- packages/website/ts/utils/mui_theme.ts | 2 +- packages/website/ts/utils/utils.ts | 23 ------------- 69 files changed, 169 insertions(+), 150 deletions(-) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/markdown_code_block.tsx (91%) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/markdown_link_block.tsx (82%) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/markdown_section.tsx (89%) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/nested_sidebar_menu.tsx (94%) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/section_header.tsx (88%) rename packages/{website/ts/pages/shared => react-shared/src/ts/components}/version_drop_down.tsx (92%) delete mode 100644 packages/react-shared/src/ts/utils.ts rename packages/{website => react-shared/src}/ts/utils/colors.ts (100%) rename packages/react-shared/src/ts/{ => utils}/constants.ts (56%) create mode 100644 packages/react-shared/src/ts/utils/utils.ts diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index e1cc2ad8b6..e96659e47d 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -7,6 +7,7 @@ "scripts": { "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", "build": "tsc", + "build:watch": "tsc -w", "clean": "shx rm -rf lib" }, "author": "Fabio Berger", @@ -29,6 +30,7 @@ "react-dom": "15.6.1", "lodash": "^4.17.4", "react-scroll": "^1.5.2", - "react-tap-event-plugin": "^2.0.1" + "react-tap-event-plugin": "^2.0.1", + "react-highlight": "0xproject/react-highlight" } } diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx index aa839cac9a..f443540975 100644 --- a/packages/react-shared/src/ts/components/anchor_title.tsx +++ b/packages/react-shared/src/ts/components/anchor_title.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { Link as ScrollLink } from 'react-scroll'; -import { constants } from '../constants'; import { HeaderSizes, Styles } from '../types'; -import { utils } from '../utils'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; const headerSizeToScrollOffset: { [headerSize: string]: number } = { h2: -20, diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/react-shared/src/ts/components/markdown_code_block.tsx similarity index 91% rename from packages/website/ts/pages/shared/markdown_code_block.tsx rename to packages/react-shared/src/ts/components/markdown_code_block.tsx index 6dfb745546..2070bb8e14 100644 --- a/packages/website/ts/pages/shared/markdown_code_block.tsx +++ b/packages/react-shared/src/ts/components/markdown_code_block.tsx @@ -2,12 +2,12 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as HighLight from 'react-highlight'; -interface MarkdownCodeBlockProps { +export interface MarkdownCodeBlockProps { value: string; language: string; } -interface MarkdownCodeBlockState {} +export interface MarkdownCodeBlockState {} export class MarkdownCodeBlock extends React.Component { // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying diff --git a/packages/website/ts/pages/shared/markdown_link_block.tsx b/packages/react-shared/src/ts/components/markdown_link_block.tsx similarity index 82% rename from packages/website/ts/pages/shared/markdown_link_block.tsx rename to packages/react-shared/src/ts/components/markdown_link_block.tsx index b1f68c9150..8f58622498 100644 --- a/packages/website/ts/pages/shared/markdown_link_block.tsx +++ b/packages/react-shared/src/ts/components/markdown_link_block.tsx @@ -1,14 +1,14 @@ -import { utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { configs } from 'ts/utils/configs'; -import { utils } from 'ts/utils/utils'; -interface MarkdownLinkBlockProps { +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +export interface MarkdownLinkBlockProps { href: string; } -interface MarkdownLinkBlockState {} +export interface MarkdownLinkBlockState {} export class MarkdownLinkBlock extends React.Component { // Re-rendering a linkBlock causes it to remain unclickable. @@ -41,7 +41,7 @@ export class MarkdownLinkBlock extends React.Component { public render() { diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts index e69de29bb2..8757215338 100644 --- a/packages/react-shared/src/ts/globals.d.ts +++ b/packages/react-shared/src/ts/globals.d.ts @@ -0,0 +1 @@ +declare module 'react-highlight'; diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts index d33638e620..dde77b7b96 100644 --- a/packages/react-shared/src/ts/index.ts +++ b/packages/react-shared/src/ts/index.ts @@ -1,6 +1,12 @@ export { AnchorTitle } from './components/anchor_title'; +export { MarkdownLinkBlock } from './components/markdown_link_block'; +export { MarkdownCodeBlock } from './components/markdown_code_block'; +export { MarkdownSection } from './components/markdown_section'; +export { NestedSidebarMenu } from './components/nested_sidebar_menu'; +export { SectionHeader } from './components/section_header'; -export { HeaderSizes, Styles } from './types'; +export { HeaderSizes, Styles, MenuSubsectionsBySection } from './types'; -export { utils } from './utils'; -export { constants } from './constants'; +export { utils } from './utils/utils'; +export { constants } from './utils/constants'; +export { colors } from './utils/colors'; diff --git a/packages/react-shared/src/ts/types.ts b/packages/react-shared/src/ts/types.ts index e4fe7bff9c..f9d561d1a2 100644 --- a/packages/react-shared/src/ts/types.ts +++ b/packages/react-shared/src/ts/types.ts @@ -7,3 +7,7 @@ export enum HeaderSizes { H2 = 'h2', H3 = 'h3', } + +export interface MenuSubsectionsBySection { + [section: string]: string[]; +} diff --git a/packages/react-shared/src/ts/utils.ts b/packages/react-shared/src/ts/utils.ts deleted file mode 100644 index 822b674961..0000000000 --- a/packages/react-shared/src/ts/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const utils = { - setUrlHash(anchorId: string) { - window.location.hash = anchorId; - }, -}; diff --git a/packages/website/ts/utils/colors.ts b/packages/react-shared/src/ts/utils/colors.ts similarity index 100% rename from packages/website/ts/utils/colors.ts rename to packages/react-shared/src/ts/utils/colors.ts diff --git a/packages/react-shared/src/ts/constants.ts b/packages/react-shared/src/ts/utils/constants.ts similarity index 56% rename from packages/react-shared/src/ts/constants.ts rename to packages/react-shared/src/ts/utils/constants.ts index 20d0c6e921..79d1d9ca3e 100644 --- a/packages/react-shared/src/ts/constants.ts +++ b/packages/react-shared/src/ts/utils/constants.ts @@ -1,4 +1,6 @@ export const constants = { DOCS_SCROLL_DURATION_MS: 0, DOCS_CONTAINER_ID: 'documentation', + SCROLL_CONTAINER_ID: 'documentation', + SCROLL_TOP_ID: 'pageScrollTop', }; diff --git a/packages/react-shared/src/ts/utils/utils.ts b/packages/react-shared/src/ts/utils/utils.ts new file mode 100644 index 0000000000..7498342b6e --- /dev/null +++ b/packages/react-shared/src/ts/utils/utils.ts @@ -0,0 +1,32 @@ +import * as _ from 'lodash'; +import { scroller } from 'react-scroll'; + +import { constants } from './constants'; + +export const utils = { + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + scrollToHash(hash: string, containerId: string): void { + let finalHash = hash; + if (_.isEmpty(hash)) { + finalHash = constants.SCROLL_TOP_ID; // scroll to the top + } + + scroller.scrollTo(finalHash, { + duration: 0, + offset: 0, + containerId, + }); + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getCurrentBaseUrl() { + const port = window.location.port; + const hasPort = !_.isUndefined(port); + const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; + return baseUrl; + }, +}; diff --git a/packages/website/package.json b/packages/website/package.json index e6790abbcf..74bc2f8a65 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -44,7 +44,6 @@ "react-document-title": "^2.0.3", "react-dom": "15.6.1", "react-ga": "^2.4.1", - "react-highlight": "0xproject/react-highlight", "react-html5video": "^2.1.0", "react-inlinesvg": "^0.5.5", "react-markdown": "^3.2.2", diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 278e2bbf56..7353a8767d 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -1,10 +1,10 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrs, Networks } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index acd4a71104..5c61f0d570 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; @@ -6,7 +7,6 @@ import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { Side, Token } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { blockchain: Blockchain; diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index bc5f052413..5836fec0e9 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; @@ -11,7 +12,6 @@ import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index 3ecc454a04..b31667121b 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; interface PortalDisclaimerDialogProps { isOpen: boolean; diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx index 098e3e26d4..6ac9cf9174 100644 --- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface U2fNotSupportedDialogProps { diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index c2cdf67515..698b308151 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -1,4 +1,5 @@ import { ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Divider from 'material-ui/Divider'; @@ -17,7 +18,6 @@ import { TokenByAddress, TokenState, } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 51e44134c6..d7eecbcd36 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -1,4 +1,5 @@ import { Order as ZeroExOrder, ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as accounting from 'accounting'; import * as _ from 'lodash'; @@ -21,7 +22,6 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { validator } from 'ts/schemas/validator'; import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx index 165d21b349..d3215a6c12 100644 --- a/packages/website/ts/components/fill_warning_dialog.tsx +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; interface FillWarningDialogProps { isOpen: boolean; diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx index 18f3716241..a3b0567580 100644 --- a/packages/website/ts/components/flash_messages/token_send_completed.tsx +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -1,9 +1,9 @@ import { ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; import { Token } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TokenSendCompletedProps { diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx index 862e382dd1..188f1f9a64 100644 --- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -1,6 +1,6 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; interface TransactionSubmittedProps { etherScanLinkIfExists?: string; diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index 810460cac4..957ed2044d 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import DropDownMenu from 'material-ui/DropDownMenu'; import MenuItem from 'material-ui/MenuItem'; @@ -5,7 +6,6 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, Language, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index a3f2227844..e14991993d 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -1,4 +1,5 @@ import { ECSignature, Order, ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; @@ -20,7 +21,6 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { validator } from 'ts/schemas/validator'; import { AlertTypes, BlockchainErrs, HashData, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index f76830a494..e7f3b93c6d 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; @@ -7,7 +8,6 @@ import { Alert } from 'ts/components/ui/alert'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { RequiredLabel } from 'ts/components/ui/required_label'; import { AlertTypes, Token, TokenByAddress } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { blockchain: Blockchain; diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx index dd4131140d..7ca4af968f 100644 --- a/packages/website/ts/components/inputs/address_input.tsx +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -1,9 +1,9 @@ +import { colors } from '@0xproject/react-shared'; import { addressUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; import { RequiredLabel } from 'ts/components/ui/required_label'; -import { colors } from 'ts/utils/colors'; interface AddressInputProps { disabled?: boolean; diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 3bbc7a5f6e..253b018711 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; @@ -5,7 +6,6 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { RequiredLabel } from 'ts/components/ui/required_label'; import { InputErrMsg, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 2b167d8757..53248c0655 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -1,4 +1,5 @@ import { ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; @@ -6,7 +7,6 @@ import { Link } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input'; import { InputErrMsg, Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface TokenAmountInputProps { userAddress: string; diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index 5df19b28cc..545e9a095c 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; import * as React from 'react'; @@ -7,7 +8,6 @@ import { InputLabel } from 'ts/components/ui/input_label'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { Dispatcher } from 'ts/redux/dispatcher'; import { AssetToken, BlockchainErrs, Side, Token, TokenByAddress } from 'ts/types'; -import { colors } from 'ts/utils/colors'; const TOKEN_ICON_DIMENSION = 80; diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 4871997ac0..d71e821c67 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; @@ -24,7 +25,6 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { validator } from 'ts/schemas/validator'; import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index e321b247b1..9065ba2d8a 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -1,5 +1,5 @@ import { ZeroEx } from '0x.js'; -import { Styles } from '@0xproject/react-shared'; +import { colors, Styles } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import DharmaLoanFrame from 'dharma-loan-frame'; import * as _ from 'lodash'; @@ -34,7 +34,6 @@ import { TokenByAddress, TokenVisibility, } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 39e7f2a8c1..89c506d0ec 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; @@ -7,7 +8,6 @@ import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index be7e57d6f2..6373d4900d 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -1,10 +1,10 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface ProviderPickerProps { diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index df6110de33..58b699b779 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -1,4 +1,4 @@ -import { Styles } from '@0xproject/react-shared'; +import { colors, MenuSubsectionsBySection, NestedSidebarMenu, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Drawer from 'material-ui/Drawer'; import Menu from 'material-ui/Menu'; @@ -13,10 +13,8 @@ import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Deco, DocsMenu, Key, MenuSubsectionsBySection, ProviderType, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; +import { Deco, DocsMenu, Key, ProviderType, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx index e70381456c..c0e674b170 100644 --- a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import { colors } from 'ts/utils/colors'; const DEFAULT_STYLE = { color: colors.darkestGrey, diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx index 76971aefa3..8c5ba7e6f1 100644 --- a/packages/website/ts/components/track_token_confirmation.tsx +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -1,8 +1,8 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Party } from 'ts/components/ui/party'; import { Token, TokenByAddress } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TrackTokenConfirmationProps { diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 7e42e64e67..3bab296919 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -1,4 +1,5 @@ import { ZeroEx } from '0x.js'; +import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; @@ -8,7 +9,6 @@ import * as ReactTooltip from 'react-tooltip'; import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; import { Party } from 'ts/components/ui/party'; import { EtherscanLinkSuffixes, Fill, Token, TokenByAddress } from 'ts/types'; -import { colors } from 'ts/utils/colors'; const PRECISION = 5; const IDENTICON_DIAMETER = 40; diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx index 54881b499b..f819392558 100644 --- a/packages/website/ts/components/ui/alert.tsx +++ b/packages/website/ts/components/ui/alert.tsx @@ -1,6 +1,6 @@ +import { colors } from '@0xproject/react-shared'; import * as React from 'react'; import { AlertTypes } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface AlertProps { type: AlertTypes; diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index df55e09220..d58e508156 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -1,9 +1,9 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as CopyToClipboard from 'react-copy-to-clipboard'; import * as ReactDOM from 'react-dom'; import ReactTooltip = require('react-tooltip'); -import { colors } from 'ts/utils/colors'; interface CopyIconProps { data: string; diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index 3b17bd0fab..e7fc510705 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -1,8 +1,8 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { EtherscanLinkSuffixes } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx index e2009ad203..6a3f261552 100644 --- a/packages/website/ts/components/ui/input_label.tsx +++ b/packages/website/ts/components/ui/input_label.tsx @@ -1,5 +1,5 @@ +import { colors } from '@0xproject/react-shared'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; export interface InputLabelProps { text: string | Element | React.ReactNode; diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index 8ff856a751..c85e11884a 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index ca2577b615..e120523fd9 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -1,10 +1,10 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { EthereumAddress } from 'ts/components/ui/ethereum_address'; import { Identicon } from 'ts/components/ui/identicon'; import { EtherscanLinkSuffixes } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; const IMAGE_DIMENSION = 100; diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx index a5e7a22cec..0f96586ec1 100644 --- a/packages/website/ts/components/ui/required_label.tsx +++ b/packages/website/ts/components/ui/required_label.tsx @@ -1,5 +1,5 @@ +import { colors } from '@0xproject/react-shared'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; export interface RequiredLabelProps { label: string | React.ReactNode; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index c415922876..e465a8074d 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -1,6 +1,6 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; interface SwapIconProps { swapTokensFn: () => void; diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 19237db14f..d43ab0f584 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -7,7 +7,6 @@ declare module 'whatwg-fetch'; declare module 'react-html5video'; declare module 'web3-provider-engine/subproviders/filters'; declare module 'thenby'; -declare module 'react-highlight'; declare module 'react-recaptcha'; declare module 'react-document-title'; declare module 'ethereumjs-tx'; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 65dcdfa7b6..7f1e0bf803 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -1,4 +1,4 @@ -import { Styles } from '@0xproject/react-shared'; +import { colors, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; @@ -7,7 +7,6 @@ import { TopBar } from 'ts/components/top_bar/top_bar'; import { Profile } from 'ts/pages/about/profile'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProfileInfo } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index 6d9d10272b..4361da103b 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -1,8 +1,7 @@ -import { Styles } from '@0xproject/react-shared'; +import { colors, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { ProfileInfo } from 'ts/types'; -import { colors } from 'ts/utils/colors'; const IMAGE_DIMENSION = 149; const styles: Styles = { diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx index 5f177e97e0..b8902679a6 100644 --- a/packages/website/ts/pages/documentation/comment.tsx +++ b/packages/website/ts/pages/documentation/comment.tsx @@ -1,7 +1,7 @@ +import { MarkdownCodeBlock } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as ReactMarkdown from 'react-markdown'; -import { MarkdownCodeBlock } from 'ts/pages/shared/markdown_code_block'; interface CommentProps { comment: string; diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 2c8f1c1036..b3b58c0c8a 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -1,3 +1,4 @@ +import { MenuSubsectionsBySection } from '@0xproject/react-shared'; import findVersions = require('find-versions'); import * as _ from 'lodash'; import * as React from 'react'; @@ -7,7 +8,7 @@ import { TopBar } from 'ts/components/top_bar/top_bar'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Documentation } from 'ts/pages/documentation/documentation'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { DocAgnosticFormat, DocPackages, DoxityDocObj, Environments, MenuSubsectionsBySection } from 'ts/types'; +import { DocAgnosticFormat, DocPackages, DoxityDocObj, Environments } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { docUtils } from 'ts/utils/doc_utils'; diff --git a/packages/website/ts/pages/documentation/docs_info.ts b/packages/website/ts/pages/documentation/docs_info.ts index 31e151fe88..09fb7a2bcd 100644 --- a/packages/website/ts/pages/documentation/docs_info.ts +++ b/packages/website/ts/pages/documentation/docs_info.ts @@ -1,3 +1,4 @@ +import { MenuSubsectionsBySection } from '@0xproject/react-shared'; import compareVersions = require('compare-versions'); import * as _ from 'lodash'; import { @@ -6,7 +7,6 @@ import { DocsInfoConfig, DocsMenu, DoxityDocObj, - MenuSubsectionsBySection, SectionsMap, SupportedDocJson, TypeDocNode, diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 88331d1a7f..3229bbd932 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -1,4 +1,13 @@ -import { Styles } from '@0xproject/react-shared'; +import { + colors, + constants as sharedConstants, + MarkdownSection, + MenuSubsectionsBySection, + NestedSidebarMenu, + SectionHeader, + Styles, + utils as sharedUtils, +} from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; @@ -11,16 +20,12 @@ import { MethodBlock } from 'ts/pages/documentation/method_block'; import { SourceLink } from 'ts/pages/documentation/source_link'; import { Type } from 'ts/pages/documentation/type'; import { TypeDefinition } from 'ts/pages/documentation/type_definition'; -import { MarkdownSection } from 'ts/pages/shared/markdown_section'; -import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; -import { SectionHeader } from 'ts/pages/shared/section_header'; import { AddressByContractName, DocAgnosticFormat, DoxityDocObj, EtherscanLinkSuffixes, Event, - MenuSubsectionsBySection, Networks, Property, SolidityMethod, @@ -28,8 +33,6 @@ import { TypeDefinitionByName, TypescriptMethod, } from 'ts/types'; -import { colors } from 'ts/utils/colors'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; @@ -77,7 +80,7 @@ export class Documentation extends React.Component
-
+
{this._renderDocumentation()}
diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx index aead27d789..06d8a3d79d 100644 --- a/packages/website/ts/pages/documentation/event_definition.tsx +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -1,10 +1,9 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Type } from 'ts/pages/documentation/type'; import { Event, EventArg } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface EventDefinitionProps { event: Event; diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index a1f0b38abf..a52ab55c3f 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes, Styles } from '@0xproject/react-shared'; +import { AnchorTitle, colors, HeaderSizes, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Comment } from 'ts/pages/documentation/comment'; @@ -6,7 +6,6 @@ import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { MethodSignature } from 'ts/pages/documentation/method_signature'; import { SourceLink } from 'ts/pages/documentation/source_link'; import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; interface MethodBlockProps { diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 31f80aba30..d39ad14c4f 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Source } from 'ts/types'; -import { colors } from 'ts/utils/colors'; interface SourceLinkProps { source: Source; diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx index 2fec4c95f6..2c90a05676 100644 --- a/packages/website/ts/pages/documentation/type.tsx +++ b/packages/website/ts/pages/documentation/type.tsx @@ -1,4 +1,4 @@ -import { constants as sharedConstants, utils as sharedUtils } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants, utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Link as ScrollLink } from 'react-scroll'; @@ -6,7 +6,6 @@ import * as ReactTooltip from 'react-tooltip'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { TypeDefinition } from 'ts/pages/documentation/type_definition'; import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx index dd83d120d9..60f307c68a 100644 --- a/packages/website/ts/pages/documentation/type_definition.tsx +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -1,4 +1,4 @@ -import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared'; +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { Comment } from 'ts/pages/documentation/comment'; @@ -9,7 +9,6 @@ import { Interface } from 'ts/pages/documentation/interface'; import { MethodSignature } from 'ts/pages/documentation/method_signature'; import { Type } from 'ts/pages/documentation/type'; import { CustomType, CustomTypeChild, KindString, TypeDocTypes } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TypeDefinitionProps { diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 70d1166227..1be3c3565e 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -1,4 +1,4 @@ -import { Styles } from '@0xproject/react-shared'; +import { colors, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; @@ -7,7 +7,6 @@ import { TopBar } from 'ts/components/top_bar/top_bar'; import { Question } from 'ts/pages/faq/question'; import { Dispatcher } from 'ts/redux/dispatcher'; import { FAQQuestion, FAQSection, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx index 988c04bc9d..240dae9108 100644 --- a/packages/website/ts/pages/faq/question.tsx +++ b/packages/website/ts/pages/faq/question.tsx @@ -1,7 +1,7 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import { Card, CardHeader, CardText } from 'material-ui/Card'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; export interface QuestionProps { prompt: string; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 044f0b41fb..6699e4e6f3 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -1,3 +1,4 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; @@ -7,7 +8,6 @@ import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index b22ce58265..f338ed9c29 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -1,4 +1,13 @@ -import { HeaderSizes, Styles } from '@0xproject/react-shared'; +import { + colors, + constants as sharedConstants, + HeaderSizes, + MarkdownSection, + NestedSidebarMenu, + SectionHeader, + Styles, + utils as sharedUtils, +} from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import RaisedButton from 'material-ui/RaisedButton'; @@ -6,12 +15,8 @@ import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; import { TopBar } from 'ts/components/top_bar/top_bar'; -import { MarkdownSection } from 'ts/pages/shared/markdown_section'; -import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; -import { SectionHeader } from 'ts/pages/shared/section_header'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Article, ArticlesBySection, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -136,11 +141,11 @@ export class Wiki extends React.Component { }} >
-
+
{this._renderWikiArticles()}
@@ -215,7 +220,7 @@ export class Wiki extends React.Component { async () => { await utils.onPageLoadAsync(); const hash = this.props.location.hash.slice(1); - utils.scrollToHash(hash, configs.SCROLL_CONTAINER_ID); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); }, ); } diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index c1b23ce1ee..3b0866d706 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -447,10 +447,6 @@ export interface S3FileObject { }; } -export interface MenuSubsectionsBySection { - [section: string]: string[]; -} - export enum ProviderType { Injected = 'INJECTED', Ledger = 'LEDGER', diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 388fc85308..7e9ba69de0 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -94,8 +94,6 @@ export const configs = { [3]: [`https://ropsten.infura.io/${INFURA_API_KEY}`], [4]: [`https://rinkeby.infura.io/${INFURA_API_KEY}`], } as PublicNodeUrlsByNetworkId, - SCROLL_CONTAINER_ID: 'documentation', - SCROLL_TOP_ID: 'pageScrollTop', SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, SYMBOLS_OF_MINTABLE_KOVAN_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], SYMBOLS_OF_MINTABLE_RINKEBY_ROPSTEN_TOKENS: [ diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts index 32891baca9..41bc2844b6 100644 --- a/packages/website/ts/utils/mui_theme.ts +++ b/packages/website/ts/utils/mui_theme.ts @@ -1,5 +1,5 @@ +import { colors } from '@0xproject/react-shared'; import { getMuiTheme } from 'material-ui/styles'; -import { colors } from 'ts/utils/colors'; export const muiTheme = getMuiTheme({ appBar: { diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index a1e11c85aa..872b44eeb7 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -4,7 +4,6 @@ import deepEqual = require('deep-equal'); import isMobile = require('is-mobile'); import * as _ from 'lodash'; import * as moment from 'moment'; -import { scroller } from 'react-scroll'; import { EtherscanLinkSuffixes, Networks, @@ -199,10 +198,6 @@ export const utils = { return 'production'; } }, - getIdFromName(name: string) { - const id = name.replace(/ /g, '-'); - return id; - }, getAddressBeginAndEnd(address: string): string { const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 return truncatedAddress; @@ -288,24 +283,6 @@ export const utils = { ); return isTestNetwork; }, - getCurrentBaseUrl() { - const port = window.location.port; - const hasPort = !_.isUndefined(port); - const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; - return baseUrl; - }, - scrollToHash(hash: string, containerId: string): void { - let finalHash = hash; - if (_.isEmpty(hash)) { - finalHash = configs.SCROLL_TOP_ID; // scroll to the top - } - - scroller.scrollTo(finalHash, { - duration: 0, - offset: 0, - containerId, - }); - }, async onPageLoadAsync(): Promise { if (document.readyState === 'complete') { return; // Already loaded From d355382f7040550cb82bab57a4935652fb710e71 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 5 Mar 2018 15:15:03 -0800 Subject: [PATCH 27/94] Update 0x package versions and fix yarn.lock --- packages/sra-report/package.json | 18 +++++++++--------- yarn.lock | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 6168c128ed..22297e12eb 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -15,26 +15,26 @@ }, "repository": { "type": "git", - "url": "https://github.com/0xProject/0x.js.git" + "url": "https://github.com/0xProject/0x-monorepo.git" }, "license": "Apache-2.0", "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" + "url": "https://github.com/0xProject/0x-monorepo/issues" }, - "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sra-report/README.md", "dependencies": { - "0x.js": "^0.32.4", - "@0xproject/assert": "^0.0.20", - "@0xproject/connect": "^0.6.1", - "@0xproject/json-schemas": "^0.7.12", - "@0xproject/utils": "^0.3.4", + "0x.js": "^0.33.0", + "@0xproject/assert": "^0.1.0", + "@0xproject/connect": "^0.6.2", + "@0xproject/json-schemas": "^0.7.13", + "@0xproject/utils": "^0.4.0", "chalk": "^2.3.0", "lodash": "^4.17.4", "newman": "^3.9.3", "yargs": "^10.0.3" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.9", + "@0xproject/tslint-config": "^0.4.10", "@types/lodash": "^4.14.86", "@types/node": "^8.0.53", "@types/yargs": "^10.0.0", diff --git a/yarn.lock b/yarn.lock index 4c38a471df..114b7f592d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3024,6 +3024,10 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" +entities@^1.1.1, entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + errno@^0.1.1, errno@^0.1.3, errno@~0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026" @@ -8922,6 +8926,14 @@ ssri@^5.0.0: dependencies: safe-buffer "^5.1.0" +stack-trace@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" + +stack-trace@~0.0.9: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + state-toggle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" @@ -9699,6 +9711,10 @@ underscore@1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + unherit@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.0.tgz#6b9aaedfbf73df1756ad9e316dd981885840cd7d" From 08ab81c54c1e845a4239648e8cea65a930fcc99d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 5 Mar 2018 15:24:55 -0800 Subject: [PATCH 28/94] Rename isHttpUrl to isWebUri in assert package --- packages/assert/CHANGELOG.md | 4 ++++ packages/assert/src/index.ts | 4 ++-- packages/assert/test/assert_test.ts | 6 +++--- packages/connect/src/http_client.ts | 2 +- packages/sra-report/src/index.ts | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index b37a810e3e..35bf4faefe 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.2.0 - _TBD, 2018_ + + * Rename `isHttpUrl` to `isWebUri` (#412) + ## v0.1.0 - _March 4, 2018_ * Remove isETHAddressHex checksum address check and assume address will be lowercased (#373) diff --git a/packages/assert/src/index.ts b/packages/assert/src/index.ts index 40d083cb61..171909c935 100644 --- a/packages/assert/src/index.ts +++ b/packages/assert/src/index.ts @@ -75,9 +75,9 @@ Encountered: ${JSON.stringify(value, null, '\t')} Validation errors: ${validationResult.errors.join(', ')}`; this.assert(!hasValidationErrors, msg); }, - isHttpUrl(variableName: string, value: any): void { + isWebUri(variableName: string, value: any): void { const isValidUrl = !_.isUndefined(validUrl.isWebUri(value)); - this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value)); + this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'web uri', value)); }, isUri(variableName: string, value: any): void { const isValidUri = !_.isUndefined(validUrl.isUri(value)); diff --git a/packages/assert/test/assert_test.ts b/packages/assert/test/assert_test.ts index b0fa398d6e..1d9a34ed9c 100644 --- a/packages/assert/test/assert_test.ts +++ b/packages/assert/test/assert_test.ts @@ -183,7 +183,7 @@ describe('Assertions', () => { ); }); }); - describe('#isHttpUrl', () => { + describe('#isWebUri', () => { it('should not throw for valid input', () => { const validInputs = [ 'http://www.google.com', @@ -191,7 +191,7 @@ describe('Assertions', () => { 'https://api.radarrelay.com/0x/v0/', 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', ]; - validInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw()); + validInputs.forEach(input => expect(assert.isWebUri.bind(assert, variableName, input)).to.not.throw()); }); it('should throw for invalid input', () => { const invalidInputs = [ @@ -205,7 +205,7 @@ describe('Assertions', () => { 'user:password@api.example-relayer.net', '//api.example-relayer.net', ]; - invalidInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw()); + invalidInputs.forEach(input => expect(assert.isWebUri.bind(assert, variableName, input)).to.throw()); }); }); describe('#isUri', () => { diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index cf0aaef0d5..a221b54e8d 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -61,7 +61,7 @@ export class HttpClient implements Client { * @return An instance of HttpClient */ constructor(url: string) { - assert.isHttpUrl('url', url); + assert.isWebUri('url', url); this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes } /** diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index bee9db192a..327c495ae1 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -42,7 +42,7 @@ const args = yargs .argv; // perform extra validation on command line arguments try { - assert.isHttpUrl('args', args.url); + assert.isWebUri('args', args.url); } catch (err) { utils.log(`${chalk.red(`Invalid url format:`)} ${args.url}`); process.exit(1); From cf342dd00eaae8667984e77eb565040c132f160a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 5 Mar 2018 15:31:42 -0800 Subject: [PATCH 29/94] Change url command line option to endpoint-url --- packages/sra-report/README.md | 2 +- packages/sra-report/src/index.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/sra-report/README.md b/packages/sra-report/README.md index 49609aeb26..53c007e10f 100644 --- a/packages/sra-report/README.md +++ b/packages/sra-report/README.md @@ -13,7 +13,7 @@ sra-report Options: --help Show help [boolean] --version Show version number [boolean] - --url, -u API endpoint to test for standard relayer API compliance + --endpoint-url, -e API endpoint url to test for standard relayer API compliance [string] [required] --output, -o, --out Folder where to write the reports [string] --network-id, -n ID of the network that the API is serving orders from diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index 327c495ae1..c74b5cb400 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -19,9 +19,9 @@ const SUPPORTED_NETWORK_IDS = [1, 42]; // extract command line arguments const args = yargs - .option('url', { - alias: ['u'], - describe: 'API endpoint to test for standard relayer API compliance', + .option('endpoint-url', { + alias: ['e'], + describe: 'API endpoint url to test for standard relayer API compliance', type: 'string', demandOption: true, }) @@ -38,13 +38,13 @@ const args = yargs type: 'number', default: DEFAULT_NETWORK_ID, }) - .example("$0 --url 'http://api.example.com' --out 'src/contracts/generated/' --network-id 42", 'Full usage example') + .example("$0 --endpoint-url 'http://api.example.com' --out 'src/contracts/generated/' --network-id 42", 'Full usage example') .argv; // perform extra validation on command line arguments try { - assert.isWebUri('args', args.url); + assert.isWebUri('args', args.endpointUrl); } catch (err) { - utils.log(`${chalk.red(`Invalid url format:`)} ${args.url}`); + utils.log(`${chalk.red(`Invalid url format:`)} ${args.endpointUrl}`); process.exit(1); } if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { @@ -54,7 +54,7 @@ if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { } const mainAsync = async () => { - const httpClient = new HttpClient(args.url); + const httpClient = new HttpClient(args.endpointUrl); const orders = await httpClient.getOrdersAsync(); const firstOrder = _.head(orders); if (_.isUndefined(firstOrder)) { @@ -62,7 +62,7 @@ const mainAsync = async () => { } const newmanEnvironmentOptions = { collection: sraReportCollectionJSON, - globals: postmanEnvironmentFactory.createGlobalEnvironment(args.url, firstOrder), + globals: postmanEnvironmentFactory.createGlobalEnvironment(args.endpointUrl, firstOrder), environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), }; const newmanReporterOptions = !_.isUndefined(args.output) From 38a4ccd9f24cd1cd583c17ebc200238e1770e5f8 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 5 Mar 2018 16:13:47 -0800 Subject: [PATCH 30/94] Change all globals in environment variables --- .../sra_report.postman_collection.json | 1881 +++++++++-------- .../kovan_tokens.postman_environment.json | 34 - .../mainnet_tokens.postman_environment.json | 34 - .../src/contract_addresses/kovan_addresses.ts | 6 + .../contract_addresses/mainnet_addresses.ts | 6 + packages/sra-report/src/index.ts | 8 +- .../src/postman_environment_factory.ts | 32 +- 7 files changed, 1006 insertions(+), 995 deletions(-) delete mode 100644 packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json delete mode 100644 packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json create mode 100644 packages/sra-report/src/contract_addresses/kovan_addresses.ts create mode 100644 packages/sra-report/src/contract_addresses/mainnet_addresses.ts diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index d1d384163c..fc7dd5b65b 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -1,909 +1,974 @@ { - "info": { - "name": "sra_report", - "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", - "description": - "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "GET /token_pairs", - "description": "", - "item": [ - { - "name": "default request", - "event": [ - { - "listen": "test", - "script": { - "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", - "type": "text/javascript", - "exec": [""] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs", - "host": ["{{url}}"], - "path": ["token_pairs"] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenA param", - "event": [ - { - "listen": "test", - "script": { - "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'WETH_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", - " const condition = tokenAIsFilterToken || tokenBIsFilterToken", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", - "host": ["{{url}}"], - "path": ["token_pairs"], - "query": [ - { - "key": "tokenA", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenB param", - "event": [ - { - "listen": "test", - "script": { - "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'WETH_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", - " const condition = tokenAIsFilterToken || tokenBIsFilterToken", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", - "host": ["{{url}}"], - "path": ["token_pairs"], - "query": [ - { - "key": "tokenB", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenA and tokenB params", - "event": [ - { - "listen": "test", - "script": { - "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", - "type": "text/javascript", - "exec": [ - "const filterTokenAEnvKey = 'WETH_address';", - "const filterTokenBEnvKey = 'ZRX_address';", - "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", - "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", - " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", - " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", - " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", - " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", - "host": ["{{url}}"], - "path": ["token_pairs"], - "query": [ - { - "key": "tokenA", - "value": "{{WETH_address}}", - "equals": true - }, - { - "key": "tokenB", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /orders", - "description": "", - "item": [ - { - "name": "default request", - "event": [ - { - "listen": "test", - "script": { - "id": "118f47dd-1d93-4288-841f-de88783eff3b", - "type": "text/javascript", - "exec": [""] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders", - "host": ["{{url}}"], - "path": ["orders"] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "exchangeContract param", - "event": [ - { - "listen": "test", - "script": { - "id": "e782ae77-66bf-4096-9190-ef9e7501b316", - "type": "text/javascript", - "exec": [ - "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", - "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", - " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "exchangeContractAddress", - "value": "{{EXCHANGE_CONTRACT_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "tokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", - " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", - " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "tokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "makerTokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "01eb5865-edec-4216-8a90-c36d790366fc", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "makerTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "takerTokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "takerTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "maker param", - "event": [ - { - "listen": "test", - "script": { - "id": "efc85eb8-7791-4b14-9dee-c5e80adec4b8", - "type": "text/javascript", - "exec": [ - "const orderMakerEnvKey = 'orderMaker';", - "const referenceOrderMakerAddress = pm.globals.get(orderMakerEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedMakerAddress = _.get(order, 'maker');", - " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?maker={{orderMaker}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "maker", - "value": "{{orderMaker}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "taker param", - "event": [ - { - "listen": "test", - "script": { - "id": "c9c18715-c7cc-4064-8241-68a362a233fe", - "type": "text/javascript", - "exec": [ - "const orderTakerEnvKey = 'orderTaker';", - "const referenceOrderTakerAddress = pm.globals.get(orderTakerEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedTakerAddress = _.get(order, 'taker');", - " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?taker={{orderTaker}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "taker", - "value": "{{orderTaker}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "trader param", - "event": [ - { - "listen": "test", - "script": { - "id": "2c9cdb6a-267c-48b3-9ad7-df7abb75ddf4", - "type": "text/javascript", - "exec": [ - "const orderTraderEnvKey = 'orderMaker';", - "const referenceOrderTraderAddress = pm.globals.get(orderTraderEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedMakerAddress = _.get(order, 'maker');", - " const returnedTakerAddress = _.get(order, 'taker');", - " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?trader={{orderMaker}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "trader", - "value": "{{orderMaker}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "feeRecipient param", - "event": [ - { - "listen": "test", - "script": { - "id": "06ea36eb-6a35-4fc7-8017-59876b1a1cb4", - "type": "text/javascript", - "exec": [ - "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", - "const referenceOrderFeeRecipientAddress = pm.globals.get(orderFeeRecipientEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", - " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", - "host": ["{{url}}"], - "path": ["orders"], - "query": [ - { - "key": "feeRecipient", - "value": "{{orderFeeRecipient}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/signedOrdersSchema');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /order", - "description": "", - "item": [ - { - "name": "orderHash param", - "event": [ - { - "listen": "test", - "script": { - "id": "3beca3a2-e0ad-45ff-b4d1-eace859af0bf", - "type": "text/javascript", - "exec": [ - "const orderEnvKey = 'order';", - "const referenceOrderString = pm.globals.get(orderEnvKey);", - "const referenceOrderJson = JSON.parse(referenceOrderString);", - "const orderPropertyNames = [", - " 'maker',", - " 'taker',", - " 'makerFee',", - " 'takerFee',", - " 'makerTokenAmount',", - " 'takerTokenAmount',", - " 'makerTokenAddress',", - " 'takerTokenAddress',", - " 'salt',", - " 'feeRecipient',", - " 'expirationUnixTimestampSec',", - " 'exchangeContractAddress'", - "];", - "const signaturePropertyNames = [", - " 'v',", - " 'r',", - " 's'", - "];", - "const returnedOrderJson = pm.response.json();", - "pm.test('Order is properly retreived', function() {", - " _.forEach(orderPropertyNames, function(propertyName) {", - " const returnedProperty = _.get(returnedOrderJson, propertyName);", - " const referenceProperty = _.get(referenceOrderJson, propertyName);", - " pm.expect(returnedProperty).to.be.equal(referenceProperty);", - " });", - " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", - " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", - " _.forEach(signaturePropertyNames, function(propertyName) {", - " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", - " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", - " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/order/{{orderHash}}", - "host": ["{{url}}"], - "path": ["order", "{{orderHash}}"] - }, - "description": "Retrieves a specific order by orderHash." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/SignedOrder');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /orderbook", - "description": "", - "item": [ - { - "name": "baseTokenAddress and quoteTokenAddress params", - "event": [ - { - "listen": "test", - "script": { - "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", - "type": "text/javascript", - "exec": [ - "const baseTokenEnvKey = 'ZRX_address';", - "const quoteTokenEnvKey = 'WETH_address';", - "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", - "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orderbook is properly filtered', function() {", - " const bids = _.get(responseJsonData, 'bids');", - " const asks = _.get(responseJsonData, 'asks');", - " _.forEach(bids, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", - " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", - " });", - " _.forEach(asks, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", - " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": - "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", - "host": ["{{url}}"], - "path": ["orderbook"], - "query": [ - { - "key": "baseTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - }, - { - "key": "quoteTokenAddress", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": - "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "POST /fees", - "description": "", - "item": [ - { - "name": "default request", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": - "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" - }, - "url": { - "raw": "{{url}}/fees", - "host": ["{{url}}"], - "path": ["fees"] - }, - "description": - "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiFeesResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "da60f639-df79-4f4d-9861-79219f5fc341", - "type": "text/javascript", - "exec": [ - "const schemaKeysString = pm.globals.get('schemaKeys');", - "const schemaKeys = JSON.parse(schemaKeysString);", - "_.forEach(schemaKeys, function(schemaKey) {", - " const schemaString = pm.globals.get(schemaKey);", - " const schema = JSON.parse(schemaString);", - " tv4.addSchema(schema);", - "});", - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", - "type": "text/javascript", - "exec": [ - "pm.test('Has Content-Type header with value application/json', function () {", - " pm.response.to.have.header('Content-Type');", - " const contentType = postman.getResponseHeader('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});", - "" - ] - } - } - ] -} + "info": { + "name": "sra_report", + "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", + "description": "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /token_pairs", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs", + "host": [ + "{{url}}" + ], + "path": [ + "token_pairs" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA param", + "event": [ + { + "listen": "test", + "script": { + "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "token_pairs" + ], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenB param", + "event": [ + { + "listen": "test", + "script": { + "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "token_pairs" + ], + "query": [ + { + "key": "tokenB", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA and tokenB params", + "event": [ + { + "listen": "test", + "script": { + "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", + "type": "text/javascript", + "exec": [ + "const filterTokenAEnvKey = 'WETH_address';", + "const filterTokenBEnvKey = 'ZRX_address';", + "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", + "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", + " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", + " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", + " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", + " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "token_pairs" + ], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + }, + { + "key": "tokenB", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orders", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "118f47dd-1d93-4288-841f-de88783eff3b", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "exchangeContract param", + "event": [ + { + "listen": "test", + "script": { + "id": "e782ae77-66bf-4096-9190-ef9e7501b316", + "type": "text/javascript", + "exec": [ + "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", + "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", + " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "exchangeContractAddress", + "value": "{{EXCHANGE_CONTRACT_address}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "tokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", + " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", + " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "tokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "makerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "01eb5865-edec-4216-8a90-c36d790366fc", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "makerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "takerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "takerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "maker param", + "event": [ + { + "listen": "test", + "script": { + "id": "1f5960de-117f-44fb-82e0-581626cbf62b", + "type": "text/javascript", + "exec": [ + "const orderMakerEnvKey = 'orderMaker';", + "const referenceOrderMakerAddress = pm.environment.get(orderMakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?maker={{orderMaker}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "maker", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "taker param", + "event": [ + { + "listen": "test", + "script": { + "id": "f23de2eb-b444-49d3-93b7-14ae712d6502", + "type": "text/javascript", + "exec": [ + "const orderTakerEnvKey = 'orderTaker';", + "const referenceOrderTakerAddress = pm.environment.get(orderTakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedTakerAddress = _.get(order, 'taker');", + " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?taker={{orderTaker}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "taker", + "value": "{{orderTaker}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "trader param", + "event": [ + { + "listen": "test", + "script": { + "id": "9689639a-47ce-4c3b-8180-859fd28437be", + "type": "text/javascript", + "exec": [ + "const orderTraderEnvKey = 'orderMaker';", + "const referenceOrderTraderAddress = pm.environment.get(orderTraderEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " const returnedTakerAddress = _.get(order, 'taker');", + " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?trader={{orderMaker}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "trader", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "feeRecipient param", + "event": [ + { + "listen": "test", + "script": { + "id": "010c0cf6-8d5b-4fe3-8b92-b2009ea43a3e", + "type": "text/javascript", + "exec": [ + "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", + "const referenceOrderFeeRecipientAddress = pm.environment.get(orderFeeRecipientEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", + " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", + "host": [ + "{{url}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "feeRecipient", + "value": "{{orderFeeRecipient}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/signedOrdersSchema');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /order", + "description": "", + "item": [ + { + "name": "orderHash param", + "event": [ + { + "listen": "test", + "script": { + "id": "18876df2-384e-43d5-93a1-7e24571e1308", + "type": "text/javascript", + "exec": [ + "const orderEnvKey = 'order';", + "const referenceOrderString = pm.environment.get(orderEnvKey);", + "const referenceOrderJson = JSON.parse(referenceOrderString);", + "const orderPropertyNames = [", + " 'maker',", + " 'taker',", + " 'makerFee',", + " 'takerFee',", + " 'makerTokenAmount',", + " 'takerTokenAmount',", + " 'makerTokenAddress',", + " 'takerTokenAddress',", + " 'salt',", + " 'feeRecipient',", + " 'expirationUnixTimestampSec',", + " 'exchangeContractAddress'", + "];", + "const signaturePropertyNames = [", + " 'v',", + " 'r',", + " 's'", + "];", + "const returnedOrderJson = pm.response.json();", + "pm.test('Order is properly retreived', function() {", + " _.forEach(orderPropertyNames, function(propertyName) {", + " const returnedProperty = _.get(returnedOrderJson, propertyName);", + " const referenceProperty = _.get(referenceOrderJson, propertyName);", + " pm.expect(returnedProperty).to.be.equal(referenceProperty);", + " });", + " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", + " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", + " _.forEach(signaturePropertyNames, function(propertyName) {", + " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", + " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", + " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/order/{{orderHash}}", + "host": [ + "{{url}}" + ], + "path": [ + "order", + "{{orderHash}}" + ] + }, + "description": "Retrieves a specific order by orderHash." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/SignedOrder');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orderbook", + "description": "", + "item": [ + { + "name": "baseTokenAddress and quoteTokenAddress params", + "event": [ + { + "listen": "test", + "script": { + "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", + "type": "text/javascript", + "exec": [ + "const baseTokenEnvKey = 'ZRX_address';", + "const quoteTokenEnvKey = 'WETH_address';", + "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", + "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orderbook is properly filtered', function() {", + " const bids = _.get(responseJsonData, 'bids');", + " const asks = _.get(responseJsonData, 'asks');", + " _.forEach(bids, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", + " });", + " _.forEach(asks, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", + "host": [ + "{{url}}" + ], + "path": [ + "orderbook" + ], + "query": [ + { + "key": "baseTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + }, + { + "key": "quoteTokenAddress", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "POST /fees", + "description": "", + "item": [ + { + "name": "default request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" + }, + "url": { + "raw": "{{url}}/fees", + "host": [ + "{{url}}" + ], + "path": [ + "fees" + ] + }, + "description": "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiFeesResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "da60f639-df79-4f4d-9861-79219f5fc341", + "type": "text/javascript", + "exec": [ + "const schemaKeysString = pm.environment.get('schemaKeys');", + "const schemaKeys = JSON.parse(schemaKeysString);", + "_.forEach(schemaKeys, function(schemaKey) {", + " const schemaString = pm.environment.get(schemaKey);", + " const schema = JSON.parse(schemaString);", + " tv4.addSchema(schema);", + "});", + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", + "type": "text/javascript", + "exec": [ + "pm.test('Has Content-Type header with value application/json', function () {", + " pm.response.to.have.header('Content-Type');", + " const contentType = postman.getResponseHeader('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json deleted file mode 100644 index 480c1231b8..0000000000 --- a/packages/sra-report/postman_configs/environments/kovan_tokens.postman_environment.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id": "8dab7221-c8b8-2931-92c8-605f6f4072e1", - "name": "Kovan Tokens", - "values": [ - { - "enabled": true, - "key": "DAI_address", - "value": "0xb18845c260f680d5b9d84649638813e342e4f8c9", - "type": "text" - }, - { - "enabled": true, - "key": "WETH_address", - "value": "0xd0a1e359811322d97991e03f863a0c30c2cf029c", - "type": "text" - }, - { - "enabled": true, - "key": "ZRX_address", - "value": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", - "type": "text" - }, - { - "enabled": true, - "key": "EXCHANGE_CONTRACT_address", - "value": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364", - "type": "text" - } - ], - "timestamp": 1518639440766, - "_postman_variable_scope": "environment", - "_postman_exported_at": "2018-02-14T22:49:13.162Z", - "_postman_exported_using": "Postman/5.5.2" -} diff --git a/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json b/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json deleted file mode 100644 index f18c38f46d..0000000000 --- a/packages/sra-report/postman_configs/environments/mainnet_tokens.postman_environment.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id": "e1087711-9cff-fd54-ba20-990b17a15f80", - "name": "Mainnet tokens", - "values": [ - { - "enabled": true, - "key": "DAI_address", - "value": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", - "type": "text" - }, - { - "enabled": true, - "key": "WETH_address", - "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "type": "text" - }, - { - "enabled": true, - "key": "ZRX_address", - "value": "0xe41d2489571d322189246dafa5ebde1f4699f498", - "type": "text" - }, - { - "enabled": true, - "key": "EXCHANGE_CONTRACT_address", - "value": "0x12459c951127e0c374ff9105dda097662a027093", - "type": "text" - } - ], - "timestamp": 1518561760642, - "_postman_variable_scope": "environment", - "_postman_exported_at": "2018-02-14T22:48:11.259Z", - "_postman_exported_using": "Postman/5.5.2" -} diff --git a/packages/sra-report/src/contract_addresses/kovan_addresses.ts b/packages/sra-report/src/contract_addresses/kovan_addresses.ts new file mode 100644 index 0000000000..e097bf12ab --- /dev/null +++ b/packages/sra-report/src/contract_addresses/kovan_addresses.ts @@ -0,0 +1,6 @@ +export const addresses = { + 'DAI_address': '0xb18845c260f680d5b9d84649638813e342e4f8c9', + 'WETH_address': '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + 'ZRX_address': '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + 'EXCHANGE_CONTRACT_address': '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', +}; diff --git a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts new file mode 100644 index 0000000000..cbd79a0e7c --- /dev/null +++ b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts @@ -0,0 +1,6 @@ +export const addresses = { + 'DAI_address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + 'WETH_address': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + 'ZRX_address': '0xe41d2489571d322189246dafa5ebde1f4699f498', + 'EXCHANGE_CONTRACT_address': '0x12459c951127e0c374ff9105dda097662a027093', +}; diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index c74b5cb400..4e93708ee0 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -60,11 +60,6 @@ const mainAsync = async () => { if (_.isUndefined(firstOrder)) { throw new Error('Could not get any orders from /orders endpoint'); } - const newmanEnvironmentOptions = { - collection: sraReportCollectionJSON, - globals: postmanEnvironmentFactory.createGlobalEnvironment(args.endpointUrl, firstOrder), - environment: postmanEnvironmentFactory.createNetworkEnvironment(args.networkId), - }; const newmanReporterOptions = !_.isUndefined(args.output) ? { reporters: 'json', @@ -78,7 +73,8 @@ const mainAsync = async () => { reporters: 'cli', }; const newmanRunOptions = { - ...newmanEnvironmentOptions, + collection: sraReportCollectionJSON, + environment: postmanEnvironmentFactory.createPostmanEnvironment(args.endpointUrl, args.networkId, firstOrder), ...newmanReporterOptions, }; await newmanRunAsync(newmanRunOptions); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index a8753d9ff0..7534baaafc 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -2,8 +2,8 @@ import { SignedOrder, ZeroEx } from '0x.js'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; import * as _ from 'lodash'; -import * as kovanTokensEnvironmentJSON from '../postman_configs/environments/kovan_tokens.postman_environment.json'; -import * as mainnetTokensEnvironmentJSON from '../postman_configs/environments/mainnet_tokens.postman_environment.json'; +import { addresses as kovanAddresses} from './contract_addresses/kovan_addresses'; +import { addresses as mainnetAddresses} from './contract_addresses/mainnet_addresses'; interface EnvironmentValue { key: string; @@ -13,7 +13,7 @@ interface EnvironmentValue { } export const postmanEnvironmentFactory = { - createGlobalEnvironment(url: string, order: SignedOrder) { + createPostmanEnvironment(url: string, networkId: number, order: SignedOrder) { const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( _.map(schemas, (schema: Schema) => { @@ -30,8 +30,14 @@ export const postmanEnvironmentFactory = { const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { return environmentValue.key; }); + const contractAddresses = getContractAddresses(networkId); + const contractAddressEnvironmentValues = _.map(_.keys(contractAddresses), (key: string) => { + const contractAddress = _.get(contractAddresses, key); + return createEnvironmentValue(key, contractAddress); + }); const allEnvironmentValues = _.concat( schemaEnvironmentValues, + contractAddressEnvironmentValues, createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)), createEnvironmentValue('url', url), createEnvironmentValue('order', JSON.stringify(order)), @@ -45,17 +51,17 @@ export const postmanEnvironmentFactory = { }; return environment; }, - createNetworkEnvironment(networkId: number) { - switch (networkId) { - case 1: - return mainnetTokensEnvironmentJSON; - case 42: - return kovanTokensEnvironmentJSON; - default: - return {}; - } - }, }; +function getContractAddresses(networkId: number) { + switch (networkId) { + case 1: + return mainnetAddresses; + case 42: + return kovanAddresses; + default: + throw new Error('Unsupported network id'); + } +} function convertSchemaIdToKey(schemaId: string) { let result = schemaId; if (_.startsWith(result, '/')) { From 61ad8d4c1055096c98075282514f8a6fad020acc Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 5 Mar 2018 16:31:39 -0800 Subject: [PATCH 31/94] Add comment to environment factory --- .../sra-report/src/postman_environment_factory.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 7534baaafc..d32d41e188 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -7,12 +7,17 @@ import { addresses as mainnetAddresses} from './contract_addresses/mainnet_addre interface EnvironmentValue { key: string; - value: string; - enabled: boolean; - type: string; } export const postmanEnvironmentFactory = { + /** + * Dynamically generates a postman environment (https://www.getpostman.com/docs/v6/postman/environments_and_globals/manage_environments) + * When running the postman collection via newman, we provide it a set of environment variables + * These variables include: + * - 0x JSON schemas for response body validation + * - Contract addresses based on the network id for making specific queries (ex. baseTokenAddress=ZRX_address) + * - Order properties for making specific queries (ex. maker=orderMaker) + */ createPostmanEnvironment(url: string, networkId: number, order: SignedOrder) { const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( From eb201c4084834e06fc8376434e8a7f793f916c24 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 00:18:58 -0800 Subject: [PATCH 32/94] Add options for exporting environment and collection files --- packages/sra-report/src/index.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index 4e93708ee0..37c7a8f04a 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -27,7 +27,7 @@ const args = yargs }) .option('output', { alias: ['o', 'out'], - describe: 'Folder where to write the reports', + describe: 'The relative path to write the report generated by the collection run, prints to console by default', type: 'string', normalize: true, demandOption: false, @@ -38,7 +38,21 @@ const args = yargs type: 'number', default: DEFAULT_NETWORK_ID, }) - .example("$0 --endpoint-url 'http://api.example.com' --out 'src/contracts/generated/' --network-id 42", 'Full usage example') + .option('export-collection', { + alias: ['ec'], + describe: 'The relative path to write the postman collection file used by the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) + .option('export-environment', { + alias: ['ee'], + describe: 'The relative path to write the postman environment file used by the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) + .example("$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --export-environment 'path/to/environment.json' --export-collection 'path/to/collection.json'", 'Full usage example') .argv; // perform extra validation on command line arguments try { @@ -52,7 +66,6 @@ if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { utils.log(`${chalk.bold(`Supported network ids:`)} ${SUPPORTED_NETWORK_IDS}`); process.exit(1); } - const mainAsync = async () => { const httpClient = new HttpClient(args.endpointUrl); const orders = await httpClient.getOrdersAsync(); @@ -75,6 +88,8 @@ const mainAsync = async () => { const newmanRunOptions = { collection: sraReportCollectionJSON, environment: postmanEnvironmentFactory.createPostmanEnvironment(args.endpointUrl, args.networkId, firstOrder), + exportCollection: args.exportCollection, + exportEnvironment: args.exportEnvironment, ...newmanReporterOptions, }; await newmanRunAsync(newmanRunOptions); From 551771235bc80209a1f3aafff56438621885f61a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 00:20:36 -0800 Subject: [PATCH 33/94] Prettier --- .../sra_report.postman_collection.json | 1881 ++++++++--------- .../src/contract_addresses/kovan_addresses.ts | 8 +- .../contract_addresses/mainnet_addresses.ts | 8 +- packages/sra-report/src/index.ts | 6 +- .../src/postman_environment_factory.ts | 4 +- 5 files changed, 922 insertions(+), 985 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index fc7dd5b65b..04c7bc51fb 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -1,974 +1,909 @@ { - "info": { - "name": "sra_report", - "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", - "description": "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "GET /token_pairs", - "description": "", - "item": [ - { - "name": "default request", - "event": [ - { - "listen": "test", - "script": { - "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs", - "host": [ - "{{url}}" - ], - "path": [ - "token_pairs" - ] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenA param", - "event": [ - { - "listen": "test", - "script": { - "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'WETH_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", - " const condition = tokenAIsFilterToken || tokenBIsFilterToken", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "token_pairs" - ], - "query": [ - { - "key": "tokenA", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenB param", - "event": [ - { - "listen": "test", - "script": { - "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'WETH_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", - " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", - " const condition = tokenAIsFilterToken || tokenBIsFilterToken", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "token_pairs" - ], - "query": [ - { - "key": "tokenB", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - }, - { - "name": "tokenA and tokenB params", - "event": [ - { - "listen": "test", - "script": { - "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", - "type": "text/javascript", - "exec": [ - "const filterTokenAEnvKey = 'WETH_address';", - "const filterTokenBEnvKey = 'ZRX_address';", - "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", - "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Token pairs are properly filtered', function() {", - " _.forEach(responseJsonData, function(tokenPair) {", - " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", - " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", - " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", - " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", - " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "token_pairs" - ], - "query": [ - { - "key": "tokenA", - "value": "{{WETH_address}}", - "equals": true - }, - { - "key": "tokenB", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": "" - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /orders", - "description": "", - "item": [ - { - "name": "default request", - "event": [ - { - "listen": "test", - "script": { - "id": "118f47dd-1d93-4288-841f-de88783eff3b", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "exchangeContract param", - "event": [ - { - "listen": "test", - "script": { - "id": "e782ae77-66bf-4096-9190-ef9e7501b316", - "type": "text/javascript", - "exec": [ - "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", - "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", - " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "exchangeContractAddress", - "value": "{{EXCHANGE_CONTRACT_address}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "tokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", - " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", - " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "tokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "makerTokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "01eb5865-edec-4216-8a90-c36d790366fc", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "makerTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "takerTokenAddress param", - "event": [ - { - "listen": "test", - "script": { - "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", - "type": "text/javascript", - "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", - "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "takerTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "maker param", - "event": [ - { - "listen": "test", - "script": { - "id": "1f5960de-117f-44fb-82e0-581626cbf62b", - "type": "text/javascript", - "exec": [ - "const orderMakerEnvKey = 'orderMaker';", - "const referenceOrderMakerAddress = pm.environment.get(orderMakerEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedMakerAddress = _.get(order, 'maker');", - " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?maker={{orderMaker}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "maker", - "value": "{{orderMaker}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "taker param", - "event": [ - { - "listen": "test", - "script": { - "id": "f23de2eb-b444-49d3-93b7-14ae712d6502", - "type": "text/javascript", - "exec": [ - "const orderTakerEnvKey = 'orderTaker';", - "const referenceOrderTakerAddress = pm.environment.get(orderTakerEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedTakerAddress = _.get(order, 'taker');", - " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?taker={{orderTaker}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "taker", - "value": "{{orderTaker}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "trader param", - "event": [ - { - "listen": "test", - "script": { - "id": "9689639a-47ce-4c3b-8180-859fd28437be", - "type": "text/javascript", - "exec": [ - "const orderTraderEnvKey = 'orderMaker';", - "const referenceOrderTraderAddress = pm.environment.get(orderTraderEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedMakerAddress = _.get(order, 'maker');", - " const returnedTakerAddress = _.get(order, 'taker');", - " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", - " pm.expect(condition).to.be.true;", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?trader={{orderMaker}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "trader", - "value": "{{orderMaker}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - }, - { - "name": "feeRecipient param", - "event": [ - { - "listen": "test", - "script": { - "id": "010c0cf6-8d5b-4fe3-8b92-b2009ea43a3e", - "type": "text/javascript", - "exec": [ - "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", - "const referenceOrderFeeRecipientAddress = pm.environment.get(orderFeeRecipientEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orders are properly filtered', function() {", - " _.forEach(responseJsonData, function(order) {", - " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", - " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", - "host": [ - "{{url}}" - ], - "path": [ - "orders" - ], - "query": [ - { - "key": "feeRecipient", - "value": "{{orderFeeRecipient}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/signedOrdersSchema');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /order", - "description": "", - "item": [ - { - "name": "orderHash param", - "event": [ - { - "listen": "test", - "script": { - "id": "18876df2-384e-43d5-93a1-7e24571e1308", - "type": "text/javascript", - "exec": [ - "const orderEnvKey = 'order';", - "const referenceOrderString = pm.environment.get(orderEnvKey);", - "const referenceOrderJson = JSON.parse(referenceOrderString);", - "const orderPropertyNames = [", - " 'maker',", - " 'taker',", - " 'makerFee',", - " 'takerFee',", - " 'makerTokenAmount',", - " 'takerTokenAmount',", - " 'makerTokenAddress',", - " 'takerTokenAddress',", - " 'salt',", - " 'feeRecipient',", - " 'expirationUnixTimestampSec',", - " 'exchangeContractAddress'", - "];", - "const signaturePropertyNames = [", - " 'v',", - " 'r',", - " 's'", - "];", - "const returnedOrderJson = pm.response.json();", - "pm.test('Order is properly retreived', function() {", - " _.forEach(orderPropertyNames, function(propertyName) {", - " const returnedProperty = _.get(returnedOrderJson, propertyName);", - " const referenceProperty = _.get(referenceOrderJson, propertyName);", - " pm.expect(returnedProperty).to.be.equal(referenceProperty);", - " });", - " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", - " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", - " _.forEach(signaturePropertyNames, function(propertyName) {", - " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", - " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", - " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/order/{{orderHash}}", - "host": [ - "{{url}}" - ], - "path": [ - "order", - "{{orderHash}}" - ] - }, - "description": "Retrieves a specific order by orderHash." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/SignedOrder');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "GET /orderbook", - "description": "", - "item": [ - { - "name": "baseTokenAddress and quoteTokenAddress params", - "event": [ - { - "listen": "test", - "script": { - "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", - "type": "text/javascript", - "exec": [ - "const baseTokenEnvKey = 'ZRX_address';", - "const quoteTokenEnvKey = 'WETH_address';", - "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", - "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", - "const responseJsonData = pm.response.json();", - "pm.test('Orderbook is properly filtered', function() {", - " const bids = _.get(responseJsonData, 'bids');", - " const asks = _.get(responseJsonData, 'asks');", - " _.forEach(bids, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", - " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", - " });", - " _.forEach(asks, function(order) {", - " const makerTokenAddress = _.get(order, 'makerTokenAddress');", - " const takerTokenAddress = _.get(order, 'takerTokenAddress');", - " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", - " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", - " });", - "});", - "" - ] - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": {}, - "url": { - "raw": "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", - "host": [ - "{{url}}" - ], - "path": [ - "orderbook" - ], - "query": [ - { - "key": "baseTokenAddress", - "value": "{{ZRX_address}}", - "equals": true - }, - { - "key": "quoteTokenAddress", - "value": "{{WETH_address}}", - "equals": true - } - ] - }, - "description": "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - }, - { - "name": "POST /fees", - "description": "", - "item": [ - { - "name": "default request", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" - }, - "url": { - "raw": "{{url}}/fees", - "host": [ - "{{url}}" - ], - "path": [ - "fees" - ] - }, - "description": "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", - "type": "text/javascript", - "exec": [ - "const schema = tv4.getSchema('/RelayerApiFeesResponse');", - "const responseJsonData = pm.response.json();", - "", - "pm.test('Schema is valid', function() {", - " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", - "});", - "" - ] - } - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "da60f639-df79-4f4d-9861-79219f5fc341", - "type": "text/javascript", - "exec": [ - "const schemaKeysString = pm.environment.get('schemaKeys');", - "const schemaKeys = JSON.parse(schemaKeysString);", - "_.forEach(schemaKeys, function(schemaKey) {", - " const schemaString = pm.environment.get(schemaKey);", - " const schema = JSON.parse(schemaString);", - " tv4.addSchema(schema);", - "});", - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", - "type": "text/javascript", - "exec": [ - "pm.test('Has Content-Type header with value application/json', function () {", - " pm.response.to.have.header('Content-Type');", - " const contentType = postman.getResponseHeader('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});", - "" - ] - } - } - ] -} \ No newline at end of file + "info": { + "name": "sra_report", + "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", + "description": + "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /token_pairs", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs", + "host": ["{{url}}"], + "path": ["token_pairs"] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA param", + "event": [ + { + "listen": "test", + "script": { + "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenB param", + "event": [ + { + "listen": "test", + "script": { + "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenB", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA and tokenB params", + "event": [ + { + "listen": "test", + "script": { + "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", + "type": "text/javascript", + "exec": [ + "const filterTokenAEnvKey = 'WETH_address';", + "const filterTokenBEnvKey = 'ZRX_address';", + "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", + "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", + " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", + " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", + " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", + " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{WETH_address}}", + "equals": true + }, + { + "key": "tokenB", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orders", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "118f47dd-1d93-4288-841f-de88783eff3b", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders", + "host": ["{{url}}"], + "path": ["orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "exchangeContract param", + "event": [ + { + "listen": "test", + "script": { + "id": "e782ae77-66bf-4096-9190-ef9e7501b316", + "type": "text/javascript", + "exec": [ + "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", + "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", + " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "exchangeContractAddress", + "value": "{{EXCHANGE_CONTRACT_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "tokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", + " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", + " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "tokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "makerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "01eb5865-edec-4216-8a90-c36d790366fc", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "makerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "takerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "takerTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "maker param", + "event": [ + { + "listen": "test", + "script": { + "id": "1f5960de-117f-44fb-82e0-581626cbf62b", + "type": "text/javascript", + "exec": [ + "const orderMakerEnvKey = 'orderMaker';", + "const referenceOrderMakerAddress = pm.environment.get(orderMakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?maker={{orderMaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "maker", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "taker param", + "event": [ + { + "listen": "test", + "script": { + "id": "f23de2eb-b444-49d3-93b7-14ae712d6502", + "type": "text/javascript", + "exec": [ + "const orderTakerEnvKey = 'orderTaker';", + "const referenceOrderTakerAddress = pm.environment.get(orderTakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedTakerAddress = _.get(order, 'taker');", + " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?taker={{orderTaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "taker", + "value": "{{orderTaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "trader param", + "event": [ + { + "listen": "test", + "script": { + "id": "9689639a-47ce-4c3b-8180-859fd28437be", + "type": "text/javascript", + "exec": [ + "const orderTraderEnvKey = 'orderMaker';", + "const referenceOrderTraderAddress = pm.environment.get(orderTraderEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " const returnedTakerAddress = _.get(order, 'taker');", + " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?trader={{orderMaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "trader", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "feeRecipient param", + "event": [ + { + "listen": "test", + "script": { + "id": "010c0cf6-8d5b-4fe3-8b92-b2009ea43a3e", + "type": "text/javascript", + "exec": [ + "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", + "const referenceOrderFeeRecipientAddress = pm.environment.get(orderFeeRecipientEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", + " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "feeRecipient", + "value": "{{orderFeeRecipient}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/signedOrdersSchema');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /order", + "description": "", + "item": [ + { + "name": "orderHash param", + "event": [ + { + "listen": "test", + "script": { + "id": "18876df2-384e-43d5-93a1-7e24571e1308", + "type": "text/javascript", + "exec": [ + "const orderEnvKey = 'order';", + "const referenceOrderString = pm.environment.get(orderEnvKey);", + "const referenceOrderJson = JSON.parse(referenceOrderString);", + "const orderPropertyNames = [", + " 'maker',", + " 'taker',", + " 'makerFee',", + " 'takerFee',", + " 'makerTokenAmount',", + " 'takerTokenAmount',", + " 'makerTokenAddress',", + " 'takerTokenAddress',", + " 'salt',", + " 'feeRecipient',", + " 'expirationUnixTimestampSec',", + " 'exchangeContractAddress'", + "];", + "const signaturePropertyNames = [", + " 'v',", + " 'r',", + " 's'", + "];", + "const returnedOrderJson = pm.response.json();", + "pm.test('Order is properly retreived', function() {", + " _.forEach(orderPropertyNames, function(propertyName) {", + " const returnedProperty = _.get(returnedOrderJson, propertyName);", + " const referenceProperty = _.get(referenceOrderJson, propertyName);", + " pm.expect(returnedProperty).to.be.equal(referenceProperty);", + " });", + " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", + " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", + " _.forEach(signaturePropertyNames, function(propertyName) {", + " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", + " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", + " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/order/{{orderHash}}", + "host": ["{{url}}"], + "path": ["order", "{{orderHash}}"] + }, + "description": "Retrieves a specific order by orderHash." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/SignedOrder');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orderbook", + "description": "", + "item": [ + { + "name": "baseTokenAddress and quoteTokenAddress params", + "event": [ + { + "listen": "test", + "script": { + "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", + "type": "text/javascript", + "exec": [ + "const baseTokenEnvKey = 'ZRX_address';", + "const quoteTokenEnvKey = 'WETH_address';", + "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", + "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orderbook is properly filtered', function() {", + " const bids = _.get(responseJsonData, 'bids');", + " const asks = _.get(responseJsonData, 'asks');", + " _.forEach(bids, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", + " });", + " _.forEach(asks, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": + "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", + "host": ["{{url}}"], + "path": ["orderbook"], + "query": [ + { + "key": "baseTokenAddress", + "value": "{{ZRX_address}}", + "equals": true + }, + { + "key": "quoteTokenAddress", + "value": "{{WETH_address}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "POST /fees", + "description": "", + "item": [ + { + "name": "default request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": + "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" + }, + "url": { + "raw": "{{url}}/fees", + "host": ["{{url}}"], + "path": ["fees"] + }, + "description": + "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiFeesResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "da60f639-df79-4f4d-9861-79219f5fc341", + "type": "text/javascript", + "exec": [ + "const schemaKeysString = pm.environment.get('schemaKeys');", + "const schemaKeys = JSON.parse(schemaKeysString);", + "_.forEach(schemaKeys, function(schemaKey) {", + " const schemaString = pm.environment.get(schemaKey);", + " const schema = JSON.parse(schemaString);", + " tv4.addSchema(schema);", + "});", + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", + "type": "text/javascript", + "exec": [ + "pm.test('Has Content-Type header with value application/json', function () {", + " pm.response.to.have.header('Content-Type');", + " const contentType = postman.getResponseHeader('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "" + ] + } + } + ] +} diff --git a/packages/sra-report/src/contract_addresses/kovan_addresses.ts b/packages/sra-report/src/contract_addresses/kovan_addresses.ts index e097bf12ab..4d2b76a51a 100644 --- a/packages/sra-report/src/contract_addresses/kovan_addresses.ts +++ b/packages/sra-report/src/contract_addresses/kovan_addresses.ts @@ -1,6 +1,6 @@ export const addresses = { - 'DAI_address': '0xb18845c260f680d5b9d84649638813e342e4f8c9', - 'WETH_address': '0xd0a1e359811322d97991e03f863a0c30c2cf029c', - 'ZRX_address': '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', - 'EXCHANGE_CONTRACT_address': '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + DAI_address: '0xb18845c260f680d5b9d84649638813e342e4f8c9', + WETH_address: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + ZRX_address: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + EXCHANGE_CONTRACT_address: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', }; diff --git a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts index cbd79a0e7c..4ccbdf12dc 100644 --- a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts +++ b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts @@ -1,6 +1,6 @@ export const addresses = { - 'DAI_address': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - 'WETH_address': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - 'ZRX_address': '0xe41d2489571d322189246dafa5ebde1f4699f498', - 'EXCHANGE_CONTRACT_address': '0x12459c951127e0c374ff9105dda097662a027093', + DAI_address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + WETH_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + ZRX_address: '0xe41d2489571d322189246dafa5ebde1f4699f498', + EXCHANGE_CONTRACT_address: '0x12459c951127e0c374ff9105dda097662a027093', }; diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index 37c7a8f04a..4419a3ec8b 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -52,8 +52,10 @@ const args = yargs normalize: true, demandOption: false, }) - .example("$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --export-environment 'path/to/environment.json' --export-collection 'path/to/collection.json'", 'Full usage example') - .argv; + .example( + "$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --export-environment 'path/to/environment.json' --export-collection 'path/to/collection.json'", + 'Full usage example', + ).argv; // perform extra validation on command line arguments try { assert.isWebUri('args', args.endpointUrl); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index d32d41e188..e4276d4a50 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -2,8 +2,8 @@ import { SignedOrder, ZeroEx } from '0x.js'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; import * as _ from 'lodash'; -import { addresses as kovanAddresses} from './contract_addresses/kovan_addresses'; -import { addresses as mainnetAddresses} from './contract_addresses/mainnet_addresses'; +import { addresses as kovanAddresses } from './contract_addresses/kovan_addresses'; +import { addresses as mainnetAddresses } from './contract_addresses/mainnet_addresses'; interface EnvironmentValue { key: string; From ddad09a93639f9060db69d131a52ef8dbaf4a3fe Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 00:44:18 -0800 Subject: [PATCH 34/94] Add support for custom environment file --- packages/sra-report/src/index.ts | 21 +++++++++++-------- .../src/postman_environment_factory.ts | 19 +++++++++++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index 4419a3ec8b..d292f15e10 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node import { assert } from '@0xproject/assert'; -import { HttpClient } from '@0xproject/connect'; import { Schema, schemas } from '@0xproject/json-schemas'; import { promisify } from '@0xproject/utils'; import chalk from 'chalk'; @@ -38,6 +37,13 @@ const args = yargs type: 'number', default: DEFAULT_NETWORK_ID, }) + .option('environment', { + alias: ['env'], + describe: 'File path to an environment file for the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) .option('export-collection', { alias: ['ec'], describe: 'The relative path to write the postman collection file used by the collection run', @@ -53,7 +59,7 @@ const args = yargs demandOption: false, }) .example( - "$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --export-environment 'path/to/environment.json' --export-collection 'path/to/collection.json'", + "$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --environment 'path/to/custom/environment.json' --export-collection 'path/to/collection.json' --export-environment 'path/to/environment.json'", 'Full usage example', ).argv; // perform extra validation on command line arguments @@ -69,12 +75,6 @@ if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { process.exit(1); } const mainAsync = async () => { - const httpClient = new HttpClient(args.endpointUrl); - const orders = await httpClient.getOrdersAsync(); - const firstOrder = _.head(orders); - if (_.isUndefined(firstOrder)) { - throw new Error('Could not get any orders from /orders endpoint'); - } const newmanReporterOptions = !_.isUndefined(args.output) ? { reporters: 'json', @@ -87,9 +87,12 @@ const mainAsync = async () => { : { reporters: 'cli', }; + const environment = !_.isUndefined(args.environment) + ? args.environment + : await postmanEnvironmentFactory.createPostmanEnvironmentAsync(args.endpointUrl, args.networkId); const newmanRunOptions = { collection: sraReportCollectionJSON, - environment: postmanEnvironmentFactory.createPostmanEnvironment(args.endpointUrl, args.networkId, firstOrder), + environment, exportCollection: args.exportCollection, exportEnvironment: args.exportEnvironment, ...newmanReporterOptions, diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index e4276d4a50..703132c6a2 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -1,4 +1,5 @@ import { SignedOrder, ZeroEx } from '0x.js'; +import { HttpClient } from '@0xproject/connect'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; import * as _ from 'lodash'; @@ -18,7 +19,7 @@ export const postmanEnvironmentFactory = { * - Contract addresses based on the network id for making specific queries (ex. baseTokenAddress=ZRX_address) * - Order properties for making specific queries (ex. maker=orderMaker) */ - createPostmanEnvironment(url: string, networkId: number, order: SignedOrder) { + async createPostmanEnvironmentAsync(url: string, networkId: number) { const schemas: Schema[] = _.values(schemasByName); const schemaEnvironmentValues = _.compact( _.map(schemas, (schema: Schema) => { @@ -40,16 +41,22 @@ export const postmanEnvironmentFactory = { const contractAddress = _.get(contractAddresses, key); return createEnvironmentValue(key, contractAddress); }); + const httpClient = new HttpClient(url); + const orders = await httpClient.getOrdersAsync(); + const firstOrder = _.head(orders); + if (_.isUndefined(firstOrder)) { + throw new Error('Could not get any orders from /orders endpoint'); + } const allEnvironmentValues = _.concat( schemaEnvironmentValues, contractAddressEnvironmentValues, createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)), createEnvironmentValue('url', url), - createEnvironmentValue('order', JSON.stringify(order)), - createEnvironmentValue('orderMaker', order.maker), - createEnvironmentValue('orderTaker', order.taker), - createEnvironmentValue('orderFeeRecipient', order.feeRecipient), - createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(order)), + createEnvironmentValue('order', JSON.stringify(firstOrder)), + createEnvironmentValue('orderMaker', firstOrder.maker), + createEnvironmentValue('orderTaker', firstOrder.taker), + createEnvironmentValue('orderFeeRecipient', firstOrder.feeRecipient), + createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(firstOrder)), ); const environment = { values: allEnvironmentValues, From 0b1ba9f9971bea9003dfb30fca535c17ce62ad08 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 16:31:55 +0100 Subject: [PATCH 35/94] Move Documentation to the `@0xproject/react-docs` package --- packages/react-docs/.npmignore | 5 + packages/react-docs/CHANGELOG.md | 3 + packages/react-docs/README.md | 47 +++ packages/react-docs/package.json | 37 +++ packages/react-docs/scripts/postpublish.js | 5 + .../src/ts/components}/badge.tsx | 4 +- .../src/ts/components}/comment.tsx | 2 +- .../src/ts/components}/custom_enum.tsx | 7 +- .../src/ts/components}/docs_info.ts | 7 +- .../src/ts/components}/documentation.tsx | 33 +- .../src/ts/components}/enum.tsx | 5 +- .../src/ts/components}/event_definition.tsx | 12 +- .../src/ts/components}/interface.tsx | 12 +- .../src/ts/components}/method_block.tsx | 18 +- .../src/ts/components}/method_signature.tsx | 12 +- .../src/ts/components}/source_link.tsx | 5 +- .../src/ts/components}/type.tsx | 16 +- .../src/ts/components}/type_definition.tsx | 24 +- packages/react-docs/src/ts/globals.d.ts | 7 + packages/react-docs/src/ts/index.ts | 20 ++ packages/react-docs/src/ts/types.ts | 266 ++++++++++++++++ packages/react-docs/src/ts/utils/constants.ts | 9 + .../src}/ts/utils/doxity_utils.ts | 3 +- .../src}/ts/utils/typedoc_utils.ts | 11 +- packages/react-docs/src/ts/utils/utils.ts | 10 + packages/react-docs/tsconfig.json | 14 + packages/react-docs/tslint.json | 9 + packages/react-shared/package.json | 1 + packages/react-shared/src/ts/globals.d.ts | 6 + packages/react-shared/src/ts/index.ts | 2 +- packages/react-shared/src/ts/types.ts | 12 + .../react-shared/src/ts/utils/constants.ts | 14 + packages/react-shared/src/ts/utils/utils.ts | 15 + packages/website/package.json | 3 +- packages/website/ts/blockchain.ts | 14 +- .../dialogs/blockchain_err_dialog.tsx | 4 +- .../dialogs/ledger_config_dialog.tsx | 6 +- .../dropdowns/network_drop_down.tsx | 4 +- .../website/ts/components/eth_wrappers.tsx | 15 +- packages/website/ts/components/fill_order.tsx | 6 +- .../generate_order/generate_order_form.tsx | 3 +- .../ts/components/inputs/allowance_toggle.tsx | 3 +- .../website/ts/components/token_balances.tsx | 24 +- .../ts/components/top_bar/provider_picker.tsx | 3 +- .../website/ts/components/top_bar/top_bar.tsx | 4 +- .../trade_history/trade_history_item.tsx | 4 +- .../ts/components/ui/ethereum_address.tsx | 2 +- .../ts/components/ui/etherscan_icon.tsx | 5 +- packages/website/ts/components/ui/party.tsx | 5 +- .../ts/containers/connect_documentation.ts | 6 +- .../smart_contracts_documentation.ts | 12 +- .../ts/containers/zero_ex_js_documentation.ts | 6 +- packages/website/ts/globals.d.ts | 12 - .../ts/pages/documentation/doc_page.tsx | 5 +- packages/website/ts/types.ts | 285 ------------------ packages/website/ts/utils/configs.ts | 2 +- packages/website/ts/utils/constants.ts | 21 +- packages/website/ts/utils/doc_utils.ts | 3 +- packages/website/ts/utils/utils.ts | 31 +- 59 files changed, 653 insertions(+), 488 deletions(-) create mode 100644 packages/react-docs/.npmignore create mode 100644 packages/react-docs/CHANGELOG.md create mode 100644 packages/react-docs/README.md create mode 100644 packages/react-docs/package.json create mode 100644 packages/react-docs/scripts/postpublish.js rename packages/{website/ts/components/ui => react-docs/src/ts/components}/badge.tsx (95%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/comment.tsx (94%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/custom_enum.tsx (88%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/docs_info.ts (97%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/documentation.tsx (94%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/enum.tsx (87%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/event_definition.tsx (91%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/interface.tsx (87%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/method_block.tsx (93%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/method_signature.tsx (94%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/source_link.tsx (89%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/type.tsx (96%) rename packages/{website/ts/pages/documentation => react-docs/src/ts/components}/type_definition.tsx (88%) create mode 100644 packages/react-docs/src/ts/globals.d.ts create mode 100644 packages/react-docs/src/ts/index.ts create mode 100644 packages/react-docs/src/ts/types.ts create mode 100644 packages/react-docs/src/ts/utils/constants.ts rename packages/{website => react-docs/src}/ts/utils/doxity_utils.ts (99%) rename packages/{website => react-docs/src}/ts/utils/typedoc_utils.ts (98%) create mode 100644 packages/react-docs/src/ts/utils/utils.ts create mode 100644 packages/react-docs/tsconfig.json create mode 100644 packages/react-docs/tslint.json diff --git a/packages/react-docs/.npmignore b/packages/react-docs/.npmignore new file mode 100644 index 0000000000..87bc304368 --- /dev/null +++ b/packages/react-docs/.npmignore @@ -0,0 +1,5 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json diff --git a/packages/react-docs/CHANGELOG.md b/packages/react-docs/CHANGELOG.md new file mode 100644 index 0000000000..7c3ac2adfd --- /dev/null +++ b/packages/react-docs/CHANGELOG.md @@ -0,0 +1,3 @@ +# CHANGELOG + +## vX.X.X - _TBD, 2018_ diff --git a/packages/react-docs/README.md b/packages/react-docs/README.md new file mode 100644 index 0000000000..9299eb2d45 --- /dev/null +++ b/packages/react-docs/README.md @@ -0,0 +1,47 @@ +## @0xproject/react-docs + +A full-page React component for rendering beautiful documentation generated with [TypeDoc](http://typedoc.org/) or [Doxity](https://github.com/0xproject/doxity). + +## Installation + +```bash +yarn add @0xproject/react-docs +``` + +## Contributing + +We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +```bash +yarn build +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json new file mode 100644 index 0000000000..b87d20feca --- /dev/null +++ b/packages/react-docs/package.json @@ -0,0 +1,37 @@ +{ + "name": "@0xproject/react-docs", + "version": "0.0.1", + "description": "React documentation component for rendering TypeDoc & Doxity generated JSON", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", + "build": "tsc", + "build:watch": "tsc -w", + "clean": "shx rm -rf lib" + }, + "author": "Fabio Berger", + "license": "MIT", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.9", + "@types/lodash": "^4.14.86", + "@types/node": "^8.0.53", + "@types/material-ui": "0.18.0", + "@types/react": "^15.0.15", + "@types/react-dom": "^0.14.23", + "shx": "^0.2.2", + "tslint": "^5.9.1", + "typescript": "2.7.1" + }, + "dependencies": { + "@0xproject/react-shared": "^0.0.1", + "basscss": "^8.0.3", + "compare-versions": "^3.0.1", + "react-tooltip": "^3.2.7", + "material-ui": "^0.17.1", + "react": "15.6.1", + "react-dom": "15.6.1", + "lodash": "^4.17.4", + "react-tap-event-plugin": "^2.0.1" + } +} diff --git a/packages/react-docs/scripts/postpublish.js b/packages/react-docs/scripts/postpublish.js new file mode 100644 index 0000000000..639656c7ee --- /dev/null +++ b/packages/react-docs/scripts/postpublish.js @@ -0,0 +1,5 @@ +const postpublish_utils = require('../../../scripts/postpublish_utils'); +const packageJSON = require('../package.json'); + +const subPackageName = packageJSON.name; +postpublish_utils.standardPostPublishAsync(subPackageName); diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/react-docs/src/ts/components/badge.tsx similarity index 95% rename from packages/website/ts/components/ui/badge.tsx rename to packages/react-docs/src/ts/components/badge.tsx index 3e1c545be8..b342f2dca4 100644 --- a/packages/website/ts/components/ui/badge.tsx +++ b/packages/react-docs/src/ts/components/badge.tsx @@ -15,12 +15,12 @@ const styles: Styles = { }, }; -interface BadgeProps { +export interface BadgeProps { title: string; backgroundColor: string; } -interface BadgeState { +export interface BadgeState { isHovering: boolean; } diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/react-docs/src/ts/components/comment.tsx similarity index 94% rename from packages/website/ts/pages/documentation/comment.tsx rename to packages/react-docs/src/ts/components/comment.tsx index b8902679a6..0d63d4d313 100644 --- a/packages/website/ts/pages/documentation/comment.tsx +++ b/packages/react-docs/src/ts/components/comment.tsx @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as ReactMarkdown from 'react-markdown'; -interface CommentProps { +export interface CommentProps { comment: string; className?: string; } diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/react-docs/src/ts/components/custom_enum.tsx similarity index 88% rename from packages/website/ts/pages/documentation/custom_enum.tsx rename to packages/react-docs/src/ts/components/custom_enum.tsx index 8d50a2f526..deb33ff1d5 100644 --- a/packages/website/ts/pages/documentation/custom_enum.tsx +++ b/packages/react-docs/src/ts/components/custom_enum.tsx @@ -1,11 +1,12 @@ import * as _ from 'lodash'; import * as React from 'react'; -import { CustomType } from 'ts/types'; -import { utils } from 'ts/utils/utils'; + +import { CustomType } from '../types'; +import { utils } from '../utils/utils'; const STRING_ENUM_CODE_PREFIX = ' strEnum('; -interface CustomEnumProps { +export interface CustomEnumProps { type: CustomType; } diff --git a/packages/website/ts/pages/documentation/docs_info.ts b/packages/react-docs/src/ts/components/docs_info.ts similarity index 97% rename from packages/website/ts/pages/documentation/docs_info.ts rename to packages/react-docs/src/ts/components/docs_info.ts index 09fb7a2bcd..509bba89ed 100644 --- a/packages/website/ts/pages/documentation/docs_info.ts +++ b/packages/react-docs/src/ts/components/docs_info.ts @@ -1,6 +1,7 @@ import { MenuSubsectionsBySection } from '@0xproject/react-shared'; import compareVersions = require('compare-versions'); import * as _ from 'lodash'; + import { ContractsByVersionByNetworkId, DocAgnosticFormat, @@ -10,9 +11,9 @@ import { SectionsMap, SupportedDocJson, TypeDocNode, -} from 'ts/types'; -import { doxityUtils } from 'ts/utils/doxity_utils'; -import { typeDocUtils } from 'ts/utils/typedoc_utils'; +} from '../types'; +import { doxityUtils } from '../utils/doxity_utils'; +import { typeDocUtils } from '../utils/typedoc_utils'; export class DocsInfo { public id: string; diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx similarity index 94% rename from packages/website/ts/pages/documentation/documentation.tsx rename to packages/react-docs/src/ts/components/documentation.tsx index 3229bbd932..62632184c8 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -1,9 +1,11 @@ import { colors, constants as sharedConstants, + EtherscanLinkSuffixes, MarkdownSection, MenuSubsectionsBySection, NestedSidebarMenu, + Networks, SectionHeader, Styles, utils as sharedUtils, @@ -12,29 +14,28 @@ import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import { scroller } from 'react-scroll'; -import { Badge } from 'ts/components/ui/badge'; -import { Comment } from 'ts/pages/documentation/comment'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { EventDefinition } from 'ts/pages/documentation/event_definition'; -import { MethodBlock } from 'ts/pages/documentation/method_block'; -import { SourceLink } from 'ts/pages/documentation/source_link'; -import { Type } from 'ts/pages/documentation/type'; -import { TypeDefinition } from 'ts/pages/documentation/type_definition'; + import { AddressByContractName, DocAgnosticFormat, DoxityDocObj, - EtherscanLinkSuffixes, Event, - Networks, Property, SolidityMethod, SupportedDocJson, TypeDefinitionByName, TypescriptMethod, -} from 'ts/types'; -import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; +} from '../types'; +import { utils } from '../utils/utils'; + +import { Badge } from './badge'; +import { Comment } from './comment'; +import { DocsInfo } from './docs_info'; +import { EventDefinition } from './event_definition'; +import { MethodBlock } from './method_block'; +import { SourceLink } from './source_link'; +import { Type } from './type'; +import { TypeDefinition } from './type_definition'; const TOP_BAR_HEIGHT = 60; @@ -55,7 +56,7 @@ export interface DocumentationProps { sourceUrl: string; } -interface DocumentationState {} +export interface DocumentationState {} const styles: Styles = { mainContainers: { @@ -268,9 +269,9 @@ export class Documentation extends React.Component - {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( + {_.isUndefined(typeDefinition) || sharedUtils.isUserOnMobile() ? ( V0.9.0 + signatures?: TypeDocNode[]; + parameters?: TypeDocNode[]; + typeParameter?: TypeDocNode[]; + sources?: TypeDocNode[]; + children?: TypeDocNode[]; + groups?: TypeDocGroup[]; +} + +export enum TypeDocTypes { + Intrinsic = 'intrinsic', + Reference = 'reference', + Array = 'array', + StringLiteral = 'stringLiteral', + Reflection = 'reflection', + Union = 'union', + TypeParameter = 'typeParameter', + Intersection = 'intersection', + Unknown = 'unknown', +} + +// Exception: We don't make the values uppercase because these KindString's need to +// match up those returned by TypeDoc +export enum KindString { + Constructor = 'Constructor', + Property = 'Property', + Method = 'Method', + Interface = 'Interface', + TypeAlias = 'Type alias', + Variable = 'Variable', + Function = 'Function', + Enumeration = 'Enumeration', +} + +export interface DocAgnosticFormat { + [sectionName: string]: DocSection; +} + +export interface DocSection { + comment: string; + constructors: Array; + methods: Array; + properties: Property[]; + types: CustomType[]; + events?: Event[]; +} + +export interface TypescriptMethod extends BaseMethod { + source?: Source; + isStatic?: boolean; + typeParameter?: TypeParameter; +} + +export interface SolidityMethod extends BaseMethod { + isConstant?: boolean; + isPayable?: boolean; +} + +export interface Source { + fileName: string; + line: number; +} + +export interface Parameter { + name: string; + comment: string; + isOptional: boolean; + type: Type; +} + +export interface TypeParameter { + name: string; + type: Type; +} + +export interface Type { + name: string; + typeDocType: TypeDocTypes; + value?: string; + typeArguments?: Type[]; + elementType?: ElementType; + types?: Type[]; + method?: TypescriptMethod; +} + +export interface ElementType { + name: string; + typeDocType: TypeDocTypes; +} + +export interface IndexSignature { + keyName: string; + keyType: Type; + valueName: string; +} + +export interface CustomType { + name: string; + kindString: string; + type?: Type; + method?: TypescriptMethod; + indexSignature?: IndexSignature; + defaultValue?: string; + comment?: string; + children?: CustomTypeChild[]; +} + +export interface CustomTypeChild { + name: string; + type?: Type; + defaultValue?: string; +} + +export interface Event { + name: string; + eventArgs: EventArg[]; +} + +export interface EventArg { + isIndexed: boolean; + name: string; + type: Type; +} + +export interface Property { + name: string; + type: Type; + source?: Source; + comment?: string; +} + +export interface BaseMethod { + isConstructor: boolean; + name: string; + returnComment?: string | undefined; + callPath: string; + parameters: Parameter[]; + returnType: Type; + comment?: string; +} + +export interface TypeDefinitionByName { + [typeName: string]: CustomType; +} + +export enum SupportedDocJson { + Doxity = 'DOXITY', + TypeDoc = 'TYPEDOC', +} + +export interface ContractsByVersionByNetworkId { + [version: string]: { + [networkName: string]: { + [contractName: string]: string; + }; + }; +} + +export interface DoxityDocObj { + [contractName: string]: DoxityContractObj; +} + +export interface DoxityContractObj { + title: string; + fileName: string; + name: string; + abiDocs: DoxityAbiDoc[]; +} + +export interface DoxityAbiDoc { + constant: boolean; + inputs: DoxityInput[]; + name: string; + outputs: DoxityOutput[]; + payable: boolean; + type: string; + details?: string; + return?: string; +} + +export interface DoxityOutput { + name: string; + type: string; +} + +export interface DoxityInput { + name: string; + type: string; + description: string; + indexed?: boolean; +} + +export interface AddressByContractName { + [contractName: string]: string; +} + +export interface EnumValue { + name: string; + defaultValue?: string; +} + +export enum AbiTypes { + Constructor = 'constructor', + Function = 'function', + Event = 'event', +} diff --git a/packages/react-docs/src/ts/utils/constants.ts b/packages/react-docs/src/ts/utils/constants.ts new file mode 100644 index 0000000000..6692ce7e43 --- /dev/null +++ b/packages/react-docs/src/ts/utils/constants.ts @@ -0,0 +1,9 @@ +export const constants = { + TYPES_SECTION_NAME: 'types', + URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + URL_WEB3_DECODED_LOG_ENTRY_EVENT: + 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', + URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', +}; diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/react-docs/src/ts/utils/doxity_utils.ts similarity index 99% rename from packages/website/ts/utils/doxity_utils.ts rename to packages/react-docs/src/ts/utils/doxity_utils.ts index 35ce056727..26dea69666 100644 --- a/packages/website/ts/utils/doxity_utils.ts +++ b/packages/react-docs/src/ts/utils/doxity_utils.ts @@ -1,4 +1,5 @@ import * as _ from 'lodash'; + import { AbiTypes, DocAgnosticFormat, @@ -13,7 +14,7 @@ import { SolidityMethod, Type, TypeDocTypes, -} from 'ts/types'; +} from '../types'; export const doxityUtils = { convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/react-docs/src/ts/utils/typedoc_utils.ts similarity index 98% rename from packages/website/ts/utils/typedoc_utils.ts rename to packages/react-docs/src/ts/utils/typedoc_utils.ts index ce7df4dbb2..13798889a3 100644 --- a/packages/website/ts/utils/typedoc_utils.ts +++ b/packages/react-docs/src/ts/utils/typedoc_utils.ts @@ -1,10 +1,10 @@ import * as _ from 'lodash'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; + +import { DocsInfo } from '../components/docs_info'; import { CustomType, CustomTypeChild, DocAgnosticFormat, - DocPackages, DocSection, IndexSignature, KindString, @@ -16,8 +16,8 @@ import { TypeDocType, TypeParameter, TypescriptMethod, -} from 'ts/types'; -import { utils } from 'ts/utils/utils'; +} from '../types'; +import { utils } from '../utils/utils'; export const typeDocUtils = { isType(entity: TypeDocNode): boolean { @@ -267,7 +267,8 @@ export const typeDocUtils = { let callPath; if (isConstructor || entity.name === '__type') { callPath = ''; - } else if (docId === DocPackages.ZeroExJs) { + // TODO: Get rid of this 0x-specific logic + } else if (docId === 'ZERO_EX_JS') { const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; callPath = !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx diff --git a/packages/react-docs/src/ts/utils/utils.ts b/packages/react-docs/src/ts/utils/utils.ts new file mode 100644 index 0000000000..8e1a80a445 --- /dev/null +++ b/packages/react-docs/src/ts/utils/utils.ts @@ -0,0 +1,10 @@ +export const utils = { + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, +}; diff --git a/packages/react-docs/tsconfig.json b/packages/react-docs/tsconfig.json new file mode 100644 index 0000000000..6e71fb2e47 --- /dev/null +++ b/packages/react-docs/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "./lib/", + "jsx": "react", + "baseUrl": "./", + "strictNullChecks": false, + "noImplicitThis": false, + "paths": { + "*": ["node_modules/@types/*", "*"] + } + }, + "include": ["./src/ts/**/*"] +} diff --git a/packages/react-docs/tslint.json b/packages/react-docs/tslint.json new file mode 100644 index 0000000000..d6a5f5031e --- /dev/null +++ b/packages/react-docs/tslint.json @@ -0,0 +1,9 @@ +{ + "extends": ["@0xproject/tslint-config"], + "rules": { + "no-implicit-dependencies": false, + "no-object-literal-type-assertion": false, + "completed-docs": false, + "prefer-function-over-method": false + } +} diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index e96659e47d..4b9055e60b 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "basscss": "^8.0.3", + "is-mobile": "^0.2.2", "material-ui": "^0.17.1", "react": "15.6.1", "react-dom": "15.6.1", diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts index 8757215338..9b0bcf8457 100644 --- a/packages/react-shared/src/ts/globals.d.ts +++ b/packages/react-shared/src/ts/globals.d.ts @@ -1 +1,7 @@ declare module 'react-highlight'; + +// is-mobile declarations +declare function isMobile(): boolean; +declare module 'is-mobile' { + export = isMobile; +} diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts index dde77b7b96..3b50c01178 100644 --- a/packages/react-shared/src/ts/index.ts +++ b/packages/react-shared/src/ts/index.ts @@ -5,7 +5,7 @@ export { MarkdownSection } from './components/markdown_section'; export { NestedSidebarMenu } from './components/nested_sidebar_menu'; export { SectionHeader } from './components/section_header'; -export { HeaderSizes, Styles, MenuSubsectionsBySection } from './types'; +export { HeaderSizes, Styles, MenuSubsectionsBySection, EtherscanLinkSuffixes, Networks } from './types'; export { utils } from './utils/utils'; export { constants } from './utils/constants'; diff --git a/packages/react-shared/src/ts/types.ts b/packages/react-shared/src/ts/types.ts index f9d561d1a2..88fadcc098 100644 --- a/packages/react-shared/src/ts/types.ts +++ b/packages/react-shared/src/ts/types.ts @@ -11,3 +11,15 @@ export enum HeaderSizes { export interface MenuSubsectionsBySection { [section: string]: string[]; } + +export enum EtherscanLinkSuffixes { + Address = 'address', + Tx = 'tx', +} + +export enum Networks { + Mainnet = 'Mainnet', + Kovan = 'Kovan', + Ropsten = 'Ropsten', + Rinkeby = 'Rinkeby', +} diff --git a/packages/react-shared/src/ts/utils/constants.ts b/packages/react-shared/src/ts/utils/constants.ts index 79d1d9ca3e..562ab776b5 100644 --- a/packages/react-shared/src/ts/utils/constants.ts +++ b/packages/react-shared/src/ts/utils/constants.ts @@ -1,6 +1,20 @@ +import { Networks } from '../types'; + export const constants = { DOCS_SCROLL_DURATION_MS: 0, DOCS_CONTAINER_ID: 'documentation', SCROLL_CONTAINER_ID: 'documentation', SCROLL_TOP_ID: 'pageScrollTop', + NETWORK_NAME_BY_ID: { + 1: Networks.Mainnet, + 3: Networks.Ropsten, + 4: Networks.Rinkeby, + 42: Networks.Kovan, + } as { [symbol: number]: string }, + NETWORK_ID_BY_NAME: { + [Networks.Mainnet]: 1, + [Networks.Ropsten]: 3, + [Networks.Rinkeby]: 4, + [Networks.Kovan]: 42, + } as { [networkName: string]: number }, }; diff --git a/packages/react-shared/src/ts/utils/utils.ts b/packages/react-shared/src/ts/utils/utils.ts index 7498342b6e..0d2e045b86 100644 --- a/packages/react-shared/src/ts/utils/utils.ts +++ b/packages/react-shared/src/ts/utils/utils.ts @@ -1,6 +1,9 @@ +import isMobile = require('is-mobile'); import * as _ from 'lodash'; import { scroller } from 'react-scroll'; +import { EtherscanLinkSuffixes, Networks } from '../types'; + import { constants } from './constants'; export const utils = { @@ -19,6 +22,10 @@ export const utils = { containerId, }); }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, getIdFromName(name: string) { const id = name.replace(/ /g, '-'); return id; @@ -29,4 +36,12 @@ export const utils = { const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; return baseUrl; }, + getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, }; diff --git a/packages/website/package.json b/packages/website/package.json index 74bc2f8a65..248f9d14ce 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -18,6 +18,7 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { + "@0xproject/react-docs": "^0.0.1", "@0xproject/react-shared": "^0.0.1", "@0xproject/subproviders": "^0.6.0", "@0xproject/utils": "^0.4.0", @@ -25,14 +26,12 @@ "accounting": "^0.4.1", "basscss": "^8.0.3", "blockies": "^0.0.2", - "compare-versions": "^3.0.1", "dateformat": "^2.0.0", "deep-equal": "^1.0.1", "dharma-loan-frame": "^0.0.12", "ethereumjs-tx": "^1.3.3", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", - "is-mobile": "^0.2.2", "jsonschema": "^1.2.0", "less": "^2.7.2", "lodash": "^4.17.4", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 156dc44e81..fca9504d71 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -15,6 +15,7 @@ import { TransactionReceiptWithDecodedLogs, ZeroEx, } from '0x.js'; +import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { InjectedWeb3Subprovider, ledgerEthereumBrowserClientFactoryAsync, @@ -35,7 +36,6 @@ import { BlockchainCallErrs, BlockchainErrs, ContractInstance, - EtherscanLinkSuffixes, Order as PortalOrder, ProviderType, Side, @@ -271,7 +271,11 @@ export class Blockchain { }, ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( + txHash, + this.networkId, + EtherscanLinkSuffixes.Tx, + ); this._dispatcher.showFlashMessage( React.createElement(TokenSendCompleted, { etherScanLinkIfExists, @@ -542,7 +546,11 @@ export class Blockchain { private async _showEtherScanLinkAndAwaitTransactionMinedAsync( txHash: string, ): Promise { - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( + txHash, + this.networkId, + EtherscanLinkSuffixes.Tx, + ); this._dispatcher.showFlashMessage( React.createElement(TransactionSubmitted, { etherScanLinkIfExists, diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 7353a8767d..e71a0f7d1a 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -1,10 +1,10 @@ -import { colors } from '@0xproject/react-shared'; +import { colors, Networks } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; -import { BlockchainErrs, Networks } from 'ts/types'; +import { BlockchainErrs } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 5836fec0e9..8a242cd335 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -1,4 +1,4 @@ -import { colors } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; @@ -82,7 +82,7 @@ export class LedgerConfigDialog extends React.Component
Follow these instructions before proceeding:
@@ -163,7 +163,7 @@ export class LedgerConfigDialog extends React.Component void; @@ -24,7 +24,7 @@ export class NetworkDropDown extends React.Component { - const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + const networkName = sharedConstants.NETWORK_NAME_BY_ID[networkId]; const primaryText = (
diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 698b308151..7ac5d5c9c5 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -1,5 +1,5 @@ import { ZeroEx } from '0x.js'; -import { colors } from '@0xproject/react-shared'; +import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Divider from 'material-ui/Divider'; @@ -10,14 +10,7 @@ import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { - EtherscanLinkSuffixes, - OutdatedWrappedEtherByNetworkId, - Side, - Token, - TokenByAddress, - TokenState, -} from 'ts/types'; +import { OutdatedWrappedEtherByNetworkId, Side, Token, TokenByAddress, TokenState } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; @@ -99,7 +92,7 @@ export class EthWrappers extends React.Component { }); return; } - const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const eventLabel = `${parsedOrder.metadata.takerToken.symbol}-${networkName}`; try { const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( @@ -623,7 +623,7 @@ export class FillOrder extends React.Component { }); return; } - const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const eventLabel = `${parsedOrder.metadata.makerToken.symbol}-${networkName}`; try { await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index e14991993d..ad78357f0e 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -1,3 +1,4 @@ +import { constants as sharedConstants } from '@0xproject/react-shared'; import { ECSignature, Order, ZeroEx } from '0x.js'; import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; @@ -253,7 +254,7 @@ export class GenerateOrderForm extends React.Component We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index 6373d4900d..c837ed60ef 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -1,3 +1,4 @@ +import { constants as sharedConstants } from '@0xproject/react-shared'; import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; @@ -56,7 +57,7 @@ export class ProviderPicker extends React.Component
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 58b699b779..0161da2d35 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -1,3 +1,4 @@ +import { DocsInfo, DocsMenu } from '@0xproject/react-docs'; import { colors, MenuSubsectionsBySection, NestedSidebarMenu, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import Drawer from 'material-ui/Drawer'; @@ -12,9 +13,8 @@ import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Deco, DocsMenu, Key, ProviderType, WebsitePaths } from 'ts/types'; +import { Deco, Key, ProviderType, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 3bab296919..6b8d7c7b52 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -1,5 +1,5 @@ import { ZeroEx } from '0x.js'; -import { colors } from '@0xproject/react-shared'; +import { colors, EtherscanLinkSuffixes } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; @@ -8,7 +8,7 @@ import * as React from 'react'; import * as ReactTooltip from 'react-tooltip'; import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; import { Party } from 'ts/components/ui/party'; -import { EtherscanLinkSuffixes, Fill, Token, TokenByAddress } from 'ts/types'; +import { Fill, Token, TokenByAddress } from 'ts/types'; const PRECISION = 5; const IDENTICON_DIAMETER = 40; diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx index b75d97e391..f449a8e752 100644 --- a/packages/website/ts/components/ui/ethereum_address.tsx +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -1,7 +1,7 @@ +import { EtherscanLinkSuffixes } from '@0xproject/react-shared'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; -import { EtherscanLinkSuffixes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface EthereumAddressProps { diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index e7fc510705..040b84a0b4 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -1,8 +1,7 @@ -import { colors } from '@0xproject/react-shared'; +import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import { EtherscanLinkSuffixes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { @@ -12,7 +11,7 @@ interface EtherScanIconProps { } export const EtherScanIcon = (props: EtherScanIconProps) => { - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + const etherscanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( props.addressOrTxHash, props.networkId, EtherscanLinkSuffixes.Address, diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index e120523fd9..3d94903d1b 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -1,10 +1,9 @@ -import { colors } from '@0xproject/react-shared'; +import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { EthereumAddress } from 'ts/components/ui/ethereum_address'; import { Identicon } from 'ts/components/ui/identicon'; -import { EtherscanLinkSuffixes } from 'ts/types'; import { utils } from 'ts/utils/utils'; const IMAGE_DIMENSION = 100; @@ -43,7 +42,7 @@ export class Party extends React.Component { width: IMAGE_DIMENSION, height: IMAGE_DIMENSION, }; - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + const etherscanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( this.props.address, this.props.networkId, EtherscanLinkSuffixes.Address, diff --git a/packages/website/ts/containers/connect_documentation.ts b/packages/website/ts/containers/connect_documentation.ts index 6a5ba1f995..33e12f5d0d 100644 --- a/packages/website/ts/containers/connect_documentation.ts +++ b/packages/website/ts/containers/connect_documentation.ts @@ -1,12 +1,12 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, DocsInfoConfig, Environments, SupportedDocJson, WebsitePaths } from 'ts/types'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -21,7 +21,7 @@ const connectDocSections = { installation: 'installation', httpClient: 'httpClient', webSocketOrderbookChannel: 'webSocketOrderbookChannel', - types: constants.TYPES_SECTION_NAME, + types: docConstants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { diff --git a/packages/website/ts/containers/smart_contracts_documentation.ts b/packages/website/ts/containers/smart_contracts_documentation.ts index a839529aa1..b1b2ea9224 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.ts +++ b/packages/website/ts/containers/smart_contracts_documentation.ts @@ -1,19 +1,13 @@ +import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import { Networks } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { - DocPackages, - DocsInfoConfig, - Networks, - SmartContractDocSections as Sections, - SupportedDocJson, - WebsitePaths, -} from 'ts/types'; +import { DocPackages, SmartContractDocSections as Sections, WebsitePaths } from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index d0d697e703..aaf17533cd 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -1,12 +1,12 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, DocsInfoConfig, Environments, SupportedDocJson, WebsitePaths } from 'ts/types'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -33,7 +33,7 @@ const zeroExJsDocSections = { etherToken: 'etherToken', proxy: 'proxy', orderWatcher: 'orderWatcher', - types: constants.TYPES_SECTION_NAME, + types: docConstants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index d43ab0f584..ef276519cb 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -27,12 +27,6 @@ declare module 'find-versions' { export = findVersions; } -// compare-version declarations -declare function compareVersions(firstVersion: string, secondVersion: string): number; -declare module 'compare-versions' { - export = compareVersions; -} - // semver-sort declarations declare module 'semver-sort' { const desc: (versions: string[]) => string[]; @@ -115,12 +109,6 @@ declare module 'blockies' { export = blockies; } -// is-mobile declarations -declare function isMobile(): boolean; -declare module 'is-mobile' { - export = isMobile; -} - // web3-provider-engine declarations declare class Subprovider {} declare module 'web3-provider-engine/subproviders/subprovider' { diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 83da811897..9bf6b528ec 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -1,3 +1,4 @@ +import { DocAgnosticFormat, DocsInfo, Documentation, DoxityDocObj } from '@0xproject/react-docs'; import { MenuSubsectionsBySection } from '@0xproject/react-shared'; import findVersions = require('find-versions'); import * as _ from 'lodash'; @@ -5,10 +6,8 @@ import * as React from 'react'; import DocumentTitle = require('react-document-title'); import semverSort = require('semver-sort'); import { TopBar } from 'ts/components/top_bar/top_bar'; -import { DocsInfo } from 'ts/pages/documentation/docs_info'; -import { Documentation } from 'ts/pages/documentation/documentation'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { DocAgnosticFormat, DocPackages, DoxityDocObj, Environments } from 'ts/types'; +import { DocPackages, Environments } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { docUtils } from 'ts/utils/doc_utils'; diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 3b0866d706..41fbc6a860 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -225,11 +225,6 @@ export enum AlertTypes { SUCCESS, } -export enum EtherscanLinkSuffixes { - Address = 'address', - Tx = 'tx', -} - export enum BlockchainErrs { AContractNotDeployedOnNetwork = 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', DisconnectedFromEthereumNode = 'DISCONNECTED_FROM_ETHEREUM_NODE', @@ -243,24 +238,6 @@ export enum BlockchainCallErrs { TokenAddressIsInvalid = 'TOKEN_ADDRESS_IS_INVALID', } -// Exception: We don't make the values uppercase because these KindString's need to -// match up those returned by TypeDoc -export enum KindString { - Constructor = 'Constructor', - Property = 'Property', - Method = 'Method', - Interface = 'Interface', - TypeAlias = 'Type alias', - Variable = 'Variable', - Function = 'Function', - Enumeration = 'Enumeration', -} - -export interface EnumValue { - name: string; - defaultValue?: string; -} - export enum Environments { DEVELOPMENT, PRODUCTION, @@ -268,170 +245,6 @@ export enum Environments { export type ContractInstance = any; // TODO: add type definition for Contract -export interface TypeDocType { - type: TypeDocTypes; - value: string; - name: string; - types: TypeDocType[]; - typeArguments?: TypeDocType[]; - declaration: TypeDocNode; - elementType?: TypeDocType; -} - -export interface TypeDocFlags { - isStatic?: boolean; - isOptional?: boolean; - isPublic?: boolean; -} - -export interface TypeDocGroup { - title: string; - children: number[]; -} - -export interface TypeDocNode { - id?: number; - name?: string; - kind?: string; - defaultValue?: string; - kindString?: string; - type?: TypeDocType; - fileName?: string; - line?: number; - comment?: TypeDocNode; - text?: string; - shortText?: string; - returns?: string; - declaration: TypeDocNode; - flags?: TypeDocFlags; - indexSignature?: TypeDocNode | TypeDocNode[]; // TypeDocNode in TypeDoc V0.9.0 - signatures?: TypeDocNode[]; - parameters?: TypeDocNode[]; - typeParameter?: TypeDocNode[]; - sources?: TypeDocNode[]; - children?: TypeDocNode[]; - groups?: TypeDocGroup[]; -} - -export enum TypeDocTypes { - Intrinsic = 'intrinsic', - Reference = 'reference', - Array = 'array', - StringLiteral = 'stringLiteral', - Reflection = 'reflection', - Union = 'union', - TypeParameter = 'typeParameter', - Intersection = 'intersection', - Unknown = 'unknown', -} - -export interface DocAgnosticFormat { - [sectionName: string]: DocSection; -} - -export interface DocSection { - comment: string; - constructors: Array; - methods: Array; - properties: Property[]; - types: CustomType[]; - events?: Event[]; -} - -export interface Event { - name: string; - eventArgs: EventArg[]; -} - -export interface EventArg { - isIndexed: boolean; - name: string; - type: Type; -} - -export interface Property { - name: string; - type: Type; - source?: Source; - comment?: string; -} - -export interface BaseMethod { - isConstructor: boolean; - name: string; - returnComment?: string | undefined; - callPath: string; - parameters: Parameter[]; - returnType: Type; - comment?: string; -} - -export interface TypescriptMethod extends BaseMethod { - source?: Source; - isStatic?: boolean; - typeParameter?: TypeParameter; -} - -export interface SolidityMethod extends BaseMethod { - isConstant?: boolean; - isPayable?: boolean; -} - -export interface Source { - fileName: string; - line: number; -} - -export interface Parameter { - name: string; - comment: string; - isOptional: boolean; - type: Type; -} - -export interface TypeParameter { - name: string; - type: Type; -} - -export interface Type { - name: string; - typeDocType: TypeDocTypes; - value?: string; - typeArguments?: Type[]; - elementType?: ElementType; - types?: Type[]; - method?: TypescriptMethod; -} - -export interface ElementType { - name: string; - typeDocType: TypeDocTypes; -} - -export interface IndexSignature { - keyName: string; - keyType: Type; - valueName: string; -} - -export interface CustomType { - name: string; - kindString: string; - type?: Type; - method?: TypescriptMethod; - indexSignature?: IndexSignature; - defaultValue?: string; - comment?: string; - children?: CustomTypeChild[]; -} - -export interface CustomTypeChild { - name: string; - type?: Type; - defaultValue?: string; -} - export interface FAQQuestion { prompt: string; answer: React.ReactNode; @@ -500,10 +313,6 @@ export interface BlogPost { url: string; } -export interface TypeDefinitionByName { - [typeName: string]: CustomType; -} - export interface Article { section: string; title: string; @@ -527,40 +336,6 @@ export enum TokenVisibility { TRACKED = 'TRACKED', } -export interface DoxityDocObj { - [contractName: string]: DoxityContractObj; -} - -export interface DoxityContractObj { - title: string; - fileName: string; - name: string; - abiDocs: DoxityAbiDoc[]; -} - -export interface DoxityAbiDoc { - constant: boolean; - inputs: DoxityInput[]; - name: string; - outputs: DoxityOutput[]; - payable: boolean; - type: string; - details?: string; - return?: string; -} - -export interface DoxityOutput { - name: string; - type: string; -} - -export interface DoxityInput { - name: string; - type: string; - description: string; - indexed?: boolean; -} - export interface VersionToFileName { [version: string]: string; } @@ -570,29 +345,6 @@ export enum Docs { SmartContracts, } -export interface ContractAddresses { - [version: string]: { - [network: string]: AddressByContractName; - }; -} - -export interface AddressByContractName { - [contractName: string]: string; -} - -export enum Networks { - Mainnet = 'Mainnet', - Kovan = 'Kovan', - Ropsten = 'Ropsten', - Rinkeby = 'Rinkeby', -} - -export enum AbiTypes { - Constructor = 'constructor', - Function = 'function', - Event = 'event', -} - export enum WebsitePaths { Portal = '/portal', Wiki = '/wiki', @@ -605,49 +357,12 @@ export enum WebsitePaths { Connect = '/docs/connect', } -export interface DocsMenu { - [sectionName: string]: string[]; -} - -export interface SectionsMap { - [sectionName: string]: string; -} - export enum DocPackages { Connect = 'CONNECT', ZeroExJs = 'ZERO_EX_JS', SmartContracts = 'SMART_CONTRACTS', } -export enum SupportedDocJson { - Doxity = 'DOXITY', - TypeDoc = 'TYPEDOC', -} - -export interface ContractsByVersionByNetworkId { - [version: string]: { - [networkName: string]: { - [contractName: string]: string; - }; - }; -} - -export interface DocsInfoConfig { - id: string; - type: SupportedDocJson; - displayName: string; - packageUrl: string; - menu: DocsMenu; - sections: SectionsMap; - sectionNameToMarkdown: { [sectionName: string]: string }; - visibleConstructors: string[]; - subPackageName?: string; - publicTypes?: string[]; - sectionNameToModulePath?: { [sectionName: string]: string[] }; - menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; - contractsByVersionByNetworkId?: ContractsByVersionByNetworkId; -} - export interface TimestampMsRange { startTimestampMs: number; endTimestampMs: number; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 7e9ba69de0..f33b06c0a9 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { ContractAddresses, Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; +import { Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 1f87fe1899..745af6cf55 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -1,5 +1,5 @@ +import { Networks } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; -import { Networks } from 'ts/types'; export const constants = { DECIMAL_PLACES_ETH: 18, @@ -21,18 +21,6 @@ export const constants = { NETWORK_ID_MAINNET: 1, NETWORK_ID_KOVAN: 42, NETWORK_ID_TESTRPC: 50, - NETWORK_NAME_BY_ID: { - 1: Networks.Mainnet, - 3: Networks.Ropsten, - 4: Networks.Rinkeby, - 42: Networks.Kovan, - } as { [symbol: number]: string }, - NETWORK_ID_BY_NAME: { - [Networks.Mainnet]: 1, - [Networks.Ropsten]: 3, - [Networks.Rinkeby]: 4, - [Networks.Kovan]: 42, - } as { [networkName: string]: number }, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', PROVIDER_NAME_LEDGER: 'Ledger', PROVIDER_NAME_METAMASK: 'Metamask', @@ -45,7 +33,6 @@ export const constants = { UNAVAILABLE_STATUS: 503, TAKER_FEE: new BigNumber(0), TESTNET_NAME: 'Kovan', - TYPES_SECTION_NAME: 'types', PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', PROJECT_URL_AMADEUS: 'http://amadeusrelay.org', PROJECT_URL_DDEX: 'https://ddex.io', @@ -70,7 +57,6 @@ export const constants = { PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', PROJECT_URL_IDT: 'https://kinalpha.com', URL_ANGELLIST: 'https://angel.co/0xproject/jobs', - URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', URL_BITLY_API: 'https://api-ssl.bitly.com', URL_BLOG: 'https://blog.0xproject.com/latest', URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', @@ -85,11 +71,6 @@ export const constants = { URL_REDDIT: 'https://reddit.com/r/0xproject', URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', URL_TWITTER: 'https://twitter.com/0xproject', - URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', - URL_WEB3_DECODED_LOG_ENTRY_EVENT: - 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', - URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', - URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', URL_WETH_IO: 'https://weth.io/', URL_ZEROEX_CHAT: 'https://chat.0xproject.com', }; diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 1f5f75ee2e..0686e3e7f6 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -1,6 +1,7 @@ +import { DoxityDocObj, TypeDocNode } from '@0xproject/react-docs'; import findVersions = require('find-versions'); import * as _ from 'lodash'; -import { DoxityDocObj, S3FileObject, TypeDocNode, VersionToFileName } from 'ts/types'; +import { S3FileObject, VersionToFileName } from 'ts/types'; import { utils } from 'ts/utils/utils'; import convert = require('xml-js'); diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 872b44eeb7..d35f77e943 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -1,19 +1,10 @@ import { ECSignature, ExchangeContractErrs, ZeroEx, ZeroExError } from '0x.js'; +import { constants as sharedConstants, EtherscanLinkSuffixes, Networks } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import deepEqual = require('deep-equal'); -import isMobile = require('is-mobile'); import * as _ from 'lodash'; import * as moment from 'moment'; -import { - EtherscanLinkSuffixes, - Networks, - Order, - ScreenWidths, - Side, - SideToAssetToken, - Token, - TokenByAddress, -} from 'ts/types'; +import { Order, ScreenWidths, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import * as u2f from 'ts/vendor/u2f_api'; @@ -139,18 +130,6 @@ export const utils = { return ScreenWidths.Sm; } }, - isUserOnMobile(): boolean { - const isUserOnMobile = isMobile(); - return isUserOnMobile; - }, - getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { - const networkName = constants.NETWORK_NAME_BY_ID[networkId]; - if (_.isUndefined(networkName)) { - return undefined; - } - const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`; - return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; - }, async isU2FSupportedAsync(): Promise { const w = window as any; return new Promise((resolve: (isSupported: boolean) => void) => { @@ -275,9 +254,9 @@ export const utils = { isTestNetwork(networkId: number): boolean { const isTestNetwork = _.includes( [ - constants.NETWORK_ID_BY_NAME[Networks.Kovan], - constants.NETWORK_ID_BY_NAME[Networks.Rinkeby], - constants.NETWORK_ID_BY_NAME[Networks.Ropsten], + sharedConstants.NETWORK_ID_BY_NAME[Networks.Kovan], + sharedConstants.NETWORK_ID_BY_NAME[Networks.Rinkeby], + sharedConstants.NETWORK_ID_BY_NAME[Networks.Ropsten], ], networkId, ); From c8ace2edc017969f405ffaa1293620850ed5fc05 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 16:37:37 +0100 Subject: [PATCH 36/94] Remove location prop --- packages/react-docs/src/ts/components/documentation.tsx | 3 +-- packages/website/ts/pages/documentation/doc_page.tsx | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx index 62632184c8..f5a117797c 100644 --- a/packages/react-docs/src/ts/components/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -47,7 +47,6 @@ const networkNameToColor: { [network: string]: string } = { }; export interface DocumentationProps { - location: Location; docsVersion: string; availableDocVersions: string[]; docsInfo: DocsInfo; @@ -80,7 +79,7 @@ const styles: Styles = { export class Documentation extends React.Component { public componentDidUpdate(prevProps: DocumentationProps, prevState: DocumentationState) { if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) { - const hash = this.props.location.hash.slice(1); + const hash = window.location.hash.slice(1); sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); } } diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 9bf6b528ec..98a6a64130 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -81,7 +81,6 @@ export class DocPage extends React.Component { translate={this.props.translate} /> Date: Tue, 6 Mar 2018 16:45:25 +0100 Subject: [PATCH 37/94] Move DocsInfo out of the components folder --- packages/react-docs/src/ts/components/documentation.tsx | 2 +- packages/react-docs/src/ts/components/event_definition.tsx | 2 +- packages/react-docs/src/ts/components/interface.tsx | 2 +- packages/react-docs/src/ts/components/method_block.tsx | 2 +- packages/react-docs/src/ts/components/method_signature.tsx | 2 +- packages/react-docs/src/ts/components/type.tsx | 2 +- packages/react-docs/src/ts/components/type_definition.tsx | 2 +- packages/react-docs/src/ts/{components => }/docs_info.ts | 6 +++--- packages/react-docs/src/ts/index.ts | 2 +- packages/react-docs/src/ts/utils/typedoc_utils.ts | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) rename packages/react-docs/src/ts/{components => }/docs_info.ts (97%) diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx index f5a117797c..5c261a39b1 100644 --- a/packages/react-docs/src/ts/components/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -15,6 +15,7 @@ import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import { scroller } from 'react-scroll'; +import { DocsInfo } from '../docs_info'; import { AddressByContractName, DocAgnosticFormat, @@ -30,7 +31,6 @@ import { utils } from '../utils/utils'; import { Badge } from './badge'; import { Comment } from './comment'; -import { DocsInfo } from './docs_info'; import { EventDefinition } from './event_definition'; import { MethodBlock } from './method_block'; import { SourceLink } from './source_link'; diff --git a/packages/react-docs/src/ts/components/event_definition.tsx b/packages/react-docs/src/ts/components/event_definition.tsx index 8289650f53..68f60ddf94 100644 --- a/packages/react-docs/src/ts/components/event_definition.tsx +++ b/packages/react-docs/src/ts/components/event_definition.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Event, EventArg } from '../types'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { Type } from './type'; export interface EventDefinitionProps { diff --git a/packages/react-docs/src/ts/components/interface.tsx b/packages/react-docs/src/ts/components/interface.tsx index 1c99495d79..92883089ad 100644 --- a/packages/react-docs/src/ts/components/interface.tsx +++ b/packages/react-docs/src/ts/components/interface.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { CustomType, TypeDocTypes } from '../types'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { MethodSignature } from './method_signature'; import { Type } from './type'; diff --git a/packages/react-docs/src/ts/components/method_block.tsx b/packages/react-docs/src/ts/components/method_block.tsx index 5ed7f42a13..529b9f9c77 100644 --- a/packages/react-docs/src/ts/components/method_block.tsx +++ b/packages/react-docs/src/ts/components/method_block.tsx @@ -6,7 +6,7 @@ import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } fro import { typeDocUtils } from '../utils/typedoc_utils'; import { Comment } from './comment'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { MethodSignature } from './method_signature'; import { SourceLink } from './source_link'; diff --git a/packages/react-docs/src/ts/components/method_signature.tsx b/packages/react-docs/src/ts/components/method_signature.tsx index e21d822877..6a394dd6db 100644 --- a/packages/react-docs/src/ts/components/method_signature.tsx +++ b/packages/react-docs/src/ts/components/method_signature.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom'; import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; import { constants } from '../utils/constants'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { Type } from './type'; export interface MethodSignatureProps { diff --git a/packages/react-docs/src/ts/components/type.tsx b/packages/react-docs/src/ts/components/type.tsx index 780d87eae1..504570aad1 100644 --- a/packages/react-docs/src/ts/components/type.tsx +++ b/packages/react-docs/src/ts/components/type.tsx @@ -8,7 +8,7 @@ import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types'; import { constants } from '../utils/constants'; import { utils } from '../utils/utils'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { TypeDefinition } from './type_definition'; // Some types reference other libraries. For these types, we want to link the user to the relevant documentation. diff --git a/packages/react-docs/src/ts/components/type_definition.tsx b/packages/react-docs/src/ts/components/type_definition.tsx index 944a31f957..20a24495bb 100644 --- a/packages/react-docs/src/ts/components/type_definition.tsx +++ b/packages/react-docs/src/ts/components/type_definition.tsx @@ -7,7 +7,7 @@ import { utils } from '../utils/utils'; import { Comment } from './comment'; import { CustomEnum } from './custom_enum'; -import { DocsInfo } from './docs_info'; +import { DocsInfo } from '../docs_info'; import { Enum } from './enum'; import { Interface } from './interface'; import { MethodSignature } from './method_signature'; diff --git a/packages/react-docs/src/ts/components/docs_info.ts b/packages/react-docs/src/ts/docs_info.ts similarity index 97% rename from packages/react-docs/src/ts/components/docs_info.ts rename to packages/react-docs/src/ts/docs_info.ts index 509bba89ed..84d41f58dc 100644 --- a/packages/react-docs/src/ts/components/docs_info.ts +++ b/packages/react-docs/src/ts/docs_info.ts @@ -11,9 +11,9 @@ import { SectionsMap, SupportedDocJson, TypeDocNode, -} from '../types'; -import { doxityUtils } from '../utils/doxity_utils'; -import { typeDocUtils } from '../utils/typedoc_utils'; +} from './types'; +import { doxityUtils } from './utils/doxity_utils'; +import { typeDocUtils } from './utils/typedoc_utils'; export class DocsInfo { public id: string; diff --git a/packages/react-docs/src/ts/index.ts b/packages/react-docs/src/ts/index.ts index ee2950c0ed..85b0cca270 100644 --- a/packages/react-docs/src/ts/index.ts +++ b/packages/react-docs/src/ts/index.ts @@ -1,5 +1,5 @@ export { Documentation } from './components/documentation'; -export { DocsInfo } from './components/docs_info'; +export { DocsInfo } from './docs_info'; // Exported to give users of this library added flexibility if they want to build // a docs page from scratch using the individual components. diff --git a/packages/react-docs/src/ts/utils/typedoc_utils.ts b/packages/react-docs/src/ts/utils/typedoc_utils.ts index 13798889a3..e4cea1e401 100644 --- a/packages/react-docs/src/ts/utils/typedoc_utils.ts +++ b/packages/react-docs/src/ts/utils/typedoc_utils.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash'; -import { DocsInfo } from '../components/docs_info'; +import { DocsInfo } from '../docs_info'; import { CustomType, CustomTypeChild, From 9301173f7d42548935e79ce64e64ad9320c860b5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 16:46:07 +0100 Subject: [PATCH 38/94] Rename docsVersion prop to selectedVersion and docsVersions to versions for clarity --- .../src/ts/components/documentation.tsx | 16 ++++++++-------- .../website/ts/pages/documentation/doc_page.tsx | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx index 5c261a39b1..dfc0413614 100644 --- a/packages/react-docs/src/ts/components/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -47,8 +47,8 @@ const networkNameToColor: { [network: string]: string } = { }; export interface DocumentationProps { - docsVersion: string; - availableDocVersions: string[]; + selectedVersion: string; + availableVersions: string[]; docsInfo: DocsInfo; docAgnosticFormat?: DocAgnosticFormat; menuSubsectionsBySection: MenuSubsectionsBySection; @@ -107,10 +107,10 @@ export class Documentation extends React.Component
@@ -259,7 +259,7 @@ export class Documentation extends React.Component {property.source && ( @@ -327,7 +327,7 @@ export class Documentation extends React.Component diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 98a6a64130..eabeddabda 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -81,8 +81,8 @@ export class DocPage extends React.Component { translate={this.props.translate} /> Date: Tue, 6 Mar 2018 19:49:00 +0100 Subject: [PATCH 39/94] Remove menuSubsectionsBySection prop from Documentation component --- packages/react-docs/src/ts/components/documentation.tsx | 4 ++-- packages/website/ts/pages/documentation/doc_page.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx index dfc0413614..43ffc39c60 100644 --- a/packages/react-docs/src/ts/components/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -51,7 +51,6 @@ export interface DocumentationProps { availableVersions: string[]; docsInfo: DocsInfo; docAgnosticFormat?: DocAgnosticFormat; - menuSubsectionsBySection: MenuSubsectionsBySection; sourceUrl: string; } @@ -84,6 +83,7 @@ export class Documentation extends React.Component {_.isUndefined(this.props.docAgnosticFormat) ? ( @@ -111,7 +111,7 @@ export class Documentation extends React.Component
diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index eabeddabda..cf2111b296 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -85,7 +85,6 @@ export class DocPage extends React.Component { availableVersions={this.props.availableDocVersions} docsInfo={this.props.docsInfo} docAgnosticFormat={this.state.docAgnosticFormat} - menuSubsectionsBySection={menuSubsectionsBySection} sourceUrl={sourceUrl} />
From 5b2d9a466841e0a70da66d050e30e6dc735d56f5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 19:50:33 +0100 Subject: [PATCH 40/94] re-org index.ts --- packages/react-docs/src/ts/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-docs/src/ts/index.ts b/packages/react-docs/src/ts/index.ts index 85b0cca270..a62c913767 100644 --- a/packages/react-docs/src/ts/index.ts +++ b/packages/react-docs/src/ts/index.ts @@ -1,11 +1,9 @@ -export { Documentation } from './components/documentation'; -export { DocsInfo } from './docs_info'; - // Exported to give users of this library added flexibility if they want to build // a docs page from scratch using the individual components. export { Badge } from './components/badge'; export { Comment } from './components/comment'; export { CustomEnum } from './components/custom_enum'; +export { Documentation } from './components/documentation'; export { Enum } from './components/enum'; export { EventDefinition } from './components/event_definition'; export { Interface } from './components/interface'; @@ -15,6 +13,8 @@ export { SourceLink } from './components/source_link'; export { TypeDefinition } from './components/type_definition'; export { Type } from './components/type'; +export { DocsInfo } from './docs_info'; + export { DocsInfoConfig, DocAgnosticFormat, DoxityDocObj, DocsMenu, SupportedDocJson, TypeDocNode } from './types'; export { constants } from './utils/constants'; From 01e505a5f4b514d285eedc6456d819d0649032d4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 20:14:55 +0100 Subject: [PATCH 41/94] Add publishConfig to package.json so that packages are published as public under the @0xproject namespace --- packages/react-docs/package.json | 3 +++ packages/react-shared/package.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index b87d20feca..4b582edff2 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -33,5 +33,8 @@ "react-dom": "15.6.1", "lodash": "^4.17.4", "react-tap-event-plugin": "^2.0.1" + }, + "publishConfig": { + "access": "public" } } diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 4b9055e60b..46dad47aa2 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -33,5 +33,8 @@ "react-scroll": "^1.5.2", "react-tap-event-plugin": "^2.0.1", "react-highlight": "0xproject/react-highlight" + }, + "publishConfig": { + "access": "public" } } From f8b8a10b8f914d05edfbc57081a40dccc8f464de Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 20:38:45 +0100 Subject: [PATCH 42/94] Make sidebar header configurable --- .../src/ts/components/documentation.tsx | 3 +- .../src/ts/components/nested_sidebar_menu.tsx | 36 +-------------- .../website/ts/components/sidebar_header.tsx | 46 +++++++++++++++++++ .../website/ts/components/top_bar/top_bar.tsx | 5 +- .../ts/pages/documentation/doc_page.tsx | 2 + packages/website/ts/pages/wiki/wiki.tsx | 3 +- 6 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 packages/website/ts/components/sidebar_header.tsx diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx index 43ffc39c60..8be7cd62df 100644 --- a/packages/react-docs/src/ts/components/documentation.tsx +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -51,6 +51,7 @@ export interface DocumentationProps { availableVersions: string[]; docsInfo: DocsInfo; docAgnosticFormat?: DocAgnosticFormat; + sidebarHeader?: React.ReactNode; sourceUrl: string; } @@ -109,7 +110,7 @@ export class Documentation extends React.Component diff --git a/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx index 2506124af6..f907022d65 100644 --- a/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx +++ b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx @@ -13,7 +13,7 @@ import { VersionDropDown } from './version_drop_down'; export interface NestedSidebarMenuProps { topLevelMenu: { [topLevel: string]: string[] }; menuSubsectionsBySection: MenuSubsectionsBySection; - title: string; + sidebarHeader?: React.ReactNode; shouldDisplaySectionHeaders?: boolean; onMenuItemClick?: () => void; selectedVersion?: string; @@ -37,13 +37,6 @@ const styles: Styles = { }, }; -const titleToIcon: { [title: string]: string } = { - '0x.js': 'zeroExJs.png', - '0x Connect': 'connect.png', - '0x Smart Contracts': 'contracts.png', - Wiki: 'wiki.png', -}; - export class NestedSidebarMenu extends React.Component { public static defaultProps: Partial = { shouldDisplaySectionHeaders: true, @@ -68,7 +61,7 @@ export class NestedSidebarMenu extends React.Component - {this._renderEmblem()} + {this.props.sidebarHeader} {!_.isUndefined(this.props.versions) && !_.isUndefined(this.props.selectedVersion) && ( @@ -77,31 +70,6 @@ export class NestedSidebarMenu extends React.Component ); } - private _renderEmblem() { - return ( -
-
-
- 0x -
-
- docs -
-
-
- | -
-
-
- -
-
- {this.props.title} -
-
-
- ); - } private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { const menuItemStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemWithHeaders diff --git a/packages/website/ts/components/sidebar_header.tsx b/packages/website/ts/components/sidebar_header.tsx new file mode 100644 index 0000000000..132606ddd9 --- /dev/null +++ b/packages/website/ts/components/sidebar_header.tsx @@ -0,0 +1,46 @@ +import { colors } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +const SHOW_DURATION_MS = 4000; + +const titleToIcon: { [title: string]: string } = { + '0x.js': 'zeroExJs.png', + '0x Connect': 'connect.png', + '0x Smart Contracts': 'contracts.png', + Wiki: 'wiki.png', +}; + +interface SidebarHeaderProps { + title: string; +} + +interface SidebarHeaderState {} + +export class SidebarHeader extends React.Component { + public render() { + return ( +
+
+
+ 0x +
+
+ docs +
+
+
+ | +
+
+
+ +
+
+ {this.props.title} +
+
+
+ ); + } +} diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 0161da2d35..2d6f2b235e 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'; import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; import { PortalMenu } from 'ts/components/portal_menu'; +import { SidebarHeader } from 'ts/components/sidebar_header'; import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; @@ -314,7 +315,7 @@ export class TopBar extends React.Component { } shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} selectedVersion={this.props.docsVersion} @@ -333,7 +334,7 @@ export class TopBar extends React.Component { } shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} /> diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index cf2111b296..bc01dc728c 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import semverSort = require('semver-sort'); +import { SidebarHeader } from 'ts/components/sidebar_header'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; import { DocPackages, Environments } from 'ts/types'; @@ -85,6 +86,7 @@ export class DocPage extends React.Component { availableVersions={this.props.availableDocVersions} docsInfo={this.props.docsInfo} docAgnosticFormat={this.state.docAgnosticFormat} + sidebarHeader={} sourceUrl={sourceUrl} />
diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index f338ed9c29..18074c3020 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -14,6 +14,7 @@ import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; +import { SidebarHeader } from 'ts/components/sidebar_header'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Article, ArticlesBySection, WebsitePaths } from 'ts/types'; @@ -128,7 +129,7 @@ export class Wiki extends React.Component { } />
From e88eba18772015b07c9ae305260c12f1cdf69e8c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 6 Mar 2018 20:55:57 +0100 Subject: [PATCH 43/94] Fix tslint errors --- packages/react-docs/src/ts/components/event_definition.tsx | 2 +- packages/react-docs/src/ts/components/interface.tsx | 2 +- packages/react-docs/src/ts/components/method_block.tsx | 2 +- packages/react-docs/src/ts/components/method_signature.tsx | 2 +- packages/react-docs/src/ts/components/type.tsx | 2 +- packages/react-docs/src/ts/components/type_definition.tsx | 2 +- .../ts/components/generate_order/generate_order_form.tsx | 3 +-- packages/website/ts/components/top_bar/provider_picker.tsx | 3 +-- 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/react-docs/src/ts/components/event_definition.tsx b/packages/react-docs/src/ts/components/event_definition.tsx index 68f60ddf94..4789b21f2a 100644 --- a/packages/react-docs/src/ts/components/event_definition.tsx +++ b/packages/react-docs/src/ts/components/event_definition.tsx @@ -2,9 +2,9 @@ import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; +import { DocsInfo } from '../docs_info'; import { Event, EventArg } from '../types'; -import { DocsInfo } from '../docs_info'; import { Type } from './type'; export interface EventDefinitionProps { diff --git a/packages/react-docs/src/ts/components/interface.tsx b/packages/react-docs/src/ts/components/interface.tsx index 92883089ad..01f4942ef2 100644 --- a/packages/react-docs/src/ts/components/interface.tsx +++ b/packages/react-docs/src/ts/components/interface.tsx @@ -1,9 +1,9 @@ import * as _ from 'lodash'; import * as React from 'react'; +import { DocsInfo } from '../docs_info'; import { CustomType, TypeDocTypes } from '../types'; -import { DocsInfo } from '../docs_info'; import { MethodSignature } from './method_signature'; import { Type } from './type'; diff --git a/packages/react-docs/src/ts/components/method_block.tsx b/packages/react-docs/src/ts/components/method_block.tsx index 529b9f9c77..029662b3f3 100644 --- a/packages/react-docs/src/ts/components/method_block.tsx +++ b/packages/react-docs/src/ts/components/method_block.tsx @@ -2,11 +2,11 @@ import { AnchorTitle, colors, HeaderSizes, Styles } from '@0xproject/react-share import * as _ from 'lodash'; import * as React from 'react'; +import { DocsInfo } from '../docs_info'; import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; import { typeDocUtils } from '../utils/typedoc_utils'; import { Comment } from './comment'; -import { DocsInfo } from '../docs_info'; import { MethodSignature } from './method_signature'; import { SourceLink } from './source_link'; diff --git a/packages/react-docs/src/ts/components/method_signature.tsx b/packages/react-docs/src/ts/components/method_signature.tsx index 6a394dd6db..1400182eaa 100644 --- a/packages/react-docs/src/ts/components/method_signature.tsx +++ b/packages/react-docs/src/ts/components/method_signature.tsx @@ -2,10 +2,10 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import { DocsInfo } from '../docs_info'; import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; import { constants } from '../utils/constants'; -import { DocsInfo } from '../docs_info'; import { Type } from './type'; export interface MethodSignatureProps { diff --git a/packages/react-docs/src/ts/components/type.tsx b/packages/react-docs/src/ts/components/type.tsx index 504570aad1..f3efbc8dda 100644 --- a/packages/react-docs/src/ts/components/type.tsx +++ b/packages/react-docs/src/ts/components/type.tsx @@ -4,11 +4,11 @@ import * as React from 'react'; import { Link as ScrollLink } from 'react-scroll'; import * as ReactTooltip from 'react-tooltip'; +import { DocsInfo } from '../docs_info'; import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types'; import { constants } from '../utils/constants'; import { utils } from '../utils/utils'; -import { DocsInfo } from '../docs_info'; import { TypeDefinition } from './type_definition'; // Some types reference other libraries. For these types, we want to link the user to the relevant documentation. diff --git a/packages/react-docs/src/ts/components/type_definition.tsx b/packages/react-docs/src/ts/components/type_definition.tsx index 20a24495bb..e3cd218f98 100644 --- a/packages/react-docs/src/ts/components/type_definition.tsx +++ b/packages/react-docs/src/ts/components/type_definition.tsx @@ -2,12 +2,12 @@ import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; +import { DocsInfo } from '../docs_info'; import { CustomType, CustomTypeChild, KindString, TypeDocTypes } from '../types'; import { utils } from '../utils/utils'; import { Comment } from './comment'; import { CustomEnum } from './custom_enum'; -import { DocsInfo } from '../docs_info'; import { Enum } from './enum'; import { Interface } from './interface'; import { MethodSignature } from './method_signature'; diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index ad78357f0e..26fa904fe9 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -1,6 +1,5 @@ -import { constants as sharedConstants } from '@0xproject/react-shared'; import { ECSignature, Order, ZeroEx } from '0x.js'; -import { colors } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index c837ed60ef..b986da873a 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -1,5 +1,4 @@ -import { constants as sharedConstants } from '@0xproject/react-shared'; -import { colors } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import * as _ from 'lodash'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import * as React from 'react'; From 696f49497b98d3069f1e70d07b4c83414d0bbb99 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 14:46:32 -0800 Subject: [PATCH 44/94] Flesh out README --- packages/sra-report/README.md | 70 ++++++++++++++++++++++++++++---- packages/sra-report/src/index.ts | 2 +- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/packages/sra-report/README.md b/packages/sra-report/README.md index 53c007e10f..080fa05f24 100644 --- a/packages/sra-report/README.md +++ b/packages/sra-report/README.md @@ -1,25 +1,77 @@ # SRA Report -This package allows you to generate reports detailing an endpoint's [standard relayer API HTTP specification](https://github.com/0xProject/standard-relayer-api/blob/master/http/v0.md) compliance. +This tool allows you to generate reports that detail an endpoint's [standard relayer API HTTP specification](https://github.com/0xProject/standard-relayer-api/blob/master/http/v0.md) compliance. The tool will perform a [Postman collection](https://www.getpostman.com/docs/v6/postman/collections/creating_collections) run and either print a report to the console or save it to disk as a json file. SRA report can also output a Postman collection file and [Postman environment](https://www.getpostman.com/docs/v6/postman/environments_and_globals/manage_environments) file in order to facilitate replication and debugging of collection runs using the [Postman native app](https://www.getpostman.com/docs/v6/postman/launching_postman/installation_and_updates). ## Installation `yarn add -g @0xproject/sra-report` -## Usage +## Options ``` sra-report Options: - --help Show help [boolean] - --version Show version number [boolean] - --endpoint-url, -e API endpoint url to test for standard relayer API compliance - [string] [required] - --output, -o, --out Folder where to write the reports [string] - --network-id, -n ID of the network that the API is serving orders from - [number] [default: 1] + --help Show help [boolean] + --version Show version number [boolean] + --endpoint-url, -e API endpoint url to test for standard relayer API + compliance [string] [required] + --output, -o, --out The relative path to write the report generated by + the collection run, prints to console by default + [string] + --network-id, -n ID of the network that the API is serving orders + from [number] [default: 1] + --environment, --env The relative path to a postman environment file + for the collection run [string] + --export-collection, --ec The relative path to write the postman collection + file used by the collection run [string] + --export-environment, --ee The relative path to write the postman environment + file used by the collection run [string] ``` +## Example Usage + +### Print report to console + +```bash +sra-report --endpoint-url 'http://api.example.com' +``` + +### Save a report to disk + +```bash +sra-report --endpoint-url 'http://api.example.com' --output 'path/to/report.json' +``` + +### Generate report for an endpoint that serves kovan testnet orders + +```bash +sra-report --endpoint-url 'http://kovan.api.example.com' --network-id 42 +``` + +### Write Postman collection and environment files for use in the Postman native app + +```bash +sra-report --endpoint-url 'http://.api.example.com' --export-collection 'path/to/collection.json' --export-environment 'path/to/environment.json' +``` + +### Run the report using a custom environment + +```bash +sra-report --endpoint-url 'http://.api.example.com' --environment 'path/to/custom/environment.json' +``` + +## Custom environments + +When testing your standard relayer API endpoint in development, it may be useful to modify the Postman environment file generated by this tool such that specific query parameters are used during the collection run. For example, by default, this tool will grab the first order it can from the `/orders` endpoint and use properties from that order as query parameters for the rest of the run. Another example is the tool will default to the [WETH](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) and [ZRX](https://etherscan.io/address/0xe41d2489571d322189246dafa5ebde1f4699f498) token contracts when querying some endpoints but you may want to specify these. + +In order to provide a custom environment to the tool, perform the following steps: + +1. Export a Postman environment file using the tool: [example](#Write-Postman-collection-and-environment-files-for-use-in-the-Postman-native-app) +2. Open the Postman environment file using your favorite text editor or in the Postman native app +3. Modify the specific values you want +4. Save the environment file and export it if using the Postman native app +5. Run the tool and supply a path to your modified environment file: [example](#Run-the-report-using-a-custom-environment) + ## Contributing We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts index d292f15e10..83fb25d6ce 100644 --- a/packages/sra-report/src/index.ts +++ b/packages/sra-report/src/index.ts @@ -39,7 +39,7 @@ const args = yargs }) .option('environment', { alias: ['env'], - describe: 'File path to an environment file for the collection run', + describe: 'The relative path to a postman environment file for the collection run', type: 'string', normalize: true, demandOption: false, From 1789025da912aee536f0d9c9cbb66a56492251fc Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 15:22:56 -0800 Subject: [PATCH 45/94] Refactor environment factory and generalize token environment keys --- .../sra_report.postman_collection.json | 73 +++++++-------- .../src/contract_addresses/kovan_addresses.ts | 7 +- .../contract_addresses/mainnet_addresses.ts | 7 +- .../src/postman_environment_factory.ts | 91 ++++++++++++------- 4 files changed, 99 insertions(+), 79 deletions(-) diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json index 04c7bc51fb..9a8e8e0fa6 100644 --- a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -42,10 +42,10 @@ { "listen": "test", "script": { - "id": "1b61aabb-ef9f-4269-9a0c-cb7559afcbd8", + "id": "0b0712ff-7846-40a3-9253-4bca2551350c", "type": "text/javascript", "exec": [ - "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenEnvKey = 'tokenContractAddress1';", "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Token pairs are properly filtered', function() {", @@ -66,13 +66,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}", + "raw": "{{url}}/token_pairs?tokenA={{tokenContractAddress1}}", "host": ["{{url}}"], "path": ["token_pairs"], "query": [ { "key": "tokenA", - "value": "{{WETH_address}}", + "value": "{{tokenContractAddress1}}", "equals": true } ] @@ -87,10 +87,10 @@ { "listen": "test", "script": { - "id": "a5f946b1-fc97-4bcf-ae01-e0f97864a6c1", + "id": "ef0e6be8-06d8-4975-a1c4-2199bc8b5aa6", "type": "text/javascript", "exec": [ - "const filterTokenEnvKey = 'WETH_address';", + "const filterTokenEnvKey = 'tokenContractAddress1';", "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Token pairs are properly filtered', function() {", @@ -111,13 +111,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/token_pairs?tokenB={{WETH_address}}", + "raw": "{{url}}/token_pairs?tokenB={{tokenContractAddress1}}", "host": ["{{url}}"], "path": ["token_pairs"], "query": [ { "key": "tokenB", - "value": "{{WETH_address}}", + "value": "{{tokenContractAddress1}}", "equals": true } ] @@ -132,11 +132,11 @@ { "listen": "test", "script": { - "id": "bcd06e4f-aa3a-42f8-9f8c-bc902eb7a075", + "id": "9ca4bed3-b8e0-4a90-96ba-42f0da3a7d68", "type": "text/javascript", "exec": [ - "const filterTokenAEnvKey = 'WETH_address';", - "const filterTokenBEnvKey = 'ZRX_address';", + "const filterTokenAEnvKey = 'tokenContractAddress1';", + "const filterTokenBEnvKey = 'tokenContractAddress2';", "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", "const responseJsonData = pm.response.json();", @@ -160,18 +160,19 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/token_pairs?tokenA={{WETH_address}}&tokenB={{ZRX_address}}", + "raw": + "{{url}}/token_pairs?tokenA={{tokenContractAddress1}}&tokenB={{tokenContractAddress2}}", "host": ["{{url}}"], "path": ["token_pairs"], "query": [ { "key": "tokenA", - "value": "{{WETH_address}}", + "value": "{{tokenContractAddress1}}", "equals": true }, { "key": "tokenB", - "value": "{{ZRX_address}}", + "value": "{{tokenContractAddress2}}", "equals": true } ] @@ -244,10 +245,10 @@ { "listen": "test", "script": { - "id": "e782ae77-66bf-4096-9190-ef9e7501b316", + "id": "374c4b28-5672-400c-8c23-9cb1a3e63117", "type": "text/javascript", "exec": [ - "const exchangeContractEnvKey = 'EXCHANGE_CONTRACT_address';", + "const exchangeContractEnvKey = 'exchangeContractAddress';", "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Orders are properly filtered', function() {", @@ -266,13 +267,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/orders?exchangeContractAddress={{EXCHANGE_CONTRACT_address}}", + "raw": "{{url}}/orders?exchangeContractAddress={{exchangeContractAddress}}", "host": ["{{url}}"], "path": ["orders"], "query": [ { "key": "exchangeContractAddress", - "value": "{{EXCHANGE_CONTRACT_address}}", + "value": "{{exchangeContractAddress}}", "equals": true } ] @@ -288,10 +289,10 @@ { "listen": "test", "script": { - "id": "1144a2d6-6175-40a6-8568-d0d3884492a1", + "id": "e74a9069-18b9-42d3-b2d0-e18580ad73f2", "type": "text/javascript", "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenEnvKey = 'tokenContractAddress2';", "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Orders are properly filtered', function() {", @@ -314,13 +315,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/orders?tokenAddress={{ZRX_address}}", + "raw": "{{url}}/orders?tokenAddress={{tokenContractAddress2}}", "host": ["{{url}}"], "path": ["orders"], "query": [ { "key": "tokenAddress", - "value": "{{ZRX_address}}", + "value": "{{tokenContractAddress2}}", "equals": true } ] @@ -336,10 +337,10 @@ { "listen": "test", "script": { - "id": "01eb5865-edec-4216-8a90-c36d790366fc", + "id": "c539f306-aa03-495d-a90a-0179e1b751aa", "type": "text/javascript", "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenEnvKey = 'tokenContractAddress2';", "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Orders are properly filtered', function() {", @@ -358,13 +359,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/orders?makerTokenAddress={{ZRX_address}}", + "raw": "{{url}}/orders?makerTokenAddress={{tokenContractAddress2}}", "host": ["{{url}}"], "path": ["orders"], "query": [ { "key": "makerTokenAddress", - "value": "{{ZRX_address}}", + "value": "{{tokenContractAddress2}}", "equals": true } ] @@ -380,10 +381,10 @@ { "listen": "test", "script": { - "id": "bbbb6e15-60c7-4666-96be-a0ad56b6058b", + "id": "49b2fcaf-5fe2-471f-ae10-e48a440d4c6d", "type": "text/javascript", "exec": [ - "const filterTokenEnvKey = 'ZRX_address';", + "const filterTokenEnvKey = 'tokenContractAddress2';", "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", "const responseJsonData = pm.response.json();", "pm.test('Orders are properly filtered', function() {", @@ -402,13 +403,13 @@ "header": [], "body": {}, "url": { - "raw": "{{url}}/orders?takerTokenAddress={{ZRX_address}}", + "raw": "{{url}}/orders?takerTokenAddress={{tokenContractAddress2}}", "host": ["{{url}}"], "path": ["orders"], "query": [ { "key": "takerTokenAddress", - "value": "{{ZRX_address}}", + "value": "{{tokenContractAddress2}}", "equals": true } ] @@ -730,11 +731,11 @@ { "listen": "test", "script": { - "id": "0198622e-9705-4ae1-b1f2-0c40b87de856", + "id": "9ed05327-1a2f-4e50-b4aa-e21f961dbe78", "type": "text/javascript", "exec": [ - "const baseTokenEnvKey = 'ZRX_address';", - "const quoteTokenEnvKey = 'WETH_address';", + "const baseTokenEnvKey = 'tokenContractAddress2';", + "const quoteTokenEnvKey = 'tokenContractAddress1';", "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", "const responseJsonData = pm.response.json();", @@ -765,18 +766,18 @@ "body": {}, "url": { "raw": - "{{url}}/orderbook?baseTokenAddress={{ZRX_address}}"eTokenAddress={{WETH_address}}", + "{{url}}/orderbook?baseTokenAddress={{tokenContractAddress2}}"eTokenAddress={{tokenContractAddress1}}", "host": ["{{url}}"], "path": ["orderbook"], "query": [ { "key": "baseTokenAddress", - "value": "{{ZRX_address}}", + "value": "{{tokenContractAddress2}}", "equals": true }, { "key": "quoteTokenAddress", - "value": "{{WETH_address}}", + "value": "{{tokenContractAddress1}}", "equals": true } ] diff --git a/packages/sra-report/src/contract_addresses/kovan_addresses.ts b/packages/sra-report/src/contract_addresses/kovan_addresses.ts index 4d2b76a51a..e06568f526 100644 --- a/packages/sra-report/src/contract_addresses/kovan_addresses.ts +++ b/packages/sra-report/src/contract_addresses/kovan_addresses.ts @@ -1,6 +1,5 @@ export const addresses = { - DAI_address: '0xb18845c260f680d5b9d84649638813e342e4f8c9', - WETH_address: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', - ZRX_address: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', - EXCHANGE_CONTRACT_address: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + ZRX: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + EXCHANGE: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', }; diff --git a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts index 4ccbdf12dc..e9aa0f1678 100644 --- a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts +++ b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts @@ -1,6 +1,5 @@ export const addresses = { - DAI_address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - WETH_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - ZRX_address: '0xe41d2489571d322189246dafa5ebde1f4699f498', - EXCHANGE_CONTRACT_address: '0x12459c951127e0c374ff9105dda097662a027093', + WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + ZRX: '0xe41d2489571d322189246dafa5ebde1f4699f498', + EXCHANGE: '0x12459c951127e0c374ff9105dda097662a027093', }; diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 703132c6a2..91fbb5d2ca 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -20,43 +20,12 @@ export const postmanEnvironmentFactory = { * - Order properties for making specific queries (ex. maker=orderMaker) */ async createPostmanEnvironmentAsync(url: string, networkId: number) { - const schemas: Schema[] = _.values(schemasByName); - const schemaEnvironmentValues = _.compact( - _.map(schemas, (schema: Schema) => { - if (_.isUndefined(schema.id)) { - return undefined; - } else { - const schemaKey = convertSchemaIdToKey(schema.id); - const stringifiedSchema = JSON.stringify(schema); - const schemaEnvironmentValue = createEnvironmentValue(schemaKey, stringifiedSchema); - return schemaEnvironmentValue; - } - }), - ); - const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { - return environmentValue.key; - }); - const contractAddresses = getContractAddresses(networkId); - const contractAddressEnvironmentValues = _.map(_.keys(contractAddresses), (key: string) => { - const contractAddress = _.get(contractAddresses, key); - return createEnvironmentValue(key, contractAddress); - }); - const httpClient = new HttpClient(url); - const orders = await httpClient.getOrdersAsync(); - const firstOrder = _.head(orders); - if (_.isUndefined(firstOrder)) { - throw new Error('Could not get any orders from /orders endpoint'); - } + const orderEnvironmentValues = await createOrderEnvironmentValuesAsync(url); const allEnvironmentValues = _.concat( - schemaEnvironmentValues, - contractAddressEnvironmentValues, - createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys)), + createSchemaEnvironmentValues(), + createContractAddressEnvironmentValues(networkId), + orderEnvironmentValues, createEnvironmentValue('url', url), - createEnvironmentValue('order', JSON.stringify(firstOrder)), - createEnvironmentValue('orderMaker', firstOrder.maker), - createEnvironmentValue('orderTaker', firstOrder.taker), - createEnvironmentValue('orderFeeRecipient', firstOrder.feeRecipient), - createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(firstOrder)), ); const environment = { values: allEnvironmentValues, @@ -64,6 +33,58 @@ export const postmanEnvironmentFactory = { return environment; }, }; +function createSchemaEnvironmentValues() { + const schemas: Schema[] = _.values(schemasByName); + const schemaEnvironmentValues = _.compact( + _.map(schemas, (schema: Schema) => { + if (_.isUndefined(schema.id)) { + return undefined; + } else { + const schemaKey = convertSchemaIdToKey(schema.id); + const stringifiedSchema = JSON.stringify(schema); + const schemaEnvironmentValue = createEnvironmentValue(schemaKey, stringifiedSchema); + return schemaEnvironmentValue; + } + }), + ); + const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { + return environmentValue.key; + }); + const result = _.concat(schemaEnvironmentValues, createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys))); + return result; +} +function createContractAddressEnvironmentValues(networkId: number) { + const contractAddresses = getContractAddresses(networkId); + return [ + createEnvironmentValue('tokenContractAddress1', contractAddresses.WETH), + createEnvironmentValue('tokenContractAddress2', contractAddresses.ZRX), + createEnvironmentValue('exchangeContractAddress', contractAddresses.EXCHANGE), + ]; +} + +async function createOrderEnvironmentValuesAsync(url: string) { + const httpClient = new HttpClient(url); + const orders = await httpClient.getOrdersAsync(); + const orderIfExists = _.head(orders); + if (!_.isUndefined(orderIfExists)) { + return [ + createEnvironmentValue('order', JSON.stringify(orderIfExists)), + createEnvironmentValue('orderMaker', orderIfExists.maker), + createEnvironmentValue('orderTaker', orderIfExists.taker), + createEnvironmentValue('orderFeeRecipient', orderIfExists.feeRecipient), + createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(orderIfExists)), + ]; + } else { + return [ + createEnvironmentValue('order', ''), + createEnvironmentValue('orderMaker', ''), + createEnvironmentValue('orderTaker', ''), + createEnvironmentValue('orderFeeRecipient', ''), + createEnvironmentValue('orderHash', ''), + ]; + } +} + function getContractAddresses(networkId: number) { switch (networkId) { case 1: From d39af6c9eb71501ec3a41bf12a3b0bb5d336ce46 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 15:34:32 -0800 Subject: [PATCH 46/94] Print error message when no orders are found from /orders --- packages/sra-report/src/postman_environment_factory.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 91fbb5d2ca..066e9ac046 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -1,10 +1,12 @@ import { SignedOrder, ZeroEx } from '0x.js'; import { HttpClient } from '@0xproject/connect'; import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; +import chalk from 'chalk'; import * as _ from 'lodash'; import { addresses as kovanAddresses } from './contract_addresses/kovan_addresses'; import { addresses as mainnetAddresses } from './contract_addresses/mainnet_addresses'; +import { utils } from './utils'; interface EnvironmentValue { key: string; @@ -61,7 +63,6 @@ function createContractAddressEnvironmentValues(networkId: number) { createEnvironmentValue('exchangeContractAddress', contractAddresses.EXCHANGE), ]; } - async function createOrderEnvironmentValuesAsync(url: string) { const httpClient = new HttpClient(url); const orders = await httpClient.getOrdersAsync(); @@ -75,6 +76,7 @@ async function createOrderEnvironmentValuesAsync(url: string) { createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(orderIfExists)), ]; } else { + utils.log(`${chalk.red(`No orders from /orders found`)}`); return [ createEnvironmentValue('order', ''), createEnvironmentValue('orderMaker', ''), From 5a670683487584a09382c1ebf9d6a16d7db98e6a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 15:39:38 -0800 Subject: [PATCH 47/94] Random fixes --- packages/sra-report/scripts/postpublish.js | 2 +- packages/sra-report/src/postman_environment_factory.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sra-report/scripts/postpublish.js b/packages/sra-report/scripts/postpublish.js index b3e5407c81..639656c7ee 100644 --- a/packages/sra-report/scripts/postpublish.js +++ b/packages/sra-report/scripts/postpublish.js @@ -2,4 +2,4 @@ const postpublish_utils = require('../../../scripts/postpublish_utils'); const packageJSON = require('../package.json'); const subPackageName = packageJSON.name; -postpublish_utils.standardPostPublishAsync(subPackageName); \ No newline at end of file +postpublish_utils.standardPostPublishAsync(subPackageName); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 066e9ac046..13991b8945 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -86,7 +86,6 @@ async function createOrderEnvironmentValuesAsync(url: string) { ]; } } - function getContractAddresses(networkId: number) { switch (networkId) { case 1: From 2ff485d2e074d45c31924df1925fe24f6a2b6b5f Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 16:25:51 -0800 Subject: [PATCH 48/94] Add relevant newman typings --- packages/sra-report/src/globals.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sra-report/src/globals.d.ts b/packages/sra-report/src/globals.d.ts index eb09d3fcfc..a35aa20360 100644 --- a/packages/sra-report/src/globals.d.ts +++ b/packages/sra-report/src/globals.d.ts @@ -1,4 +1,6 @@ -declare module 'newman'; +declare module 'newman' { + export function run(options: any, callback?: () => void): void; +} declare module '*.json' { const value: any; From 6288a720366b30a3969b9087bd3811026896a043 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 6 Mar 2018 23:55:58 -0800 Subject: [PATCH 49/94] Add name to environment --- packages/sra-report/src/postman_environment_factory.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts index 13991b8945..f4094315ea 100644 --- a/packages/sra-report/src/postman_environment_factory.ts +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -8,6 +8,8 @@ import { addresses as kovanAddresses } from './contract_addresses/kovan_addresse import { addresses as mainnetAddresses } from './contract_addresses/mainnet_addresses'; import { utils } from './utils'; +const ENVIRONMENT_NAME = 'SRA Report'; + interface EnvironmentValue { key: string; } @@ -30,6 +32,7 @@ export const postmanEnvironmentFactory = { createEnvironmentValue('url', url), ); const environment = { + name: ENVIRONMENT_NAME, values: allEnvironmentValues, }; return environment; From f61b59fa89dbb957c388c9034721c34bcf8ae7df Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 7 Mar 2018 00:17:39 -0800 Subject: [PATCH 50/94] Disable completed docs linting for globals --- packages/sra-report/src/globals.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sra-report/src/globals.d.ts b/packages/sra-report/src/globals.d.ts index a35aa20360..0d3beb4469 100644 --- a/packages/sra-report/src/globals.d.ts +++ b/packages/sra-report/src/globals.d.ts @@ -1,4 +1,5 @@ declare module 'newman' { + // tslint:disable-next-line:completed-docs export function run(options: any, callback?: () => void): void; } From f66efed777f1046718478a28f5dd3c4942379774 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 7 Mar 2018 10:20:15 +0100 Subject: [PATCH 51/94] Add example docs to react-docs package --- packages/react-docs/package.json | 13 +- packages/react-docs/src/less/all.less | 37 + packages/react-docs/src/md/introduction.md | 1 + .../public/css/basscss_responsive_custom.css | 85 + .../public/css/basscss_responsive_margin.css | 160 ++ .../public/css/basscss_responsive_padding.css | 134 ++ .../css/basscss_responsive_type_scale.css | 35 + .../react-docs/src/public/css/github-gist.css | 71 + packages/react-docs/src/public/css/roboto.css | 83 + .../react-docs/src/public/css/roboto_mono.css | 69 + .../src/public/fonts/Roboto-Black.ttf | Bin 0 -> 171480 bytes .../src/public/fonts/Roboto-BlackItalic.ttf | Bin 0 -> 177552 bytes .../src/public/fonts/Roboto-Bold.ttf | Bin 0 -> 170760 bytes .../src/public/fonts/Roboto-BoldItalic.ttf | Bin 0 -> 174952 bytes .../src/public/fonts/Roboto-Italic.ttf | Bin 0 -> 173932 bytes .../src/public/fonts/Roboto-Light.ttf | Bin 0 -> 170420 bytes .../src/public/fonts/Roboto-LightItalic.ttf | Bin 0 -> 176616 bytes .../src/public/fonts/Roboto-Medium.ttf | Bin 0 -> 172064 bytes .../src/public/fonts/Roboto-MediumItalic.ttf | Bin 0 -> 176864 bytes .../src/public/fonts/Roboto-Regular.ttf | Bin 0 -> 171676 bytes .../src/public/fonts/Roboto-Thin.ttf | Bin 0 -> 171904 bytes .../src/public/fonts/Roboto-ThinItalic.ttf | Bin 0 -> 176300 bytes .../src/public/fonts/RobotoMono-Bold.ttf | Bin 0 -> 114752 bytes .../public/fonts/RobotoMono-BoldItalic.ttf | Bin 0 -> 122808 bytes .../src/public/fonts/RobotoMono-Italic.ttf | Bin 0 -> 120832 bytes .../src/public/fonts/RobotoMono-Light.ttf | Bin 0 -> 118976 bytes .../public/fonts/RobotoMono-LightItalic.ttf | Bin 0 -> 127568 bytes .../src/public/fonts/RobotoMono-Medium.ttf | Bin 0 -> 114696 bytes .../public/fonts/RobotoMono-MediumItalic.ttf | Bin 0 -> 123640 bytes .../src/public/fonts/RobotoMono-Regular.ttf | Bin 0 -> 114624 bytes .../src/public/fonts/RobotoMono-Thin.ttf | Bin 0 -> 118132 bytes .../public/fonts/RobotoMono-ThinItalic.ttf | Bin 0 -> 121456 bytes packages/react-docs/src/public/index.html | 23 + .../src/ts/components/documentation.tsx | 66 +- packages/react-docs/src/ts/example/index.tsx | 67 + .../json/web3_wrapper_typedoc_output.json | 1364 +++++++++++++++++ packages/react-docs/src/ts/globals.d.ts | 7 + packages/react-docs/webpack.config.js | 79 + packages/website/less/all.less | 4 - .../website/public/css/atom-one-light.css | 96 -- .../website/ts/components/sidebar_header.tsx | 4 +- .../ts/pages/documentation/doc_page.tsx | 3 +- yarn.lock | 136 +- 43 files changed, 2396 insertions(+), 141 deletions(-) create mode 100644 packages/react-docs/src/less/all.less create mode 100644 packages/react-docs/src/md/introduction.md create mode 100644 packages/react-docs/src/public/css/basscss_responsive_custom.css create mode 100644 packages/react-docs/src/public/css/basscss_responsive_margin.css create mode 100644 packages/react-docs/src/public/css/basscss_responsive_padding.css create mode 100644 packages/react-docs/src/public/css/basscss_responsive_type_scale.css create mode 100644 packages/react-docs/src/public/css/github-gist.css create mode 100644 packages/react-docs/src/public/css/roboto.css create mode 100644 packages/react-docs/src/public/css/roboto_mono.css create mode 100755 packages/react-docs/src/public/fonts/Roboto-Black.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-BlackItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Bold.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-BoldItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Italic.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Light.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-LightItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Medium.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-MediumItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Regular.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-Thin.ttf create mode 100755 packages/react-docs/src/public/fonts/Roboto-ThinItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Bold.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-BoldItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Italic.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Light.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-LightItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Medium.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-MediumItalic.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Regular.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-Thin.ttf create mode 100755 packages/react-docs/src/public/fonts/RobotoMono-ThinItalic.ttf create mode 100644 packages/react-docs/src/public/index.html create mode 100644 packages/react-docs/src/ts/example/index.tsx create mode 100644 packages/react-docs/src/ts/example/json/web3_wrapper_typedoc_output.json create mode 100644 packages/react-docs/webpack.config.js delete mode 100644 packages/website/public/css/atom-one-light.css diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 4b582edff2..e38aef2e59 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -8,7 +8,8 @@ "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", "build": "tsc", "build:watch": "tsc -w", - "clean": "shx rm -rf lib" + "clean": "shx rm -rf lib", + "dev": "webpack-dev-server --open" }, "author": "Fabio Berger", "license": "MIT", @@ -21,7 +22,15 @@ "@types/react-dom": "^0.14.23", "shx": "^0.2.2", "tslint": "^5.9.1", - "typescript": "2.7.1" + "typescript": "2.7.1", + "webpack": "^3.11.0", + "webpack-dev-server": "^2.11.1", + "css-loader": "^0.28.9", + "source-map-loader": "^0.2.3", + "style-loader": "^0.20.2", + "json-loader": "^0.5.4", + "raw-loader": "^0.5.1", + "less-loader": "^2.2.3" }, "dependencies": { "@0xproject/react-shared": "^0.0.1", diff --git a/packages/react-docs/src/less/all.less b/packages/react-docs/src/less/all.less new file mode 100644 index 0000000000..4060b8128b --- /dev/null +++ b/packages/react-docs/src/less/all.less @@ -0,0 +1,37 @@ +/* + * Adds always visible scrollbars on OSX so that user knows the content is scrollable + * Source: https://davidwalsh.name/osx-overflow + */ +::-webkit-scrollbar { + -webkit-appearance: none; + width: 4px; + height: 2px; +} +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); +} + +#documentation { + p { + line-height: 1.5; + } + + .comment { + p { + margin: 0px; + } + } + + .typeTooltip { + border: 1px solid lightgray; + opacity: 1; + } +} + +code { + border: 1px solid #e3eefe; + font-family: 'Roboto Mono'; + background-color: #f2f6ff !important; // lightBlue +} diff --git a/packages/react-docs/src/md/introduction.md b/packages/react-docs/src/md/introduction.md new file mode 100644 index 0000000000..b8569576e2 --- /dev/null +++ b/packages/react-docs/src/md/introduction.md @@ -0,0 +1 @@ +Welcome to the [Web3Wrapper](https://github.com/0xProject/0x-monorepo/packages/web3-wrapper) documentation! Web3Wrapper is a convenience wrapper around Web3.js, adding support for promises and other niceties. diff --git a/packages/react-docs/src/public/css/basscss_responsive_custom.css b/packages/react-docs/src/public/css/basscss_responsive_custom.css new file mode 100644 index 0000000000..5f8bd9117d --- /dev/null +++ b/packages/react-docs/src/public/css/basscss_responsive_custom.css @@ -0,0 +1,85 @@ +/* Custom Basscss Responsive Utilities */ + +@media (max-width: 52em) { + .sm-center { + text-align: center; + } + .sm-align-middle { + vertical-align: middle; + } + .sm-align-top { + vertical-align: top; + } + .sm-left-align { + text-align: left; + } + .sm-right-align { + text-align: right; + } + .sm-table-cell { + display: table-cell; + } + .sm-mx-auto { + margin-left: auto; + margin-right: auto; + } + .sm-right { + float: right; + } +} + +@media (min-width: 52em) { + .md-center { + text-align: center; + } + .md-align-middle { + vertical-align: middle; + } + .md-align-top { + vertical-align: top; + } + .md-left-align { + text-align: left; + } + .md-right-align { + text-align: right; + } + .md-table-cell { + display: table-cell; + } + .md-mx-auto { + margin-left: auto; + margin-right: auto; + } + .md-right { + float: right; + } +} + +@media (min-width: 64em) { + .lg-center { + text-align: center; + } + .lg-align-middle { + vertical-align: middle; + } + .lg-align-top { + vertical-align: top; + } + .lg-left-align { + text-align: left; + } + .lg-right-align { + text-align: right; + } + .lg-table-cell { + display: table-cell; + } + .lg-mx-auto { + margin-left: auto; + margin-right: auto; + } + .lg-right { + float: right; + } +} diff --git a/packages/react-docs/src/public/css/basscss_responsive_margin.css b/packages/react-docs/src/public/css/basscss_responsive_margin.css new file mode 100644 index 0000000000..b601bd4919 --- /dev/null +++ b/packages/react-docs/src/public/css/basscss_responsive_margin.css @@ -0,0 +1,160 @@ +/* Basscss Responsive Margin */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-m0 { margin: 0 } + .sm-mt0 { margin-top: 0 } + .sm-mr0 { margin-right: 0 } + .sm-mb0 { margin-bottom: 0 } + .sm-ml0 { margin-left: 0 } + .sm-mx0 { margin-left: 0; margin-right: 0 } + .sm-my0 { margin-top: 0; margin-bottom: 0 } + + .sm-m1 { margin: .5rem } + .sm-mt1 { margin-top: .5rem } + .sm-mr1 { margin-right: .5rem } + .sm-mb1 { margin-bottom: .5rem } + .sm-ml1 { margin-left: .5rem } + .sm-mx1 { margin-left: .5rem; margin-right: .5rem } + .sm-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .sm-m2 { margin: 1rem } + .sm-mt2 { margin-top: 1rem } + .sm-mr2 { margin-right: 1rem } + .sm-mb2 { margin-bottom: 1rem } + .sm-ml2 { margin-left: 1rem } + .sm-mx2 { margin-left: 1rem; margin-right: 1rem } + .sm-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .sm-m3 { margin: 2rem } + .sm-mt3 { margin-top: 2rem } + .sm-mr3 { margin-right: 2rem } + .sm-mb3 { margin-bottom: 2rem } + .sm-ml3 { margin-left: 2rem } + .sm-mx3 { margin-left: 2rem; margin-right: 2rem } + .sm-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .sm-m4 { margin: 4rem } + .sm-mt4 { margin-top: 4rem } + .sm-mr4 { margin-right: 4rem } + .sm-mb4 { margin-bottom: 4rem } + .sm-ml4 { margin-left: 4rem } + .sm-mx4 { margin-left: 4rem; margin-right: 4rem } + .sm-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .sm-mxn1 { margin-left: -.5rem; margin-right: -.5rem } + .sm-mxn2 { margin-left: -1rem; margin-right: -1rem } + .sm-mxn3 { margin-left: -2rem; margin-right: -2rem } + .sm-mxn4 { margin-left: -4rem; margin-right: -4rem } + + .sm-ml-auto { margin-left: auto } + .sm-mr-auto { margin-right: auto } + .sm-mx-auto { margin-left: auto; margin-right: auto } + +} + +@media (min-width: 52em) { + + .md-m0 { margin: 0 } + .md-mt0 { margin-top: 0 } + .md-mr0 { margin-right: 0 } + .md-mb0 { margin-bottom: 0 } + .md-ml0 { margin-left: 0 } + .md-mx0 { margin-left: 0; margin-right: 0 } + .md-my0 { margin-top: 0; margin-bottom: 0 } + + .md-m1 { margin: .5rem } + .md-mt1 { margin-top: .5rem } + .md-mr1 { margin-right: .5rem } + .md-mb1 { margin-bottom: .5rem } + .md-ml1 { margin-left: .5rem } + .md-mx1 { margin-left: .5rem; margin-right: .5rem } + .md-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .md-m2 { margin: 1rem } + .md-mt2 { margin-top: 1rem } + .md-mr2 { margin-right: 1rem } + .md-mb2 { margin-bottom: 1rem } + .md-ml2 { margin-left: 1rem } + .md-mx2 { margin-left: 1rem; margin-right: 1rem } + .md-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .md-m3 { margin: 2rem } + .md-mt3 { margin-top: 2rem } + .md-mr3 { margin-right: 2rem } + .md-mb3 { margin-bottom: 2rem } + .md-ml3 { margin-left: 2rem } + .md-mx3 { margin-left: 2rem; margin-right: 2rem } + .md-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .md-m4 { margin: 4rem } + .md-mt4 { margin-top: 4rem } + .md-mr4 { margin-right: 4rem } + .md-mb4 { margin-bottom: 4rem } + .md-ml4 { margin-left: 4rem } + .md-mx4 { margin-left: 4rem; margin-right: 4rem } + .md-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .md-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .md-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .md-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .md-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .md-ml-auto { margin-left: auto } + .md-mr-auto { margin-right: auto } + .md-mx-auto { margin-left: auto; margin-right: auto; } + +} + +@media (min-width: 64em) { + + .lg-m0 { margin: 0 } + .lg-mt0 { margin-top: 0 } + .lg-mr0 { margin-right: 0 } + .lg-mb0 { margin-bottom: 0 } + .lg-ml0 { margin-left: 0 } + .lg-mx0 { margin-left: 0; margin-right: 0 } + .lg-my0 { margin-top: 0; margin-bottom: 0 } + + .lg-m1 { margin: .5rem } + .lg-mt1 { margin-top: .5rem } + .lg-mr1 { margin-right: .5rem } + .lg-mb1 { margin-bottom: .5rem } + .lg-ml1 { margin-left: .5rem } + .lg-mx1 { margin-left: .5rem; margin-right: .5rem } + .lg-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .lg-m2 { margin: 1rem } + .lg-mt2 { margin-top: 1rem } + .lg-mr2 { margin-right: 1rem } + .lg-mb2 { margin-bottom: 1rem } + .lg-ml2 { margin-left: 1rem } + .lg-mx2 { margin-left: 1rem; margin-right: 1rem } + .lg-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .lg-m3 { margin: 2rem } + .lg-mt3 { margin-top: 2rem } + .lg-mr3 { margin-right: 2rem } + .lg-mb3 { margin-bottom: 2rem } + .lg-ml3 { margin-left: 2rem } + .lg-mx3 { margin-left: 2rem; margin-right: 2rem } + .lg-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .lg-m4 { margin: 4rem } + .lg-mt4 { margin-top: 4rem } + .lg-mr4 { margin-right: 4rem } + .lg-mb4 { margin-bottom: 4rem } + .lg-ml4 { margin-left: 4rem } + .lg-mx4 { margin-left: 4rem; margin-right: 4rem } + .lg-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .lg-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .lg-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .lg-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .lg-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .lg-ml-auto { margin-left: auto } + .lg-mr-auto { margin-right: auto } + .lg-mx-auto { margin-left: auto; margin-right: auto; } + +} diff --git a/packages/react-docs/src/public/css/basscss_responsive_padding.css b/packages/react-docs/src/public/css/basscss_responsive_padding.css new file mode 100644 index 0000000000..e027c2d65d --- /dev/null +++ b/packages/react-docs/src/public/css/basscss_responsive_padding.css @@ -0,0 +1,134 @@ +/* Basscss Responsive Padding */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-p0 { padding: 0 } + .sm-pt0 { padding-top: 0 } + .sm-pr0 { padding-right: 0 } + .sm-pb0 { padding-bottom: 0 } + .sm-pl0 { padding-left: 0 } + .sm-px0 { padding-left: 0; padding-right: 0 } + .sm-py0 { padding-top: 0; padding-bottom: 0 } + + .sm-p1 { padding: .5rem } + .sm-pt1 { padding-top: .5rem } + .sm-pr1 { padding-right: .5rem } + .sm-pb1 { padding-bottom: .5rem } + .sm-pl1 { padding-left: .5rem } + .sm-px1 { padding-left: .5rem; padding-right: .5rem } + .sm-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .sm-p2 { padding: 1rem } + .sm-pt2 { padding-top: 1rem } + .sm-pr2 { padding-right: 1rem } + .sm-pb2 { padding-bottom: 1rem } + .sm-pl2 { padding-left: 1rem } + .sm-px2 { padding-left: 1rem; padding-right: 1rem } + .sm-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .sm-p3 { padding: 2rem } + .sm-pt3 { padding-top: 2rem } + .sm-pr3 { padding-right: 2rem } + .sm-pb3 { padding-bottom: 2rem } + .sm-pl3 { padding-left: 2rem } + .sm-px3 { padding-left: 2rem; padding-right: 2rem } + .sm-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .sm-p4 { padding: 4rem } + .sm-pt4 { padding-top: 4rem } + .sm-pr4 { padding-right: 4rem } + .sm-pb4 { padding-bottom: 4rem } + .sm-pl4 { padding-left: 4rem } + .sm-px4 { padding-left: 4rem; padding-right: 4rem } + .sm-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 52em) { + + .md-p0 { padding: 0 } + .md-pt0 { padding-top: 0 } + .md-pr0 { padding-right: 0 } + .md-pb0 { padding-bottom: 0 } + .md-pl0 { padding-left: 0 } + .md-px0 { padding-left: 0; padding-right: 0 } + .md-py0 { padding-top: 0; padding-bottom: 0 } + + .md-p1 { padding: .5rem } + .md-pt1 { padding-top: .5rem } + .md-pr1 { padding-right: .5rem } + .md-pb1 { padding-bottom: .5rem } + .md-pl1 { padding-left: .5rem } + .md-px1 { padding-left: .5rem; padding-right: .5rem } + .md-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .md-p2 { padding: 1rem } + .md-pt2 { padding-top: 1rem } + .md-pr2 { padding-right: 1rem } + .md-pb2 { padding-bottom: 1rem } + .md-pl2 { padding-left: 1rem } + .md-px2 { padding-left: 1rem; padding-right: 1rem } + .md-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .md-p3 { padding: 2rem } + .md-pt3 { padding-top: 2rem } + .md-pr3 { padding-right: 2rem } + .md-pb3 { padding-bottom: 2rem } + .md-pl3 { padding-left: 2rem } + .md-px3 { padding-left: 2rem; padding-right: 2rem } + .md-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .md-p4 { padding: 4rem } + .md-pt4 { padding-top: 4rem } + .md-pr4 { padding-right: 4rem } + .md-pb4 { padding-bottom: 4rem } + .md-pl4 { padding-left: 4rem } + .md-px4 { padding-left: 4rem; padding-right: 4rem } + .md-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 64em) { + + .lg-p0 { padding: 0 } + .lg-pt0 { padding-top: 0 } + .lg-pr0 { padding-right: 0 } + .lg-pb0 { padding-bottom: 0 } + .lg-pl0 { padding-left: 0 } + .lg-px0 { padding-left: 0; padding-right: 0 } + .lg-py0 { padding-top: 0; padding-bottom: 0 } + + .lg-p1 { padding: .5rem } + .lg-pt1 { padding-top: .5rem } + .lg-pr1 { padding-right: .5rem } + .lg-pb1 { padding-bottom: .5rem } + .lg-pl1 { padding-left: .5rem } + .lg-px1 { padding-left: .5rem; padding-right: .5rem } + .lg-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .lg-p2 { padding: 1rem } + .lg-pt2 { padding-top: 1rem } + .lg-pr2 { padding-right: 1rem } + .lg-pb2 { padding-bottom: 1rem } + .lg-pl2 { padding-left: 1rem } + .lg-px2 { padding-left: 1rem; padding-right: 1rem } + .lg-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .lg-p3 { padding: 2rem } + .lg-pt3 { padding-top: 2rem } + .lg-pr3 { padding-right: 2rem } + .lg-pb3 { padding-bottom: 2rem } + .lg-pl3 { padding-left: 2rem } + .lg-px3 { padding-left: 2rem; padding-right: 2rem } + .lg-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .lg-p4 { padding: 4rem } + .lg-pt4 { padding-top: 4rem } + .lg-pr4 { padding-right: 4rem } + .lg-pb4 { padding-bottom: 4rem } + .lg-pl4 { padding-left: 4rem } + .lg-px4 { padding-left: 4rem; padding-right: 4rem } + .lg-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} diff --git a/packages/react-docs/src/public/css/basscss_responsive_type_scale.css b/packages/react-docs/src/public/css/basscss_responsive_type_scale.css new file mode 100644 index 0000000000..cae23b4e71 --- /dev/null +++ b/packages/react-docs/src/public/css/basscss_responsive_type_scale.css @@ -0,0 +1,35 @@ +/* Basscss Responsive Type Scale */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + .sm-h00 { font-size: 4rem } + .sm-h0 { font-size: 3rem } + .sm-h1 { font-size: 2rem } + .sm-h2 { font-size: 1.5rem } + .sm-h3 { font-size: 1.25rem } + .sm-h4 { font-size: 1rem } + .sm-h5 { font-size: .875rem } + .sm-h6 { font-size: .75rem } +} + +@media (min-width: 52em) { + .md-h00 { font-size: 4rem } + .md-h0 { font-size: 3rem } + .md-h1 { font-size: 2rem } + .md-h2 { font-size: 1.5rem } + .md-h3 { font-size: 1.25rem } + .md-h4 { font-size: 1rem } + .md-h5 { font-size: .875rem } + .md-h6 { font-size: .75rem } +} + +@media (min-width: 64em) { + .lg-h00 { font-size: 4rem } + .lg-h0 { font-size: 3rem } + .lg-h1 { font-size: 2rem } + .lg-h2 { font-size: 1.5rem } + .lg-h3 { font-size: 1.25rem } + .lg-h4 { font-size: 1rem } + .lg-h5 { font-size: .875rem } + .lg-h6 { font-size: .75rem } +} diff --git a/packages/react-docs/src/public/css/github-gist.css b/packages/react-docs/src/public/css/github-gist.css new file mode 100644 index 0000000000..d5c8751c54 --- /dev/null +++ b/packages/react-docs/src/public/css/github-gist.css @@ -0,0 +1,71 @@ +/** + * GitHub Gist Theme + * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro + */ + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/react-docs/src/public/css/roboto.css b/packages/react-docs/src/public/css/roboto.css new file mode 100644 index 0000000000..7af568a749 --- /dev/null +++ b/packages/react-docs/src/public/css/roboto.css @@ -0,0 +1,83 @@ +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +}*/ diff --git a/packages/react-docs/src/public/css/roboto_mono.css b/packages/react-docs/src/public/css/roboto_mono.css new file mode 100644 index 0000000000..f8159d35f2 --- /dev/null +++ b/packages/react-docs/src/public/css/roboto_mono.css @@ -0,0 +1,69 @@ +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +}*/ diff --git a/packages/react-docs/src/public/fonts/Roboto-Black.ttf b/packages/react-docs/src/public/fonts/Roboto-Black.ttf new file mode 100755 index 0000000000000000000000000000000000000000..689fe5cb3c715f2944fec30e43ccb8a2b10625d3 GIT binary patch literal 171480 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

~M8)?dE#I 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 Xk2aT?%S{K<(OhA!BxPw6dg^}wc$YKS literal 0 HcmV?d00001 diff --git a/packages/react-docs/src/public/fonts/Roboto-BlackItalic.ttf b/packages/react-docs/src/public/fonts/Roboto-BlackItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..0b4e0ee108899ddfef739f48d2aa9475b8b41a03 GIT binary patch literal 177552 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�_SD^G{wdrrHYdIGDG`C$ZMlG79#yZF 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 literal 0 HcmV?d00001 diff --git a/packages/react-docs/src/public/fonts/Roboto-Bold.ttf b/packages/react-docs/src/public/fonts/Roboto-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..d3f01ad245b628f386ac95786f53167038720eb2 GIT binary patch literal 170760 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�!3r53e9=KAt@g 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