├── .circleci
└── config.yml
├── .dockerignore
├── .editorconfig
├── .env
├── .gitattributes
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── db
└── .gitkeep
├── js
├── config.js
├── constants.js
├── db_connection.js
├── entity
│ └── SignedOrderEntity.js
├── errors.js
├── fee_strategy.js
├── handlers.js
├── index.js
├── mesh_utils.js
├── middleware
│ ├── error_handling.js
│ └── url_params_parsing.js
├── models
│ └── SignedOrderModel.js
├── paginator.js
├── runners
│ ├── http_service_runner.js
│ ├── order_watcher_service_runner.js
│ └── websocket_service_runner.js
├── services
│ ├── http_service.js
│ ├── order_watcher_service.js
│ ├── orderbook_service.js
│ ├── orderbook_utils.js
│ └── websocket_service.js
├── types.js
└── utils.js
├── launch_kit_banner.png
├── metadata.json
├── ormconfig.json
├── package.json
├── ts
├── src
│ ├── config.ts
│ ├── constants.ts
│ ├── db_connection.ts
│ ├── entity
│ │ └── SignedOrderEntity.ts
│ ├── errors.ts
│ ├── fee_strategy.ts
│ ├── globals.d.ts
│ ├── handlers.ts
│ ├── index.ts
│ ├── mesh_utils.ts
│ ├── middleware
│ │ ├── error_handling.ts
│ │ └── url_params_parsing.ts
│ ├── models
│ │ └── SignedOrderModel.ts
│ ├── paginator.ts
│ ├── runners
│ │ ├── http_service_runner.ts
│ │ ├── order_watcher_service_runner.ts
│ │ └── websocket_service_runner.ts
│ ├── services
│ │ ├── http_service.ts
│ │ ├── order_watcher_service.ts
│ │ ├── orderbook_service.ts
│ │ ├── orderbook_utils.ts
│ │ └── websocket_service.ts
│ ├── types.ts
│ └── utils.ts
├── tsconfig.json
└── tslint.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | - image: circleci/node:8.12.0-browsers
10 |
11 | working_directory: ~/repo
12 |
13 | steps:
14 | - checkout
15 | # Download and cache dependencies
16 | - restore_cache:
17 | keys:
18 | - v1-dependencies-{{ checksum "yarn.lock" }}
19 | # fallback to using the latest cache if no exact match is found
20 | - v1-dependencies-
21 |
22 | - run: yarn install
23 |
24 | - save_cache:
25 | paths:
26 | - node_modules
27 | key: v1-dependencies-{{ checksum "yarn.lock" }}
28 | - run: yarn build
29 | - run: yarn lint:ts
30 | - run: yarn prettier
31 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # All files
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | charset = utf-8
11 | indent_style = space
12 | indent_size = 4
13 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | HTTP_PORT=
2 | NETWORK_ID=
3 | FEE_RECIPIENT=
4 | MAKER_FEE_ZRX_UNIT_AMOUNT=
5 | TAKER_FEE_ZRX_UNIT_AMOUNT=
6 | RPC_URL=
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-generated
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Webstorm config folder
2 | .idea
3 |
4 | # VSCode config folder
5 | .vscode
6 |
7 | # dotenv environment variables file
8 | .env
9 |
10 | # Generated metadata file
11 | metadata.json
12 |
13 | # Logs
14 | logs
15 | *.log
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (https://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # TypeScript v1 declaration files
52 | typings/
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Optional REPL history
61 | .node_repl_history
62 |
63 | # Output of 'npm pack'
64 | *.tgz
65 |
66 | # Yarn Integrity file
67 | .yarn-integrity
68 |
69 | # next.js build output
70 | .next
71 |
72 | # compiled ts sources
73 | ts/lib
74 |
75 | # sqlite DB
76 | db/database.sqlite
77 |
78 | TODO.txt
79 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "printWidth": 120,
4 | "singleQuote": true,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 |
3 | ## v0.1.1 - _March 29, 2019_
4 |
5 | * Add support for environment variable WHITELIST_ALL_TOKENS. If set (to any value), the whitelist will be set to '*'.
6 |
7 | ## v0.1.0 - _March 27, 2019_
8 |
9 | * Add the sorting of bids/asks for the GET /v2/orderbook endpoint as stated in the SRA v2:
10 | https://github.com/0xProject/standard-relayer-api/blob/master/http/v2.md#get-v2orderbook
11 |
12 | ## v0.0.2 - _February 5, 2019_
13 |
14 | * Fix bug where orders in DB weren't being added to OrderWatcher on re-start
15 | * Fix bug where order fillability wasn't being checked before being added to OrderWatcher & the database
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Stage 1
2 | FROM node:11-alpine as yarn-install
3 | WORKDIR /usr/src/app
4 | # Install app dependencies
5 | COPY package.json yarn.lock ./
6 | RUN apk update && \
7 | apk upgrade && \
8 | apk add --no-cache --virtual build-dependencies bash git openssh python make g++ && \
9 | yarn --frozen-lockfile --no-cache && \
10 | apk del build-dependencies && \
11 | yarn cache clean
12 |
13 | # Runtime container with minimal dependencies
14 | FROM node:11-alpine
15 | WORKDIR /usr/src/app
16 | COPY --from=yarn-install /usr/src/app/node_modules /usr/src/app/node_modules
17 | # Bundle app source
18 | COPY . .
19 |
20 | RUN yarn build
21 |
22 | EXPOSE 3000
23 | CMD [ "./node_modules/.bin/forever", "ts/lib/index.js" ]
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright 2018 ZeroEx Intl.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## ⚠️ Deprecation Warning ️️⚠️
4 |
5 | This project is no longer being actively maintained.
6 | To fork or run a [Standard Relayer API](https://0x.org/docs/api#sra) instance, you can use the [0x API](https://github.com/0xProject/0x-api) instead.
7 |
8 | ## Table of contents
9 |
10 | - [Introduction](#introduction)
11 | - [Language choice](#language-choice)
12 | - [Getting started](#getting-started)
13 | - [Client for your relayers API](#client-for-your-relayers-api)
14 | - [Commands](#commands)
15 | - [Database](#database)
16 | - [Deployment](#deployment)
17 | - [Legal Disclaimer](#legal-disclaimer)
18 |
19 | ## Introduction
20 |
21 | Launch a 0x relayer in under a minute with Launch Kit. This repository contains an open-source, free-to-use 0x relayer template that you can use as a starting point for your own project.
22 |
23 | - Quickly launch a market for your community token
24 | - Seemlessly create an in-game marketplace for digital items and collectibles
25 | - Enable trading of any ERC-20 or ERC-721 asset
26 |
27 | Fork this repository to get started!
28 |
29 | ### [See website](https://0xproject.com/launch-kit)
30 |
31 | ## Language choice
32 |
33 | `0x-launch-kit-backend` ships with 2 codebases, one in Typescript and another in Javascript. Although the Javascript is auto-generated from the Typescript, we made sure the Javascript generated is readable.
34 |
35 | Before you start using `0x-launch-kit-backend`, choose whether you want your codebase to be in Typescript or Javascript.
36 |
37 | **If you want to work in Javascript:**
38 |
39 | - delete the `ts` directory
40 | - delete all scripts from `package.json` that end with `:ts`
41 |
42 | Note: If you also wish to build and use the Docker image, please update the command in the Dockerfile to `CMD [ "forever", "js/index.js" ]`
43 |
44 | **If you want to work in Typescript:**
45 |
46 | - delete the `js` directory
47 | - delete all scripts from `package.json` that end with `:js`
48 |
49 | ## Getting started
50 |
51 | #### Pre-requirements
52 |
53 | - [Node.js](https://nodejs.org/en/download/) > v8.x
54 | - [Yarn](https://yarnpkg.com/en/) > v1.x
55 | - [0x Mesh](https://github.com/0xProject/0x-mesh) > v5.0.1 for v3. Docker image `0xorg/mesh:5.0.1-beta-0xv3` or greater
56 |
57 | To develop ontop of `0x-launch-kit`, follow the following instructions:
58 |
59 | 1. Fork this repository
60 |
61 | 2. Clone your fork of this repository
62 |
63 | 3. Open the `config.ts`/`config.js` file (depending on the language you've chosen above) and edit the whitelisted tokens:
64 |
65 | - `WHITELISTED_TOKENS` -- Which tokens you would like to host orderbooks for.
66 |
67 | 4. Open the `.env` file and edit the following fields. Defaults are defined in `config.ts`/`config.js`. The bash environment takes precedence over the `.env` file. If you run `source .env`, changes to the `.env` file will have no effect until you unset the colliding variables.
68 |
69 | - `CHAIN_ID` -- the chain you'd like your relayer to run on (e.g: `1` -> mainnet, `42` -> Kovan, 3 -> Ropsten, etc...). Defaults to `42`
70 | - `MESH_ENDPOINT` -- the url pointing to the 0x Mesh node. Defaults to `ws://localhost:60557`
71 | - `FEE_RECIPIENT` -- The Ethereum address which should be specified as the fee recipient in orders your relayer accepts. Defaults to a fake address that helps the 0x core team use anonymous, already public data to understand Launch Kit developer usage. Defaults to an auto-generated address
72 | - `MAKER_FEE_ASSET_DATA` -- The maker fee token asset data. Defaults to `0x`, i.e no fee
73 | - `MAKER_FEE_UNIT_AMOUNT` -- The flat maker fee amount you'd like to receive for filled orders hosted by you. Defaults to `0`
74 | - `MAKER_FEE_ASSET_DATA` -- The taker fee token asset data. Defaults to `0x`, i.e no fee
75 | - `TAKER_FEE_UNIT_AMOUNT` -- The flat taker fee you'd like to receive for filled orders hosted by you. Defaults to `0`
76 |
77 | [Instructions for using Launch Kit with Ganache](https://hackmd.io/-rC79gYWRyG7h6M9jUf5qA)
78 |
79 | 5. Make sure you have [Yarn](https://yarnpkg.com/en/) installed.
80 |
81 | 6. Install the dependencies:
82 |
83 | ```sh
84 | yarn
85 | ```
86 |
87 | 7. Build the project [This step is for Typescript users only]
88 |
89 | ```sh
90 | yarn build:ts
91 | ```
92 |
93 | or build & watch:
94 |
95 | ```sh
96 | yarn watch:ts
97 | ```
98 |
99 | **Note:** There isn't currently a build step when working on the Javascript codebase because we assume `0x-launch-kit` will be running on Node.js > v8.0. If you want this project to work in an environment that doesn't support many of the latest Javascript features, you will need to add a transpiler (e.g [Babel](https://babeljs.io/)) to this project.
100 |
101 | 8. Start the relayer
102 |
103 | ```sh
104 | yarn start:ts
105 | ```
106 |
107 | OR
108 |
109 | ```sh
110 | yarn start:js
111 | ```
112 |
113 | ## Client for your relayer's API
114 |
115 | Since the `0x-launch-kit-backend` relayer adheres to V3 of the [Standard Relayer API Specification](https://github.com/0xProject/standard-relayer-api/), you can use [0x Connect](https://0xproject.com/docs/connect) (an HTTP/Websocket client) to make calls to your relayer (e.g submit an order, get all orders, etc...)
116 |
117 | Learn how to use 0x Connect to interact with your `0x-launch-kit` relayer in [this tutorial](https://0xproject.com/wiki#Find,-Submit,-Fill-Order-From-Relayer).
118 |
119 | To quickly check if your relayer is up-and-running, send it this CURL request from the command-line:
120 |
121 | ```sh
122 | curl http://localhost:3000/v3/orders
123 | ```
124 |
125 | If everything is working as expected, you should see this response:
126 |
127 | ```
128 | {
129 | "total": 0,
130 | "page": 0,
131 | "perPage": 20,
132 | "records": []
133 | }
134 | ```
135 |
136 | Since no orders have been added to your relayer yet, the `records` array is empty.
137 |
138 | ## Commands
139 |
140 | Typescript project commands:
141 |
142 | - `yarn build:ts` - Build the code
143 | - `yarn lint:ts` - Lint the code
144 | - `yarn start:ts` - Starts the relayer
145 | - `yarn watch:ts` - Watch the source code and rebuild on change
146 | - `yarn prettier:ts` - Auto-format the code
147 |
148 | Javascript project commands:
149 |
150 | - `yarn start:js` - Start the relayer
151 | - `yarn prettier:js` - Auto-format the code
152 |
153 | ## Database
154 |
155 | This project uses [TypeORM](https://github.com/typeorm/typeorm). It makes it easier for anyone to switch out the backing database used by this project. By default, this project uses an [SQLite](https://sqlite.org/docs.html) database.
156 |
157 | Because we want to support both Javascript and Typescript codebases, we don't use `TypeORM`'s [decorators](https://github.com/typeorm/typeorm/blob/master/docs/decorator-reference.md) (since they don't transpile nicely into readable Javascript). TypeORM shines with decorators however, so you might want to use them if you're going to be working in Typescript.
158 |
159 | ## Deployment
160 |
161 | `0x-launch-kit` ships as a docker container. First, install Docker ([mac](https://docs.docker.com/docker-for-mac/install/), [windows](https://docs.docker.com/docker-for-windows/install/)). To build the image run:
162 |
163 | ```sh
164 | docker build -t 0x-launch-kit-backend .
165 | ```
166 |
167 | You can check that the image was built by running:
168 |
169 | ```sh
170 | docker images
171 | ```
172 |
173 | And launch it with
174 |
175 | ```sh
176 | docker run -p 3000:3000 -d 0x-launch-kit-backend
177 | ```
178 |
179 | Check that it's working by running
180 |
181 | ```
182 | curl http://localhost:3000/v3/asset_pairs
183 | ```
184 |
185 | ## Legal Disclaimer
186 |
187 | The laws and regulations applicable to the use and exchange of digital assets and blockchain-native tokens, including through any software developed using the licensed work created by ZeroEx Intl. as described here (the “Work”), vary by jurisdiction. As set forth in the Apache License, Version 2.0 applicable to the Work, developers are “solely responsible for determining the appropriateness of using or redistributing the Work,” which includes responsibility for ensuring compliance with any such applicable laws and regulations.
188 | See the Apache License, Version 2.0 for the specific language governing all applicable permissions and limitations: http://www.apache.org/licenses/LICENSE-2.0
189 |
--------------------------------------------------------------------------------
/db/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-backend/b87060f41a475a014f19e0869f175ea8dd06a903/db/.gitkeep
--------------------------------------------------------------------------------
/js/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | // tslint:disable:custom-no-magic-numbers
4 | var assert_1 = require('@0x/assert');
5 | var utils_1 = require('@0x/utils');
6 | var crypto = require('crypto');
7 | var fs = require('fs');
8 | var _ = require('lodash');
9 | var path = require('path');
10 | var constants_1 = require('./constants');
11 | var metadataPath = path.join(__dirname, '../../metadata.json');
12 | var EnvVarType;
13 | (function(EnvVarType) {
14 | EnvVarType[(EnvVarType['Port'] = 0)] = 'Port';
15 | EnvVarType[(EnvVarType['ChainId'] = 1)] = 'ChainId';
16 | EnvVarType[(EnvVarType['FeeRecipient'] = 2)] = 'FeeRecipient';
17 | EnvVarType[(EnvVarType['UnitAmount'] = 3)] = 'UnitAmount';
18 | EnvVarType[(EnvVarType['Url'] = 4)] = 'Url';
19 | EnvVarType[(EnvVarType['WhitelistAllTokens'] = 5)] = 'WhitelistAllTokens';
20 | EnvVarType[(EnvVarType['Boolean'] = 6)] = 'Boolean';
21 | EnvVarType[(EnvVarType['FeeAssetData'] = 7)] = 'FeeAssetData';
22 | })(EnvVarType || (EnvVarType = {}));
23 | // Whitelisted token addresses. Set to a '*' instead of an array to allow all tokens.
24 | exports.WHITELISTED_TOKENS = _.isEmpty(process.env.WHITELIST_ALL_TOKENS)
25 | ? ['0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', '0xd0a1e359811322d97991e03f863a0c30c2cf029c']
26 | : assertEnvVarType('WHITELIST_ALL_TOKENS', process.env.WHITELIST_ALL_TOKENS, EnvVarType.WhitelistAllTokens);
27 | // Network port to listen on
28 | exports.HTTP_PORT = _.isEmpty(process.env.HTTP_PORT)
29 | ? 3000
30 | : assertEnvVarType('HTTP_PORT', process.env.HTTP_PORT, EnvVarType.Port);
31 | // Default chain id to use when not specified
32 | exports.CHAIN_ID = _.isEmpty(process.env.CHAIN_ID)
33 | ? 42
34 | : assertEnvVarType('CHAIN_ID', process.env.CHAIN_ID, EnvVarType.ChainId);
35 | // Mesh Endpoint
36 | exports.MESH_ENDPOINT = _.isEmpty(process.env.MESH_ENDPOINT)
37 | ? 'ws://localhost:60557'
38 | : assertEnvVarType('MESH_ENDPOINT', process.env.MESH_ENDPOINT, EnvVarType.Url);
39 | // The fee recipient for orders
40 | exports.FEE_RECIPIENT = _.isEmpty(process.env.FEE_RECIPIENT)
41 | ? getDefaultFeeRecipient()
42 | : assertEnvVarType('FEE_RECIPIENT', process.env.FEE_RECIPIENT, EnvVarType.FeeRecipient);
43 | // A flat fee that should be charged to the order maker
44 | exports.MAKER_FEE_UNIT_AMOUNT = _.isEmpty(process.env.MAKER_FEE_UNIT_AMOUNT)
45 | ? new utils_1.BigNumber(0)
46 | : assertEnvVarType('MAKER_FEE_UNIT_AMOUNT', process.env.MAKER_FEE_UNIT_AMOUNT, EnvVarType.UnitAmount);
47 | // A flat fee that should be charged to the order taker
48 | exports.TAKER_FEE_UNIT_AMOUNT = _.isEmpty(process.env.TAKER_FEE_UNIT_AMOUNT)
49 | ? new utils_1.BigNumber(0)
50 | : assertEnvVarType('TAKER_FEE_UNIT_AMOUNT', process.env.TAKER_FEE_UNIT_AMOUNT, EnvVarType.UnitAmount);
51 | // The maker fee token encoded as asset data
52 | exports.MAKER_FEE_ASSET_DATA = _.isEmpty(process.env.MAKER_FEE_ASSET_DATA)
53 | ? constants_1.NULL_BYTES
54 | : assertEnvVarType('MAKER_FEE_ASSET_DATA', process.env.MAKER_FEE_ASSET_DATA, EnvVarType.FeeAssetData);
55 | // The taker fee token encoded as asset data
56 | exports.TAKER_FEE_ASSET_DATA = _.isEmpty(process.env.TAKER_FEE_ASSET_DATA)
57 | ? constants_1.NULL_BYTES
58 | : assertEnvVarType('TAKER_FEE_ASSET_DATA', process.env.TAKER_FEE_ASSET_DATA, EnvVarType.FeeAssetData);
59 | // Max number of entities per page
60 | exports.MAX_PER_PAGE = 1000;
61 | // Default ERC20 token precision
62 | exports.DEFAULT_ERC20_TOKEN_PRECISION = 18;
63 | // Address used when simulating transfers from the maker as part of 0x order validation
64 | exports.DEFAULT_TAKER_SIMULATION_ADDRESS = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
65 | function assertEnvVarType(name, value, expectedType) {
66 | var returnValue;
67 | switch (expectedType) {
68 | case EnvVarType.Port:
69 | try {
70 | returnValue = parseInt(value, 10);
71 | var isWithinRange = returnValue >= 0 && returnValue <= 65535;
72 | if (!isWithinRange) {
73 | throw new Error();
74 | }
75 | } catch (err) {
76 | throw new Error(name + ' must be between 0 to 65535, found ' + value + '.');
77 | }
78 | return returnValue;
79 | case EnvVarType.ChainId:
80 | try {
81 | returnValue = parseInt(value, 10);
82 | } catch (err) {
83 | throw new Error(name + ' must be a valid integer, found ' + value + '.');
84 | }
85 | return returnValue;
86 | case EnvVarType.FeeRecipient:
87 | assert_1.assert.isETHAddressHex(name, value);
88 | return value;
89 | case EnvVarType.Url:
90 | assert_1.assert.isUri(name, value);
91 | return value;
92 | case EnvVarType.Boolean:
93 | return value === 'true';
94 | case EnvVarType.UnitAmount:
95 | try {
96 | returnValue = new utils_1.BigNumber(parseFloat(value));
97 | if (returnValue.isNegative()) {
98 | throw new Error();
99 | }
100 | } catch (err) {
101 | throw new Error(name + ' must be valid number greater than 0.');
102 | }
103 | return returnValue;
104 | case EnvVarType.WhitelistAllTokens:
105 | return '*';
106 | case EnvVarType.FeeAssetData:
107 | assert_1.assert.isString(name, value);
108 | return value;
109 | default:
110 | throw new Error('Unrecognised EnvVarType: ' + expectedType + ' encountered for variable ' + name + '.');
111 | }
112 | }
113 | function getDefaultFeeRecipient() {
114 | var metadata = JSON.parse(fs.readFileSync(metadataPath).toString());
115 | var existingDefault = metadata.DEFAULT_FEE_RECIPIENT;
116 | var newDefault = existingDefault || '0xabcabc' + crypto.randomBytes(17).toString('hex');
117 | if (_.isEmpty(existingDefault)) {
118 | var metadataCopy = JSON.parse(JSON.stringify(metadata));
119 | metadataCopy.DEFAULT_FEE_RECIPIENT = newDefault;
120 | fs.writeFileSync(metadataPath, JSON.stringify(metadataCopy));
121 | }
122 | return newDefault;
123 | }
124 |
--------------------------------------------------------------------------------
/js/constants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var utils_1 = require('@0x/utils');
4 | exports.NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
5 | exports.NULL_BYTES = '0x';
6 | exports.ZRX_DECIMALS = 18;
7 | exports.DEFAULT_PAGE = 1;
8 | exports.DEFAULT_PER_PAGE = 20;
9 | exports.ZERO = new utils_1.BigNumber(0);
10 | exports.MAX_TOKEN_SUPPLY_POSSIBLE = new utils_1.BigNumber(2).pow(256); // tslint:disable-line custom-no-magic-numbers
11 |
--------------------------------------------------------------------------------
/js/db_connection.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | Object.defineProperty(exports, '__esModule', { value: true });
131 | var typeorm_1 = require('typeorm');
132 | var connectionIfExists;
133 | /**
134 | * Returns the DB connnection
135 | */
136 | function getDBConnection() {
137 | if (connectionIfExists === undefined) {
138 | throw new Error('DB connection not initialized');
139 | }
140 | return connectionIfExists;
141 | }
142 | exports.getDBConnection = getDBConnection;
143 | /**
144 | * Creates the DB connnection to use in an app
145 | */
146 | function initDBConnectionAsync() {
147 | return __awaiter(this, void 0, void 0, function() {
148 | return __generator(this, function(_a) {
149 | switch (_a.label) {
150 | case 0:
151 | if (connectionIfExists !== undefined) {
152 | throw new Error('DB connection already exists');
153 | }
154 | return [4 /*yield*/, typeorm_1.createConnection()];
155 | case 1:
156 | connectionIfExists = _a.sent();
157 | return [2 /*return*/];
158 | }
159 | });
160 | });
161 | }
162 | exports.initDBConnectionAsync = initDBConnectionAsync;
163 |
--------------------------------------------------------------------------------
/js/entity/SignedOrderEntity.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var typeorm_1 = require('typeorm');
4 | var SignedOrderModel_1 = require('../models/SignedOrderModel');
5 | exports.signedOrderEntity = new typeorm_1.EntitySchema({
6 | name: 'SignedOrder',
7 | target: SignedOrderModel_1.SignedOrderModel,
8 | columns: {
9 | hash: {
10 | primary: true,
11 | type: 'varchar',
12 | },
13 | senderAddress: {
14 | type: 'varchar',
15 | },
16 | makerAddress: {
17 | type: 'varchar',
18 | },
19 | takerAddress: {
20 | type: 'varchar',
21 | },
22 | makerAssetData: {
23 | type: 'varchar',
24 | },
25 | takerAssetData: {
26 | type: 'varchar',
27 | },
28 | exchangeAddress: {
29 | type: 'varchar',
30 | },
31 | feeRecipientAddress: {
32 | type: 'varchar',
33 | },
34 | expirationTimeSeconds: {
35 | type: 'varchar',
36 | },
37 | makerFee: {
38 | type: 'varchar',
39 | },
40 | takerFee: {
41 | type: 'varchar',
42 | },
43 | makerFeeAssetData: {
44 | type: 'varchar',
45 | },
46 | takerFeeAssetData: {
47 | type: 'varchar',
48 | },
49 | makerAssetAmount: {
50 | type: 'varchar',
51 | },
52 | takerAssetAmount: {
53 | type: 'varchar',
54 | },
55 | salt: {
56 | type: 'varchar',
57 | },
58 | signature: {
59 | type: 'varchar',
60 | },
61 | remainingFillableTakerAssetAmount: {
62 | type: 'varchar',
63 | },
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/js/errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __extends =
3 | (this && this.__extends) ||
4 | (function() {
5 | var extendStatics = function(d, b) {
6 | extendStatics =
7 | Object.setPrototypeOf ||
8 | ({ __proto__: [] } instanceof Array &&
9 | function(d, b) {
10 | d.__proto__ = b;
11 | }) ||
12 | function(d, b) {
13 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
14 | };
15 | return extendStatics(d, b);
16 | };
17 | return function(d, b) {
18 | extendStatics(d, b);
19 | function __() {
20 | this.constructor = d;
21 | }
22 | d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
23 | };
24 | })();
25 | Object.defineProperty(exports, '__esModule', { value: true });
26 | var _a;
27 | // tslint:disable:max-classes-per-file
28 | var RelayerBaseError = /** @class */ (function(_super) {
29 | __extends(RelayerBaseError, _super);
30 | function RelayerBaseError() {
31 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
32 | _this.isRelayerError = true;
33 | return _this;
34 | }
35 | return RelayerBaseError;
36 | })(Error);
37 | exports.RelayerBaseError = RelayerBaseError;
38 | var BadRequestError = /** @class */ (function(_super) {
39 | __extends(BadRequestError, _super);
40 | function BadRequestError() {
41 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
42 | _this.statusCode = 400;
43 | return _this;
44 | }
45 | return BadRequestError;
46 | })(RelayerBaseError);
47 | exports.BadRequestError = BadRequestError;
48 | var ValidationError = /** @class */ (function(_super) {
49 | __extends(ValidationError, _super);
50 | function ValidationError(validationErrors) {
51 | var _this = _super.call(this) || this;
52 | _this.generalErrorCode = GeneralErrorCodes.ValidationError;
53 | _this.validationErrors = validationErrors;
54 | return _this;
55 | }
56 | return ValidationError;
57 | })(BadRequestError);
58 | exports.ValidationError = ValidationError;
59 | var MalformedJSONError = /** @class */ (function(_super) {
60 | __extends(MalformedJSONError, _super);
61 | function MalformedJSONError() {
62 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
63 | _this.generalErrorCode = GeneralErrorCodes.MalformedJson;
64 | return _this;
65 | }
66 | return MalformedJSONError;
67 | })(BadRequestError);
68 | exports.MalformedJSONError = MalformedJSONError;
69 | var TooManyRequestsError = /** @class */ (function(_super) {
70 | __extends(TooManyRequestsError, _super);
71 | function TooManyRequestsError() {
72 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
73 | _this.statusCode = 429;
74 | _this.generalErrorCode = GeneralErrorCodes.Throttled;
75 | return _this;
76 | }
77 | return TooManyRequestsError;
78 | })(BadRequestError);
79 | exports.TooManyRequestsError = TooManyRequestsError;
80 | var NotImplementedError = /** @class */ (function(_super) {
81 | __extends(NotImplementedError, _super);
82 | function NotImplementedError() {
83 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
84 | _this.statusCode = 501;
85 | _this.generalErrorCode = GeneralErrorCodes.NotImplemented;
86 | return _this;
87 | }
88 | return NotImplementedError;
89 | })(BadRequestError);
90 | exports.NotImplementedError = NotImplementedError;
91 | var NotFoundError = /** @class */ (function(_super) {
92 | __extends(NotFoundError, _super);
93 | function NotFoundError() {
94 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
95 | _this.statusCode = 404;
96 | return _this;
97 | }
98 | return NotFoundError;
99 | })(RelayerBaseError);
100 | exports.NotFoundError = NotFoundError;
101 | var InternalServerError = /** @class */ (function(_super) {
102 | __extends(InternalServerError, _super);
103 | function InternalServerError() {
104 | var _this = (_super !== null && _super.apply(this, arguments)) || this;
105 | _this.statusCode = 500;
106 | return _this;
107 | }
108 | return InternalServerError;
109 | })(RelayerBaseError);
110 | exports.InternalServerError = InternalServerError;
111 | var GeneralErrorCodes;
112 | (function(GeneralErrorCodes) {
113 | GeneralErrorCodes[(GeneralErrorCodes['ValidationError'] = 100)] = 'ValidationError';
114 | GeneralErrorCodes[(GeneralErrorCodes['MalformedJson'] = 101)] = 'MalformedJson';
115 | GeneralErrorCodes[(GeneralErrorCodes['OrderSubmissionDisabled'] = 102)] = 'OrderSubmissionDisabled';
116 | GeneralErrorCodes[(GeneralErrorCodes['Throttled'] = 103)] = 'Throttled';
117 | GeneralErrorCodes[(GeneralErrorCodes['NotImplemented'] = 104)] = 'NotImplemented';
118 | })((GeneralErrorCodes = exports.GeneralErrorCodes || (exports.GeneralErrorCodes = {})));
119 | exports.generalErrorCodeToReason =
120 | ((_a = {}),
121 | (_a[GeneralErrorCodes.ValidationError] = 'Validation Failed'),
122 | (_a[GeneralErrorCodes.MalformedJson] = 'Malformed JSON'),
123 | (_a[GeneralErrorCodes.OrderSubmissionDisabled] = 'Order submission disabled'),
124 | (_a[GeneralErrorCodes.Throttled] = 'Throttled'),
125 | (_a[GeneralErrorCodes.NotImplemented] = 'Not Implemented'),
126 | _a);
127 | var ValidationErrorCodes;
128 | (function(ValidationErrorCodes) {
129 | ValidationErrorCodes[(ValidationErrorCodes['RequiredField'] = 1000)] = 'RequiredField';
130 | ValidationErrorCodes[(ValidationErrorCodes['IncorrectFormat'] = 1001)] = 'IncorrectFormat';
131 | ValidationErrorCodes[(ValidationErrorCodes['InvalidAddress'] = 1002)] = 'InvalidAddress';
132 | ValidationErrorCodes[(ValidationErrorCodes['AddressNotSupported'] = 1003)] = 'AddressNotSupported';
133 | ValidationErrorCodes[(ValidationErrorCodes['ValueOutOfRange'] = 1004)] = 'ValueOutOfRange';
134 | ValidationErrorCodes[(ValidationErrorCodes['InvalidSignatureOrHash'] = 1005)] = 'InvalidSignatureOrHash';
135 | ValidationErrorCodes[(ValidationErrorCodes['UnsupportedOption'] = 1006)] = 'UnsupportedOption';
136 | ValidationErrorCodes[(ValidationErrorCodes['InvalidOrder'] = 1007)] = 'InvalidOrder';
137 | ValidationErrorCodes[(ValidationErrorCodes['InternalError'] = 1008)] = 'InternalError';
138 | })((ValidationErrorCodes = exports.ValidationErrorCodes || (exports.ValidationErrorCodes = {})));
139 |
--------------------------------------------------------------------------------
/js/fee_strategy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var config_1 = require('./config');
4 | var constants_1 = require('./constants');
5 | exports.fixedFeeStrategy = {
6 | getOrderConfig: function(_order) {
7 | var normalizedFeeRecipient = config_1.FEE_RECIPIENT.toLowerCase();
8 | var orderConfigResponse = {
9 | senderAddress: constants_1.NULL_ADDRESS,
10 | feeRecipientAddress: normalizedFeeRecipient,
11 | makerFee: config_1.MAKER_FEE_UNIT_AMOUNT,
12 | takerFee: config_1.TAKER_FEE_UNIT_AMOUNT,
13 | makerFeeAssetData: config_1.MAKER_FEE_ASSET_DATA,
14 | takerFeeAssetData: config_1.TAKER_FEE_ASSET_DATA,
15 | };
16 | return orderConfigResponse;
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var _this = this;
131 | Object.defineProperty(exports, '__esModule', { value: true });
132 | var mesh_rpc_client_1 = require('@0x/mesh-rpc-client');
133 | var express = require('express');
134 | require('reflect-metadata');
135 | var config = require('./config');
136 | var db_connection_1 = require('./db_connection');
137 | var http_service_1 = require('./services/http_service');
138 | var order_watcher_service_1 = require('./services/order_watcher_service');
139 | var orderbook_service_1 = require('./services/orderbook_service');
140 | var websocket_service_1 = require('./services/websocket_service');
141 | var utils_1 = require('./utils');
142 | (function() {
143 | return __awaiter(_this, void 0, void 0, function() {
144 | var app, server, meshClient, orderWatcherService, orderBookService;
145 | var _this = this;
146 | return __generator(this, function(_a) {
147 | switch (_a.label) {
148 | case 0:
149 | return [4 /*yield*/, db_connection_1.initDBConnectionAsync()];
150 | case 1:
151 | _a.sent();
152 | app = express();
153 | server = app.listen(config.HTTP_PORT, function() {
154 | utils_1.utils.log(
155 | 'Standard relayer API (HTTP) listening on port ' +
156 | config.HTTP_PORT +
157 | '!\nConfig: ' +
158 | JSON.stringify(config, null, 2),
159 | );
160 | });
161 | return [
162 | 4 /*yield*/,
163 | utils_1.utils.attemptAsync(
164 | function() {
165 | return __awaiter(_this, void 0, void 0, function() {
166 | return __generator(this, function(_a) {
167 | switch (_a.label) {
168 | case 0:
169 | meshClient = new mesh_rpc_client_1.WSClient(config.MESH_ENDPOINT);
170 | return [4 /*yield*/, meshClient.getStatsAsync()];
171 | case 1:
172 | _a.sent();
173 | return [2 /*return*/];
174 | }
175 | });
176 | });
177 | },
178 | { interval: 3000, maxRetries: 10 },
179 | ),
180 | ];
181 | case 2:
182 | _a.sent();
183 | if (!meshClient) {
184 | throw new Error('Unable to establish connection to Mesh');
185 | }
186 | utils_1.utils.log('Connected to Mesh');
187 | orderWatcherService = new order_watcher_service_1.OrderWatcherService(meshClient);
188 | return [4 /*yield*/, orderWatcherService.syncOrderbookAsync()];
189 | case 3:
190 | _a.sent();
191 | // tslint:disable-next-line:no-unused-expression
192 | new websocket_service_1.WebsocketService(server, meshClient);
193 | orderBookService = new orderbook_service_1.OrderBookService(meshClient);
194 | // tslint:disable-next-line:no-unused-expression
195 | new http_service_1.HttpService(app, orderBookService);
196 | return [2 /*return*/];
197 | }
198 | });
199 | });
200 | })().catch(utils_1.utils.log);
201 |
--------------------------------------------------------------------------------
/js/mesh_utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var __values =
131 | (this && this.__values) ||
132 | function(o) {
133 | var m = typeof Symbol === 'function' && o[Symbol.iterator],
134 | i = 0;
135 | if (m) return m.call(o);
136 | return {
137 | next: function() {
138 | if (o && i >= o.length) o = void 0;
139 | return { value: o && o[i++], done: !o };
140 | },
141 | };
142 | };
143 | var __read =
144 | (this && this.__read) ||
145 | function(o, n) {
146 | var m = typeof Symbol === 'function' && o[Symbol.iterator];
147 | if (!m) return o;
148 | var i = m.call(o),
149 | r,
150 | ar = [],
151 | e;
152 | try {
153 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
154 | } catch (error) {
155 | e = { error: error };
156 | } finally {
157 | try {
158 | if (r && !r.done && (m = i['return'])) m.call(i);
159 | } finally {
160 | if (e) throw e.error;
161 | }
162 | }
163 | return ar;
164 | };
165 | var __spread =
166 | (this && this.__spread) ||
167 | function() {
168 | for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
169 | return ar;
170 | };
171 | Object.defineProperty(exports, '__esModule', { value: true });
172 | var mesh_rpc_client_1 = require('@0x/mesh-rpc-client');
173 | var _ = require('lodash');
174 | var constants_1 = require('./constants');
175 | var errors_1 = require('./errors');
176 | // tslint:disable-next-line:no-var-requires
177 | var d = require('debug')('MESH');
178 | // tslint:disable-next-line:no-unnecessary-class
179 | var MeshUtils = /** @class */ (function() {
180 | function MeshUtils() {}
181 | MeshUtils.addOrdersToMeshAsync = function(meshClient, orders, batchSize) {
182 | if (batchSize === void 0) {
183 | batchSize = 100;
184 | }
185 | return __awaiter(this, void 0, void 0, function() {
186 | var e_1, _a, validationResults, chunks, chunks_1, chunks_1_1, chunk, results, e_1_1;
187 | return __generator(this, function(_b) {
188 | switch (_b.label) {
189 | case 0:
190 | validationResults = { accepted: [], rejected: [] };
191 | chunks = _.chunk(orders, batchSize);
192 | _b.label = 1;
193 | case 1:
194 | _b.trys.push([1, 6, 7, 8]);
195 | (chunks_1 = __values(chunks)), (chunks_1_1 = chunks_1.next());
196 | _b.label = 2;
197 | case 2:
198 | if (!!chunks_1_1.done) return [3 /*break*/, 5];
199 | chunk = chunks_1_1.value;
200 | return [4 /*yield*/, meshClient.addOrdersAsync(chunk, true)];
201 | case 3:
202 | results = _b.sent();
203 | validationResults.accepted = __spread(validationResults.accepted, results.accepted);
204 | validationResults.rejected = __spread(validationResults.rejected, results.rejected);
205 | _b.label = 4;
206 | case 4:
207 | chunks_1_1 = chunks_1.next();
208 | return [3 /*break*/, 2];
209 | case 5:
210 | return [3 /*break*/, 8];
211 | case 6:
212 | e_1_1 = _b.sent();
213 | e_1 = { error: e_1_1 };
214 | return [3 /*break*/, 8];
215 | case 7:
216 | try {
217 | if (chunks_1_1 && !chunks_1_1.done && (_a = chunks_1.return)) _a.call(chunks_1);
218 | } finally {
219 | if (e_1) throw e_1.error;
220 | }
221 | return [7 /*endfinally*/];
222 | case 8:
223 | return [2 /*return*/, validationResults];
224 | }
225 | });
226 | });
227 | };
228 | MeshUtils.orderInfosToApiOrders = function(orderEvent) {
229 | return orderEvent.map(function(e) {
230 | return MeshUtils.orderInfoToAPIOrder(e);
231 | });
232 | };
233 | MeshUtils.orderInfoToAPIOrder = function(orderEvent) {
234 | var remainingFillableTakerAssetAmount = orderEvent.fillableTakerAssetAmount
235 | ? orderEvent.fillableTakerAssetAmount
236 | : constants_1.ZERO;
237 | return {
238 | order: orderEvent.signedOrder,
239 | metaData: {
240 | orderHash: orderEvent.orderHash,
241 | remainingFillableTakerAssetAmount: remainingFillableTakerAssetAmount,
242 | },
243 | };
244 | };
245 | MeshUtils.rejectedCodeToSRACode = function(code) {
246 | switch (code) {
247 | case mesh_rpc_client_1.RejectedCode.OrderCancelled:
248 | case mesh_rpc_client_1.RejectedCode.OrderExpired:
249 | case mesh_rpc_client_1.RejectedCode.OrderUnfunded:
250 | case mesh_rpc_client_1.RejectedCode.OrderHasInvalidMakerAssetAmount:
251 | case mesh_rpc_client_1.RejectedCode.OrderHasInvalidMakerAssetData:
252 | case mesh_rpc_client_1.RejectedCode.OrderHasInvalidTakerAssetAmount:
253 | case mesh_rpc_client_1.RejectedCode.OrderHasInvalidTakerAssetData:
254 | case mesh_rpc_client_1.RejectedCode.OrderFullyFilled: {
255 | return errors_1.ValidationErrorCodes.InvalidOrder;
256 | }
257 | case mesh_rpc_client_1.RejectedCode.OrderHasInvalidSignature: {
258 | return errors_1.ValidationErrorCodes.InvalidSignatureOrHash;
259 | }
260 | case mesh_rpc_client_1.RejectedCode.OrderForIncorrectChain: {
261 | return errors_1.ValidationErrorCodes.InvalidAddress;
262 | }
263 | default:
264 | return errors_1.ValidationErrorCodes.InternalError;
265 | }
266 | };
267 | MeshUtils.calculateAddedRemovedUpdated = function(orderEvents) {
268 | var e_2, _a;
269 | var added = [];
270 | var removed = [];
271 | var updated = [];
272 | try {
273 | for (
274 | var orderEvents_1 = __values(orderEvents), orderEvents_1_1 = orderEvents_1.next();
275 | !orderEvents_1_1.done;
276 | orderEvents_1_1 = orderEvents_1.next()
277 | ) {
278 | var event_1 = orderEvents_1_1.value;
279 | var apiOrder = MeshUtils.orderInfoToAPIOrder(event_1);
280 | switch (event_1.endState) {
281 | case mesh_rpc_client_1.OrderEventEndState.Added: {
282 | added.push(apiOrder);
283 | break;
284 | }
285 | case mesh_rpc_client_1.OrderEventEndState.Cancelled:
286 | case mesh_rpc_client_1.OrderEventEndState.Expired:
287 | case mesh_rpc_client_1.OrderEventEndState.FullyFilled:
288 | case mesh_rpc_client_1.OrderEventEndState.Unfunded: {
289 | removed.push(apiOrder);
290 | break;
291 | }
292 | case mesh_rpc_client_1.OrderEventEndState.FillabilityIncreased:
293 | case mesh_rpc_client_1.OrderEventEndState.Filled: {
294 | updated.push(apiOrder);
295 | break;
296 | }
297 | default:
298 | d('Unknown Event', event_1.endState, event_1);
299 | break;
300 | }
301 | }
302 | } catch (e_2_1) {
303 | e_2 = { error: e_2_1 };
304 | } finally {
305 | try {
306 | if (orderEvents_1_1 && !orderEvents_1_1.done && (_a = orderEvents_1.return)) _a.call(orderEvents_1);
307 | } finally {
308 | if (e_2) throw e_2.error;
309 | }
310 | }
311 | return { added: added, removed: removed, updated: updated };
312 | };
313 | return MeshUtils;
314 | })();
315 | exports.MeshUtils = MeshUtils;
316 |
--------------------------------------------------------------------------------
/js/middleware/error_handling.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var HttpStatus = require('http-status-codes');
4 | var errors_1 = require('../errors');
5 | /**
6 | * Wraps an Error with a JSON human readable reason and status code.
7 | */
8 | function generateError(err) {
9 | if (err.isRelayerError) {
10 | var relayerError = err;
11 | var statusCode = relayerError.statusCode;
12 | if (relayerError.statusCode === HttpStatus.BAD_REQUEST) {
13 | var badRequestError = relayerError;
14 | if (badRequestError.generalErrorCode === errors_1.GeneralErrorCodes.ValidationError) {
15 | var validationError = badRequestError;
16 | return {
17 | statusCode: statusCode,
18 | errorBody: {
19 | code: badRequestError.generalErrorCode,
20 | reason: errors_1.generalErrorCodeToReason[badRequestError.generalErrorCode],
21 | validationErrors: validationError.validationErrors,
22 | },
23 | };
24 | } else {
25 | return {
26 | statusCode: statusCode,
27 | errorBody: {
28 | code: badRequestError.generalErrorCode,
29 | reason: errors_1.generalErrorCodeToReason[badRequestError.generalErrorCode],
30 | },
31 | };
32 | }
33 | } else {
34 | return {
35 | statusCode: statusCode,
36 | errorBody: {
37 | reason: HttpStatus.getStatusText(relayerError.statusCode),
38 | },
39 | };
40 | }
41 | }
42 | return {
43 | statusCode: HttpStatus.BAD_REQUEST,
44 | errorBody: {
45 | reason: err.message,
46 | },
47 | };
48 | }
49 | exports.generateError = generateError;
50 | /**
51 | * Catches errors thrown by our code and serialies them
52 | */
53 | function errorHandler(err, _req, res, next) {
54 | // If you call next() with an error after you have started writing the response
55 | // (for example, if you encounter an error while streaming the response to the client)
56 | // the Express default error handler closes the connection and fails the request.
57 | if (res.headersSent) {
58 | return next(err);
59 | }
60 | if (err.isRelayerError || err.statusCode) {
61 | var _a = generateError(err),
62 | statusCode = _a.statusCode,
63 | errorBody = _a.errorBody;
64 | res.status(statusCode).send(errorBody);
65 | return;
66 | } else {
67 | return next(err);
68 | }
69 | }
70 | exports.errorHandler = errorHandler;
71 |
--------------------------------------------------------------------------------
/js/middleware/url_params_parsing.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var _ = require('lodash');
4 | var config_1 = require('../config');
5 | var errors_1 = require('../errors');
6 | /**
7 | * Parses URL params and stores them on the request object
8 | */
9 | function urlParamsParsing(req, _res, next) {
10 | var chainId = parseChainId(req.query.chainId);
11 | // HACK: This is the recommended way to pass data from middlewares on. It's not beautiful nor fully type-safe.
12 | req.chainId = chainId;
13 | next();
14 | }
15 | exports.urlParamsParsing = urlParamsParsing;
16 | function parseChainId(chainIdStrIfExists) {
17 | if (chainIdStrIfExists === undefined) {
18 | return config_1.CHAIN_ID;
19 | } else {
20 | var chainId = _.parseInt(chainIdStrIfExists);
21 | if (chainId !== config_1.CHAIN_ID) {
22 | var validationErrorItem = {
23 | field: 'chainId',
24 | code: 1004,
25 | reason: 'Incorrect Chain ID: ' + chainIdStrIfExists,
26 | };
27 | throw new errors_1.ValidationError([validationErrorItem]);
28 | }
29 | return chainId;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/js/models/SignedOrderModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var SignedOrderModel = /** @class */ (function() {
4 | function SignedOrderModel(opts) {
5 | if (opts === void 0) {
6 | opts = {};
7 | }
8 | this.hash = opts.hash;
9 | this.senderAddress = opts.senderAddress;
10 | this.makerAddress = opts.makerAddress;
11 | this.takerAddress = opts.takerAddress;
12 | this.makerAssetData = opts.makerAssetData;
13 | this.takerAssetData = opts.takerAssetData;
14 | this.exchangeAddress = opts.exchangeAddress;
15 | this.feeRecipientAddress = opts.feeRecipientAddress;
16 | this.expirationTimeSeconds = opts.expirationTimeSeconds;
17 | this.makerFee = opts.makerFee;
18 | this.takerFee = opts.takerFee;
19 | this.makerFeeAssetData = opts.makerFeeAssetData;
20 | this.takerFeeAssetData = opts.takerFeeAssetData;
21 | this.makerAssetAmount = opts.makerAssetAmount;
22 | this.takerAssetAmount = opts.takerAssetAmount;
23 | this.salt = opts.salt;
24 | this.signature = opts.signature;
25 | this.remainingFillableTakerAssetAmount = opts.remainingFillableTakerAssetAmount;
26 | }
27 | return SignedOrderModel;
28 | })();
29 | exports.SignedOrderModel = SignedOrderModel;
30 |
--------------------------------------------------------------------------------
/js/paginator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | exports.paginate = function(collection, page, perPage) {
4 | var paginatedCollection = {
5 | total: collection.length,
6 | page: page,
7 | perPage: perPage,
8 | records: collection.slice((page - 1) * perPage, page * perPage),
9 | };
10 | return paginatedCollection;
11 | };
12 |
--------------------------------------------------------------------------------
/js/runners/http_service_runner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var _this = this;
131 | Object.defineProperty(exports, '__esModule', { value: true });
132 | var mesh_rpc_client_1 = require('@0x/mesh-rpc-client');
133 | var express = require('express');
134 | require('reflect-metadata');
135 | var config = require('../config');
136 | var db_connection_1 = require('../db_connection');
137 | var http_service_1 = require('../services/http_service');
138 | var orderbook_service_1 = require('../services/orderbook_service');
139 | var utils_1 = require('../utils');
140 | /**
141 | * This service handles the HTTP requests. This involves fetching from the database
142 | * as well as adding orders to mesh.
143 | */
144 | (function() {
145 | return __awaiter(_this, void 0, void 0, function() {
146 | var app, meshClient, orderBookService;
147 | return __generator(this, function(_a) {
148 | switch (_a.label) {
149 | case 0:
150 | return [4 /*yield*/, db_connection_1.initDBConnectionAsync()];
151 | case 1:
152 | _a.sent();
153 | app = express();
154 | app.listen(config.HTTP_PORT, function() {
155 | utils_1.utils.log(
156 | 'Standard relayer API (HTTP) listening on port ' +
157 | config.HTTP_PORT +
158 | '!\nConfig: ' +
159 | JSON.stringify(config, null, 2),
160 | );
161 | });
162 | meshClient = new mesh_rpc_client_1.WSClient(config.MESH_ENDPOINT);
163 | orderBookService = new orderbook_service_1.OrderBookService(meshClient);
164 | // tslint:disable-next-line:no-unused-expression
165 | new http_service_1.HttpService(app, orderBookService);
166 | return [2 /*return*/];
167 | }
168 | });
169 | });
170 | })().catch(utils_1.utils.log);
171 |
--------------------------------------------------------------------------------
/js/runners/order_watcher_service_runner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var _this = this;
131 | Object.defineProperty(exports, '__esModule', { value: true });
132 | var mesh_rpc_client_1 = require('@0x/mesh-rpc-client');
133 | require('reflect-metadata');
134 | var config = require('../config');
135 | var db_connection_1 = require('../db_connection');
136 | var order_watcher_service_1 = require('../services/order_watcher_service');
137 | var utils_1 = require('../utils');
138 | /**
139 | * This service is a simple writer from the Mesh events. On order discovery
140 | * or an order update it will be persisted to the database. It also is responsible
141 | * for syncing the database with Mesh on start or after a disconnect.
142 | */
143 | (function() {
144 | return __awaiter(_this, void 0, void 0, function() {
145 | var meshClient, orderWatcherService;
146 | return __generator(this, function(_a) {
147 | switch (_a.label) {
148 | case 0:
149 | return [4 /*yield*/, db_connection_1.initDBConnectionAsync()];
150 | case 1:
151 | _a.sent();
152 | utils_1.utils.log('Order Watching Service started!\nConfig: ' + JSON.stringify(config, null, 2));
153 | meshClient = new mesh_rpc_client_1.WSClient(config.MESH_ENDPOINT);
154 | orderWatcherService = new order_watcher_service_1.OrderWatcherService(meshClient);
155 | return [4 /*yield*/, orderWatcherService.syncOrderbookAsync()];
156 | case 2:
157 | _a.sent();
158 | return [2 /*return*/];
159 | }
160 | });
161 | });
162 | })().catch(utils_1.utils.log);
163 |
--------------------------------------------------------------------------------
/js/runners/websocket_service_runner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var _this = this;
131 | Object.defineProperty(exports, '__esModule', { value: true });
132 | var mesh_rpc_client_1 = require('@0x/mesh-rpc-client');
133 | var express = require('express');
134 | require('reflect-metadata');
135 | var config = require('../config');
136 | var db_connection_1 = require('../db_connection');
137 | var websocket_service_1 = require('../services/websocket_service');
138 | var utils_1 = require('../utils');
139 | /**
140 | * This service handles websocket updates using a subscription from Mesh.
141 | */
142 | (function() {
143 | return __awaiter(_this, void 0, void 0, function() {
144 | var app, server, meshClient;
145 | return __generator(this, function(_a) {
146 | switch (_a.label) {
147 | case 0:
148 | return [4 /*yield*/, db_connection_1.initDBConnectionAsync()];
149 | case 1:
150 | _a.sent();
151 | app = express();
152 | server = app.listen(config.HTTP_PORT, function() {
153 | utils_1.utils.log(
154 | 'Standard relayer API (WS) listening on port ' +
155 | config.HTTP_PORT +
156 | '!\nConfig: ' +
157 | JSON.stringify(config, null, 2),
158 | );
159 | });
160 | meshClient = new mesh_rpc_client_1.WSClient(config.MESH_ENDPOINT);
161 | // tslint:disable-next-line:no-unused-expression
162 | new websocket_service_1.WebsocketService(server, meshClient);
163 | return [2 /*return*/];
164 | }
165 | });
166 | });
167 | })().catch(utils_1.utils.log);
168 |
--------------------------------------------------------------------------------
/js/services/http_service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var bodyParser = require('body-parser');
4 | var cors = require('cors');
5 | var asyncHandler = require('express-async-handler');
6 | require('reflect-metadata');
7 | var handlers_1 = require('../handlers');
8 | var error_handling_1 = require('../middleware/error_handling');
9 | var url_params_parsing_1 = require('../middleware/url_params_parsing');
10 | // tslint:disable-next-line:no-unnecessary-class
11 | var HttpService = /** @class */ (function() {
12 | function HttpService(app, orderBook) {
13 | var handlers = new handlers_1.Handlers(orderBook);
14 | app.use(cors());
15 | app.use(bodyParser.json());
16 | app.use(url_params_parsing_1.urlParamsParsing);
17 | /**
18 | * GET AssetPairs endpoint retrieves a list of available asset pairs and the information required to trade them.
19 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getAssetPairs
20 | */
21 | app.get('/v3/asset_pairs', asyncHandler(handlers_1.Handlers.assetPairsAsync.bind(handlers_1.Handlers)));
22 | /**
23 | * GET Orders endpoint retrieves a list of orders given query parameters.
24 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrders
25 | */
26 | app.get('/v3/orders', asyncHandler(handlers.ordersAsync.bind(handlers)));
27 | /**
28 | * GET Orderbook endpoint retrieves the orderbook for a given asset pair.
29 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrderbook
30 | */
31 | app.get('/v3/orderbook', asyncHandler(handlers.orderbookAsync.bind(handlers)));
32 | /**
33 | * GET FeeRecepients endpoint retrieves a collection of all fee recipient addresses for a relayer.
34 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/v3/fee_recipients
35 | */
36 | app.get('/v3/fee_recipients', handlers_1.Handlers.feeRecipients.bind(handlers_1.Handlers));
37 | /**
38 | * POST Order config endpoint retrives the values for order fields that the relayer requires.
39 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrderConfig
40 | */
41 | app.post('/v3/order_config', handlers_1.Handlers.orderConfig.bind(handlers_1.Handlers));
42 | /**
43 | * POST Order endpoint submits an order to the Relayer.
44 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/postOrder
45 | */
46 | app.post('/v3/order', asyncHandler(handlers.postOrderAsync.bind(handlers)));
47 | /**
48 | * GET Order endpoint retrieves the order by order hash.
49 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrder
50 | */
51 | app.get(
52 | '/v3/order/:orderHash',
53 | asyncHandler(handlers_1.Handlers.getOrderByHashAsync.bind(handlers_1.Handlers)),
54 | );
55 | app.use(error_handling_1.errorHandler);
56 | }
57 | return HttpService;
58 | })();
59 | exports.HttpService = HttpService;
60 |
--------------------------------------------------------------------------------
/js/services/order_watcher_service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var __values =
131 | (this && this.__values) ||
132 | function(o) {
133 | var m = typeof Symbol === 'function' && o[Symbol.iterator],
134 | i = 0;
135 | if (m) return m.call(o);
136 | return {
137 | next: function() {
138 | if (o && i >= o.length) o = void 0;
139 | return { value: o && o[i++], done: !o };
140 | },
141 | };
142 | };
143 | Object.defineProperty(exports, '__esModule', { value: true });
144 | var _ = require('lodash');
145 | var db_connection_1 = require('../db_connection');
146 | var mesh_utils_1 = require('../mesh_utils');
147 | var SignedOrderModel_1 = require('../models/SignedOrderModel');
148 | var types_1 = require('../types');
149 | var orderbook_utils_1 = require('./orderbook_utils');
150 | // tslint:disable-next-line:no-var-requires
151 | var d = require('debug')('orderbook');
152 | var OrderWatcherService = /** @class */ (function() {
153 | function OrderWatcherService(meshClient) {
154 | var _this = this;
155 | this._meshClient = meshClient;
156 | void this._meshClient.subscribeToOrdersAsync(function(orders) {
157 | return __awaiter(_this, void 0, void 0, function() {
158 | var _a, added, removed, updated;
159 | return __generator(this, function(_b) {
160 | switch (_b.label) {
161 | case 0:
162 | (_a = mesh_utils_1.MeshUtils.calculateAddedRemovedUpdated(orders)),
163 | (added = _a.added),
164 | (removed = _a.removed),
165 | (updated = _a.updated);
166 | return [
167 | 4 /*yield*/,
168 | OrderWatcherService._onOrderLifeCycleEventAsync(
169 | types_1.OrderWatcherLifeCycleEvents.Removed,
170 | removed,
171 | ),
172 | ];
173 | case 1:
174 | _b.sent();
175 | return [
176 | 4 /*yield*/,
177 | OrderWatcherService._onOrderLifeCycleEventAsync(
178 | types_1.OrderWatcherLifeCycleEvents.Updated,
179 | updated,
180 | ),
181 | ];
182 | case 2:
183 | _b.sent();
184 | return [
185 | 4 /*yield*/,
186 | OrderWatcherService._onOrderLifeCycleEventAsync(
187 | types_1.OrderWatcherLifeCycleEvents.Added,
188 | added,
189 | ),
190 | ];
191 | case 3:
192 | _b.sent();
193 | return [2 /*return*/];
194 | }
195 | });
196 | });
197 | });
198 | this._meshClient.onReconnected(function() {
199 | return __awaiter(_this, void 0, void 0, function() {
200 | return __generator(this, function(_a) {
201 | switch (_a.label) {
202 | case 0:
203 | d('Reconnecting to Mesh');
204 | return [4 /*yield*/, this.syncOrderbookAsync()];
205 | case 1:
206 | _a.sent();
207 | return [2 /*return*/];
208 | }
209 | });
210 | });
211 | });
212 | }
213 | OrderWatcherService._onOrderLifeCycleEventAsync = function(lifecycleEvent, orders) {
214 | return __awaiter(this, void 0, void 0, function() {
215 | var e_1, _a, connection, _b, signedOrdersModel, orderHashes, chunks, chunks_1, chunks_1_1, chunk, e_1_1;
216 | return __generator(this, function(_c) {
217 | switch (_c.label) {
218 | case 0:
219 | if (orders.length <= 0) {
220 | return [2 /*return*/];
221 | }
222 | connection = db_connection_1.getDBConnection();
223 | _b = lifecycleEvent;
224 | switch (_b) {
225 | case types_1.OrderWatcherLifeCycleEvents.Updated:
226 | return [3 /*break*/, 1];
227 | case types_1.OrderWatcherLifeCycleEvents.Added:
228 | return [3 /*break*/, 1];
229 | case types_1.OrderWatcherLifeCycleEvents.Removed:
230 | return [3 /*break*/, 3];
231 | }
232 | return [3 /*break*/, 12];
233 | case 1:
234 | signedOrdersModel = orders.map(function(o) {
235 | return orderbook_utils_1.serializeOrder(o);
236 | });
237 | // MAX SQL variable size is 999. This limit is imposed via Sqlite.
238 | // The SELECT query is not entirely effecient and pulls in all attributes
239 | // so we need to leave space for the attributes on the model represented
240 | // as SQL variables in the "AS" syntax. We leave 99 free for the
241 | // signedOrders model
242 | return [4 /*yield*/, connection.manager.save(signedOrdersModel, { chunk: 900 })];
243 | case 2:
244 | // MAX SQL variable size is 999. This limit is imposed via Sqlite.
245 | // The SELECT query is not entirely effecient and pulls in all attributes
246 | // so we need to leave space for the attributes on the model represented
247 | // as SQL variables in the "AS" syntax. We leave 99 free for the
248 | // signedOrders model
249 | _c.sent();
250 | return [3 /*break*/, 12];
251 | case 3:
252 | orderHashes = orders.map(function(o) {
253 | return o.metaData.orderHash;
254 | });
255 | chunks = _.chunk(orderHashes, 999);
256 | _c.label = 4;
257 | case 4:
258 | _c.trys.push([4, 9, 10, 11]);
259 | (chunks_1 = __values(chunks)), (chunks_1_1 = chunks_1.next());
260 | _c.label = 5;
261 | case 5:
262 | if (!!chunks_1_1.done) return [3 /*break*/, 8];
263 | chunk = chunks_1_1.value;
264 | return [4 /*yield*/, connection.manager.delete(SignedOrderModel_1.SignedOrderModel, chunk)];
265 | case 6:
266 | _c.sent();
267 | _c.label = 7;
268 | case 7:
269 | chunks_1_1 = chunks_1.next();
270 | return [3 /*break*/, 5];
271 | case 8:
272 | return [3 /*break*/, 11];
273 | case 9:
274 | e_1_1 = _c.sent();
275 | e_1 = { error: e_1_1 };
276 | return [3 /*break*/, 11];
277 | case 10:
278 | try {
279 | if (chunks_1_1 && !chunks_1_1.done && (_a = chunks_1.return)) _a.call(chunks_1);
280 | } finally {
281 | if (e_1) throw e_1.error;
282 | }
283 | return [7 /*endfinally*/];
284 | case 11:
285 | return [3 /*break*/, 12];
286 | case 12:
287 | return [2 /*return*/];
288 | }
289 | });
290 | });
291 | };
292 | OrderWatcherService.prototype.syncOrderbookAsync = function() {
293 | return __awaiter(this, void 0, void 0, function() {
294 | var connection, signedOrderModels, signedOrders, orders, _a, accepted, rejected;
295 | return __generator(this, function(_b) {
296 | switch (_b.label) {
297 | case 0:
298 | d('SYNC orderbook with Mesh');
299 | connection = db_connection_1.getDBConnection();
300 | return [4 /*yield*/, connection.manager.find(SignedOrderModel_1.SignedOrderModel)];
301 | case 1:
302 | signedOrderModels = _b.sent();
303 | signedOrders = signedOrderModels.map(orderbook_utils_1.deserializeOrder);
304 | return [4 /*yield*/, this._meshClient.getOrdersAsync()];
305 | case 2:
306 | orders = _b.sent();
307 | return [
308 | 4 /*yield*/,
309 | mesh_utils_1.MeshUtils.addOrdersToMeshAsync(this._meshClient, signedOrders),
310 | ];
311 | case 3:
312 | (_a = _b.sent()), (accepted = _a.accepted), (rejected = _a.rejected);
313 | d(
314 | 'SYNC ' +
315 | rejected.length +
316 | ' rejected ' +
317 | accepted.length +
318 | ' accepted ' +
319 | signedOrders.length +
320 | ' sent',
321 | );
322 | if (!(rejected.length > 0)) return [3 /*break*/, 5];
323 | return [
324 | 4 /*yield*/,
325 | OrderWatcherService._onOrderLifeCycleEventAsync(
326 | types_1.OrderWatcherLifeCycleEvents.Removed,
327 | mesh_utils_1.MeshUtils.orderInfosToApiOrders(rejected),
328 | ),
329 | ];
330 | case 4:
331 | _b.sent();
332 | _b.label = 5;
333 | case 5:
334 | if (!(orders.length > 0)) return [3 /*break*/, 7];
335 | return [
336 | 4 /*yield*/,
337 | OrderWatcherService._onOrderLifeCycleEventAsync(
338 | types_1.OrderWatcherLifeCycleEvents.Added,
339 | mesh_utils_1.MeshUtils.orderInfosToApiOrders(orders),
340 | ),
341 | ];
342 | case 6:
343 | _b.sent();
344 | _b.label = 7;
345 | case 7:
346 | d('SYNC complete');
347 | return [2 /*return*/];
348 | }
349 | });
350 | });
351 | };
352 | return OrderWatcherService;
353 | })();
354 | exports.OrderWatcherService = OrderWatcherService;
355 |
--------------------------------------------------------------------------------
/js/services/orderbook_utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __assign =
3 | (this && this.__assign) ||
4 | function() {
5 | __assign =
6 | Object.assign ||
7 | function(t) {
8 | for (var s, i = 1, n = arguments.length; i < n; i++) {
9 | s = arguments[i];
10 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
11 | }
12 | return t;
13 | };
14 | return __assign.apply(this, arguments);
15 | };
16 | var __values =
17 | (this && this.__values) ||
18 | function(o) {
19 | var m = typeof Symbol === 'function' && o[Symbol.iterator],
20 | i = 0;
21 | if (m) return m.call(o);
22 | return {
23 | next: function() {
24 | if (o && i >= o.length) o = void 0;
25 | return { value: o && o[i++], done: !o };
26 | },
27 | };
28 | };
29 | var __read =
30 | (this && this.__read) ||
31 | function(o, n) {
32 | var m = typeof Symbol === 'function' && o[Symbol.iterator];
33 | if (!m) return o;
34 | var i = m.call(o),
35 | r,
36 | ar = [],
37 | e;
38 | try {
39 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
40 | } catch (error) {
41 | e = { error: error };
42 | } finally {
43 | try {
44 | if (r && !r.done && (m = i['return'])) m.call(i);
45 | } finally {
46 | if (e) throw e.error;
47 | }
48 | }
49 | return ar;
50 | };
51 | Object.defineProperty(exports, '__esModule', { value: true });
52 | var order_utils_1 = require('@0x/order-utils');
53 | var types_1 = require('@0x/types');
54 | var utils_1 = require('@0x/utils');
55 | var config_1 = require('../config');
56 | var constants_1 = require('../constants');
57 | var SignedOrderModel_1 = require('../models/SignedOrderModel');
58 | var DEFAULT_ERC721_ASSET = {
59 | minAmount: new utils_1.BigNumber(0),
60 | maxAmount: new utils_1.BigNumber(1),
61 | precision: 0,
62 | };
63 | var DEFAULT_ERC20_ASSET = {
64 | minAmount: new utils_1.BigNumber(0),
65 | maxAmount: constants_1.MAX_TOKEN_SUPPLY_POSSIBLE,
66 | precision: config_1.DEFAULT_ERC20_TOKEN_PRECISION,
67 | };
68 | exports.compareAskOrder = function(orderA, orderB) {
69 | var orderAPrice = orderA.takerAssetAmount.div(orderA.makerAssetAmount);
70 | var orderBPrice = orderB.takerAssetAmount.div(orderB.makerAssetAmount);
71 | if (!orderAPrice.isEqualTo(orderBPrice)) {
72 | return orderAPrice.comparedTo(orderBPrice);
73 | }
74 | return exports.compareOrderByFeeRatio(orderA, orderB);
75 | };
76 | exports.compareBidOrder = function(orderA, orderB) {
77 | var orderAPrice = orderA.makerAssetAmount.div(orderA.takerAssetAmount);
78 | var orderBPrice = orderB.makerAssetAmount.div(orderB.takerAssetAmount);
79 | if (!orderAPrice.isEqualTo(orderBPrice)) {
80 | return orderBPrice.comparedTo(orderAPrice);
81 | }
82 | return exports.compareOrderByFeeRatio(orderA, orderB);
83 | };
84 | exports.compareOrderByFeeRatio = function(orderA, orderB) {
85 | var orderAFeePrice = orderA.takerFee.div(orderA.takerAssetAmount);
86 | var orderBFeePrice = orderB.takerFee.div(orderB.takerAssetAmount);
87 | if (!orderAFeePrice.isEqualTo(orderBFeePrice)) {
88 | return orderBFeePrice.comparedTo(orderAFeePrice);
89 | }
90 | return orderA.expirationTimeSeconds.comparedTo(orderB.expirationTimeSeconds);
91 | };
92 | exports.includesTokenAddress = function(assetData, tokenAddress) {
93 | var e_1, _a;
94 | var decodedAssetData = order_utils_1.assetDataUtils.decodeAssetDataOrThrow(assetData);
95 | if (order_utils_1.assetDataUtils.isMultiAssetData(decodedAssetData)) {
96 | try {
97 | for (
98 | var _b = __values(decodedAssetData.nestedAssetData.entries()), _c = _b.next();
99 | !_c.done;
100 | _c = _b.next()
101 | ) {
102 | var _d = __read(_c.value, 2),
103 | nestedAssetDataElement = _d[1];
104 | if (exports.includesTokenAddress(nestedAssetDataElement, tokenAddress)) {
105 | return true;
106 | }
107 | }
108 | } catch (e_1_1) {
109 | e_1 = { error: e_1_1 };
110 | } finally {
111 | try {
112 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
113 | } finally {
114 | if (e_1) throw e_1.error;
115 | }
116 | }
117 | return false;
118 | } else if (!order_utils_1.assetDataUtils.isStaticCallAssetData(decodedAssetData)) {
119 | return decodedAssetData.tokenAddress === tokenAddress;
120 | }
121 | return false;
122 | };
123 | exports.deserializeOrder = function(signedOrderModel) {
124 | var signedOrder = {
125 | signature: signedOrderModel.signature,
126 | senderAddress: signedOrderModel.senderAddress,
127 | makerAddress: signedOrderModel.makerAddress,
128 | takerAddress: signedOrderModel.takerAddress,
129 | makerFee: new utils_1.BigNumber(signedOrderModel.makerFee),
130 | takerFee: new utils_1.BigNumber(signedOrderModel.takerFee),
131 | makerAssetAmount: new utils_1.BigNumber(signedOrderModel.makerAssetAmount),
132 | takerAssetAmount: new utils_1.BigNumber(signedOrderModel.takerAssetAmount),
133 | makerAssetData: signedOrderModel.makerAssetData,
134 | takerAssetData: signedOrderModel.takerAssetData,
135 | salt: new utils_1.BigNumber(signedOrderModel.salt),
136 | exchangeAddress: signedOrderModel.exchangeAddress,
137 | feeRecipientAddress: signedOrderModel.feeRecipientAddress,
138 | expirationTimeSeconds: new utils_1.BigNumber(signedOrderModel.expirationTimeSeconds),
139 | makerFeeAssetData: signedOrderModel.makerFeeAssetData,
140 | takerFeeAssetData: signedOrderModel.takerFeeAssetData,
141 | chainId: config_1.CHAIN_ID,
142 | };
143 | return signedOrder;
144 | };
145 | exports.deserializeOrderToAPIOrder = function(signedOrderModel) {
146 | var order = exports.deserializeOrder(signedOrderModel);
147 | var apiOrder = {
148 | order: order,
149 | metaData: {
150 | orderHash: signedOrderModel.hash,
151 | remainingFillableTakerAssetAmount: signedOrderModel.remainingFillableTakerAssetAmount,
152 | },
153 | };
154 | return apiOrder;
155 | };
156 | exports.serializeOrder = function(apiOrder) {
157 | var signedOrder = apiOrder.order;
158 | var signedOrderModel = new SignedOrderModel_1.SignedOrderModel({
159 | signature: signedOrder.signature,
160 | senderAddress: signedOrder.senderAddress,
161 | makerAddress: signedOrder.makerAddress,
162 | takerAddress: signedOrder.takerAddress,
163 | makerAssetAmount: signedOrder.makerAssetAmount.toString(),
164 | takerAssetAmount: signedOrder.takerAssetAmount.toString(),
165 | makerAssetData: signedOrder.makerAssetData,
166 | takerAssetData: signedOrder.takerAssetData,
167 | makerFee: signedOrder.makerFee.toString(),
168 | takerFee: signedOrder.takerFee.toString(),
169 | makerFeeAssetData: signedOrder.makerFeeAssetData.toString(),
170 | takerFeeAssetData: signedOrder.takerFeeAssetData.toString(),
171 | salt: signedOrder.salt.toString(),
172 | exchangeAddress: signedOrder.exchangeAddress,
173 | feeRecipientAddress: signedOrder.feeRecipientAddress,
174 | expirationTimeSeconds: signedOrder.expirationTimeSeconds.toString(),
175 | hash: apiOrder.metaData.orderHash,
176 | remainingFillableTakerAssetAmount: apiOrder.metaData.remainingFillableTakerAssetAmount.toString(),
177 | });
178 | return signedOrderModel;
179 | };
180 | var erc721AssetDataToAsset = function(assetData) {
181 | return __assign({}, DEFAULT_ERC721_ASSET, { assetData: assetData });
182 | };
183 | var erc20AssetDataToAsset = function(assetData) {
184 | return __assign({}, DEFAULT_ERC20_ASSET, { assetData: assetData });
185 | };
186 | var assetDataToAsset = function(assetData) {
187 | var assetProxyId = order_utils_1.assetDataUtils.decodeAssetProxyId(assetData);
188 | var asset;
189 | switch (assetProxyId) {
190 | case types_1.AssetProxyId.ERC20:
191 | asset = erc20AssetDataToAsset(assetData);
192 | break;
193 | case types_1.AssetProxyId.ERC721:
194 | asset = erc721AssetDataToAsset(assetData);
195 | break;
196 | default:
197 | throw utils_1.errorUtils.spawnSwitchErr('assetProxyId', assetProxyId);
198 | }
199 | return asset;
200 | };
201 | exports.signedOrderToAssetPair = function(signedOrder) {
202 | return {
203 | assetDataA: assetDataToAsset(signedOrder.makerAssetData),
204 | assetDataB: assetDataToAsset(signedOrder.takerAssetData),
205 | };
206 | };
207 |
--------------------------------------------------------------------------------
/js/services/websocket_service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __assign =
3 | (this && this.__assign) ||
4 | function() {
5 | __assign =
6 | Object.assign ||
7 | function(t) {
8 | for (var s, i = 1, n = arguments.length; i < n; i++) {
9 | s = arguments[i];
10 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
11 | }
12 | return t;
13 | };
14 | return __assign.apply(this, arguments);
15 | };
16 | var __values =
17 | (this && this.__values) ||
18 | function(o) {
19 | var m = typeof Symbol === 'function' && o[Symbol.iterator],
20 | i = 0;
21 | if (m) return m.call(o);
22 | return {
23 | next: function() {
24 | if (o && i >= o.length) o = void 0;
25 | return { value: o && o[i++], done: !o };
26 | },
27 | };
28 | };
29 | var __read =
30 | (this && this.__read) ||
31 | function(o, n) {
32 | var m = typeof Symbol === 'function' && o[Symbol.iterator];
33 | if (!m) return o;
34 | var i = m.call(o),
35 | r,
36 | ar = [],
37 | e;
38 | try {
39 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
40 | } catch (error) {
41 | e = { error: error };
42 | } finally {
43 | try {
44 | if (r && !r.done && (m = i['return'])) m.call(i);
45 | } finally {
46 | if (e) throw e.error;
47 | }
48 | }
49 | return ar;
50 | };
51 | var __spread =
52 | (this && this.__spread) ||
53 | function() {
54 | for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
55 | return ar;
56 | };
57 | Object.defineProperty(exports, '__esModule', { value: true });
58 | var json_schemas_1 = require('@0x/json-schemas');
59 | var order_utils_1 = require('@0x/order-utils');
60 | var types_1 = require('@0x/types');
61 | var WebSocket = require('ws');
62 | var errors_1 = require('../errors');
63 | var mesh_utils_1 = require('../mesh_utils');
64 | var error_handling_1 = require('../middleware/error_handling');
65 | var types_2 = require('../types');
66 | var utils_1 = require('../utils');
67 | var DEFAULT_OPTS = {
68 | pongInterval: 5000,
69 | };
70 | var WebsocketService = /** @class */ (function() {
71 | function WebsocketService(server, meshClient, opts) {
72 | var _this = this;
73 | this._requestIdToSocket = new Map(); // requestId to WebSocket mapping
74 | this._requestIdToSubscriptionOpts = new Map(); // requestId -> { base, quote }
75 | var wsOpts = __assign({}, DEFAULT_OPTS, opts);
76 | this._server = new WebSocket.Server({ server: server });
77 | this._server.on('connection', this._processConnection.bind(this));
78 | this._pongIntervalId = setInterval(this._cleanupConnections.bind(this), wsOpts.pongInterval);
79 | this._meshClient = meshClient;
80 | this._meshClient
81 | .subscribeToOrdersAsync(function(e) {
82 | return _this.orderUpdate(mesh_utils_1.MeshUtils.orderInfosToApiOrders(e));
83 | })
84 | .then(function(subscriptionId) {
85 | return (_this._meshSubscriptionId = subscriptionId);
86 | });
87 | }
88 | WebsocketService._decodedContractAndAssetData = function(assetData) {
89 | var e_1, _a;
90 | var data = [assetData];
91 | var decodedAssetData = order_utils_1.assetDataUtils.decodeAssetDataOrThrow(assetData);
92 | if (order_utils_1.assetDataUtils.isMultiAssetData(decodedAssetData)) {
93 | try {
94 | for (var _b = __values(decodedAssetData.nestedAssetData), _c = _b.next(); !_c.done; _c = _b.next()) {
95 | var nested = _c.value;
96 | data = __spread(data, WebsocketService._decodedContractAndAssetData(nested).data);
97 | }
98 | } catch (e_1_1) {
99 | e_1 = { error: e_1_1 };
100 | } finally {
101 | try {
102 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
103 | } finally {
104 | if (e_1) throw e_1.error;
105 | }
106 | }
107 | } else if (order_utils_1.assetDataUtils.isStaticCallAssetData(decodedAssetData)) {
108 | // do nothing
109 | } else {
110 | data = __spread(data, [decodedAssetData.tokenAddress]);
111 | }
112 | return { data: data, assetProxyId: decodedAssetData.assetProxyId };
113 | };
114 | WebsocketService._matchesOrdersChannelSubscription = function(order, opts) {
115 | if (opts === 'ALL_SUBSCRIPTION_OPTS') {
116 | return true;
117 | }
118 | var makerAssetData = order.makerAssetData,
119 | takerAssetData = order.takerAssetData;
120 | var makerAssetDataTakerAssetData = [makerAssetData, takerAssetData];
121 | // Handle the specific, unambiguous asset datas
122 | // traderAssetData?: string;
123 | if (opts.traderAssetData && makerAssetDataTakerAssetData.includes(opts.traderAssetData)) {
124 | return true;
125 | }
126 | // makerAssetData?: string;
127 | // takerAssetData?: string;
128 | if (
129 | opts.makerAssetData &&
130 | opts.takerAssetData &&
131 | makerAssetDataTakerAssetData.includes(opts.makerAssetData) &&
132 | makerAssetDataTakerAssetData.includes(opts.takerAssetData)
133 | ) {
134 | return true;
135 | }
136 | // makerAssetAddress?: string;
137 | // takerAssetAddress?: string;
138 | var makerContractAndAssetData = WebsocketService._decodedContractAndAssetData(makerAssetData);
139 | var takerContractAndAssetData = WebsocketService._decodedContractAndAssetData(takerAssetData);
140 | if (
141 | opts.makerAssetAddress &&
142 | opts.takerAssetAddress &&
143 | makerContractAndAssetData.assetProxyId !== types_1.AssetProxyId.MultiAsset &&
144 | makerContractAndAssetData.assetProxyId !== types_1.AssetProxyId.StaticCall &&
145 | takerContractAndAssetData.assetProxyId !== types_1.AssetProxyId.MultiAsset &&
146 | takerContractAndAssetData.assetProxyId !== types_1.AssetProxyId.StaticCall &&
147 | makerContractAndAssetData.data.includes(opts.makerAssetAddress) &&
148 | takerContractAndAssetData.data.includes(opts.takerAssetAddress)
149 | ) {
150 | return true;
151 | }
152 | // TODO (dekz)handle MAP
153 | // makerAssetProxyId?: string;
154 | // takerAssetProxyId?: string;
155 | return false;
156 | };
157 | WebsocketService.prototype.destroy = function() {
158 | var e_2, _a;
159 | clearInterval(this._pongIntervalId);
160 | try {
161 | for (var _b = __values(this._server.clients), _c = _b.next(); !_c.done; _c = _b.next()) {
162 | var ws = _c.value;
163 | ws.terminate();
164 | }
165 | } catch (e_2_1) {
166 | e_2 = { error: e_2_1 };
167 | } finally {
168 | try {
169 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
170 | } finally {
171 | if (e_2) throw e_2.error;
172 | }
173 | }
174 | this._requestIdToSocket.clear();
175 | this._requestIdToSubscriptionOpts.clear();
176 | this._server.close();
177 | if (this._meshSubscriptionId) {
178 | void this._meshClient.unsubscribeAsync(this._meshSubscriptionId);
179 | }
180 | };
181 | WebsocketService.prototype.orderUpdate = function(apiOrders) {
182 | var e_3, _a, e_4, _b, e_5, _c;
183 | if (this._server.clients.size === 0) {
184 | return;
185 | }
186 | var response = {
187 | type: types_1.OrdersChannelMessageTypes.Update,
188 | channel: types_2.MessageChannels.Orders,
189 | payload: apiOrders,
190 | };
191 | try {
192 | for (
193 | var apiOrders_1 = __values(apiOrders), apiOrders_1_1 = apiOrders_1.next();
194 | !apiOrders_1_1.done;
195 | apiOrders_1_1 = apiOrders_1.next()
196 | ) {
197 | var order = apiOrders_1_1.value;
198 | // Future optimisation is to invert this structure so the order isn't duplicated over many request ids
199 | // order->requestIds it is less likely to get multiple order updates and more likely
200 | // to have many subscribers and a single order
201 | var requestIdToOrders = {};
202 | try {
203 | for (
204 | var _d = __values(this._requestIdToSubscriptionOpts), _e = _d.next();
205 | !_e.done;
206 | _e = _d.next()
207 | ) {
208 | var _f = __read(_e.value, 2),
209 | requestId = _f[0],
210 | subscriptionOpts = _f[1];
211 | if (WebsocketService._matchesOrdersChannelSubscription(order.order, subscriptionOpts)) {
212 | if (requestIdToOrders[requestId]) {
213 | var orderSet = requestIdToOrders[requestId];
214 | orderSet.add(order);
215 | } else {
216 | var orderSet = new Set();
217 | orderSet.add(order);
218 | requestIdToOrders[requestId] = orderSet;
219 | }
220 | }
221 | }
222 | } catch (e_4_1) {
223 | e_4 = { error: e_4_1 };
224 | } finally {
225 | try {
226 | if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
227 | } finally {
228 | if (e_4) throw e_4.error;
229 | }
230 | }
231 | try {
232 | for (
233 | var _g = __values(Object.entries(requestIdToOrders)), _h = _g.next();
234 | !_h.done;
235 | _h = _g.next()
236 | ) {
237 | var _j = __read(_h.value, 2),
238 | requestId = _j[0],
239 | orders = _j[1];
240 | var ws = this._requestIdToSocket.get(requestId);
241 | if (ws) {
242 | ws.send(
243 | JSON.stringify(
244 | __assign({}, response, { payload: Array.from(orders), requestId: requestId }),
245 | ),
246 | );
247 | }
248 | }
249 | } catch (e_5_1) {
250 | e_5 = { error: e_5_1 };
251 | } finally {
252 | try {
253 | if (_h && !_h.done && (_c = _g.return)) _c.call(_g);
254 | } finally {
255 | if (e_5) throw e_5.error;
256 | }
257 | }
258 | }
259 | } catch (e_3_1) {
260 | e_3 = { error: e_3_1 };
261 | } finally {
262 | try {
263 | if (apiOrders_1_1 && !apiOrders_1_1.done && (_a = apiOrders_1.return)) _a.call(apiOrders_1);
264 | } finally {
265 | if (e_3) throw e_3.error;
266 | }
267 | }
268 | };
269 | WebsocketService.prototype._processConnection = function(ws, _req) {
270 | ws.on('pong', this._pongHandler(ws).bind(this));
271 | ws.on(types_1.WebsocketConnectionEventType.Message, this._messageHandler(ws).bind(this));
272 | ws.on(types_1.WebsocketConnectionEventType.Close, this._closeHandler(ws).bind(this));
273 | ws.isAlive = true;
274 | ws.requestIds = new Set();
275 | };
276 | WebsocketService.prototype._processMessage = function(ws, data) {
277 | var message;
278 | try {
279 | message = JSON.parse(data.toString());
280 | } catch (e) {
281 | throw new errors_1.MalformedJSONError();
282 | }
283 | utils_1.utils.validateSchema(message, json_schemas_1.schemas.relayerApiOrdersChannelSubscribeSchema);
284 | var requestId = message.requestId,
285 | payload = message.payload,
286 | type = message.type;
287 | switch (type) {
288 | case types_2.MessageTypes.Subscribe:
289 | ws.requestIds.add(requestId);
290 | var subscriptionOpts = payload || 'ALL_SUBSCRIPTION_OPTS';
291 | this._requestIdToSubscriptionOpts.set(requestId, subscriptionOpts);
292 | this._requestIdToSocket.set(requestId, ws);
293 | break;
294 | default:
295 | throw new errors_1.NotImplementedError(message.type);
296 | }
297 | };
298 | WebsocketService.prototype._cleanupConnections = function() {
299 | var e_6, _a;
300 | try {
301 | // Ping every connection and if it is unresponsive
302 | // terminate it during the next check
303 | for (var _b = __values(this._server.clients), _c = _b.next(); !_c.done; _c = _b.next()) {
304 | var ws = _c.value;
305 | if (!ws.isAlive) {
306 | ws.terminate();
307 | } else {
308 | ws.isAlive = false;
309 | ws.ping();
310 | }
311 | }
312 | } catch (e_6_1) {
313 | e_6 = { error: e_6_1 };
314 | } finally {
315 | try {
316 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
317 | } finally {
318 | if (e_6) throw e_6.error;
319 | }
320 | }
321 | };
322 | WebsocketService.prototype._messageHandler = function(ws) {
323 | var _this = this;
324 | return function(data) {
325 | try {
326 | _this._processMessage(ws, data);
327 | } catch (err) {
328 | _this._processError(ws, err);
329 | }
330 | };
331 | };
332 | // tslint:disable-next-line:prefer-function-over-method
333 | WebsocketService.prototype._processError = function(ws, err) {
334 | var errorBody = error_handling_1.generateError(err).errorBody;
335 | ws.send(JSON.stringify(errorBody));
336 | ws.terminate();
337 | };
338 | // tslint:disable-next-line:prefer-function-over-method
339 | WebsocketService.prototype._pongHandler = function(ws) {
340 | return function() {
341 | ws.isAlive = true;
342 | };
343 | };
344 | WebsocketService.prototype._closeHandler = function(ws) {
345 | var _this = this;
346 | return function() {
347 | var e_7, _a;
348 | try {
349 | for (var _b = __values(ws.requestIds), _c = _b.next(); !_c.done; _c = _b.next()) {
350 | var requestId = _c.value;
351 | _this._requestIdToSocket.delete(requestId);
352 | _this._requestIdToSubscriptionOpts.delete(requestId);
353 | }
354 | } catch (e_7_1) {
355 | e_7 = { error: e_7_1 };
356 | } finally {
357 | try {
358 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
359 | } finally {
360 | if (e_7) throw e_7.error;
361 | }
362 | }
363 | };
364 | };
365 | return WebsocketService;
366 | })();
367 | exports.WebsocketService = WebsocketService;
368 |
--------------------------------------------------------------------------------
/js/types.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, '__esModule', { value: true });
3 | var OrderWatcherLifeCycleEvents;
4 | (function(OrderWatcherLifeCycleEvents) {
5 | OrderWatcherLifeCycleEvents[(OrderWatcherLifeCycleEvents['Added'] = 0)] = 'Added';
6 | OrderWatcherLifeCycleEvents[(OrderWatcherLifeCycleEvents['Removed'] = 1)] = 'Removed';
7 | OrderWatcherLifeCycleEvents[(OrderWatcherLifeCycleEvents['Updated'] = 2)] = 'Updated';
8 | })((OrderWatcherLifeCycleEvents = exports.OrderWatcherLifeCycleEvents || (exports.OrderWatcherLifeCycleEvents = {})));
9 | var MessageTypes;
10 | (function(MessageTypes) {
11 | MessageTypes['Subscribe'] = 'subscribe';
12 | })((MessageTypes = exports.MessageTypes || (exports.MessageTypes = {})));
13 | var MessageChannels;
14 | (function(MessageChannels) {
15 | MessageChannels['Orders'] = 'orders';
16 | })((MessageChannels = exports.MessageChannels || (exports.MessageChannels = {})));
17 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var __awaiter =
3 | (this && this.__awaiter) ||
4 | function(thisArg, _arguments, P, generator) {
5 | return new (P || (P = Promise))(function(resolve, reject) {
6 | function fulfilled(value) {
7 | try {
8 | step(generator.next(value));
9 | } catch (e) {
10 | reject(e);
11 | }
12 | }
13 | function rejected(value) {
14 | try {
15 | step(generator['throw'](value));
16 | } catch (e) {
17 | reject(e);
18 | }
19 | }
20 | function step(result) {
21 | result.done
22 | ? resolve(result.value)
23 | : new P(function(resolve) {
24 | resolve(result.value);
25 | }).then(fulfilled, rejected);
26 | }
27 | step((generator = generator.apply(thisArg, _arguments || [])).next());
28 | });
29 | };
30 | var __generator =
31 | (this && this.__generator) ||
32 | function(thisArg, body) {
33 | var _ = {
34 | label: 0,
35 | sent: function() {
36 | if (t[0] & 1) throw t[1];
37 | return t[1];
38 | },
39 | trys: [],
40 | ops: [],
41 | },
42 | f,
43 | y,
44 | t,
45 | g;
46 | return (
47 | (g = { next: verb(0), throw: verb(1), return: verb(2) }),
48 | typeof Symbol === 'function' &&
49 | (g[Symbol.iterator] = function() {
50 | return this;
51 | }),
52 | g
53 | );
54 | function verb(n) {
55 | return function(v) {
56 | return step([n, v]);
57 | };
58 | }
59 | function step(op) {
60 | if (f) throw new TypeError('Generator is already executing.');
61 | while (_)
62 | try {
63 | if (
64 | ((f = 1),
65 | y &&
66 | (t =
67 | op[0] & 2
68 | ? y['return']
69 | : op[0]
70 | ? y['throw'] || ((t = y['return']) && t.call(y), 0)
71 | : y.next) &&
72 | !(t = t.call(y, op[1])).done)
73 | )
74 | return t;
75 | if (((y = 0), t)) op = [op[0] & 2, t.value];
76 | switch (op[0]) {
77 | case 0:
78 | case 1:
79 | t = op;
80 | break;
81 | case 4:
82 | _.label++;
83 | return { value: op[1], done: false };
84 | case 5:
85 | _.label++;
86 | y = op[1];
87 | op = [0];
88 | continue;
89 | case 7:
90 | op = _.ops.pop();
91 | _.trys.pop();
92 | continue;
93 | default:
94 | if (
95 | !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
96 | (op[0] === 6 || op[0] === 2)
97 | ) {
98 | _ = 0;
99 | continue;
100 | }
101 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
102 | _.label = op[1];
103 | break;
104 | }
105 | if (op[0] === 6 && _.label < t[1]) {
106 | _.label = t[1];
107 | t = op;
108 | break;
109 | }
110 | if (t && _.label < t[2]) {
111 | _.label = t[2];
112 | _.ops.push(op);
113 | break;
114 | }
115 | if (t[2]) _.ops.pop();
116 | _.trys.pop();
117 | continue;
118 | }
119 | op = body.call(thisArg, _);
120 | } catch (e) {
121 | op = [6, e];
122 | y = 0;
123 | } finally {
124 | f = t = 0;
125 | }
126 | if (op[0] & 5) throw op[1];
127 | return { value: op[0] ? op[1] : void 0, done: true };
128 | }
129 | };
130 | var __read =
131 | (this && this.__read) ||
132 | function(o, n) {
133 | var m = typeof Symbol === 'function' && o[Symbol.iterator];
134 | if (!m) return o;
135 | var i = m.call(o),
136 | r,
137 | ar = [],
138 | e;
139 | try {
140 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
141 | } catch (error) {
142 | e = { error: error };
143 | } finally {
144 | try {
145 | if (r && !r.done && (m = i['return'])) m.call(i);
146 | } finally {
147 | if (e) throw e.error;
148 | }
149 | }
150 | return ar;
151 | };
152 | var __spread =
153 | (this && this.__spread) ||
154 | function() {
155 | for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
156 | return ar;
157 | };
158 | Object.defineProperty(exports, '__esModule', { value: true });
159 | var json_schemas_1 = require('@0x/json-schemas');
160 | var _ = require('lodash');
161 | var errors_1 = require('./errors');
162 | var schemaValidator = new json_schemas_1.SchemaValidator();
163 | exports.utils = {
164 | log: function() {
165 | var args = [];
166 | for (var _i = 0; _i < arguments.length; _i++) {
167 | args[_i] = arguments[_i];
168 | }
169 | // tslint:disable-next-line:no-console
170 | console.log.apply(console, __spread(args));
171 | },
172 | validateSchema: function(instance, schema) {
173 | var validationResult = schemaValidator.validate(instance, schema);
174 | if (_.isEmpty(validationResult.errors)) {
175 | return;
176 | } else {
177 | var validationErrorItems = _.map(validationResult.errors, function(schemaValidationError) {
178 | return schemaValidationErrorToValidationErrorItem(schemaValidationError);
179 | });
180 | throw new errors_1.ValidationError(validationErrorItems);
181 | }
182 | },
183 | delayAsync: function(ms) {
184 | return __awaiter(this, void 0, void 0, function() {
185 | return __generator(this, function(_a) {
186 | // tslint:disable:no-inferred-empty-object-type
187 | return [
188 | 2 /*return*/,
189 | new Promise(function(resolve) {
190 | return setTimeout(resolve, ms);
191 | }),
192 | ];
193 | });
194 | });
195 | },
196 | attemptAsync: function(fn, opts) {
197 | if (opts === void 0) {
198 | opts = { interval: 1000, maxRetries: 10 };
199 | }
200 | return __awaiter(this, void 0, void 0, function() {
201 | var result, attempt, error, isSuccess, err_1;
202 | return __generator(this, function(_a) {
203 | switch (_a.label) {
204 | case 0:
205 | attempt = 0;
206 | isSuccess = false;
207 | _a.label = 1;
208 | case 1:
209 | if (!(!result && attempt < opts.maxRetries)) return [3 /*break*/, 7];
210 | attempt++;
211 | _a.label = 2;
212 | case 2:
213 | _a.trys.push([2, 4, , 6]);
214 | return [4 /*yield*/, fn()];
215 | case 3:
216 | result = _a.sent();
217 | isSuccess = true;
218 | error = undefined;
219 | return [3 /*break*/, 6];
220 | case 4:
221 | err_1 = _a.sent();
222 | error = err_1;
223 | return [4 /*yield*/, exports.utils.delayAsync(opts.interval)];
224 | case 5:
225 | _a.sent();
226 | return [3 /*break*/, 6];
227 | case 6:
228 | return [3 /*break*/, 1];
229 | case 7:
230 | if (!isSuccess) {
231 | throw error;
232 | }
233 | return [2 /*return*/, result];
234 | }
235 | });
236 | });
237 | },
238 | };
239 | function schemaValidationErrorToValidationErrorItem(schemaValidationError) {
240 | if (
241 | _.includes(
242 | [
243 | 'type',
244 | 'anyOf',
245 | 'allOf',
246 | 'oneOf',
247 | 'additionalProperties',
248 | 'minProperties',
249 | 'maxProperties',
250 | 'pattern',
251 | 'format',
252 | 'uniqueItems',
253 | 'items',
254 | 'dependencies',
255 | ],
256 | schemaValidationError.name,
257 | )
258 | ) {
259 | return {
260 | field: schemaValidationError.property,
261 | code: errors_1.ValidationErrorCodes.IncorrectFormat,
262 | reason: schemaValidationError.message,
263 | };
264 | } else if (
265 | _.includes(
266 | ['minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems', 'enum', 'const'],
267 | schemaValidationError.name,
268 | )
269 | ) {
270 | return {
271 | field: schemaValidationError.property,
272 | code: errors_1.ValidationErrorCodes.ValueOutOfRange,
273 | reason: schemaValidationError.message,
274 | };
275 | } else if (schemaValidationError.name === 'required') {
276 | return {
277 | field: schemaValidationError.argument,
278 | code: errors_1.ValidationErrorCodes.RequiredField,
279 | reason: schemaValidationError.message,
280 | };
281 | } else if (schemaValidationError.name === 'not') {
282 | return {
283 | field: schemaValidationError.property,
284 | code: errors_1.ValidationErrorCodes.UnsupportedOption,
285 | reason: schemaValidationError.message,
286 | };
287 | } else {
288 | throw new Error('Unknnown schema validation error name: ' + schemaValidationError.name);
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/launch_kit_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-backend/b87060f41a475a014f19e0869f175ea8dd06a903/launch_kit_banner.png
--------------------------------------------------------------------------------
/metadata.json:
--------------------------------------------------------------------------------
1 | {"DEFAULT_FEE_RECIPIENT":"0xabcabc71026c35ed33c87a006d68a54ef1a122fa"}
--------------------------------------------------------------------------------
/ormconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "sqlite",
3 | "database": "db/database.sqlite",
4 | "synchronize": true,
5 | "logging": true,
6 | "logger": "debug",
7 | "entities": ["ts/lib/entity/**/*.js", "js/entity/**/*.js"],
8 | "cli": {
9 | "entitiesDir": "ts/lib/entity"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "0x-launch-kit",
3 | "version": "0.3.1",
4 | "description": "0x Relayer",
5 | "repository": "git@github.com:0xProject/0x-launch-kit-backend.git",
6 | "author": "Leonid Logvinov ",
7 | "license": "Apache-2.0",
8 | "scripts": {
9 | "clean": "shx rm -rf ts/lib",
10 | "prettier": "yarn prettier:js && yarn prettier:ts",
11 | "build": "yarn build:ts && shx rm -rf js && shx cp -r ts/lib/ js",
12 | "prettier:js": "prettier --write 'js/**/*.js' --config .prettierrc",
13 | "start:js": "node -r dotenv/config js/index.js",
14 | "build:ts": "tsc -p ts/tsconfig.json",
15 | "watch:ts": "tsc -w -p ts/tsconfig.json",
16 | "prettier:ts": "prettier --write 'ts/**/*.ts' --config .prettierrc",
17 | "start:ts": "node -r dotenv/config ts/lib/index.js",
18 | "start:service:http": "node -r dotenv/config ts/lib/runners/http_service_runner.js",
19 | "start:service:ws": "node -r dotenv/config ts/lib/runners/websocket_service_runner.js",
20 | "start:service:order_watcher": "node -r dotenv/config ts/lib/runners/order_watcher_service_runner.js",
21 | "lint:ts": "tslint --project ts --format stylish"
22 | },
23 | "devDependencies": {
24 | "@0x/tslint-config": "^4.0.0",
25 | "@types/cors": "^2.8.6",
26 | "@types/dotenv": "^6.1.1",
27 | "@types/express": "^4.17.1",
28 | "@types/lodash": "^4.14.137",
29 | "@types/web3": "^1.0.19",
30 | "@types/web3-provider-engine": "^14.0.0",
31 | "@0x/typescript-typings": "^5.0.0",
32 | "@0x/types": "^3.0.0",
33 | "@types/ws": "^6.0.2",
34 | "ethereum-types": "^2.1.4",
35 | "prettier": "^1.18.2",
36 | "shx": "^0.3.2",
37 | "tslint": "^5.19.0",
38 | "typescript": "3.0.1"
39 | },
40 | "dependencies": {
41 | "@0x/assert": "^3.0.0",
42 | "@0x/connect": "^6.0.0",
43 | "@0x/json-schemas": "^5.0.0",
44 | "@0x/mesh-rpc-client": "^7.1.0-beta-0xv3",
45 | "@0x/order-utils": "^8.5.0-beta.3",
46 | "@0x/utils": "^5.0.0",
47 | "body-parser": "^1.19.0",
48 | "cors": "^2.8.5",
49 | "debug": "^4.1.1",
50 | "dotenv": "^8.1.0",
51 | "express": "^4.17.1",
52 | "express-async-handler": "^1.1.4",
53 | "forever": "^1.0.0",
54 | "http-status-codes": "^1.3.2",
55 | "jsonschema": "^1.2.4",
56 | "lodash": "^4.17.15",
57 | "reflect-metadata": "^0.1.13",
58 | "sqlite3": "^4.1.0",
59 | "typeorm": "0.2.18",
60 | "ws": "^7.1.2"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ts/src/config.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:custom-no-magic-numbers
2 | import { assert } from '@0x/assert';
3 | import { BigNumber } from '@0x/utils';
4 | import * as crypto from 'crypto';
5 | import * as fs from 'fs';
6 | import * as _ from 'lodash';
7 | import * as path from 'path';
8 |
9 | import { NULL_BYTES } from './constants';
10 |
11 | const metadataPath = path.join(__dirname, '../../metadata.json');
12 | enum EnvVarType {
13 | Port,
14 | ChainId,
15 | FeeRecipient,
16 | UnitAmount,
17 | Url,
18 | WhitelistAllTokens,
19 | Boolean,
20 | FeeAssetData,
21 | }
22 | // Whitelisted token addresses. Set to a '*' instead of an array to allow all tokens.
23 | export const WHITELISTED_TOKENS: string[] | '*' = _.isEmpty(process.env.WHITELIST_ALL_TOKENS)
24 | ? [
25 | '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', // ZRX on Kovan
26 | '0xd0a1e359811322d97991e03f863a0c30c2cf029c', // WETH on Kovan
27 | ]
28 | : assertEnvVarType('WHITELIST_ALL_TOKENS', process.env.WHITELIST_ALL_TOKENS, EnvVarType.WhitelistAllTokens);
29 |
30 | // Network port to listen on
31 | export const HTTP_PORT = _.isEmpty(process.env.HTTP_PORT)
32 | ? 3000
33 | : assertEnvVarType('HTTP_PORT', process.env.HTTP_PORT, EnvVarType.Port);
34 | // Default chain id to use when not specified
35 | export const CHAIN_ID = _.isEmpty(process.env.CHAIN_ID)
36 | ? 42
37 | : assertEnvVarType('CHAIN_ID', process.env.CHAIN_ID, EnvVarType.ChainId);
38 |
39 | // Mesh Endpoint
40 | export const MESH_ENDPOINT = _.isEmpty(process.env.MESH_ENDPOINT)
41 | ? 'ws://localhost:60557'
42 | : assertEnvVarType('MESH_ENDPOINT', process.env.MESH_ENDPOINT, EnvVarType.Url);
43 | // The fee recipient for orders
44 | export const FEE_RECIPIENT = _.isEmpty(process.env.FEE_RECIPIENT)
45 | ? getDefaultFeeRecipient()
46 | : assertEnvVarType('FEE_RECIPIENT', process.env.FEE_RECIPIENT, EnvVarType.FeeRecipient);
47 | // A flat fee that should be charged to the order maker
48 | export const MAKER_FEE_UNIT_AMOUNT = _.isEmpty(process.env.MAKER_FEE_UNIT_AMOUNT)
49 | ? new BigNumber(0)
50 | : assertEnvVarType('MAKER_FEE_UNIT_AMOUNT', process.env.MAKER_FEE_UNIT_AMOUNT, EnvVarType.UnitAmount);
51 | // A flat fee that should be charged to the order taker
52 | export const TAKER_FEE_UNIT_AMOUNT = _.isEmpty(process.env.TAKER_FEE_UNIT_AMOUNT)
53 | ? new BigNumber(0)
54 | : assertEnvVarType('TAKER_FEE_UNIT_AMOUNT', process.env.TAKER_FEE_UNIT_AMOUNT, EnvVarType.UnitAmount);
55 | // The maker fee token encoded as asset data
56 | export const MAKER_FEE_ASSET_DATA = _.isEmpty(process.env.MAKER_FEE_ASSET_DATA)
57 | ? NULL_BYTES
58 | : assertEnvVarType('MAKER_FEE_ASSET_DATA', process.env.MAKER_FEE_ASSET_DATA, EnvVarType.FeeAssetData);
59 | // The taker fee token encoded as asset data
60 | export const TAKER_FEE_ASSET_DATA = _.isEmpty(process.env.TAKER_FEE_ASSET_DATA)
61 | ? NULL_BYTES
62 | : assertEnvVarType('TAKER_FEE_ASSET_DATA', process.env.TAKER_FEE_ASSET_DATA, EnvVarType.FeeAssetData);
63 |
64 | // Max number of entities per page
65 | export const MAX_PER_PAGE = 1000;
66 | // Default ERC20 token precision
67 | export const DEFAULT_ERC20_TOKEN_PRECISION = 18;
68 | // Address used when simulating transfers from the maker as part of 0x order validation
69 | export const DEFAULT_TAKER_SIMULATION_ADDRESS = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
70 |
71 | function assertEnvVarType(name: string, value: any, expectedType: EnvVarType): any {
72 | let returnValue;
73 | switch (expectedType) {
74 | case EnvVarType.Port:
75 | try {
76 | returnValue = parseInt(value, 10);
77 | const isWithinRange = returnValue >= 0 && returnValue <= 65535;
78 | if (!isWithinRange) {
79 | throw new Error();
80 | }
81 | } catch (err) {
82 | throw new Error(`${name} must be between 0 to 65535, found ${value}.`);
83 | }
84 | return returnValue;
85 | case EnvVarType.ChainId:
86 | try {
87 | returnValue = parseInt(value, 10);
88 | } catch (err) {
89 | throw new Error(`${name} must be a valid integer, found ${value}.`);
90 | }
91 | return returnValue;
92 | case EnvVarType.FeeRecipient:
93 | assert.isETHAddressHex(name, value);
94 | return value;
95 | case EnvVarType.Url:
96 | assert.isUri(name, value);
97 | return value;
98 | case EnvVarType.Boolean:
99 | return value === 'true';
100 | case EnvVarType.UnitAmount:
101 | try {
102 | returnValue = new BigNumber(parseFloat(value));
103 | if (returnValue.isNegative()) {
104 | throw new Error();
105 | }
106 | } catch (err) {
107 | throw new Error(`${name} must be valid number greater than 0.`);
108 | }
109 | return returnValue;
110 | case EnvVarType.WhitelistAllTokens:
111 | return '*';
112 | case EnvVarType.FeeAssetData:
113 | assert.isString(name, value);
114 | return value;
115 | default:
116 | throw new Error(`Unrecognised EnvVarType: ${expectedType} encountered for variable ${name}.`);
117 | }
118 | }
119 | function getDefaultFeeRecipient(): string {
120 | const metadata = JSON.parse(fs.readFileSync(metadataPath).toString());
121 | const existingDefault: string = metadata.DEFAULT_FEE_RECIPIENT;
122 | const newDefault: string = existingDefault || `0xabcabc${crypto.randomBytes(17).toString('hex')}`;
123 | if (_.isEmpty(existingDefault)) {
124 | const metadataCopy = JSON.parse(JSON.stringify(metadata));
125 | metadataCopy.DEFAULT_FEE_RECIPIENT = newDefault;
126 | fs.writeFileSync(metadataPath, JSON.stringify(metadataCopy));
127 | }
128 | return newDefault;
129 | }
130 |
--------------------------------------------------------------------------------
/ts/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
4 | export const NULL_BYTES = '0x';
5 | export const ZRX_DECIMALS = 18;
6 | export const DEFAULT_PAGE = 1;
7 | export const DEFAULT_PER_PAGE = 20;
8 | export const ZERO = new BigNumber(0);
9 | export const MAX_TOKEN_SUPPLY_POSSIBLE = new BigNumber(2).pow(256); // tslint:disable-line custom-no-magic-numbers
10 |
--------------------------------------------------------------------------------
/ts/src/db_connection.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import { Connection, createConnection } from 'typeorm';
3 |
4 | let connectionIfExists: Connection | undefined;
5 |
6 | /**
7 | * Returns the DB connnection
8 | */
9 | export function getDBConnection(): Connection {
10 | if (connectionIfExists === undefined) {
11 | throw new Error('DB connection not initialized');
12 | }
13 | return connectionIfExists;
14 | }
15 |
16 | /**
17 | * Creates the DB connnection to use in an app
18 | */
19 | export async function initDBConnectionAsync(): Promise {
20 | if (connectionIfExists !== undefined) {
21 | throw new Error('DB connection already exists');
22 | }
23 | connectionIfExists = await createConnection();
24 | }
25 |
--------------------------------------------------------------------------------
/ts/src/entity/SignedOrderEntity.ts:
--------------------------------------------------------------------------------
1 | import { EntitySchema } from 'typeorm';
2 |
3 | import { SignedOrderModel } from '../models/SignedOrderModel';
4 |
5 | export const signedOrderEntity = new EntitySchema({
6 | name: 'SignedOrder',
7 | target: SignedOrderModel,
8 | columns: {
9 | hash: {
10 | primary: true,
11 | type: 'varchar',
12 | },
13 | senderAddress: {
14 | type: 'varchar',
15 | },
16 | makerAddress: {
17 | type: 'varchar',
18 | },
19 | takerAddress: {
20 | type: 'varchar',
21 | },
22 | makerAssetData: {
23 | type: 'varchar',
24 | },
25 | takerAssetData: {
26 | type: 'varchar',
27 | },
28 | exchangeAddress: {
29 | type: 'varchar',
30 | },
31 | feeRecipientAddress: {
32 | type: 'varchar',
33 | },
34 | expirationTimeSeconds: {
35 | type: 'varchar',
36 | },
37 | makerFee: {
38 | type: 'varchar',
39 | },
40 | takerFee: {
41 | type: 'varchar',
42 | },
43 | makerFeeAssetData: {
44 | type: 'varchar',
45 | },
46 | takerFeeAssetData: {
47 | type: 'varchar',
48 | },
49 | makerAssetAmount: {
50 | type: 'varchar',
51 | },
52 | takerAssetAmount: {
53 | type: 'varchar',
54 | },
55 | salt: {
56 | type: 'varchar',
57 | },
58 | signature: {
59 | type: 'varchar',
60 | },
61 | remainingFillableTakerAssetAmount: {
62 | type: 'varchar',
63 | },
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/ts/src/errors.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:max-classes-per-file
2 | export abstract class RelayerBaseError extends Error {
3 | public abstract statusCode: number;
4 | public isRelayerError = true;
5 | }
6 |
7 | export abstract class BadRequestError extends RelayerBaseError {
8 | public statusCode = 400;
9 | public abstract generalErrorCode: GeneralErrorCodes;
10 | }
11 |
12 | export interface ValidationErrorItem {
13 | field: string;
14 | code: ValidationErrorCodes;
15 | reason: string;
16 | }
17 |
18 | export interface ErrorBodyWithHTTPStatusCode {
19 | statusCode: number;
20 | errorBody: ErrorBody;
21 | }
22 |
23 | export interface ErrorBody {
24 | reason: string;
25 | code?: number;
26 | validationErrors?: ValidationErrorItem[];
27 | }
28 |
29 | export class ValidationError extends BadRequestError {
30 | public generalErrorCode = GeneralErrorCodes.ValidationError;
31 | public validationErrors: ValidationErrorItem[];
32 | constructor(validationErrors: ValidationErrorItem[]) {
33 | super();
34 | this.validationErrors = validationErrors;
35 | }
36 | }
37 |
38 | export class MalformedJSONError extends BadRequestError {
39 | public generalErrorCode = GeneralErrorCodes.MalformedJson;
40 | }
41 |
42 | export class TooManyRequestsError extends BadRequestError {
43 | public statusCode = 429;
44 | public generalErrorCode = GeneralErrorCodes.Throttled;
45 | }
46 |
47 | export class NotImplementedError extends BadRequestError {
48 | public statusCode = 501;
49 | public generalErrorCode = GeneralErrorCodes.NotImplemented;
50 | }
51 |
52 | export class NotFoundError extends RelayerBaseError {
53 | public statusCode = 404;
54 | }
55 |
56 | export class InternalServerError extends RelayerBaseError {
57 | public statusCode = 500;
58 | }
59 |
60 | export enum GeneralErrorCodes {
61 | ValidationError = 100,
62 | MalformedJson = 101,
63 | OrderSubmissionDisabled = 102,
64 | Throttled = 103,
65 | NotImplemented = 104,
66 | }
67 |
68 | export const generalErrorCodeToReason: { [key in GeneralErrorCodes]: string } = {
69 | [GeneralErrorCodes.ValidationError]: 'Validation Failed',
70 | [GeneralErrorCodes.MalformedJson]: 'Malformed JSON',
71 | [GeneralErrorCodes.OrderSubmissionDisabled]: 'Order submission disabled',
72 | [GeneralErrorCodes.Throttled]: 'Throttled',
73 | [GeneralErrorCodes.NotImplemented]: 'Not Implemented',
74 | };
75 |
76 | export enum ValidationErrorCodes {
77 | RequiredField = 1000,
78 | IncorrectFormat = 1001,
79 | InvalidAddress = 1002,
80 | AddressNotSupported = 1003,
81 | ValueOutOfRange = 1004,
82 | InvalidSignatureOrHash = 1005,
83 | UnsupportedOption = 1006,
84 | InvalidOrder = 1007,
85 | InternalError = 1008,
86 | }
87 |
--------------------------------------------------------------------------------
/ts/src/fee_strategy.ts:
--------------------------------------------------------------------------------
1 | import { OrderConfigRequest, OrderConfigResponse } from '@0x/connect';
2 |
3 | import {
4 | FEE_RECIPIENT,
5 | MAKER_FEE_ASSET_DATA,
6 | MAKER_FEE_UNIT_AMOUNT,
7 | TAKER_FEE_ASSET_DATA,
8 | TAKER_FEE_UNIT_AMOUNT,
9 | } from './config';
10 | import { NULL_ADDRESS } from './constants';
11 |
12 | export const fixedFeeStrategy = {
13 | getOrderConfig: (_order: Partial): OrderConfigResponse => {
14 | const normalizedFeeRecipient = FEE_RECIPIENT.toLowerCase();
15 | const orderConfigResponse: OrderConfigResponse = {
16 | senderAddress: NULL_ADDRESS,
17 | feeRecipientAddress: normalizedFeeRecipient,
18 | makerFee: MAKER_FEE_UNIT_AMOUNT,
19 | takerFee: TAKER_FEE_UNIT_AMOUNT,
20 | makerFeeAssetData: MAKER_FEE_ASSET_DATA,
21 | takerFeeAssetData: TAKER_FEE_ASSET_DATA,
22 | };
23 | return orderConfigResponse;
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/ts/src/globals.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace Express {
2 | export interface Request {
3 | networkId: number;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/ts/src/handlers.ts:
--------------------------------------------------------------------------------
1 | import { schemas } from '@0x/json-schemas';
2 | import { assetDataUtils, SignedOrder } from '@0x/order-utils';
3 | import { BigNumber } from '@0x/utils';
4 | import * as express from 'express';
5 | import * as HttpStatus from 'http-status-codes';
6 | import * as _ from 'lodash';
7 |
8 | import { FEE_RECIPIENT, MAX_PER_PAGE, WHITELISTED_TOKENS } from './config';
9 | import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from './constants';
10 | import { NotFoundError, ValidationError, ValidationErrorCodes } from './errors';
11 | import { fixedFeeStrategy } from './fee_strategy';
12 | import { paginate } from './paginator';
13 | import { OrderBookService } from './services/orderbook_service';
14 | import { utils } from './utils';
15 |
16 | const parsePaginationConfig = (req: express.Request): { page: number; perPage: number } => {
17 | const page = req.query.page === undefined ? DEFAULT_PAGE : Number(req.query.page);
18 | const perPage = req.query.perPage === undefined ? DEFAULT_PER_PAGE : Number(req.query.perPage);
19 | if (perPage > MAX_PER_PAGE) {
20 | throw new ValidationError([
21 | {
22 | field: 'perPage',
23 | code: ValidationErrorCodes.ValueOutOfRange,
24 | reason: `perPage should be less or equal to ${MAX_PER_PAGE}`,
25 | },
26 | ]);
27 | }
28 | return { page, perPage };
29 | };
30 |
31 | export class Handlers {
32 | private readonly _orderBook: OrderBookService;
33 | public static feeRecipients(req: express.Request, res: express.Response): void {
34 | const { page, perPage } = parsePaginationConfig(req);
35 | const normalizedFeeRecipient = FEE_RECIPIENT.toLowerCase();
36 | const feeRecipients = [normalizedFeeRecipient];
37 | const paginatedFeeRecipients = paginate(feeRecipients, page, perPage);
38 | res.status(HttpStatus.OK).send(paginatedFeeRecipients);
39 | }
40 | public static orderConfig(req: express.Request, res: express.Response): void {
41 | utils.validateSchema(req.body, schemas.orderConfigRequestSchema);
42 | const orderConfigResponse = fixedFeeStrategy.getOrderConfig(req.body);
43 | res.status(HttpStatus.OK).send(orderConfigResponse);
44 | }
45 | public static async assetPairsAsync(req: express.Request, res: express.Response): Promise {
46 | utils.validateSchema(req.query, schemas.assetPairsRequestOptsSchema);
47 | const { page, perPage } = parsePaginationConfig(req);
48 | const assetPairs = await OrderBookService.getAssetPairsAsync(
49 | page,
50 | perPage,
51 | req.query.assetDataA,
52 | req.query.assetDataB,
53 | );
54 | res.status(HttpStatus.OK).send(assetPairs);
55 | }
56 | public static async getOrderByHashAsync(req: express.Request, res: express.Response): Promise {
57 | const orderIfExists = await OrderBookService.getOrderByHashIfExistsAsync(req.params.orderHash);
58 | if (orderIfExists === undefined) {
59 | throw new NotFoundError();
60 | } else {
61 | res.status(HttpStatus.OK).send(orderIfExists);
62 | }
63 | }
64 | constructor(orderBook: OrderBookService) {
65 | this._orderBook = orderBook;
66 | }
67 | public async ordersAsync(req: express.Request, res: express.Response): Promise {
68 | utils.validateSchema(req.query, schemas.ordersRequestOptsSchema);
69 | const { page, perPage } = parsePaginationConfig(req);
70 | const paginatedOrders = await this._orderBook.getOrdersAsync(page, perPage, req.query);
71 | res.status(HttpStatus.OK).send(paginatedOrders);
72 | }
73 | public async orderbookAsync(req: express.Request, res: express.Response): Promise {
74 | utils.validateSchema(req.query, schemas.orderBookRequestSchema);
75 | const { page, perPage } = parsePaginationConfig(req);
76 | const baseAssetData = req.query.baseAssetData;
77 | const quoteAssetData = req.query.quoteAssetData;
78 | const orderbookResponse = await this._orderBook.getOrderBookAsync(page, perPage, baseAssetData, quoteAssetData);
79 | res.status(HttpStatus.OK).send(orderbookResponse);
80 | }
81 | public async postOrderAsync(req: express.Request, res: express.Response): Promise {
82 | utils.validateSchema(req.body, schemas.signedOrderSchema);
83 | const signedOrder = unmarshallOrder(req.body);
84 | if (WHITELISTED_TOKENS !== '*') {
85 | const allowedTokens: string[] = WHITELISTED_TOKENS;
86 | validateAssetDataIsWhitelistedOrThrow(allowedTokens, signedOrder.makerAssetData, 'makerAssetData');
87 | validateAssetDataIsWhitelistedOrThrow(allowedTokens, signedOrder.takerAssetData, 'takerAssetData');
88 | }
89 | await this._orderBook.addOrderAsync(signedOrder);
90 | res.status(HttpStatus.OK).send();
91 | }
92 | }
93 |
94 | function validateAssetDataIsWhitelistedOrThrow(allowedTokens: string[], assetData: string, field: string): void {
95 | const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
96 | if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
97 | for (const [, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
98 | validateAssetDataIsWhitelistedOrThrow(allowedTokens, nestedAssetDataElement, field);
99 | }
100 | } else if (!assetDataUtils.isStaticCallAssetData(decodedAssetData)) {
101 | if (!_.includes(allowedTokens, decodedAssetData.tokenAddress)) {
102 | throw new ValidationError([
103 | {
104 | field,
105 | code: ValidationErrorCodes.ValueOutOfRange,
106 | reason: `Token ${decodedAssetData.tokenAddress} not supported`,
107 | },
108 | ]);
109 | }
110 | }
111 | }
112 |
113 | // As the orders come in as JSON they need to be turned into the correct types such as BigNumber
114 | function unmarshallOrder(signedOrderRaw: any): SignedOrder {
115 | const signedOrder = {
116 | ...signedOrderRaw,
117 | salt: new BigNumber(signedOrderRaw.salt),
118 | makerAssetAmount: new BigNumber(signedOrderRaw.makerAssetAmount),
119 | takerAssetAmount: new BigNumber(signedOrderRaw.takerAssetAmount),
120 | makerFee: new BigNumber(signedOrderRaw.makerFee),
121 | takerFee: new BigNumber(signedOrderRaw.takerFee),
122 | expirationTimeSeconds: new BigNumber(signedOrderRaw.expirationTimeSeconds),
123 | };
124 | return signedOrder;
125 | }
126 |
--------------------------------------------------------------------------------
/ts/src/index.ts:
--------------------------------------------------------------------------------
1 | import { WSClient } from '@0x/mesh-rpc-client';
2 | import * as express from 'express';
3 | import 'reflect-metadata';
4 |
5 | import * as config from './config';
6 | import { initDBConnectionAsync } from './db_connection';
7 | import { HttpService } from './services/http_service';
8 | import { OrderWatcherService } from './services/order_watcher_service';
9 | import { OrderBookService } from './services/orderbook_service';
10 | import { WebsocketService } from './services/websocket_service';
11 | import { utils } from './utils';
12 |
13 | (async () => {
14 | await initDBConnectionAsync();
15 | const app = express();
16 | const server = app.listen(config.HTTP_PORT, () => {
17 | utils.log(
18 | `Standard relayer API (HTTP) listening on port ${config.HTTP_PORT}!\nConfig: ${JSON.stringify(
19 | config,
20 | null,
21 | 2,
22 | )}`,
23 | );
24 | });
25 | let meshClient;
26 | await utils.attemptAsync(
27 | async () => {
28 | meshClient = new WSClient(config.MESH_ENDPOINT);
29 | await meshClient.getStatsAsync();
30 | },
31 | { interval: 3000, maxRetries: 10 },
32 | );
33 | if (!meshClient) {
34 | throw new Error('Unable to establish connection to Mesh');
35 | }
36 | utils.log('Connected to Mesh');
37 | const orderWatcherService = new OrderWatcherService(meshClient);
38 | await orderWatcherService.syncOrderbookAsync();
39 | // tslint:disable-next-line:no-unused-expression
40 | new WebsocketService(server, meshClient);
41 | const orderBookService = new OrderBookService(meshClient);
42 | // tslint:disable-next-line:no-unused-expression
43 | new HttpService(app, orderBookService);
44 | })().catch(utils.log);
45 |
--------------------------------------------------------------------------------
/ts/src/mesh_utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AcceptedOrderInfo,
3 | OrderEvent,
4 | OrderEventEndState,
5 | OrderInfo,
6 | RejectedCode,
7 | RejectedOrderInfo,
8 | SignedOrder,
9 | ValidationResults,
10 | WSClient,
11 | } from '@0x/mesh-rpc-client';
12 | import * as _ from 'lodash';
13 |
14 | import { ZERO } from './constants';
15 | import { ValidationErrorCodes } from './errors';
16 | import { APIOrderWithMetaData } from './types';
17 |
18 | // tslint:disable-next-line:no-var-requires
19 | const d = require('debug')('MESH');
20 |
21 | // tslint:disable-next-line:no-unnecessary-class
22 | export class MeshUtils {
23 | public static async addOrdersToMeshAsync(
24 | meshClient: WSClient,
25 | orders: SignedOrder[],
26 | batchSize: number = 100,
27 | ): Promise {
28 | // Mesh rpc client can't handle a large amount of orders. This results in a fragmented
29 | // send which Mesh cannot accept.
30 | const validationResults: ValidationResults = { accepted: [], rejected: [] };
31 | const chunks = _.chunk(orders, batchSize);
32 | for (const chunk of chunks) {
33 | const results = await meshClient.addOrdersAsync(chunk, true);
34 | validationResults.accepted = [...validationResults.accepted, ...results.accepted];
35 | validationResults.rejected = [...validationResults.rejected, ...results.rejected];
36 | }
37 | return validationResults;
38 | }
39 | public static orderInfosToApiOrders(
40 | orderEvent: Array,
41 | ): APIOrderWithMetaData[] {
42 | return orderEvent.map(e => MeshUtils.orderInfoToAPIOrder(e));
43 | }
44 | public static orderInfoToAPIOrder(
45 | orderEvent: OrderEvent | AcceptedOrderInfo | RejectedOrderInfo | OrderInfo,
46 | ): APIOrderWithMetaData {
47 | const remainingFillableTakerAssetAmount = (orderEvent as OrderEvent).fillableTakerAssetAmount
48 | ? (orderEvent as OrderEvent).fillableTakerAssetAmount
49 | : ZERO;
50 | return {
51 | order: orderEvent.signedOrder,
52 | metaData: {
53 | orderHash: orderEvent.orderHash,
54 | remainingFillableTakerAssetAmount,
55 | },
56 | };
57 | }
58 | public static rejectedCodeToSRACode(code: RejectedCode): ValidationErrorCodes {
59 | switch (code) {
60 | case RejectedCode.OrderCancelled:
61 | case RejectedCode.OrderExpired:
62 | case RejectedCode.OrderUnfunded:
63 | case RejectedCode.OrderHasInvalidMakerAssetAmount:
64 | case RejectedCode.OrderHasInvalidMakerAssetData:
65 | case RejectedCode.OrderHasInvalidTakerAssetAmount:
66 | case RejectedCode.OrderHasInvalidTakerAssetData:
67 | case RejectedCode.OrderFullyFilled: {
68 | return ValidationErrorCodes.InvalidOrder;
69 | }
70 | case RejectedCode.OrderHasInvalidSignature: {
71 | return ValidationErrorCodes.InvalidSignatureOrHash;
72 | }
73 | case RejectedCode.OrderForIncorrectChain: {
74 | return ValidationErrorCodes.InvalidAddress;
75 | }
76 | default:
77 | return ValidationErrorCodes.InternalError;
78 | }
79 | }
80 | public static calculateAddedRemovedUpdated(
81 | orderEvents: OrderEvent[],
82 | ): { added: APIOrderWithMetaData[]; removed: APIOrderWithMetaData[]; updated: APIOrderWithMetaData[] } {
83 | const added = [];
84 | const removed = [];
85 | const updated = [];
86 | for (const event of orderEvents) {
87 | const apiOrder = MeshUtils.orderInfoToAPIOrder(event);
88 | switch (event.endState) {
89 | case OrderEventEndState.Added: {
90 | added.push(apiOrder);
91 | break;
92 | }
93 | case OrderEventEndState.Cancelled:
94 | case OrderEventEndState.Expired:
95 | case OrderEventEndState.FullyFilled:
96 | case OrderEventEndState.Unfunded: {
97 | removed.push(apiOrder);
98 | break;
99 | }
100 | case OrderEventEndState.FillabilityIncreased:
101 | case OrderEventEndState.Filled: {
102 | updated.push(apiOrder);
103 | break;
104 | }
105 | default:
106 | d('Unknown Event', event.endState, event);
107 | break;
108 | }
109 | }
110 | return { added, removed, updated };
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/ts/src/middleware/error_handling.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import * as HttpStatus from 'http-status-codes';
3 |
4 | import {
5 | BadRequestError,
6 | ErrorBodyWithHTTPStatusCode,
7 | GeneralErrorCodes,
8 | generalErrorCodeToReason,
9 | RelayerBaseError,
10 | ValidationError,
11 | } from '../errors';
12 |
13 | /**
14 | * Wraps an Error with a JSON human readable reason and status code.
15 | */
16 | export function generateError(err: Error): ErrorBodyWithHTTPStatusCode {
17 | if ((err as any).isRelayerError) {
18 | const relayerError = err as RelayerBaseError;
19 | const statusCode = relayerError.statusCode;
20 | if (relayerError.statusCode === HttpStatus.BAD_REQUEST) {
21 | const badRequestError = relayerError as BadRequestError;
22 | if (badRequestError.generalErrorCode === GeneralErrorCodes.ValidationError) {
23 | const validationError = badRequestError as ValidationError;
24 | return {
25 | statusCode,
26 | errorBody: {
27 | code: badRequestError.generalErrorCode,
28 | reason: generalErrorCodeToReason[badRequestError.generalErrorCode],
29 | validationErrors: validationError.validationErrors,
30 | },
31 | };
32 | } else {
33 | return {
34 | statusCode,
35 | errorBody: {
36 | code: badRequestError.generalErrorCode,
37 | reason: generalErrorCodeToReason[badRequestError.generalErrorCode],
38 | },
39 | };
40 | }
41 | } else {
42 | return {
43 | statusCode,
44 | errorBody: {
45 | reason: HttpStatus.getStatusText(relayerError.statusCode),
46 | },
47 | };
48 | }
49 | }
50 | return {
51 | statusCode: HttpStatus.BAD_REQUEST,
52 | errorBody: {
53 | reason: err.message,
54 | },
55 | };
56 | }
57 |
58 | /**
59 | * Catches errors thrown by our code and serialies them
60 | */
61 | export function errorHandler(
62 | err: Error,
63 | _req: express.Request,
64 | res: express.Response,
65 | next: express.NextFunction,
66 | ): void {
67 | // If you call next() with an error after you have started writing the response
68 | // (for example, if you encounter an error while streaming the response to the client)
69 | // the Express default error handler closes the connection and fails the request.
70 | if (res.headersSent) {
71 | return next(err);
72 | }
73 | if ((err as any).isRelayerError || (err as any).statusCode) {
74 | const { statusCode, errorBody } = generateError(err);
75 | res.status(statusCode).send(errorBody);
76 | return;
77 | } else {
78 | return next(err);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ts/src/middleware/url_params_parsing.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import * as _ from 'lodash';
3 |
4 | import { CHAIN_ID } from '../config';
5 | import { ValidationError } from '../errors';
6 |
7 | /**
8 | * Parses URL params and stores them on the request object
9 | */
10 | export function urlParamsParsing(req: express.Request, _res: express.Response, next: express.NextFunction): void {
11 | const chainId = parseChainId(req.query.chainId);
12 | // HACK: This is the recommended way to pass data from middlewares on. It's not beautiful nor fully type-safe.
13 | (req as any).chainId = chainId;
14 | next();
15 | }
16 |
17 | function parseChainId(chainIdStrIfExists?: string): number {
18 | if (chainIdStrIfExists === undefined) {
19 | return CHAIN_ID;
20 | } else {
21 | const chainId = _.parseInt(chainIdStrIfExists);
22 | if (chainId !== CHAIN_ID) {
23 | const validationErrorItem = {
24 | field: 'chainId',
25 | code: 1004,
26 | reason: `Incorrect Chain ID: ${chainIdStrIfExists}`,
27 | };
28 | throw new ValidationError([validationErrorItem]);
29 | }
30 | return chainId;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ts/src/models/SignedOrderModel.ts:
--------------------------------------------------------------------------------
1 | export class SignedOrderModel {
2 | public hash?: string;
3 | public senderAddress?: string;
4 | public makerAddress?: string;
5 | public takerAddress?: string;
6 | public makerAssetData?: string;
7 | public takerAssetData?: string;
8 | public exchangeAddress?: string;
9 | public feeRecipientAddress?: string;
10 | public expirationTimeSeconds?: string;
11 | public makerFee?: string;
12 | public takerFee?: string;
13 | public makerAssetAmount?: string;
14 | public takerAssetAmount?: string;
15 | public salt?: string;
16 | public signature?: string;
17 | public remainingFillableTakerAssetAmount?: string;
18 | public makerFeeAssetData?: string;
19 | public takerFeeAssetData?: string;
20 | constructor(
21 | opts: {
22 | hash?: string;
23 | senderAddress?: string;
24 | makerAddress?: string;
25 | takerAddress?: string;
26 | makerAssetData?: string;
27 | takerAssetData?: string;
28 | exchangeAddress?: string;
29 | feeRecipientAddress?: string;
30 | expirationTimeSeconds?: string;
31 | makerFee?: string;
32 | takerFee?: string;
33 | makerFeeAssetData?: string;
34 | takerFeeAssetData?: string;
35 | makerAssetAmount?: string;
36 | takerAssetAmount?: string;
37 | salt?: string;
38 | signature?: string;
39 | remainingFillableTakerAssetAmount?: string;
40 | } = {},
41 | ) {
42 | this.hash = opts.hash;
43 | this.senderAddress = opts.senderAddress;
44 | this.makerAddress = opts.makerAddress;
45 | this.takerAddress = opts.takerAddress;
46 | this.makerAssetData = opts.makerAssetData;
47 | this.takerAssetData = opts.takerAssetData;
48 | this.exchangeAddress = opts.exchangeAddress;
49 | this.feeRecipientAddress = opts.feeRecipientAddress;
50 | this.expirationTimeSeconds = opts.expirationTimeSeconds;
51 | this.makerFee = opts.makerFee;
52 | this.takerFee = opts.takerFee;
53 | this.makerFeeAssetData = opts.makerFeeAssetData;
54 | this.takerFeeAssetData = opts.takerFeeAssetData;
55 | this.makerAssetAmount = opts.makerAssetAmount;
56 | this.takerAssetAmount = opts.takerAssetAmount;
57 | this.salt = opts.salt;
58 | this.signature = opts.signature;
59 | this.remainingFillableTakerAssetAmount = opts.remainingFillableTakerAssetAmount;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ts/src/paginator.ts:
--------------------------------------------------------------------------------
1 | export const paginate = (collection: T[], page: number, perPage: number) => {
2 | const paginatedCollection = {
3 | total: collection.length,
4 | page,
5 | perPage,
6 | records: collection.slice((page - 1) * perPage, page * perPage),
7 | };
8 | return paginatedCollection;
9 | };
10 |
--------------------------------------------------------------------------------
/ts/src/runners/http_service_runner.ts:
--------------------------------------------------------------------------------
1 | import { WSClient } from '@0x/mesh-rpc-client';
2 | import * as express from 'express';
3 | import 'reflect-metadata';
4 |
5 | import * as config from '../config';
6 | import { initDBConnectionAsync } from '../db_connection';
7 | import { HttpService } from '../services/http_service';
8 | import { OrderBookService } from '../services/orderbook_service';
9 | import { utils } from '../utils';
10 |
11 | /**
12 | * This service handles the HTTP requests. This involves fetching from the database
13 | * as well as adding orders to mesh.
14 | */
15 | (async () => {
16 | await initDBConnectionAsync();
17 | const app = express();
18 | app.listen(config.HTTP_PORT, () => {
19 | utils.log(
20 | `Standard relayer API (HTTP) listening on port ${config.HTTP_PORT}!\nConfig: ${JSON.stringify(
21 | config,
22 | null,
23 | 2,
24 | )}`,
25 | );
26 | });
27 | const meshClient = new WSClient(config.MESH_ENDPOINT);
28 | const orderBookService = new OrderBookService(meshClient);
29 | // tslint:disable-next-line:no-unused-expression
30 | new HttpService(app, orderBookService);
31 | })().catch(utils.log);
32 |
--------------------------------------------------------------------------------
/ts/src/runners/order_watcher_service_runner.ts:
--------------------------------------------------------------------------------
1 | import { WSClient } from '@0x/mesh-rpc-client';
2 | import 'reflect-metadata';
3 |
4 | import * as config from '../config';
5 | import { initDBConnectionAsync } from '../db_connection';
6 | import { OrderWatcherService } from '../services/order_watcher_service';
7 | import { utils } from '../utils';
8 |
9 | /**
10 | * This service is a simple writer from the Mesh events. On order discovery
11 | * or an order update it will be persisted to the database. It also is responsible
12 | * for syncing the database with Mesh on start or after a disconnect.
13 | */
14 | (async () => {
15 | await initDBConnectionAsync();
16 | utils.log(`Order Watching Service started!\nConfig: ${JSON.stringify(config, null, 2)}`);
17 | const meshClient = new WSClient(config.MESH_ENDPOINT);
18 | const orderWatcherService = new OrderWatcherService(meshClient);
19 | await orderWatcherService.syncOrderbookAsync();
20 | })().catch(utils.log);
21 |
--------------------------------------------------------------------------------
/ts/src/runners/websocket_service_runner.ts:
--------------------------------------------------------------------------------
1 | import { WSClient } from '@0x/mesh-rpc-client';
2 | import * as express from 'express';
3 | import 'reflect-metadata';
4 |
5 | import * as config from '../config';
6 | import { initDBConnectionAsync } from '../db_connection';
7 | import { WebsocketService } from '../services/websocket_service';
8 | import { utils } from '../utils';
9 |
10 | /**
11 | * This service handles websocket updates using a subscription from Mesh.
12 | */
13 | (async () => {
14 | await initDBConnectionAsync();
15 | const app = express();
16 | const server = app.listen(config.HTTP_PORT, () => {
17 | utils.log(
18 | `Standard relayer API (WS) listening on port ${config.HTTP_PORT}!\nConfig: ${JSON.stringify(
19 | config,
20 | null,
21 | 2,
22 | )}`,
23 | );
24 | });
25 | const meshClient = new WSClient(config.MESH_ENDPOINT);
26 | // tslint:disable-next-line:no-unused-expression
27 | new WebsocketService(server, meshClient);
28 | })().catch(utils.log);
29 |
--------------------------------------------------------------------------------
/ts/src/services/http_service.ts:
--------------------------------------------------------------------------------
1 | import * as bodyParser from 'body-parser';
2 | import * as cors from 'cors';
3 | import * as asyncHandler from 'express-async-handler';
4 | // tslint:disable-next-line:no-implicit-dependencies
5 | import * as core from 'express-serve-static-core';
6 | import 'reflect-metadata';
7 |
8 | import { Handlers } from '../handlers';
9 | import { errorHandler } from '../middleware/error_handling';
10 | import { urlParamsParsing } from '../middleware/url_params_parsing';
11 | import { OrderBookService } from '../services/orderbook_service';
12 |
13 | // tslint:disable-next-line:no-unnecessary-class
14 | export class HttpService {
15 | constructor(app: core.Express, orderBook: OrderBookService) {
16 | const handlers = new Handlers(orderBook);
17 | app.use(cors());
18 | app.use(bodyParser.json());
19 | app.use(urlParamsParsing);
20 |
21 | /**
22 | * GET AssetPairs endpoint retrieves a list of available asset pairs and the information required to trade them.
23 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getAssetPairs
24 | */
25 | app.get('/v3/asset_pairs', asyncHandler(Handlers.assetPairsAsync.bind(Handlers)));
26 | /**
27 | * GET Orders endpoint retrieves a list of orders given query parameters.
28 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrders
29 | */
30 | app.get('/v3/orders', asyncHandler(handlers.ordersAsync.bind(handlers)));
31 | /**
32 | * GET Orderbook endpoint retrieves the orderbook for a given asset pair.
33 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrderbook
34 | */
35 | app.get('/v3/orderbook', asyncHandler(handlers.orderbookAsync.bind(handlers)));
36 | /**
37 | * GET FeeRecepients endpoint retrieves a collection of all fee recipient addresses for a relayer.
38 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/v3/fee_recipients
39 | */
40 | app.get('/v3/fee_recipients', Handlers.feeRecipients.bind(Handlers));
41 | /**
42 | * POST Order config endpoint retrives the values for order fields that the relayer requires.
43 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrderConfig
44 | */
45 | app.post('/v3/order_config', Handlers.orderConfig.bind(Handlers));
46 | /**
47 | * POST Order endpoint submits an order to the Relayer.
48 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/postOrder
49 | */
50 | app.post('/v3/order', asyncHandler(handlers.postOrderAsync.bind(handlers)));
51 | /**
52 | * GET Order endpoint retrieves the order by order hash.
53 | * http://sra-spec.s3-website-us-east-1.amazonaws.com/#operation/getOrder
54 | */
55 | app.get('/v3/order/:orderHash', asyncHandler(Handlers.getOrderByHashAsync.bind(Handlers)));
56 |
57 | app.use(errorHandler);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ts/src/services/order_watcher_service.ts:
--------------------------------------------------------------------------------
1 | import { WSClient } from '@0x/mesh-rpc-client';
2 | import * as _ from 'lodash';
3 |
4 | import { getDBConnection } from '../db_connection';
5 | import { MeshUtils } from '../mesh_utils';
6 | import { SignedOrderModel } from '../models/SignedOrderModel';
7 | import { APIOrderWithMetaData, OrderWatcherLifeCycleEvents } from '../types';
8 |
9 | import { deserializeOrder, serializeOrder } from './orderbook_utils';
10 |
11 | // tslint:disable-next-line:no-var-requires
12 | const d = require('debug')('orderbook');
13 |
14 | export class OrderWatcherService {
15 | private readonly _meshClient: WSClient;
16 | private static async _onOrderLifeCycleEventAsync(
17 | lifecycleEvent: OrderWatcherLifeCycleEvents,
18 | orders: APIOrderWithMetaData[],
19 | ): Promise {
20 | if (orders.length <= 0) {
21 | return;
22 | }
23 | const connection = getDBConnection();
24 | switch (lifecycleEvent) {
25 | case OrderWatcherLifeCycleEvents.Updated:
26 | case OrderWatcherLifeCycleEvents.Added: {
27 | const signedOrdersModel = orders.map(o => serializeOrder(o));
28 | // MAX SQL variable size is 999. This limit is imposed via Sqlite.
29 | // The SELECT query is not entirely effecient and pulls in all attributes
30 | // so we need to leave space for the attributes on the model represented
31 | // as SQL variables in the "AS" syntax. We leave 99 free for the
32 | // signedOrders model
33 | await connection.manager.save(signedOrdersModel, { chunk: 900 });
34 | break;
35 | }
36 | case OrderWatcherLifeCycleEvents.Removed: {
37 | const orderHashes = orders.map(o => o.metaData.orderHash);
38 | // MAX SQL variable size is 999. This limit is imposed via Sqlite
39 | // and other databases have higher limits (or no limits at all, eg postgresql)
40 | // tslint:disable-next-line:custom-no-magic-numbers
41 | const chunks = _.chunk(orderHashes, 999);
42 | for (const chunk of chunks) {
43 | await connection.manager.delete(SignedOrderModel, chunk);
44 | }
45 | break;
46 | }
47 | default:
48 | // Do Nothing
49 | }
50 | }
51 | constructor(meshClient: WSClient) {
52 | this._meshClient = meshClient;
53 | void this._meshClient.subscribeToOrdersAsync(async orders => {
54 | const { added, removed, updated } = MeshUtils.calculateAddedRemovedUpdated(orders);
55 | await OrderWatcherService._onOrderLifeCycleEventAsync(OrderWatcherLifeCycleEvents.Removed, removed);
56 | await OrderWatcherService._onOrderLifeCycleEventAsync(OrderWatcherLifeCycleEvents.Updated, updated);
57 | await OrderWatcherService._onOrderLifeCycleEventAsync(OrderWatcherLifeCycleEvents.Added, added);
58 | });
59 | this._meshClient.onReconnected(async () => {
60 | d('Reconnecting to Mesh');
61 | await this.syncOrderbookAsync();
62 | });
63 | }
64 | public async syncOrderbookAsync(): Promise {
65 | d('SYNC orderbook with Mesh');
66 | const connection = getDBConnection();
67 | const signedOrderModels = (await connection.manager.find(SignedOrderModel)) as Array<
68 | Required
69 | >;
70 | const signedOrders = signedOrderModels.map(deserializeOrder);
71 | // Sync the order watching service state locally
72 | const orders = await this._meshClient.getOrdersAsync();
73 | // TODO(dekz): Mesh can reject due to InternalError or EthRPCRequestFailed.
74 | // in the future we can attempt to retry these a few times. Ultimately if we
75 | // cannot validate the order we cannot keep the order around
76 | // Validate the local state and notify the order watcher of any missed orders
77 | const { accepted, rejected } = await MeshUtils.addOrdersToMeshAsync(this._meshClient, signedOrders);
78 | d(`SYNC ${rejected.length} rejected ${accepted.length} accepted ${signedOrders.length} sent`);
79 | // Remove all of the rejected orders
80 | if (rejected.length > 0) {
81 | await OrderWatcherService._onOrderLifeCycleEventAsync(
82 | OrderWatcherLifeCycleEvents.Removed,
83 | MeshUtils.orderInfosToApiOrders(rejected),
84 | );
85 | }
86 | // Sync the order watching service state locally
87 | if (orders.length > 0) {
88 | await OrderWatcherService._onOrderLifeCycleEventAsync(
89 | OrderWatcherLifeCycleEvents.Added,
90 | MeshUtils.orderInfosToApiOrders(orders),
91 | );
92 | }
93 | d('SYNC complete');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/ts/src/services/orderbook_service.ts:
--------------------------------------------------------------------------------
1 | import { APIOrder, OrderbookResponse, PaginatedCollection } from '@0x/connect';
2 | import { WSClient } from '@0x/mesh-rpc-client';
3 | import { assetDataUtils } from '@0x/order-utils';
4 | import { AssetPairsItem, OrdersRequestOpts, SignedOrder } from '@0x/types';
5 | import * as _ from 'lodash';
6 |
7 | import { getDBConnection } from '../db_connection';
8 | import { ValidationError } from '../errors';
9 | import { MeshUtils } from '../mesh_utils';
10 | import { SignedOrderModel } from '../models/SignedOrderModel';
11 | import { paginate } from '../paginator';
12 |
13 | import {
14 | compareAskOrder,
15 | compareBidOrder,
16 | deserializeOrder,
17 | deserializeOrderToAPIOrder,
18 | includesTokenAddress,
19 | signedOrderToAssetPair,
20 | } from './orderbook_utils';
21 |
22 | export class OrderBookService {
23 | private readonly _meshClient: WSClient;
24 | public static async getOrderByHashIfExistsAsync(orderHash: string): Promise {
25 | const connection = getDBConnection();
26 | const signedOrderModelIfExists = await connection.manager.findOne(SignedOrderModel, orderHash);
27 | if (signedOrderModelIfExists === undefined) {
28 | return undefined;
29 | } else {
30 | const deserializedOrder = deserializeOrderToAPIOrder(signedOrderModelIfExists as Required<
31 | SignedOrderModel
32 | >);
33 | return deserializedOrder;
34 | }
35 | }
36 | public static async getAssetPairsAsync(
37 | page: number,
38 | perPage: number,
39 | assetDataA: string,
40 | assetDataB: string,
41 | ): Promise> {
42 | const connection = getDBConnection();
43 | const signedOrderModels = (await connection.manager.find(SignedOrderModel)) as Array<
44 | Required
45 | >;
46 |
47 | const assetPairsItems: AssetPairsItem[] = signedOrderModels.map(deserializeOrder).map(signedOrderToAssetPair);
48 | let nonPaginatedFilteredAssetPairs: AssetPairsItem[];
49 | if (assetDataA === undefined && assetDataB === undefined) {
50 | nonPaginatedFilteredAssetPairs = assetPairsItems;
51 | } else if (assetDataA !== undefined && assetDataB !== undefined) {
52 | const containsAssetDataAAndAssetDataB = (assetPair: AssetPairsItem) =>
53 | (assetPair.assetDataA.assetData === assetDataA && assetPair.assetDataB.assetData === assetDataB) ||
54 | (assetPair.assetDataA.assetData === assetDataB && assetPair.assetDataB.assetData === assetDataA);
55 | nonPaginatedFilteredAssetPairs = assetPairsItems.filter(containsAssetDataAAndAssetDataB);
56 | } else {
57 | const assetData = assetDataA || assetDataB;
58 | const containsAssetData = (assetPair: AssetPairsItem) =>
59 | assetPair.assetDataA.assetData === assetData || assetPair.assetDataB.assetData === assetData;
60 | nonPaginatedFilteredAssetPairs = assetPairsItems.filter(containsAssetData);
61 | }
62 | const uniqueNonPaginatedFilteredAssetPairs = _.uniqWith(nonPaginatedFilteredAssetPairs, _.isEqual.bind(_));
63 | const paginatedFilteredAssetPairs = paginate(uniqueNonPaginatedFilteredAssetPairs, page, perPage);
64 | return paginatedFilteredAssetPairs;
65 | }
66 | // tslint:disable-next-line:prefer-function-over-method
67 | public async getOrderBookAsync(
68 | page: number,
69 | perPage: number,
70 | baseAssetData: string,
71 | quoteAssetData: string,
72 | ): Promise {
73 | const connection = getDBConnection();
74 | const bidSignedOrderModels = (await connection.manager.find(SignedOrderModel, {
75 | where: { takerAssetData: baseAssetData, makerAssetData: quoteAssetData },
76 | })) as Array>;
77 | const askSignedOrderModels = (await connection.manager.find(SignedOrderModel, {
78 | where: { takerAssetData: quoteAssetData, makerAssetData: baseAssetData },
79 | })) as Array>;
80 | const bidApiOrders: APIOrder[] = bidSignedOrderModels
81 | .map(deserializeOrderToAPIOrder)
82 | .sort((orderA, orderB) => compareBidOrder(orderA.order, orderB.order));
83 | const askApiOrders: APIOrder[] = askSignedOrderModels
84 | .map(deserializeOrderToAPIOrder)
85 | .sort((orderA, orderB) => compareAskOrder(orderA.order, orderB.order));
86 | const paginatedBidApiOrders = paginate(bidApiOrders, page, perPage);
87 | const paginatedAskApiOrders = paginate(askApiOrders, page, perPage);
88 | return {
89 | bids: paginatedBidApiOrders,
90 | asks: paginatedAskApiOrders,
91 | };
92 | }
93 | // TODO:(leo) Do all filtering and pagination in a DB (requires stored procedures or redundant fields)
94 | // tslint:disable-next-line:prefer-function-over-method
95 | public async getOrdersAsync(
96 | page: number,
97 | perPage: number,
98 | ordersFilterParams: OrdersRequestOpts,
99 | ): Promise> {
100 | const connection = getDBConnection();
101 | // Pre-filters
102 | const filterObjectWithValuesIfExist: Partial = {
103 | exchangeAddress: ordersFilterParams.exchangeAddress,
104 | senderAddress: ordersFilterParams.senderAddress,
105 | makerAssetData: ordersFilterParams.makerAssetData,
106 | takerAssetData: ordersFilterParams.takerAssetData,
107 | makerAddress: ordersFilterParams.makerAddress,
108 | takerAddress: ordersFilterParams.takerAddress,
109 | feeRecipientAddress: ordersFilterParams.feeRecipientAddress,
110 | makerFeeAssetData: ordersFilterParams.makerFeeAssetData,
111 | takerFeeAssetData: ordersFilterParams.takerFeeAssetData,
112 | };
113 | const filterObject = _.pickBy(filterObjectWithValuesIfExist, _.identity.bind(_));
114 | const signedOrderModels = (await connection.manager.find(SignedOrderModel, { where: filterObject })) as Array<
115 | Required
116 | >;
117 | let apiOrders = _.map(signedOrderModels, deserializeOrderToAPIOrder);
118 | // Post-filters
119 | apiOrders = apiOrders
120 | .filter(
121 | // traderAddress
122 | apiOrder =>
123 | ordersFilterParams.traderAddress === undefined ||
124 | apiOrder.order.makerAddress === ordersFilterParams.traderAddress ||
125 | apiOrder.order.takerAddress === ordersFilterParams.traderAddress,
126 | )
127 | .filter(
128 | // makerAssetAddress
129 | apiOrder =>
130 | ordersFilterParams.makerAssetAddress === undefined ||
131 | includesTokenAddress(apiOrder.order.makerAssetData, ordersFilterParams.makerAssetAddress),
132 | )
133 | .filter(
134 | // takerAssetAddress
135 | apiOrder =>
136 | ordersFilterParams.takerAssetAddress === undefined ||
137 | includesTokenAddress(apiOrder.order.takerAssetData, ordersFilterParams.takerAssetAddress),
138 | )
139 | .filter(
140 | // makerAssetProxyId
141 | apiOrder =>
142 | ordersFilterParams.makerAssetProxyId === undefined ||
143 | assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.makerAssetData).assetProxyId ===
144 | ordersFilterParams.makerAssetProxyId,
145 | )
146 | .filter(
147 | // takerAssetProxyId
148 | apiOrder =>
149 | ordersFilterParams.takerAssetProxyId === undefined ||
150 | assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.takerAssetData).assetProxyId ===
151 | ordersFilterParams.takerAssetProxyId,
152 | );
153 | const paginatedApiOrders = paginate(apiOrders, page, perPage);
154 | return paginatedApiOrders;
155 | }
156 | constructor(meshClient: WSClient) {
157 | this._meshClient = meshClient;
158 | }
159 | public async addOrderAsync(signedOrder: SignedOrder): Promise {
160 | const { rejected } = await this._meshClient.addOrdersAsync([signedOrder], true);
161 | if (rejected.length !== 0) {
162 | throw new ValidationError([
163 | {
164 | field: 'signedOrder',
165 | code: MeshUtils.rejectedCodeToSRACode(rejected[0].status.code),
166 | reason: `${rejected[0].status.code}: ${rejected[0].status.message}`,
167 | },
168 | ]);
169 | }
170 | // Order Watcher Service will handle persistence
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/ts/src/services/orderbook_utils.ts:
--------------------------------------------------------------------------------
1 | import { APIOrder } from '@0x/connect';
2 | import { assetDataUtils } from '@0x/order-utils';
3 | import { Asset, AssetPairsItem, AssetProxyId, SignedOrder } from '@0x/types';
4 | import { BigNumber, errorUtils } from '@0x/utils';
5 |
6 | import { CHAIN_ID, DEFAULT_ERC20_TOKEN_PRECISION } from '../config';
7 | import { MAX_TOKEN_SUPPLY_POSSIBLE } from '../constants';
8 | import { SignedOrderModel } from '../models/SignedOrderModel';
9 | import { APIOrderWithMetaData } from '../types';
10 |
11 | const DEFAULT_ERC721_ASSET = {
12 | minAmount: new BigNumber(0),
13 | maxAmount: new BigNumber(1),
14 | precision: 0,
15 | };
16 | const DEFAULT_ERC20_ASSET = {
17 | minAmount: new BigNumber(0),
18 | maxAmount: MAX_TOKEN_SUPPLY_POSSIBLE,
19 | precision: DEFAULT_ERC20_TOKEN_PRECISION,
20 | };
21 |
22 | export const compareAskOrder = (orderA: SignedOrder, orderB: SignedOrder): number => {
23 | const orderAPrice = orderA.takerAssetAmount.div(orderA.makerAssetAmount);
24 | const orderBPrice = orderB.takerAssetAmount.div(orderB.makerAssetAmount);
25 | if (!orderAPrice.isEqualTo(orderBPrice)) {
26 | return orderAPrice.comparedTo(orderBPrice);
27 | }
28 |
29 | return compareOrderByFeeRatio(orderA, orderB);
30 | };
31 |
32 | export const compareBidOrder = (orderA: SignedOrder, orderB: SignedOrder): number => {
33 | const orderAPrice = orderA.makerAssetAmount.div(orderA.takerAssetAmount);
34 | const orderBPrice = orderB.makerAssetAmount.div(orderB.takerAssetAmount);
35 | if (!orderAPrice.isEqualTo(orderBPrice)) {
36 | return orderBPrice.comparedTo(orderAPrice);
37 | }
38 |
39 | return compareOrderByFeeRatio(orderA, orderB);
40 | };
41 |
42 | export const compareOrderByFeeRatio = (orderA: SignedOrder, orderB: SignedOrder): number => {
43 | const orderAFeePrice = orderA.takerFee.div(orderA.takerAssetAmount);
44 | const orderBFeePrice = orderB.takerFee.div(orderB.takerAssetAmount);
45 | if (!orderAFeePrice.isEqualTo(orderBFeePrice)) {
46 | return orderBFeePrice.comparedTo(orderAFeePrice);
47 | }
48 |
49 | return orderA.expirationTimeSeconds.comparedTo(orderB.expirationTimeSeconds);
50 | };
51 |
52 | export const includesTokenAddress = (assetData: string, tokenAddress: string): boolean => {
53 | const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
54 | if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
55 | for (const [, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
56 | if (includesTokenAddress(nestedAssetDataElement, tokenAddress)) {
57 | return true;
58 | }
59 | }
60 | return false;
61 | } else if (!assetDataUtils.isStaticCallAssetData(decodedAssetData)) {
62 | return decodedAssetData.tokenAddress === tokenAddress;
63 | }
64 | return false;
65 | };
66 |
67 | export const deserializeOrder = (signedOrderModel: Required): SignedOrder => {
68 | const signedOrder: SignedOrder = {
69 | signature: signedOrderModel.signature,
70 | senderAddress: signedOrderModel.senderAddress,
71 | makerAddress: signedOrderModel.makerAddress,
72 | takerAddress: signedOrderModel.takerAddress,
73 | makerFee: new BigNumber(signedOrderModel.makerFee),
74 | takerFee: new BigNumber(signedOrderModel.takerFee),
75 | makerAssetAmount: new BigNumber(signedOrderModel.makerAssetAmount),
76 | takerAssetAmount: new BigNumber(signedOrderModel.takerAssetAmount),
77 | makerAssetData: signedOrderModel.makerAssetData,
78 | takerAssetData: signedOrderModel.takerAssetData,
79 | salt: new BigNumber(signedOrderModel.salt),
80 | exchangeAddress: signedOrderModel.exchangeAddress,
81 | feeRecipientAddress: signedOrderModel.feeRecipientAddress,
82 | expirationTimeSeconds: new BigNumber(signedOrderModel.expirationTimeSeconds),
83 | makerFeeAssetData: signedOrderModel.makerFeeAssetData,
84 | takerFeeAssetData: signedOrderModel.takerFeeAssetData,
85 | chainId: CHAIN_ID,
86 | };
87 | return signedOrder;
88 | };
89 |
90 | export const deserializeOrderToAPIOrder = (signedOrderModel: Required): APIOrder => {
91 | const order = deserializeOrder(signedOrderModel);
92 | const apiOrder: APIOrder = {
93 | order,
94 | metaData: {
95 | orderHash: signedOrderModel.hash,
96 | remainingFillableTakerAssetAmount: signedOrderModel.remainingFillableTakerAssetAmount,
97 | },
98 | };
99 | return apiOrder;
100 | };
101 |
102 | export const serializeOrder = (apiOrder: APIOrderWithMetaData): SignedOrderModel => {
103 | const signedOrder = apiOrder.order;
104 | const signedOrderModel = new SignedOrderModel({
105 | signature: signedOrder.signature,
106 | senderAddress: signedOrder.senderAddress,
107 | makerAddress: signedOrder.makerAddress,
108 | takerAddress: signedOrder.takerAddress,
109 | makerAssetAmount: signedOrder.makerAssetAmount.toString(),
110 | takerAssetAmount: signedOrder.takerAssetAmount.toString(),
111 | makerAssetData: signedOrder.makerAssetData,
112 | takerAssetData: signedOrder.takerAssetData,
113 | makerFee: signedOrder.makerFee.toString(),
114 | takerFee: signedOrder.takerFee.toString(),
115 | makerFeeAssetData: signedOrder.makerFeeAssetData.toString(),
116 | takerFeeAssetData: signedOrder.takerFeeAssetData.toString(),
117 | salt: signedOrder.salt.toString(),
118 | exchangeAddress: signedOrder.exchangeAddress,
119 | feeRecipientAddress: signedOrder.feeRecipientAddress,
120 | expirationTimeSeconds: signedOrder.expirationTimeSeconds.toString(),
121 | hash: apiOrder.metaData.orderHash,
122 | remainingFillableTakerAssetAmount: apiOrder.metaData.remainingFillableTakerAssetAmount.toString(),
123 | });
124 | return signedOrderModel;
125 | };
126 |
127 | const erc721AssetDataToAsset = (assetData: string): Asset => ({
128 | ...DEFAULT_ERC721_ASSET,
129 | assetData,
130 | });
131 |
132 | const erc20AssetDataToAsset = (assetData: string): Asset => ({
133 | ...DEFAULT_ERC20_ASSET,
134 | assetData,
135 | });
136 |
137 | const assetDataToAsset = (assetData: string): Asset => {
138 | const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
139 | let asset: Asset;
140 | switch (assetProxyId) {
141 | case AssetProxyId.ERC20:
142 | asset = erc20AssetDataToAsset(assetData);
143 | break;
144 | case AssetProxyId.ERC721:
145 | asset = erc721AssetDataToAsset(assetData);
146 | break;
147 | default:
148 | throw errorUtils.spawnSwitchErr('assetProxyId', assetProxyId);
149 | }
150 | return asset;
151 | };
152 |
153 | export const signedOrderToAssetPair = (signedOrder: SignedOrder): AssetPairsItem => {
154 | return {
155 | assetDataA: assetDataToAsset(signedOrder.makerAssetData),
156 | assetDataB: assetDataToAsset(signedOrder.takerAssetData),
157 | };
158 | };
159 |
--------------------------------------------------------------------------------
/ts/src/services/websocket_service.ts:
--------------------------------------------------------------------------------
1 | import { schemas } from '@0x/json-schemas';
2 | import { WSClient } from '@0x/mesh-rpc-client';
3 | import { assetDataUtils } from '@0x/order-utils';
4 | import {
5 | APIOrder,
6 | AssetProxyId,
7 | OrdersChannelMessageTypes,
8 | OrdersChannelSubscriptionOpts,
9 | SignedOrder,
10 | WebsocketConnectionEventType,
11 | } from '@0x/types';
12 | import * as http from 'http';
13 | import * as WebSocket from 'ws';
14 |
15 | import { MalformedJSONError, NotImplementedError } from '../errors';
16 | import { MeshUtils } from '../mesh_utils';
17 | import { generateError } from '../middleware/error_handling';
18 | import {
19 | MessageChannels,
20 | MessageTypes,
21 | OrderChannelRequest,
22 | UpdateOrdersChannelMessageWithChannel,
23 | WebsocketSRAOpts,
24 | } from '../types';
25 | import { utils } from '../utils';
26 |
27 | interface WrappedWebSocket extends WebSocket {
28 | isAlive: boolean;
29 | requestIds: Set;
30 | }
31 |
32 | const DEFAULT_OPTS: WebsocketSRAOpts = {
33 | pongInterval: 5000,
34 | };
35 |
36 | type ALL_SUBSCRIPTION_OPTS = 'ALL_SUBSCRIPTION_OPTS';
37 |
38 | export class WebsocketService {
39 | private readonly _server: WebSocket.Server;
40 | private readonly _meshClient: WSClient;
41 | private readonly _pongIntervalId: number;
42 | private readonly _requestIdToSocket: Map = new Map(); // requestId to WebSocket mapping
43 | private readonly _requestIdToSubscriptionOpts: Map<
44 | string,
45 | OrdersChannelSubscriptionOpts | ALL_SUBSCRIPTION_OPTS
46 | > = new Map(); // requestId -> { base, quote }
47 | private _meshSubscriptionId?: string;
48 | private static _decodedContractAndAssetData(assetData: string): { assetProxyId: string; data: string[] } {
49 | let data: string[] = [assetData];
50 | const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
51 | if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
52 | for (const nested of decodedAssetData.nestedAssetData) {
53 | data = [...data, ...WebsocketService._decodedContractAndAssetData(nested).data];
54 | }
55 | } else if (assetDataUtils.isStaticCallAssetData(decodedAssetData)) {
56 | // do nothing
57 | } else {
58 | data = [...data, decodedAssetData.tokenAddress];
59 | }
60 | return { data, assetProxyId: decodedAssetData.assetProxyId };
61 | }
62 | private static _matchesOrdersChannelSubscription(
63 | order: SignedOrder,
64 | opts: OrdersChannelSubscriptionOpts | ALL_SUBSCRIPTION_OPTS,
65 | ): boolean {
66 | if (opts === 'ALL_SUBSCRIPTION_OPTS') {
67 | return true;
68 | }
69 | const { makerAssetData, takerAssetData } = order;
70 | const makerAssetDataTakerAssetData = [makerAssetData, takerAssetData];
71 | // Handle the specific, unambiguous asset datas
72 | // traderAssetData?: string;
73 | if (opts.traderAssetData && makerAssetDataTakerAssetData.includes(opts.traderAssetData)) {
74 | return true;
75 | }
76 | // makerAssetData?: string;
77 | // takerAssetData?: string;
78 | if (
79 | opts.makerAssetData &&
80 | opts.takerAssetData &&
81 | makerAssetDataTakerAssetData.includes(opts.makerAssetData) &&
82 | makerAssetDataTakerAssetData.includes(opts.takerAssetData)
83 | ) {
84 | return true;
85 | }
86 | // makerAssetAddress?: string;
87 | // takerAssetAddress?: string;
88 | const makerContractAndAssetData = WebsocketService._decodedContractAndAssetData(makerAssetData);
89 | const takerContractAndAssetData = WebsocketService._decodedContractAndAssetData(takerAssetData);
90 | if (
91 | opts.makerAssetAddress &&
92 | opts.takerAssetAddress &&
93 | makerContractAndAssetData.assetProxyId !== AssetProxyId.MultiAsset &&
94 | makerContractAndAssetData.assetProxyId !== AssetProxyId.StaticCall &&
95 | takerContractAndAssetData.assetProxyId !== AssetProxyId.MultiAsset &&
96 | takerContractAndAssetData.assetProxyId !== AssetProxyId.StaticCall &&
97 | makerContractAndAssetData.data.includes(opts.makerAssetAddress) &&
98 | takerContractAndAssetData.data.includes(opts.takerAssetAddress)
99 | ) {
100 | return true;
101 | }
102 |
103 | // TODO (dekz)handle MAP
104 | // makerAssetProxyId?: string;
105 | // takerAssetProxyId?: string;
106 | return false;
107 | }
108 | constructor(server: http.Server, meshClient: WSClient, opts?: WebsocketSRAOpts) {
109 | const wsOpts = {
110 | ...DEFAULT_OPTS,
111 | ...opts,
112 | };
113 | this._server = new WebSocket.Server({ server });
114 | this._server.on('connection', this._processConnection.bind(this));
115 | this._pongIntervalId = setInterval(this._cleanupConnections.bind(this), wsOpts.pongInterval);
116 | this._meshClient = meshClient;
117 | this._meshClient
118 | .subscribeToOrdersAsync(e => this.orderUpdate(MeshUtils.orderInfosToApiOrders(e)))
119 | .then(subscriptionId => (this._meshSubscriptionId = subscriptionId));
120 | }
121 | public destroy(): void {
122 | clearInterval(this._pongIntervalId);
123 | for (const ws of this._server.clients) {
124 | ws.terminate();
125 | }
126 | this._requestIdToSocket.clear();
127 | this._requestIdToSubscriptionOpts.clear();
128 | this._server.close();
129 | if (this._meshSubscriptionId) {
130 | void this._meshClient.unsubscribeAsync(this._meshSubscriptionId);
131 | }
132 | }
133 | public orderUpdate(apiOrders: APIOrder[]): void {
134 | if (this._server.clients.size === 0) {
135 | return;
136 | }
137 | const response: Partial = {
138 | type: OrdersChannelMessageTypes.Update,
139 | channel: MessageChannels.Orders,
140 | payload: apiOrders,
141 | };
142 | for (const order of apiOrders) {
143 | // Future optimisation is to invert this structure so the order isn't duplicated over many request ids
144 | // order->requestIds it is less likely to get multiple order updates and more likely
145 | // to have many subscribers and a single order
146 | const requestIdToOrders: { [requestId: string]: Set } = {};
147 | for (const [requestId, subscriptionOpts] of this._requestIdToSubscriptionOpts) {
148 | if (WebsocketService._matchesOrdersChannelSubscription(order.order, subscriptionOpts)) {
149 | if (requestIdToOrders[requestId]) {
150 | const orderSet = requestIdToOrders[requestId];
151 | orderSet.add(order);
152 | } else {
153 | const orderSet = new Set();
154 | orderSet.add(order);
155 | requestIdToOrders[requestId] = orderSet;
156 | }
157 | }
158 | }
159 | for (const [requestId, orders] of Object.entries(requestIdToOrders)) {
160 | const ws = this._requestIdToSocket.get(requestId);
161 | if (ws) {
162 | ws.send(JSON.stringify({ ...response, payload: Array.from(orders), requestId }));
163 | }
164 | }
165 | }
166 | }
167 | private _processConnection(ws: WrappedWebSocket, _req: http.IncomingMessage): void {
168 | ws.on('pong', this._pongHandler(ws).bind(this));
169 | ws.on(WebsocketConnectionEventType.Message, this._messageHandler(ws).bind(this));
170 | ws.on(WebsocketConnectionEventType.Close, this._closeHandler(ws).bind(this));
171 | ws.isAlive = true;
172 | ws.requestIds = new Set();
173 | }
174 | private _processMessage(ws: WrappedWebSocket, data: WebSocket.Data): void {
175 | let message: OrderChannelRequest;
176 | try {
177 | message = JSON.parse(data.toString());
178 | } catch (e) {
179 | throw new MalformedJSONError();
180 | }
181 |
182 | utils.validateSchema(message, schemas.relayerApiOrdersChannelSubscribeSchema);
183 | const { requestId, payload, type } = message;
184 | switch (type) {
185 | case MessageTypes.Subscribe:
186 | ws.requestIds.add(requestId);
187 | const subscriptionOpts = payload || 'ALL_SUBSCRIPTION_OPTS';
188 | this._requestIdToSubscriptionOpts.set(requestId, subscriptionOpts);
189 | this._requestIdToSocket.set(requestId, ws);
190 | break;
191 | default:
192 | throw new NotImplementedError(message.type);
193 | }
194 | }
195 | private _cleanupConnections(): void {
196 | // Ping every connection and if it is unresponsive
197 | // terminate it during the next check
198 | for (const ws of this._server.clients) {
199 | if (!(ws as WrappedWebSocket).isAlive) {
200 | ws.terminate();
201 | } else {
202 | (ws as WrappedWebSocket).isAlive = false;
203 | ws.ping();
204 | }
205 | }
206 | }
207 | private _messageHandler(ws: WrappedWebSocket): (data: WebSocket.Data) => void {
208 | return (data: WebSocket.Data) => {
209 | try {
210 | this._processMessage(ws, data);
211 | } catch (err) {
212 | this._processError(ws, err);
213 | }
214 | };
215 | }
216 | // tslint:disable-next-line:prefer-function-over-method
217 | private _processError(ws: WrappedWebSocket, err: Error): void {
218 | const { errorBody } = generateError(err);
219 | ws.send(JSON.stringify(errorBody));
220 | ws.terminate();
221 | }
222 | // tslint:disable-next-line:prefer-function-over-method
223 | private _pongHandler(ws: WrappedWebSocket): () => void {
224 | return () => {
225 | ws.isAlive = true;
226 | };
227 | }
228 | private _closeHandler(ws: WrappedWebSocket): () => void {
229 | return () => {
230 | for (const requestId of ws.requestIds) {
231 | this._requestIdToSocket.delete(requestId);
232 | this._requestIdToSubscriptionOpts.delete(requestId);
233 | }
234 | };
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/ts/src/types.ts:
--------------------------------------------------------------------------------
1 | import { AcceptedOrderInfo, RejectedOrderInfo } from '@0x/mesh-rpc-client';
2 | import { APIOrder, OrdersChannelSubscriptionOpts, UpdateOrdersChannelMessage } from '@0x/types';
3 | import { BigNumber } from '@0x/utils';
4 |
5 | export enum OrderWatcherLifeCycleEvents {
6 | Added,
7 | Removed,
8 | Updated,
9 | }
10 |
11 | export type onOrdersUpdateCallback = (orders: APIOrderWithMetaData[]) => void;
12 |
13 | export interface AcceptedRejectedResults {
14 | accepted: AcceptedOrderInfo[];
15 | rejected: RejectedOrderInfo[];
16 | }
17 |
18 | export interface APIOrderMetaData {
19 | orderHash: string;
20 | remainingFillableTakerAssetAmount: BigNumber;
21 | }
22 |
23 | export interface APIOrderWithMetaData extends APIOrder {
24 | metaData: APIOrderMetaData;
25 | }
26 |
27 | export interface WebsocketSRAOpts {
28 | pongInterval?: number;
29 | }
30 |
31 | export interface OrderChannelRequest {
32 | type: string;
33 | channel: MessageChannels;
34 | requestId: string;
35 | payload?: OrdersChannelSubscriptionOpts;
36 | }
37 |
38 | export enum MessageTypes {
39 | Subscribe = 'subscribe',
40 | }
41 |
42 | export enum MessageChannels {
43 | Orders = 'orders',
44 | }
45 | export interface UpdateOrdersChannelMessageWithChannel extends UpdateOrdersChannelMessage {
46 | channel: MessageChannels;
47 | }
48 |
--------------------------------------------------------------------------------
/ts/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { Schema, SchemaValidator } from '@0x/json-schemas';
2 | import { ValidationError as SchemaValidationError } from 'jsonschema';
3 | import * as _ from 'lodash';
4 |
5 | import { ValidationError, ValidationErrorCodes, ValidationErrorItem } from './errors';
6 |
7 | const schemaValidator = new SchemaValidator();
8 |
9 | export const utils = {
10 | log: (...args: any[]) => {
11 | // tslint:disable-next-line:no-console
12 | console.log(...args);
13 | },
14 | validateSchema(instance: any, schema: Schema): void {
15 | const validationResult = schemaValidator.validate(instance, schema);
16 | if (_.isEmpty(validationResult.errors)) {
17 | return;
18 | } else {
19 | const validationErrorItems = _.map(
20 | validationResult.errors,
21 | (schemaValidationError: SchemaValidationError) =>
22 | schemaValidationErrorToValidationErrorItem(schemaValidationError),
23 | );
24 | throw new ValidationError(validationErrorItems);
25 | }
26 | },
27 | async delayAsync(ms: number): Promise {
28 | // tslint:disable:no-inferred-empty-object-type
29 | return new Promise(resolve => setTimeout(resolve, ms));
30 | },
31 | async attemptAsync(
32 | fn: () => Promise,
33 | opts: { interval: number; maxRetries: number } = { interval: 1000, maxRetries: 10 },
34 | ): Promise {
35 | let result: T | undefined;
36 | let attempt = 0;
37 | let error;
38 | let isSuccess = false;
39 | while (!result && attempt < opts.maxRetries) {
40 | attempt++;
41 | try {
42 | result = await fn();
43 | isSuccess = true;
44 | error = undefined;
45 | } catch (err) {
46 | error = err;
47 | await utils.delayAsync(opts.interval);
48 | }
49 | }
50 | if (!isSuccess) {
51 | throw error;
52 | }
53 | return result as T;
54 | },
55 | };
56 |
57 | function schemaValidationErrorToValidationErrorItem(schemaValidationError: SchemaValidationError): ValidationErrorItem {
58 | if (
59 | _.includes(
60 | [
61 | 'type',
62 | 'anyOf',
63 | 'allOf',
64 | 'oneOf',
65 | 'additionalProperties',
66 | 'minProperties',
67 | 'maxProperties',
68 | 'pattern',
69 | 'format',
70 | 'uniqueItems',
71 | 'items',
72 | 'dependencies',
73 | ],
74 | schemaValidationError.name,
75 | )
76 | ) {
77 | return {
78 | field: schemaValidationError.property,
79 | code: ValidationErrorCodes.IncorrectFormat,
80 | reason: schemaValidationError.message,
81 | };
82 | } else if (
83 | _.includes(
84 | ['minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems', 'enum', 'const'],
85 | schemaValidationError.name,
86 | )
87 | ) {
88 | return {
89 | field: schemaValidationError.property,
90 | code: ValidationErrorCodes.ValueOutOfRange,
91 | reason: schemaValidationError.message,
92 | };
93 | } else if (schemaValidationError.name === 'required') {
94 | return {
95 | field: schemaValidationError.argument,
96 | code: ValidationErrorCodes.RequiredField,
97 | reason: schemaValidationError.message,
98 | };
99 | } else if (schemaValidationError.name === 'not') {
100 | return {
101 | field: schemaValidationError.property,
102 | code: ValidationErrorCodes.UnsupportedOption,
103 | reason: schemaValidationError.message,
104 | };
105 | } else {
106 | throw new Error(`Unknnown schema validation error name: ${schemaValidationError.name}`);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": ["es2017", "dom"],
5 | "target": "es5",
6 | "outDir": "lib",
7 | "strict": true,
8 | "downlevelIteration": true,
9 | "noUnusedLocals": true,
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "sourceMap": false,
13 | "typeRoots": ["../node_modules/@0x/typescript-typings/types", "../node_modules/@types"],
14 | "noUnusedParameters": true
15 | },
16 | "include": ["src"]
17 | }
18 |
--------------------------------------------------------------------------------
/ts/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@0x/tslint-config"]
3 | }
4 |
--------------------------------------------------------------------------------