diff options
author | Fabio Berger <me@fabioberger.com> | 2018-08-17 06:10:12 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-08-17 06:10:12 +0800 |
commit | 0337b5a40104ef83ebd527184785def7f399d573 (patch) | |
tree | 1fcbd0effd3ad4a88e99a41082825818e3f02302 /packages | |
parent | e47e9c5b34a2c189b6913c4c7082cb7ec256617f (diff) | |
parent | 72752bcb68bac29ff5897ed57de357ad7af09abe (diff) | |
download | dexon-0x-contracts-0337b5a40104ef83ebd527184785def7f399d573.tar.gz dexon-0x-contracts-0337b5a40104ef83ebd527184785def7f399d573.tar.zst dexon-0x-contracts-0337b5a40104ef83ebd527184785def7f399d573.zip |
Merge development
Diffstat (limited to 'packages')
50 files changed, 651 insertions, 214 deletions
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 006c1ed27..f1170ae5e 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -57,7 +57,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "json-loader": "^0.5.4", "make-promises-safe": "^1.1.0", @@ -70,7 +70,7 @@ "source-map-support": "^0.5.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2", + "typescript": "3.0.1", "webpack": "^3.1.0" }, "dependencies": { diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 3dae2941b..5d5fc146d 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -54,14 +54,14 @@ "@types/tmp": "^0.0.33", "@types/yargs": "^10.0.0", "chai": "^4.1.2", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^5.2.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/assert/package.json b/packages/assert/package.json index b2099c967..43e9380ec 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -34,7 +34,7 @@ "@types/mocha": "^2.2.42", "@types/valid-url": "^1.0.2", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -42,7 +42,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/json-schemas": "^1.0.1-rc.4", diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 419db34f9..df4b76e88 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -32,13 +32,13 @@ "@0xproject/tslint-config": "^1.0.5", "@types/lodash": "4.14.104", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/typescript-typings": "^1.0.4", diff --git a/packages/connect/package.json b/packages/connect/package.json index d0f824dd9..4e61ed361 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@0xproject/tslint-config": "^1.0.5", - "@types/fetch-mock": "^5.12.2", + "@types/fetch-mock": "^6.0.3", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/query-string": "^5.0.1", @@ -66,7 +66,7 @@ "async-child-process": "^1.1.1", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "fetch-mock": "^5.13.1", "make-promises-safe": "^1.1.0", @@ -76,7 +76,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index 8316d4733..401157dff 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -60,7 +60,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -72,7 +72,7 @@ "source-map-support": "^0.5.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2", + "typescript": "3.0.1", "web3-provider-engine": "14.0.6" }, "dependencies": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 799f72d9e..8bd808a27 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -59,7 +59,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -68,7 +68,7 @@ "solc": "^0.4.24", "solhint": "^1.2.1", "tslint": "5.11.0", - "typescript": "2.9.2", + "typescript": "3.0.1", "yargs": "^10.0.3" }, "dependencies": { diff --git a/packages/contracts/test/exchange/fill_order.ts b/packages/contracts/test/exchange/fill_order.ts index 1494fe093..e79e2239e 100644 --- a/packages/contracts/test/exchange/fill_order.ts +++ b/packages/contracts/test/exchange/fill_order.ts @@ -104,6 +104,17 @@ describe('FillOrder Tests', () => { }; await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); }); + it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount with zero decimals', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Small, + makerAssetDataScenario: AssetDataScenario.ERC20ZeroDecimals, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { const fillScenario = { ...defaultFillScenario, diff --git a/packages/contracts/test/utils/fill_order_combinatorial_utils.ts b/packages/contracts/test/utils/fill_order_combinatorial_utils.ts index 284c4a2db..f18ad0dd3 100644 --- a/packages/contracts/test/utils/fill_order_combinatorial_utils.ts +++ b/packages/contracts/test/utils/fill_order_combinatorial_utils.ts @@ -81,6 +81,12 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( erc20FiveDecimalTokenCount, fiveDecimals, ); + const zeroDecimals = new BigNumber(0); + const erc20ZeroDecimalTokenCount = 2; + const [erc20ZeroDecimalTokenA, erc20ZeroDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( + erc20ZeroDecimalTokenCount, + zeroDecimals, + ); const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -119,6 +125,7 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( zrxToken.address, [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address], [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address], + [erc20ZeroDecimalTokenA.address, erc20ZeroDecimalTokenB.address], erc721Token, erc721Balances, exchangeContract.address, diff --git a/packages/contracts/test/utils/order_factory_from_scenario.ts b/packages/contracts/test/utils/order_factory_from_scenario.ts index a908140b9..8e04db588 100644 --- a/packages/contracts/test/utils/order_factory_from_scenario.ts +++ b/packages/contracts/test/utils/order_factory_from_scenario.ts @@ -21,6 +21,8 @@ const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000) const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000); const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000); const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000); +const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10); +const ONE_THOUSAND_UNITS_ZERO_DECIMALS = new BigNumber(1000); const ONE_NFT_UNIT = new BigNumber(1); export class OrderFactoryFromScenario { @@ -28,6 +30,7 @@ export class OrderFactoryFromScenario { private readonly _zrxAddress: string; private readonly _nonZrxERC20EighteenDecimalTokenAddresses: string[]; private readonly _erc20FiveDecimalTokenAddresses: string[]; + private readonly _erc20ZeroDecimalTokenAddresses: string[]; private readonly _erc721Token: DummyERC721TokenContract; private readonly _erc721Balances: ERC721TokenIdsByOwner; private readonly _exchangeAddress: string; @@ -36,6 +39,7 @@ export class OrderFactoryFromScenario { zrxAddress: string, nonZrxERC20EighteenDecimalTokenAddresses: string[], erc20FiveDecimalTokenAddresses: string[], + erc20ZeroDecimalTokenAddresses: string[], erc721Token: DummyERC721TokenContract, erc721Balances: ERC721TokenIdsByOwner, exchangeAddress: string, @@ -44,6 +48,7 @@ export class OrderFactoryFromScenario { this._zrxAddress = zrxAddress; this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses; this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses; + this._erc20ZeroDecimalTokenAddresses = erc20ZeroDecimalTokenAddresses; this._erc721Token = erc721Token; this._erc721Balances = erc721Balances; this._exchangeAddress = exchangeAddress; @@ -89,6 +94,9 @@ export class OrderFactoryFromScenario { erc721MakerAssetIds[0], ); break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[0]); + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -109,6 +117,9 @@ export class OrderFactoryFromScenario { erc721TakerAssetIds[0], ); break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[1]); + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } @@ -126,6 +137,9 @@ export class OrderFactoryFromScenario { case AssetDataScenario.ERC721: makerAssetAmount = ONE_NFT_UNIT; break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -142,6 +156,9 @@ export class OrderFactoryFromScenario { case AssetDataScenario.ERC721: makerAssetAmount = ONE_NFT_UNIT; break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -166,6 +183,9 @@ export class OrderFactoryFromScenario { case AssetDataScenario.ERC721: takerAssetAmount = ONE_NFT_UNIT; break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } @@ -182,6 +202,9 @@ export class OrderFactoryFromScenario { case AssetDataScenario.ERC721: takerAssetAmount = ONE_NFT_UNIT; break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } diff --git a/packages/contracts/test/utils/types.ts b/packages/contracts/test/utils/types.ts index 67313b647..481ee87d6 100644 --- a/packages/contracts/test/utils/types.ts +++ b/packages/contracts/test/utils/types.ts @@ -177,10 +177,11 @@ export enum ExpirationTimeSecondsScenario { } export enum AssetDataScenario { - ERC721 = 'ERC721', + ERC20ZeroDecimals = 'ERC20_ZERO_DECIMALS', ZRXFeeToken = 'ZRX_FEE_TOKEN', ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS', ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS', + ERC721 = 'ERC721', } export enum TakerAssetFillAmountScenario { diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 40a13e340..6bdf3a37d 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -33,14 +33,14 @@ "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", "npm-run-all": "^4.1.2", "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/subproviders": "^1.0.5", diff --git a/packages/ethereum-types/package.json b/packages/ethereum-types/package.json index b5de3f1ed..98596929f 100644 --- a/packages/ethereum-types/package.json +++ b/packages/ethereum-types/package.json @@ -31,11 +31,11 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/ethereum-types/README.md", "devDependencies": { "@0xproject/tslint-config": "^1.0.5", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@types/node": "^8.0.53", diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index 2e0b20757..122499db6 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -31,12 +31,12 @@ "@0xproject/sol-compiler": "^1.0.5", "@0xproject/tslint-config": "^1.0.5", "@types/lodash": "4.14.104", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/base-contract": "^2.0.0-rc.1", diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 011b73046..ec79d40cb 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -50,7 +50,7 @@ "@types/lodash.values": "^4.3.3", "@types/mocha": "^2.2.42", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "lodash.foreach": "^4.5.0", "make-promises-safe": "^1.1.0", @@ -60,7 +60,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 6900da198..322708b50 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -56,6 +56,6 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" } } diff --git a/packages/migrations/package.json b/packages/migrations/package.json index dd731b769..f196ffad6 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -42,12 +42,12 @@ "@0xproject/tslint-config": "^1.0.5", "@0xproject/types": "^1.0.1-rc.4", "@types/yargs": "^10.0.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2", + "typescript": "3.0.1", "yargs": "^10.0.3" }, "dependencies": { diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index a5b03ac80..41a6e7c91 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -43,7 +43,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@lerna/batch-packages": "^3.0.0-beta.18", diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index a81eb077d..86f0da65a 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.1-rc.4", + "changes": [ + { + "note": "Remove rounding error being thrown when maker amount is very small", + "pr": 959 + }, + { + "note": "Added rateUtils and sortingUtils", + "pr": 953 + }, + { + "note": + "Update marketUtils api such that all optional parameters are bundled into one optional param and more defaults are provided", + "pr": 954 + } + ] + }, + { "version": "1.0.1-rc.3", "changes": [ { diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index bcfb0611f..576f4dbc1 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -51,7 +51,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -60,7 +60,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/assert": "^1.0.5", diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 638126af6..1f393b0c4 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -4,6 +4,8 @@ export { generatePseudoRandomSalt } from './salt'; export { assetDataUtils } from './asset_data_utils'; export { eip712Utils } from './eip712_utils'; export { marketUtils } from './market_utils'; +export { rateUtils } from './rate_utils'; +export { sortingUtils } from './sorting_utils'; export { OrderStateUtils } from './order_state_utils'; export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index 681059ddf..a0a827546 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -1,46 +1,51 @@ import { schemas } from '@0xproject/json-schemas'; -import { SignedOrder } from '@0xproject/types'; +import { Order } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import { assert } from './assert'; import { constants } from './constants'; +import { FindFeeOrdersThatCoverFeesForTargetOrdersOpts, FindOrdersThatCoverMakerAssetFillAmountOpts } from './types'; export const marketUtils = { /** - * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, - * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. + * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount + * in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last order. * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. - * All orders should specify WETH as the takerAsset. - * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. - * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups - * for these values. - * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills. + * @param orders An array of objects that extend the Order interface. All orders should specify the same makerAsset. + * All orders should specify WETH as the takerAsset. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param opts Optional arguments this function accepts. * @return Resulting orders and remaining fill amount that could not be covered by the input. */ - findOrdersThatCoverMakerAssetFillAmount( - signedOrders: SignedOrder[], - remainingFillableMakerAssetAmounts: BigNumber[], + findOrdersThatCoverMakerAssetFillAmount<T extends Order>( + orders: T[], makerAssetFillAmount: BigNumber, - slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, - ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + opts?: FindOrdersThatCoverMakerAssetFillAmountOpts, + ): { resultOrders: T[]; remainingFillAmount: BigNumber } { + assert.doesConformToSchema('orders', orders, schemas.ordersSchema); + assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); + // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders + const remainingFillableMakerAssetAmounts = _.get( + opts, + 'remainingFillableMakerAssetAmounts', + _.map(orders, order => order.makerAssetAmount), + ) as BigNumber[]; _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), ); - assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); - assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); assert.assert( - signedOrders.length === remainingFillableMakerAssetAmounts.length, - 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + orders.length === remainingFillableMakerAssetAmounts.length, + 'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length', ); + // try to get slippageBufferAmount from opts, if it's not there, default to 0 + const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber; + assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount); // calculate total amount of makerAsset needed to be filled const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); - // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount + // iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount const result = _.reduce( - signedOrders, + orders, ({ resultOrders, remainingFillAmount }, order, index) => { if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; @@ -59,55 +64,61 @@ export const marketUtils = { }; } }, - { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount }, + { resultOrders: [] as T[], remainingFillAmount: totalFillAmount }, ); return result; }, /** - * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account - * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a - * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of + * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX + * in order to fill the takerFees required by orders plus a slippageBufferAmount. + * Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of * feeOrders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. - * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups - * for these values. - * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter. - * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups - * for these values. - * @param slippageBufferAmount An additional amount of fee to be covered by the result in case of trade collisions or partial fills. + * @param orders An array of objects that extend the Order interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param feeOrders An array of objects that extend the Order interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param opts Optional arguments this function accepts. * @return Resulting orders and remaining fee amount that could not be covered by the input. */ - findFeeOrdersThatCoverFeesForTargetOrders( - signedOrders: SignedOrder[], - remainingFillableMakerAssetAmounts: BigNumber[], - signedFeeOrders: SignedOrder[], - remainingFillableFeeAmounts: BigNumber[], - slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, - ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } { - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + findFeeOrdersThatCoverFeesForTargetOrders<T extends Order>( + orders: T[], + feeOrders: T[], + opts?: FindFeeOrdersThatCoverFeesForTargetOrdersOpts, + ): { resultOrders: T[]; remainingFeeAmount: BigNumber } { + assert.doesConformToSchema('orders', orders, schemas.ordersSchema); + assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema); + // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders + const remainingFillableMakerAssetAmounts = _.get( + opts, + 'remainingFillableMakerAssetAmounts', + _.map(orders, order => order.makerAssetAmount), + ) as BigNumber[]; _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), ); - assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + assert.assert( + orders.length === remainingFillableMakerAssetAmounts.length, + 'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length', + ); + // try to get remainingFillableFeeAmounts from opts, if it's not there, use makerAssetAmount values from feeOrders + const remainingFillableFeeAmounts = _.get( + opts, + 'remainingFillableFeeAmounts', + _.map(feeOrders, order => order.makerAssetAmount), + ) as BigNumber[]; _.forEach(remainingFillableFeeAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount), ); - assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); assert.assert( - signedOrders.length === remainingFillableMakerAssetAmounts.length, - 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + feeOrders.length === remainingFillableFeeAmounts.length, + 'Expected feeOrders.length to equal opts.remainingFillableFeeAmounts.length', ); - assert.assert( - signedOrders.length === remainingFillableMakerAssetAmounts.length, - 'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length', - ); - // calculate total amount of ZRX needed to fill signedOrders + // try to get slippageBufferAmount from opts, if it's not there, default to 0 + const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber; + assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount); + // calculate total amount of ZRX needed to fill orders const totalFeeAmount = _.reduce( - signedOrders, + orders, (accFees, order, index) => { const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable @@ -118,10 +129,12 @@ export const marketUtils = { constants.ZERO_AMOUNT, ); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( - signedFeeOrders, - remainingFillableFeeAmounts, + feeOrders, totalFeeAmount, - slippageBufferAmount, + { + remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts, + slippageBufferAmount, + }, ); return { resultOrders, diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts index cb08c5ae2..a0e24acf0 100644 --- a/packages/order-utils/src/order_state_utils.ts +++ b/packages/order-utils/src/order_state_utils.ts @@ -11,6 +11,7 @@ import { BigNumber } from '@0xproject/utils'; import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; import { orderHashUtils } from './order_hash'; +import { OrderValidationUtils } from './order_validation_utils'; import { RemainingFillableCalculator } from './remaining_fillable_calculator'; import { utils } from './utils'; @@ -22,9 +23,16 @@ interface SidedOrderRelevantState { traderFeeProxyAllowance: BigNumber; filledTakerAssetAmount: BigNumber; remainingFillableAssetAmount: BigNumber; + isOrderCancelled: boolean; } - -const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001; +interface OrderValidResult { + isValid: true; +} +interface OrderInvalidResult { + isValid: false; + error: ExchangeContractErrs; +} +type OrderValidationResult = OrderValidResult | OrderInvalidResult; export class OrderStateUtils { private readonly _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher; @@ -32,64 +40,56 @@ export class OrderStateUtils { private static _validateIfOrderIsValid( signedOrder: SignedOrder, sidedOrderRelevantState: SidedOrderRelevantState, - ): void { + ): OrderValidationResult { const isMakerSide = sidedOrderRelevantState.isMakerSide; + if (sidedOrderRelevantState.isOrderCancelled) { + return { isValid: false, error: ExchangeContractErrs.OrderCancelled }; + } const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus( sidedOrderRelevantState.filledTakerAssetAmount, ); if (availableTakerAssetAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + return { isValid: false, error: ExchangeContractErrs.OrderRemainingFillAmountZero }; } if (sidedOrderRelevantState.traderBalance.eq(0)) { - throw new Error( - isMakerSide - ? ExchangeContractErrs.InsufficientMakerBalance - : ExchangeContractErrs.InsufficientTakerBalance, - ); + const error = isMakerSide + ? ExchangeContractErrs.InsufficientMakerBalance + : ExchangeContractErrs.InsufficientTakerBalance; + return { isValid: false, error }; } if (sidedOrderRelevantState.traderProxyAllowance.eq(0)) { - throw new Error( - isMakerSide - ? ExchangeContractErrs.InsufficientMakerAllowance - : ExchangeContractErrs.InsufficientTakerAllowance, - ); + const error = isMakerSide + ? ExchangeContractErrs.InsufficientMakerAllowance + : ExchangeContractErrs.InsufficientTakerAllowance; + return { isValid: false, error }; } if (!signedOrder.makerFee.eq(0)) { if (sidedOrderRelevantState.traderFeeBalance.eq(0)) { - throw new Error( - isMakerSide - ? ExchangeContractErrs.InsufficientMakerFeeBalance - : ExchangeContractErrs.InsufficientTakerFeeBalance, - ); + const error = isMakerSide + ? ExchangeContractErrs.InsufficientMakerFeeBalance + : ExchangeContractErrs.InsufficientTakerFeeBalance; + return { isValid: false, error }; } if (sidedOrderRelevantState.traderFeeProxyAllowance.eq(0)) { - throw new Error( - isMakerSide - ? ExchangeContractErrs.InsufficientMakerFeeAllowance - : ExchangeContractErrs.InsufficientTakerFeeAllowance, - ); + const error = isMakerSide + ? ExchangeContractErrs.InsufficientMakerFeeAllowance + : ExchangeContractErrs.InsufficientTakerFeeAllowance; + return { isValid: false, error }; } } - - let minFillableTakerAssetAmountWithinNoRoundingErrorRange; - if (isMakerSide) { - minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount - .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) - .dividedBy(signedOrder.makerAssetAmount); - } else { - minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.makerAssetAmount - .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) - .dividedBy(signedOrder.takerAssetAmount); - } - - if ( - sidedOrderRelevantState.remainingFillableAssetAmount.lessThan( - minFillableTakerAssetAmountWithinNoRoundingErrorRange, - ) - ) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); + const remainingTakerAssetAmount = signedOrder.takerAssetAmount.minus( + sidedOrderRelevantState.filledTakerAssetAmount, + ); + const isRoundingError = OrderValidationUtils.isRoundingError( + remainingTakerAssetAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + if (isRoundingError) { + return { isValid: false, error: ExchangeContractErrs.OrderFillRoundingError }; } + return { isValid: true }; } /** * Instantiate OrderStateUtils @@ -117,6 +117,7 @@ export class OrderStateUtils { public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash); const sidedOrderRelevantState = { isMakerSide: true, traderBalance: orderRelevantState.makerBalance, @@ -125,20 +126,21 @@ export class OrderStateUtils { traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance, filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount, remainingFillableAssetAmount: orderRelevantState.remainingFillableMakerAssetAmount, + isOrderCancelled, }; - try { - OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState); + const orderValidationResult = OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState); + if (orderValidationResult.isValid) { const orderState: OrderStateValid = { isValid: true, orderHash, orderRelevantState, }; return orderState; - } catch (err) { + } else { const orderState: OrderStateInvalid = { isValid: false, orderHash, - error: err.message, + error: orderValidationResult.error, }; return orderState; } @@ -279,6 +281,7 @@ export class OrderStateUtils { traderFeeProxyAllowance, filledTakerAssetAmount, remainingFillableAssetAmount, + isOrderCancelled, }; return sidedOrderRelevantState; } diff --git a/packages/order-utils/src/rate_utils.ts b/packages/order-utils/src/rate_utils.ts new file mode 100644 index 000000000..c9ca72c59 --- /dev/null +++ b/packages/order-utils/src/rate_utils.ts @@ -0,0 +1,48 @@ +import { schemas } from '@0xproject/json-schemas'; +import { Order } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; + +import { assert } from './assert'; +import { constants } from './constants'; + +export const rateUtils = { + /** + * Takes an order and calculates the fee adjusted rate (takerAsset/makerAsset) by calculating how much takerAsset + * is required to cover the fees (feeRate * takerFee), adding the takerAssetAmount and dividing by makerAssetAmount + * @param order An object that conforms to the order interface + * @param feeRate The market rate of ZRX denominated in takerAssetAmount + * (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX) + * Defaults to 0 + * @return The rate (takerAsset/makerAsset) of the order adjusted for fees + */ + getFeeAdjustedRateOfOrder(order: Order, feeRate: BigNumber = constants.ZERO_AMOUNT): BigNumber { + assert.doesConformToSchema('order', order, schemas.orderSchema); + assert.isBigNumber('feeRate', feeRate); + assert.assert( + feeRate.gte(constants.ZERO_AMOUNT), + `Expected feeRate: ${feeRate} to be greater than or equal to 0`, + ); + const takerAssetAmountNeededToPayForFees = order.takerFee.mul(feeRate); + const totalTakerAssetAmount = takerAssetAmountNeededToPayForFees.plus(order.takerAssetAmount); + const rate = totalTakerAssetAmount.div(order.makerAssetAmount); + return rate; + }, + /** + * Takes a fee order (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH) and calculates + * the fee adjusted rate (WETH/ZRX) by dividing the takerAssetAmount by the makerAmount minus the takerFee + * @param feeOrder An object that conforms to the order interface + * @return The rate (WETH/ZRX) of the fee order adjusted for fees + */ + getFeeAdjustedRateOfFeeOrder(feeOrder: Order): BigNumber { + assert.doesConformToSchema('feeOrder', feeOrder, schemas.orderSchema); + const zrxAmountAfterFees = feeOrder.makerAssetAmount.sub(feeOrder.takerFee); + assert.assert( + zrxAmountAfterFees.greaterThan(constants.ZERO_AMOUNT), + `Expected takerFee: ${JSON.stringify(feeOrder.takerFee)} to be less than makerAssetAmount: ${JSON.stringify( + feeOrder.makerAssetAmount, + )}`, + ); + const rate = feeOrder.takerAssetAmount.div(zrxAmountAfterFees); + return rate; + }, +}; diff --git a/packages/order-utils/src/sorting_utils.ts b/packages/order-utils/src/sorting_utils.ts new file mode 100644 index 000000000..8811bcaf8 --- /dev/null +++ b/packages/order-utils/src/sorting_utils.ts @@ -0,0 +1,54 @@ +import { schemas } from '@0xproject/json-schemas'; +import { Order } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { assert } from './assert'; +import { constants } from './constants'; +import { rateUtils } from './rate_utils'; + +export const sortingUtils = { + /** + * Takes an array of orders and sorts them by takerAsset/makerAsset rate in ascending order (best rate first). + * Adjusts the rate of each order according to the feeRate and takerFee for that order. + * @param orders An array of objects that extend the Order interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param feeRate The market rate of ZRX denominated in takerAssetAmount + * (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX) + * Defaults to 0 + * @return The input orders sorted by rate in ascending order + */ + sortOrdersByFeeAdjustedRate<T extends Order>(orders: T[], feeRate: BigNumber = constants.ZERO_AMOUNT): T[] { + assert.doesConformToSchema('orders', orders, schemas.ordersSchema); + assert.isBigNumber('feeRate', feeRate); + const rateCalculator = (order: Order) => rateUtils.getFeeAdjustedRateOfOrder(order, feeRate); + const sortedOrders = sortOrders(orders, rateCalculator); + return sortedOrders; + }, + /** + * Takes an array of fee orders (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH) + * and sorts them by rate in ascending order (best rate first). Adjusts the rate according to the takerFee. + * @param feeOrders An array of objects that extend the Order interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @return The input orders sorted by rate in ascending order + */ + sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] { + assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema); + const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils); + const sortedOrders = sortOrders(feeOrders, rateCalculator); + return sortedOrders; + }, +}; + +type RateCalculator = (order: Order) => BigNumber; + +// takes an array of orders, copies them, and sorts the copy based on the rate definition provided by rateCalculator +function sortOrders<T extends Order>(orders: T[], rateCalculator: RateCalculator): T[] { + const copiedOrders = _.cloneDeep(orders); + copiedOrders.sort((firstOrder, secondOrder) => { + const firstOrderRate = rateCalculator(firstOrder); + const secondOrderRate = rateCalculator(secondOrder); + return firstOrderRate.comparedTo(secondOrderRate); + }); + return copiedOrders; +} diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index 1fbd8cf7b..2e9c79d80 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -41,3 +41,31 @@ export interface CreateOrderOpts { salt?: BigNumber; expirationTimeSeconds?: BigNumber; } + +/** + * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter. + * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * Defaults to `makerAssetAmount` values from the orders param. + * slippageBufferAmount: An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills. + * Defaults to 0 + */ +export interface FindOrdersThatCoverMakerAssetFillAmountOpts { + remainingFillableMakerAssetAmounts?: BigNumber[]; + slippageBufferAmount?: BigNumber; +} + +/** + * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter. + * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * Defaults to `makerAssetAmount` values from the orders param. + * remainingFillableFeeAmounts: An array of BigNumbers corresponding to the feeOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups for these values. + * Defaults to `makerAssetAmount` values from the feeOrders param. + * slippageBufferAmount: An additional amount of fee to be covered by the result in case of trade collisions or partial fills. + * Defaults to 0 + */ +export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts { + remainingFillableMakerAssetAmounts?: BigNumber[]; + remainingFillableFeeAmounts?: BigNumber[]; + slippageBufferAmount?: BigNumber; +} diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts index 5d94cc55c..cce97e0e4 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -19,7 +19,6 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( [], - [], fillAmount, ); expect(resultOrders).to.be.empty; @@ -35,8 +34,6 @@ describe('marketUtils', () => { }, 3, ); - // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount - const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => { // try to fill 20 units of makerAsset // include 10 units of slippageBufferAmount @@ -44,9 +41,10 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -58,9 +56,10 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -72,9 +71,10 @@ describe('marketUtils', () => { const slippageBufferAmount = new BigNumber(5); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, - slippageBufferAmount, + { + slippageBufferAmount, + }, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5)); @@ -84,7 +84,6 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal([inputOrders[0]]); @@ -95,7 +94,6 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(15); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]); @@ -121,8 +119,10 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(30); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - remainingFillableMakerAssetAmounts, fillAmount, + { + remainingFillableMakerAssetAmounts, + }, ); expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]); expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15)); @@ -138,15 +138,11 @@ describe('marketUtils', () => { }, 3, ); - // generate remainingFillableFeeAmounts that equal the zrxAmount - const remainingFillableFeeAmounts = [zrxAmount, zrxAmount, zrxAmount]; describe('no target orders', () => { it('returns empty and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( [], - [], inputFeeOrders, - remainingFillableFeeAmounts, ); expect(resultOrders).to.be.empty; expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -169,9 +165,10 @@ describe('marketUtils', () => { it('returns empty and non-zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, - [], [], + { + remainingFillableMakerAssetAmounts, + }, ); expect(resultOrders).to.be.empty; expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30)); @@ -186,14 +183,10 @@ describe('marketUtils', () => { }, 3, ); - // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount - const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns empty and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, ); expect(resultOrders).to.be.empty; expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -211,14 +204,10 @@ describe('marketUtils', () => { }, 3, ); - // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount - const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns input fee orders and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, ); expect(resultOrders).to.be.deep.equal(inputFeeOrders); expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -244,9 +233,10 @@ describe('marketUtils', () => { it('returns first two input fee orders and zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, + { + remainingFillableMakerAssetAmounts, + }, ); expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]); expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); @@ -264,14 +254,10 @@ describe('marketUtils', () => { }, 3, ); - // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount - const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns input fee orders and non-zero remainingFeeAmount', async () => { const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( inputOrders, - remainingFillableMakerAssetAmounts, inputFeeOrders, - remainingFillableFeeAmounts, ); expect(resultOrders).to.be.deep.equal(inputFeeOrders); expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30)); diff --git a/packages/order-utils/test/order_state_utils_test.ts b/packages/order-utils/test/order_state_utils_test.ts new file mode 100644 index 000000000..91ef23b69 --- /dev/null +++ b/packages/order-utils/test/order_state_utils_test.ts @@ -0,0 +1,124 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { AbstractBalanceAndProxyAllowanceFetcher } from '../src/abstract/abstract_balance_and_proxy_allowance_fetcher'; +import { AbstractOrderFilledCancelledFetcher } from '../src/abstract/abstract_order_filled_cancelled_fetcher'; +import { OrderStateUtils } from '../src/order_state_utils'; + +import { chaiSetup } from './utils/chai_setup'; +import { testOrderFactory } from './utils/test_order_factory'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('OrderStateUtils', () => { + describe('#getOpenOrderStateAsync', () => { + const buildMockBalanceFetcher = (takerBalance: BigNumber): AbstractBalanceAndProxyAllowanceFetcher => { + const balanceFetcher = { + async getBalanceAsync(_assetData: string, _userAddress: string): Promise<BigNumber> { + return takerBalance; + }, + async getProxyAllowanceAsync(_assetData: string, _userAddress: string): Promise<BigNumber> { + return takerBalance; + }, + }; + return balanceFetcher; + }; + const buildMockOrderFilledFetcher = ( + filledAmount: BigNumber = new BigNumber(0), + cancelled: boolean = false, + ): AbstractOrderFilledCancelledFetcher => { + const orderFetcher = { + async getFilledTakerAmountAsync(_orderHash: string): Promise<BigNumber> { + return filledAmount; + }, + async isOrderCancelledAsync(_orderHash: string): Promise<boolean> { + return cancelled; + }, + getZRXAssetData(): string { + return ''; + }, + }; + return orderFetcher; + }; + it('should have valid order state if order can be fully filled with small maker amount', async () => { + const makerAssetAmount = new BigNumber(10); + const takerAssetAmount = new BigNumber(10000000000000000); + const takerBalance = takerAssetAmount; + const orderFilledAmount = new BigNumber(0); + const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance); + const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount); + const [signedOrder] = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerAssetAmount, + }, + 1, + ); + + const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher); + const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder); + expect(orderState.isValid).to.eq(true); + }); + it('should be invalid when an order is partially filled where only a rounding error remains', async () => { + const makerAssetAmount = new BigNumber(1001); + const takerAssetAmount = new BigNumber(3); + const takerBalance = takerAssetAmount; + const orderFilledAmount = new BigNumber(2); + const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance); + const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount); + const [signedOrder] = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerAssetAmount, + }, + 1, + ); + + const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher); + const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder); + expect(orderState.isValid).to.eq(false); + }); + it('should be invalid when an order is cancelled', async () => { + const makerAssetAmount = new BigNumber(1000); + const takerAssetAmount = new BigNumber(2); + const takerBalance = takerAssetAmount; + const orderFilledAmount = new BigNumber(0); + const isCancelled = true; + const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance); + const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount, isCancelled); + const [signedOrder] = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerAssetAmount, + }, + 1, + ); + + const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher); + const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder); + expect(orderState.isValid).to.eq(false); + }); + it('should be invalid when an order is fully filled', async () => { + const makerAssetAmount = new BigNumber(1000); + const takerAssetAmount = new BigNumber(2); + const takerBalance = takerAssetAmount; + const orderFilledAmount = takerAssetAmount; + const isCancelled = false; + const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance); + const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount, isCancelled); + const [signedOrder] = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerAssetAmount, + }, + 1, + ); + + const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher); + const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder); + expect(orderState.isValid).to.eq(false); + }); + }); +}); diff --git a/packages/order-utils/test/rate_utils_test.ts b/packages/order-utils/test/rate_utils_test.ts new file mode 100644 index 000000000..2e299c209 --- /dev/null +++ b/packages/order-utils/test/rate_utils_test.ts @@ -0,0 +1,55 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { rateUtils } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { testOrderFactory } from './utils/test_order_factory'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('rateUtils', () => { + const testOrder = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(20), + }); + describe('#getFeeAdjustedRateOfOrder', () => { + it('throws when feeRate is less than zero', async () => { + const feeRate = new BigNumber(-1); + expect(() => rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate)).to.throw( + 'Expected feeRate: -1 to be greater than or equal to 0', + ); + }); + it('correctly calculates fee adjusted rate when feeRate is provided', async () => { + const feeRate = new BigNumber(2); // ZRX costs 2 units of takerAsset per 1 unit of ZRX + const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate); + // the order actually takes 100 + (2 * 20) takerAsset units to fill 100 units of makerAsset + expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.4)); + }); + it('correctly calculates fee adjusted rate when no feeRate is provided', async () => { + const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder); + // because no feeRate was provided we just assume 0 fees + // the order actually takes 100 takerAsset units to fill 100 units of makerAsset + expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1)); + }); + }); + describe('#getFeeAdjustedRateOfFeeOrder', () => { + it('throws when takerFee exceeds makerAssetAmount', async () => { + const badOrder = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(101), + }); + expect(() => rateUtils.getFeeAdjustedRateOfFeeOrder(badOrder)).to.throw( + 'Expected takerFee: "101" to be less than makerAssetAmount: "100"', + ); + }); + it('correctly calculates fee adjusted rate', async () => { + const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfFeeOrder(testOrder); + // the order actually takes 100 takerAsset units to fill (100 - 20) units of makerAsset + expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.25)); + }); + }); +}); diff --git a/packages/order-utils/test/sorting_utils_test.ts b/packages/order-utils/test/sorting_utils_test.ts new file mode 100644 index 000000000..016432505 --- /dev/null +++ b/packages/order-utils/test/sorting_utils_test.ts @@ -0,0 +1,67 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { sortingUtils } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { testOrderFactory } from './utils/test_order_factory'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('sortingUtils', () => { + describe('#sortOrdersByFeeAdjustedRate', () => { + const feeRate = new BigNumber(1); // ZRX costs 1 unit of takerAsset per 1 unit of ZRX + // rate: 2 takerAsset / makerAsset + const testOrder1 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(200), + }); + // rate: 1 takerAsset / makerAsset + const testOrder2 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(100), + }); + // rate: 2.5 takerAsset / makerAsset + const testOrder3 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(200), + takerFee: new BigNumber(50), + }); + it('correctly sorts by fee adjusted rate when feeRate is Provided', async () => { + const orders = [testOrder1, testOrder2, testOrder3]; + const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders, feeRate); + expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]); + }); + it('correctly sorts by fee adjusted rate when no feeRate is Provided', async () => { + const orders = [testOrder1, testOrder2, testOrder3]; + const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders); + expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]); + }); + }); + describe('#sortFeeOrdersByFeeAdjustedRate', () => { + // rate: 200 takerAsset / makerAsset + const testOrder1 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(200), + takerFee: new BigNumber(99), + }); + // rate: 1 takerAsset / makerAsset + const testOrder2 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(100), + }); + // rate: 4 takerAsset / makerAsset + const testOrder3 = testOrderFactory.generateTestSignedOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: new BigNumber(200), + takerFee: new BigNumber(50), + }); + it('correctly sorts by fee adjusted rate', async () => { + const orders = [testOrder1, testOrder2, testOrder3]; + const sortedOrders = sortingUtils.sortFeeOrdersByFeeAdjustedRate(orders); + expect(sortedOrders).to.deep.equal([testOrder2, testOrder3, testOrder1]); + }); + }); +}); diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index ad3570533..4d719e575 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -58,7 +58,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "json-loader": "^0.5.4", "make-promises-safe": "^1.1.0", @@ -70,7 +70,7 @@ "sinon": "^4.0.0", "source-map-support": "^0.5.0", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/assert": "^1.0.5", diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index 00962bed0..38bfde7ef 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -501,25 +501,27 @@ describe('OrderWatcher', () => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderCancelled); }); orderWatcher.subscribe(callback); await contractWrappers.exchange.cancelOrderAsync(signedOrder); })().catch(done); }); - it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { + it('should emit orderStateInvalid when within rounding error range after a partial fill', (done: DoneCallback) => { (async () => { - const remainingFillableAmountInBaseUnits = new BigNumber(100); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( + const fillAmountInBaseUnits = new BigNumber(2); + const makerAssetAmount = new BigNumber(1001); + const takerAssetAmount = new BigNumber(3); + signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( makerAssetData, takerAssetData, makerAddress, takerAddress, - fillableAmount, + makerAssetAmount, + takerAssetAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); await orderWatcher.addOrderAsync(signedOrder); - const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; @@ -527,11 +529,7 @@ describe('OrderWatcher', () => { expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); }); orderWatcher.subscribe(callback); - await contractWrappers.exchange.fillOrderAsync( - signedOrder, - fillableAmount.minus(remainingFillableAmountInBaseUnits), - takerAddress, - ); + await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress); })().catch(done); }); describe('erc721', () => { diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 3e2ca6794..aeface8d7 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -27,11 +27,11 @@ "@0xproject/dev-utils": "^1.0.4", "@0xproject/tslint-config": "^1.0.5", "@types/compare-versions": "^3.0.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/react-shared": "^1.0.6", diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 3c36406fd..5df4648ee 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -26,11 +26,11 @@ "devDependencies": { "@0xproject/dev-utils": "^1.0.4", "@0xproject/tslint-config": "^1.0.5", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@types/is-mobile": "0.3.0", diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index 7f4168f00..498c171e0 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -50,7 +50,7 @@ "@types/semver": "^5.5.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -60,7 +60,7 @@ "tslint": "5.11.0", "typedoc": "0xProject/typedoc", "types-bn": "^0.0.1", - "typescript": "2.9.2", + "typescript": "3.0.1", "web3-typescript-typings": "^0.10.2", "zeppelin-solidity": "1.8.0" }, diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index fdfb2d0ee..f63f6fb93 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -68,7 +68,7 @@ "@types/rimraf": "^2.0.2", "@types/solidity-parser-antlr": "^0.2.1", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -78,7 +78,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/sol-resolver/package.json b/packages/sol-resolver/package.json index 7012772ee..c1b037942 100644 --- a/packages/sol-resolver/package.json +++ b/packages/sol-resolver/package.json @@ -24,11 +24,11 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/resolver/README.md", "devDependencies": { "@0xproject/tslint-config": "^1.0.5", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.4", diff --git a/packages/sra-api/public/api.json b/packages/sra-api/public/api.json index 36b08ad1a..9c727cfbd 100644 --- a/packages/sra-api/public/api.json +++ b/packages/sra-api/public/api.json @@ -1 +1 @@ -{"openapi":"3.0.0","info":{"version":"2.0.0","title":"Standard Relayer REST API","description":"# Testing\n\nUse the [sra-report](https://github.com/0xProject/0x-monorepo/tree/development/packages/sra-report) command line tool to test your API for SRA compliance.\n\n# Schemas\n\nThe [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x.js/blob/development/packages/json-schemas/test/schema_test.ts#L1).\n\n```bash\nnpm install @0xproject/json-schemas --save\n```\n\nYou can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas) package:\n\n```js\nimport {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas';\n\nconst {relayerApiTokenPairsResponseSchema} = schemas;\nconst validator = new SchemaValidator();\n\nconst tokenPairsResponse = {\n ...\n};\nconst validatorResult: ValidatorResult = validator.validate(tokenPairsResponse, relayerApiTokenPairsResponseSchema);\n```\n\n# Pagination\n\nRequests that return potentially large collections should respond to the **?page** and **?perPage** parameters. For example:\n\n```bash\n$ curl https://api.example-relayer.com/v2/asset_pairs?page=3&perPage=20\n```\n\nPage numbering should be 1-indexed, not 0-indexed. If a query provides an unreasonable (ie. too high) `perPage` value, the response can return a validation error as specified in the [errors section](#section/Errors). If the query specifies a `page` that does not exist (ie. there are not enough `records`), the response should just return an empty `records` array.\n\nAll endpoints that are paginated should return a `total`, `page`, `perPage` and a `records` value in the top level of the collection. The value of `total` should be the total number of records for a given query, whereas `records` should be an array representing the response to the query for that page. `page` and `perPage`, are the same values that were specified in the request. See the note in [miscellaneous](#section/Misc.) about formatting `snake_case` vs. `lowerCamelCase`.\n\nThese requests include the [`/v2/asset_pairs`](#operation/getAssetPairs), [`/v2/orders`](#operation/getOrders), [`/v2/fee_recipients`](#operation/getFeeRecipients) and [`/v2/orderbook`](#operation/getOrderbook) endpoints.\n\n# Network Id\n\nAll requests should be able to specify a **?networkId** query param for all supported networks. For example:\n\n```bash\n$ curl https://api.example-relayer.com/v2/asset_pairs?networkId=1\n```\n\nIf the query param is not provided, it should default to **1** (mainnet).\n\nNetworks and their Ids:\n\n| Network Id | Network Name |\n| ---------- | ------------ |\n| 1 | Mainnet |\n| 42 | Kovan |\n| 3 | Ropsten |\n| 4 | Rinkeby |\n\nIf a certain network is not supported, the response should **400** as specified in the [error response](#section/Errors) section. For example:\n\n```json\n{\n \"code\": 100,\n \"reason\": \"Validation failed\",\n \"validationErrors\": [\n {\n \"field\": \"networkId\",\n \"code\": 1006,\n \"reason\": \"Network id 42 is not supported\"\n }\n ]\n}\n```\n\n# Link Header\n\nA [Link Header](https://tools.ietf.org/html/rfc5988) can be included in a response to provide clients with more context about paging\nFor example:\n\n```bash\nLink: <https://api.example-relayer.com/v2/asset_pairs?page=3&perPage=20>; rel=\"next\",\n<https://api.github.com/user/repos?page=10&perPage=20>; rel=\"last\"\n```\n\nThis `Link` response header contains one or more Hypermedia link relations.\n\nThe possible `rel` values are:\n\n| Name | Description |\n| ----- | ------------------------------------------------------------- |\n| next | The link relation for the immediate next page of results. |\n| last | The link relation for the last page of results. |\n| first | The link relation for the first page of results. |\n| prev | The link relation for the immediate previous page of results. |\n\n# Rate Limits\n\nRate limit guidance for clients can be optionally returned in the response headers:\n\n| Header Name | Description |\n| --------------------- | ---------------------------------------------------------------------------- |\n| X-RateLimit-Limit | The maximum number of requests you're permitted to make per hour. |\n| X-RateLimit-Remaining | The number of requests remaining in the current rate limit window. |\n| X-RateLimit-Reset | The time at which the current rate limit window resets in UTC epoch seconds. |\n\nFor example:\n\n```bash\n$ curl -i https://api.example-relayer.com/v2/asset_pairs\nHTTP/1.1 200 OK\nDate: Mon, 20 Oct 2017 12:30:06 GMT\nStatus: 200 OK\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 56\nX-RateLimit-Reset: 1372700873\n```\n\nWhen a rate limit is exceeded, a status of **429 Too Many Requests** should be returned.\n\n# Errors\n\nUnless the spec defines otherwise, errors to bad requests should respond with HTTP 4xx or status codes.\n\n## Common error codes\n\n| Code | Reason |\n| ---- | --------------------------------------- |\n| 400 | Bad Request – Invalid request format |\n| 404 | Not found |\n| 429 | Too many requests - Rate limit exceeded |\n| 500 | Internal Server Error |\n| 501 | Not Implemented |\n\n## Error reporting format\n\nFor all **400** responses, see the [error response schema](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.ts#L1).\n\n```json\n{\n \"code\": 101,\n \"reason\": \"Validation failed\",\n \"validationErrors\": [\n {\n \"field\": \"maker\",\n \"code\": 1002,\n \"reason\": \"Invalid address\"\n }\n ]\n}\n```\n\nGeneral error codes:\n\n```bash\n100 - Validation Failed\n101 - Malformed JSON\n102 - Order submission disabled\n103 - Throttled\n```\n\nValidation error codes:\n\n```bash\n1000 - Required field\n1001 - Incorrect format\n1002 - Invalid address\n1003 - Address not supported\n1004 - Value out of range\n1005 - Invalid signature or hash\n1006 - Unsupported option\n```\n\n# Asset Data Encoding\n\nAs we now support multiple [token transfer proxies](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#assetproxy), the identifier of which proxy to use for the token transfer must be encoded, along with the token information. Each proxy in 0x v2 has a unique identifier. If you're using 0x.js there will be helper methods for this [encoding](https://0xproject.com/docs/0x.js#zeroEx-encodeERC20AssetData) and [decoding](https://0xproject.com/docs/0x.js#zeroEx-decodeAssetProxyId).\n\nThe identifier for the Proxy uses a similar scheme to [ABI function selectors](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#function-selector).\n\n```js\n// ERC20 Proxy ID 0xf47261b0\nbytes4(keccak256('ERC20Token(address)'));\n// ERC721 Proxy ID 0x08e937fa\nbytes4(keccak256('ERC721Token(address,uint256)'));\n```\n\nAsset data is encoded using [ABI encoding](https://solidity.readthedocs.io/en/develop/abi-spec.html).\n\nFor example, encoding the ERC20 token contract (address: 0x1dc4c1cefef38a777b15aa20260a54e584b16c48) using the ERC20 Transfer Proxy (id: 0xf47261b0) would be:\n\n```bash\n0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48\n```\n\nEncoding the ERC721 token contract (address: `0x371b13d97f4bf77d724e78c16b7dc74099f40e84`), token id (id: `99`, which hex encoded is `0x63`) and the ERC721 Transfer Proxy (id: 0x08e937fa) would be:\n\n```bash\n0x08e937fa000000000000000000000000371b13d97f4bf77d724e78c16b7dc74099f40e840000000000000000000000000000000000000000000000000000000000000063\n```\n\nFor more information see [the Asset Proxy](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#erc20proxy) section of the v2 spec and the [Ethereum ABI Spec](https://solidity.readthedocs.io/en/develop/abi-spec.html).\n\n# Meta Data in Order Responses\n\nIn v2 of the standard relayer API we added the `metaData` field. It is meant to provide a standard place for relayers to put optional, custom or non-standard fields that may of interest to the consumer of the API.\n\nA good example of such a field is `remainingTakerAssetAmount`, which is a convenience field that communicates how much of a 0x order is potentially left to be filled. Unlike the other fields in a 0x order, it is not guaranteed to be correct as it is derived from whatever mechanism the implementer (ie. the relayer) is using. While convenient for prototyping and low stakes situations, we recommend validating the value of the field by checking the state of the blockchain yourself, such as by using [Order Watcher](https://0xproject.com/wiki#0x-OrderWatcher).\n\n# Misc.\n\n* All requests and responses should be of **application/json** content type\n* All token amounts are sent in amounts of the smallest level of precision (base units). (e.g if a token has 18 decimal places, selling 1 token would show up as selling `'1000000000000000000'` units by this API).\n* All addresses are sent as lower-case (non-checksummed) Ethereum addresses with the `0x` prefix.\n* All parameters are to be written in `lowerCamelCase`.\n","license":{"name":"Apache 2.0","url":"https://www.apache.org/licenses/LICENSE-2.0.html"}},"paths":{"/v2/asset_pairs":{"get":{"description":"Retrieves a list of available asset pairs and the information required to trade them (in any order). Setting only `assetDataA` or `assetDataB` returns pairs filtered by that asset only.","operationId":"getAssetPairs","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number"}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number"}},{"name":"assetDataA","in":"query","description":"The assetData value for the first asset in the pair.","example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"assetDataB","in":"query","description":"The assetData value for the second asset in the pair.","example":"0x0257179264389b814a946f3e92105513705ca6b990","schema":{"$ref":"#/components/schemas/hexSchema"}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"Returns a collection of available asset pairs with some meta info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiAssetDataPairsResponseSchema"},"example":{"total":43,"page":1,"perPage":100,"records":[{"assetDataA":{"minAmount":"0","maxAmount":"10000000000000000000","precision":5,"assetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d"},"assetDataB":{"minAmount":"0","maxAmount":"50000000000000000000","precision":5,"assetData":"0x0257179264389b814a946f3e92105513705ca6b990"}}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/orders":{"get":{"description":"Retrieves a list of orders given query parameters. This endpoint should be [paginated](#section/Pagination). For querying an entire orderbook snapshot, the [orderbook endpoint](#operation/getOrderbook) is recommended. If both makerAssetData and takerAssetData are specified, returned orders will be sorted by price determined by (takerTokenAmount/makerTokenAmount) in ascending order. By default, orders returned by this endpoint are unsorted.","operationId":"getOrders","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number"}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number"}},{"name":"makerAssetProxyId","in":"query","description":"The maker [asset proxy id](https://0xproject.com/docs/0x.js#types-AssetProxyId) (example: \"0xf47261b0\" for ERC20, \"0x02571792\" for ERC721).","example":"0xf47261b0","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"takerAssetProxyId","in":"query","description":"The taker asset [asset proxy id](https://0xproject.com/docs/0x.js#types-AssetProxyId) (example: \"0xf47261b0\" for ERC20, \"0x02571792\" for ERC721).","example":"0x02571792","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"makerAssetAddress","in":"query","description":"The contract address for the maker asset.","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"takerAssetAddress","in":"query","description":"The contract address for the taker asset.","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"exchangeAddress","in":"query","description":"Same as exchangeAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"senderAddress","in":"query","description":"Same as senderAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"makerAssetData","in":"query","description":"Same as makerAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"takerAssetData","in":"query","description":"Same as takerAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"traderAssetData","in":"query","description":"Same as traderAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"makerAddress","in":"query","description":"Same as makerAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"traderAddress","in":"query","description":"Same as traderAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"feeRecipientAddress","in":"query","description":"Same as feeRecipientAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"A collection of 0x orders with meta-data as specified by query params","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"},"example":{"total":984,"page":1,"perPage":100,"records":[{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order/{orderHash}":{"get":{"description":"Retrieves the 0x order with meta info that is associated with the hash.","operationId":"getOrder","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}},{"name":"orderHash","in":"path","description":"The hash of the desired 0x order.","example":"0xd4b103c42d2512eef3fee775e097f044291615d25f5d71e0ac70dbd49d223591","schema":{"$ref":"#/components/schemas/orderHashSchema"}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The order and meta info associated with the orderHash","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderSchema"},"example":{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/orderbook":{"get":{"description":"Retrieves the orderbook for a given asset pair. This endpoint should be [paginated](#section/Pagination). Bids will be sorted in descending order by price, and asks will be sorted in ascending order by price. Within the price sorted orders, the orders are further sorted by _taker fee price_ which is defined as the **takerFee** divided by **takerTokenAmount**. After _taker fee price_, orders are to be sorted by expiration in ascending order. The way pagination works for this endpoint is that the **page** and **perPage** query params apply to both `bids` and `asks` collections, and if `page` * `perPage` > `total` for a certain collection, the `records` for that collection should just be empty. ","operationId":"getOrderbook","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number"}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number"}},{"name":"baseAssetData","in":"query","description":"assetData (makerAssetData or takerAssetData) designated as the base currency in the [currency pair calculation](https://en.wikipedia.org/wiki/Currency_pair) of price.","required":true,"example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"quoteAssetData","in":"query","description":"assetData (makerAssetData or takerAssetData) designated as the quote currency in the currency pair calculation of price (required).","required":true,"example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The sorted order book for the specified asset pair.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderbookResponseSchema"},"example":{"bids":{"total":325,"page":2,"perPage":100,"records":[{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}]},"asks":{"total":500,"page":2,"perPage":100,"records":[{"order":{"makerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","takerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"20000000000000000","takerAssetAmount":"10000000000000000","makerFee":"200000000000000","takerFee":"100000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","takerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x013842a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b3518891"},"metaData":{}}]}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order_config":{"get":{"description":"Relayers have full discretion over the orders that they are willing to host on their orderbooks (e.g what fees they charge, etc...). In order for traders to discover their requirements programmatically, they can send an incomplete order to this endpoint and receive the missing fields, specifc to that order. This gives relayers a large amount of flexibility to tailor fees to unique traders, trading pairs and volume amounts. Submit a partial order and receive information required to complete the order: `senderAddress`, `feeRecipientAddress`, `makerFee`, `takerFee`. ","operationId":"getOrderConfig","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}}],"requestBody":{"description":"The fields of a 0x order the relayer may want to decide what configuration to send back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderConfigPayloadSchema"},"example":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","expirationTimeSeconds":"1532560590"}}}},"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The additional fields necessary in order to submit an order to the relayer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderConfigResponseSchema"},"example":{"senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","makerFee":"100000000000000","takerFee":"200000000000000"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/fee_recipients":{"get":{"description":"Retrieves a collection of all fee recipient addresses for a relayer. This endpoint should be [paginated](#section/Pagination).","operationId":"getFeeRecipients","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number"}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number"}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"A collection of all used fee recipient addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiFeeRecipientsResponseSchema"},"example":{"total":3,"page":1,"perPage":10,"records":["0x6eC92694ea172ebC430C30fa31De87620967A082","0x9e56625509c2f60af937f23b7b532600390e8c8b","0xa2b31dacf30a9c50ca473337c01d8a201ae33e32"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order":{"post":{"description":"Submit a signed order to the relayer.","operationId":"postOrder","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number"}}],"requestBody":{"description":"A valid signed 0x order based on the schema.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/signedOrderSchema"},"example":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"}}}},"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"OK","content":{}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}}},"components":{"schemas":{"numberSchema":{"type":"string","pattern":"^\\d+(\\.\\d+)?$"},"addressSchema":{"type":"string","pattern":"^0x[0-9a-f]{40}$"},"hexSchema":{"type":"string","pattern":"^0x(([0-9a-f][0-9a-f])+)?$"},"orderHashSchema":{"type":"string","pattern":"^0x[0-9a-fA-F]{64}$"},"orderSchema":{"properties":{"makerAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAddress":{"$ref":"#/components/schemas/addressSchema"},"makerFee":{"$ref":"#/components/schemas/numberSchema"},"takerFee":{"$ref":"#/components/schemas/numberSchema"},"senderAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"takerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"salt":{"$ref":"#/components/schemas/numberSchema"},"exchangeAddress":{"$ref":"#/components/schemas/addressSchema"},"feeRecipientAddress":{"$ref":"#/components/schemas/addressSchema"},"expirationTimeSeconds":{"$ref":"#/components/schemas/numberSchema"}},"required":["makerAddress","takerAddress","makerFee","takerFee","senderAddress","makerAssetAmount","takerAssetAmount","makerAssetData","takerAssetData","salt","exchangeAddress","feeRecipientAddress","expirationTimeSeconds"],"type":"object"},"signedOrderSchema":{"allOf":[{"$ref":"#/components/schemas/orderSchema"},{"properties":{"signature":{"$ref":"#/components/schemas/hexSchema"}},"required":["signature"]}]},"signedOrdersSchema":{"type":"array","items":{"$ref":"#/components/schemas/signedOrderSchema"}},"ordersSchema":{"type":"array","items":{"$ref":"#/components/schemas/orderSchema"}},"paginatedCollectionSchema":{"type":"object","properties":{"total":{"type":"number"},"perPage":{"type":"number"},"page":{"type":"number"}},"required":["total","perPage","page"]},"relayerApiErrorResponseSchema":{"type":"object","properties":{"code":{"type":"integer","minimum":100,"maximum":103},"reason":{"type":"string"},"validationErrors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"integer","minimum":1000,"maximum":1006},"reason":{"type":"string"}},"required":["field","code","reason"]}}},"required":["code","reason"]},"relayerApiFeeRecipientsResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/addressSchema"}},"required":["records"]}]},"relayerApiOrderSchema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/orderSchema"},"metaData":{"type":"object"}},"required":["order","metaData"]},"relayerApiOrdersSchema":{"type":"array","items":{"$ref":"#/components/schemas/relayerApiOrderSchema"}},"relayerApiOrderConfigPayloadSchema":{"type":"object","properties":{"makerAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"takerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"exchangeAddress":{"$ref":"#/components/schemas/addressSchema"},"expirationTimeSeconds":{"$ref":"#/components/schemas/numberSchema"}},"required":["makerAddress","takerAddress","makerAssetAmount","takerAssetAmount","makerAssetData","takerAssetData","exchangeAddress","expirationTimeSeconds"]},"relayerApiOrderConfigResponseSchema":{"type":"object","properties":{"makerFee":{"$ref":"#/components/schemas/numberSchema"},"takerFee":{"$ref":"#/components/schemas/numberSchema"},"feeRecipientAddress":{"$ref":"#/components/schemas/addressSchema"},"senderAddress":{"$ref":"#/components/schemas/addressSchema"}},"required":["makerFee","takerFee","feeRecipientAddress","senderAddress"]},"relayerApiOrderbookResponseSchema":{"type":"object","properties":{"bids":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"},"asks":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"}},"required":["bids","asks"]},"relayerApiAssetDataPairsResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/relayerApiAssetDataPairsSchema"}},"required":["records"]}]},"relayerApiAssetDataTradeInfoSchema":{"type":"object","properties":{"assetData":{"$ref":"#/components/schemas/hexSchema"},"minAmount":{"$ref":"#/components/schemas/numberSchema"},"maxAmount":{"$ref":"#/components/schemas/numberSchema"},"precision":{"type":"number"}},"required":["assetData"]},"relayerApiOrdersChannelSubscribeSchema":{"type":"object","properties":{"type":{"enum":["subscribe"]},"channel":{"enum":["orders"]},"requestId":{"type":"string"},"payload":{"$ref":"#/components/schemas/relayerApiOrdersChannelSubscribePayload"}},"required":["type","channel","requestId"]},"relayerApiOrdersChannelSubscribePayload":{"type":"object","properties":{"makerAssetProxyId":{"$ref":"#/components/schemas/hexSchema"},"takerAssetProxyId":{"$ref":"#/components/schemas/hexSchema"},"networkId":{"type":"number"},"makerAssetAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAssetAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"traderAssetData":{"$ref":"#/components/schemas/hexSchema"}}},"relayerApiOrdersChannelUpdateSchema":{"type":"object","properties":{"type":{"enum":["update"]},"channel":{"enum":["orders"]},"requestId":{"type":"string"},"payload":{"$ref":"#/components/schemas/relayerApiOrdersSchema"}},"required":["type","channel","requestId"]},"relayerApiOrdersResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/relayerApiOrdersSchema"}},"required":["records"]}]},"relayerApiAssetDataPairsSchema":{"type":"array","items":{"properties":{"assetDataA":{"$ref":"#/components/schemas/relayerApiAssetDataTradeInfoSchema"},"assetDataB":{"$ref":"#/components/schemas/relayerApiAssetDataTradeInfoSchema"}},"required":["assetDataA","assetDataB"],"type":"object"}}}}}
\ No newline at end of file +{"openapi":"3.0.0","info":{"version":"2.0.0","title":"Standard Relayer REST API","description":"# Testing\n\nUse the [sra-report](https://github.com/0xProject/0x-monorepo/tree/development/packages/sra-report) command line tool to test your API for SRA compliance.\n\n# Schemas\n\nThe [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x.js/blob/development/packages/json-schemas/test/schema_test.ts#L1).\n\n```bash\nnpm install @0xproject/json-schemas --save\n```\n\nYou can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas) package:\n\n```js\nimport {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas';\n\nconst {relayerApiTokenPairsResponseSchema} = schemas;\nconst validator = new SchemaValidator();\n\nconst tokenPairsResponse = {\n ...\n};\nconst validatorResult: ValidatorResult = validator.validate(tokenPairsResponse, relayerApiTokenPairsResponseSchema);\n```\n\n# Pagination\n\nRequests that return potentially large collections should respond to the **?page** and **?perPage** parameters. For example:\n\n```bash\n$ curl https://api.example-relayer.com/v2/asset_pairs?page=3&perPage=20\n```\n\nPage numbering should be 1-indexed, not 0-indexed. If a query provides an unreasonable (ie. too high) `perPage` value, the response can return a validation error as specified in the [errors section](#section/Errors). If the query specifies a `page` that does not exist (ie. there are not enough `records`), the response should just return an empty `records` array.\n\nAll endpoints that are paginated should return a `total`, `page`, `perPage` and a `records` value in the top level of the collection. The value of `total` should be the total number of records for a given query, whereas `records` should be an array representing the response to the query for that page. `page` and `perPage`, are the same values that were specified in the request. See the note in [miscellaneous](#section/Misc.) about formatting `snake_case` vs. `lowerCamelCase`.\n\nThese requests include the [`/v2/asset_pairs`](#operation/getAssetPairs), [`/v2/orders`](#operation/getOrders), [`/v2/fee_recipients`](#operation/getFeeRecipients) and [`/v2/orderbook`](#operation/getOrderbook) endpoints.\n\n# Network Id\n\nAll requests should be able to specify a **?networkId** query param for all supported networks. For example:\n\n```bash\n$ curl https://api.example-relayer.com/v2/asset_pairs?networkId=1\n```\n\nIf the query param is not provided, it should default to **1** (mainnet).\n\nNetworks and their Ids:\n\n| Network Id | Network Name |\n| ---------- | ------------ |\n| 1 | Mainnet |\n| 42 | Kovan |\n| 3 | Ropsten |\n| 4 | Rinkeby |\n\nIf a certain network is not supported, the response should **400** as specified in the [error response](#section/Errors) section. For example:\n\n```json\n{\n \"code\": 100,\n \"reason\": \"Validation failed\",\n \"validationErrors\": [\n {\n \"field\": \"networkId\",\n \"code\": 1006,\n \"reason\": \"Network id 42 is not supported\"\n }\n ]\n}\n```\n\n# Link Header\n\nA [Link Header](https://tools.ietf.org/html/rfc5988) can be included in a response to provide clients with more context about paging\nFor example:\n\n```bash\nLink: <https://api.example-relayer.com/v2/asset_pairs?page=3&perPage=20>; rel=\"next\",\n<https://api.github.com/user/repos?page=10&perPage=20>; rel=\"last\"\n```\n\nThis `Link` response header contains one or more Hypermedia link relations.\n\nThe possible `rel` values are:\n\n| Name | Description |\n| ----- | ------------------------------------------------------------- |\n| next | The link relation for the immediate next page of results. |\n| last | The link relation for the last page of results. |\n| first | The link relation for the first page of results. |\n| prev | The link relation for the immediate previous page of results. |\n\n# Rate Limits\n\nRate limit guidance for clients can be optionally returned in the response headers:\n\n| Header Name | Description |\n| --------------------- | ---------------------------------------------------------------------------- |\n| X-RateLimit-Limit | The maximum number of requests you're permitted to make per hour. |\n| X-RateLimit-Remaining | The number of requests remaining in the current rate limit window. |\n| X-RateLimit-Reset | The time at which the current rate limit window resets in UTC epoch seconds. |\n\nFor example:\n\n```bash\n$ curl -i https://api.example-relayer.com/v2/asset_pairs\nHTTP/1.1 200 OK\nDate: Mon, 20 Oct 2017 12:30:06 GMT\nStatus: 200 OK\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 56\nX-RateLimit-Reset: 1372700873\n```\n\nWhen a rate limit is exceeded, a status of **429 Too Many Requests** should be returned.\n\n# Errors\n\nUnless the spec defines otherwise, errors to bad requests should respond with HTTP 4xx or status codes.\n\n## Common error codes\n\n| Code | Reason |\n| ---- | --------------------------------------- |\n| 400 | Bad Request – Invalid request format |\n| 404 | Not found |\n| 429 | Too many requests - Rate limit exceeded |\n| 500 | Internal Server Error |\n| 501 | Not Implemented |\n\n## Error reporting format\n\nFor all **400** responses, see the [error response schema](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.ts#L1).\n\n```json\n{\n \"code\": 101,\n \"reason\": \"Validation failed\",\n \"validationErrors\": [\n {\n \"field\": \"maker\",\n \"code\": 1002,\n \"reason\": \"Invalid address\"\n }\n ]\n}\n```\n\nGeneral error codes:\n\n```bash\n100 - Validation Failed\n101 - Malformed JSON\n102 - Order submission disabled\n103 - Throttled\n```\n\nValidation error codes:\n\n```bash\n1000 - Required field\n1001 - Incorrect format\n1002 - Invalid address\n1003 - Address not supported\n1004 - Value out of range\n1005 - Invalid signature or hash\n1006 - Unsupported option\n```\n\n# Asset Data Encoding\n\nAs we now support multiple [token transfer proxies](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#assetproxy), the identifier of which proxy to use for the token transfer must be encoded, along with the token information. Each proxy in 0x v2 has a unique identifier. If you're using 0x.js there will be helper methods for this [encoding](https://0xproject.com/docs/0x.js#zeroEx-encodeERC20AssetData) and [decoding](https://0xproject.com/docs/0x.js#zeroEx-decodeAssetProxyId).\n\nThe identifier for the Proxy uses a similar scheme to [ABI function selectors](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#function-selector).\n\n```js\n// ERC20 Proxy ID 0xf47261b0\nbytes4(keccak256('ERC20Token(address)'));\n// ERC721 Proxy ID 0x08e937fa\nbytes4(keccak256('ERC721Token(address,uint256)'));\n```\n\nAsset data is encoded using [ABI encoding](https://solidity.readthedocs.io/en/develop/abi-spec.html).\n\nFor example, encoding the ERC20 token contract (address: 0x1dc4c1cefef38a777b15aa20260a54e584b16c48) using the ERC20 Transfer Proxy (id: 0xf47261b0) would be:\n\n```bash\n0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48\n```\n\nEncoding the ERC721 token contract (address: `0x371b13d97f4bf77d724e78c16b7dc74099f40e84`), token id (id: `99`, which hex encoded is `0x63`) and the ERC721 Transfer Proxy (id: 0x08e937fa) would be:\n\n```bash\n0x08e937fa000000000000000000000000371b13d97f4bf77d724e78c16b7dc74099f40e840000000000000000000000000000000000000000000000000000000000000063\n```\n\nFor more information see [the Asset Proxy](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#erc20proxy) section of the v2 spec and the [Ethereum ABI Spec](https://solidity.readthedocs.io/en/develop/abi-spec.html).\n\n# Meta Data in Order Responses\n\nIn v2 of the standard relayer API we added the `metaData` field. It is meant to provide a standard place for relayers to put optional, custom or non-standard fields that may of interest to the consumer of the API.\n\nA good example of such a field is `remainingTakerAssetAmount`, which is a convenience field that communicates how much of a 0x order is potentially left to be filled. Unlike the other fields in a 0x order, it is not guaranteed to be correct as it is derived from whatever mechanism the implementer (ie. the relayer) is using. While convenient for prototyping and low stakes situations, we recommend validating the value of the field by checking the state of the blockchain yourself, such as by using [Order Watcher](https://0xproject.com/wiki#0x-OrderWatcher).\n\n# Misc.\n\n* All requests and responses should be of **application/json** content type\n* All token amounts are sent in amounts of the smallest level of precision (base units). (e.g if a token has 18 decimal places, selling 1 token would show up as selling `'1000000000000000000'` units by this API).\n* All addresses are sent as lower-case (non-checksummed) Ethereum addresses with the `0x` prefix.\n* All parameters are to be written in `lowerCamelCase`.\n","license":{"name":"Apache 2.0","url":"https://www.apache.org/licenses/LICENSE-2.0.html"}},"paths":{"/v2/asset_pairs":{"get":{"description":"Retrieves a list of available asset pairs and the information required to trade them (in any order). Setting only `assetDataA` or `assetDataB` returns pairs filtered by that asset only.","operationId":"getAssetPairs","parameters":[{"name":"assetDataA","in":"query","description":"The assetData value for the first asset in the pair.","example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"assetDataB","in":"query","description":"The assetData value for the second asset in the pair.","example":"0x0257179264389b814a946f3e92105513705ca6b990","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number","default":1}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number","default":100}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"Returns a collection of available asset pairs with some meta info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiAssetDataPairsResponseSchema"},"example":{"total":43,"page":1,"perPage":100,"records":[{"assetDataA":{"minAmount":"0","maxAmount":"10000000000000000000","precision":5,"assetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d"},"assetDataB":{"minAmount":"0","maxAmount":"50000000000000000000","precision":5,"assetData":"0x0257179264389b814a946f3e92105513705ca6b990"}}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/orders":{"get":{"description":"Retrieves a list of orders given query parameters. This endpoint should be [paginated](#section/Pagination). For querying an entire orderbook snapshot, the [orderbook endpoint](#operation/getOrderbook) is recommended. If both makerAssetData and takerAssetData are specified, returned orders will be sorted by price determined by (takerTokenAmount/makerTokenAmount) in ascending order. By default, orders returned by this endpoint are unsorted.","operationId":"getOrders","parameters":[{"name":"makerAssetProxyId","in":"query","description":"The maker [asset proxy id](https://0xproject.com/docs/0x.js#types-AssetProxyId) (example: \"0xf47261b0\" for ERC20, \"0x02571792\" for ERC721).","example":"0xf47261b0","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"takerAssetProxyId","in":"query","description":"The taker asset [asset proxy id](https://0xproject.com/docs/0x.js#types-AssetProxyId) (example: \"0xf47261b0\" for ERC20, \"0x02571792\" for ERC721).","example":"0x02571792","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"makerAssetAddress","in":"query","description":"The contract address for the maker asset.","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"takerAssetAddress","in":"query","description":"The contract address for the taker asset.","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"exchangeAddress","in":"query","description":"Same as exchangeAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"senderAddress","in":"query","description":"Same as senderAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"makerAssetData","in":"query","description":"Same as makerAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"takerAssetData","in":"query","description":"Same as takerAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"traderAssetData","in":"query","description":"Same as traderAssetData in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"makerAddress","in":"query","description":"Same as makerAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"traderAddress","in":"query","description":"Same as traderAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"feeRecipientAddress","in":"query","description":"Same as feeRecipientAddress in the [0x Protocol v2 Specification](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#order-message-format)","example":"0xe41d2489571d322189246dafa5ebde1f4699f498","schema":{"$ref":"#/components/schemas/addressSchema"}},{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number","default":1}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number","default":100}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"A collection of 0x orders with meta-data as specified by query params","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"},"example":{"total":984,"page":1,"perPage":100,"records":[{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order/{orderHash}":{"get":{"description":"Retrieves the 0x order with meta info that is associated with the hash.","operationId":"getOrder","parameters":[{"name":"orderHash","in":"path","description":"The hash of the desired 0x order.","example":"0xd4b103c42d2512eef3fee775e097f044291615d25f5d71e0ac70dbd49d223591","schema":{"$ref":"#/components/schemas/orderHashSchema"}},{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The order and meta info associated with the orderHash","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderSchema"},"example":{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/orderbook":{"get":{"description":"Retrieves the orderbook for a given asset pair. This endpoint should be [paginated](#section/Pagination). Bids will be sorted in descending order by price, and asks will be sorted in ascending order by price. Within the price sorted orders, the orders are further sorted by _taker fee price_ which is defined as the **takerFee** divided by **takerTokenAmount**. After _taker fee price_, orders are to be sorted by expiration in ascending order. The way pagination works for this endpoint is that the **page** and **perPage** query params apply to both `bids` and `asks` collections, and if `page` * `perPage` > `total` for a certain collection, the `records` for that collection should just be empty. ","operationId":"getOrderbook","parameters":[{"name":"baseAssetData","in":"query","description":"assetData (makerAssetData or takerAssetData) designated as the base currency in the [currency pair calculation](https://en.wikipedia.org/wiki/Currency_pair) of price.","required":true,"example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"quoteAssetData","in":"query","description":"assetData (makerAssetData or takerAssetData) designated as the quote currency in the currency pair calculation of price (required).","required":true,"example":"0xf47261b04c32345ced77393b3530b1eed0f346429d","schema":{"$ref":"#/components/schemas/hexSchema"}},{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number","default":1}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number","default":100}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The sorted order book for the specified asset pair.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderbookResponseSchema"},"example":{"bids":{"total":325,"page":2,"perPage":100,"records":[{"order":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"},"metaData":{}}]},"asks":{"total":500,"page":2,"perPage":100,"records":[{"order":{"makerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","takerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"20000000000000000","takerAssetAmount":"10000000000000000","makerFee":"200000000000000","takerFee":"100000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","takerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x013842a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b3518891"},"metaData":{}}]}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order_config":{"get":{"description":"Relayers have full discretion over the orders that they are willing to host on their orderbooks (e.g what fees they charge, etc...). In order for traders to discover their requirements programmatically, they can send an incomplete order to this endpoint and receive the missing fields, specifc to that order. This gives relayers a large amount of flexibility to tailor fees to unique traders, trading pairs and volume amounts. Submit a partial order and receive information required to complete the order: `senderAddress`, `feeRecipientAddress`, `makerFee`, `takerFee`. ","operationId":"getOrderConfig","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}}],"requestBody":{"description":"The fields of a 0x order the relayer may want to decide what configuration to send back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderConfigPayloadSchema"},"example":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","expirationTimeSeconds":"1532560590"}}}},"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"The additional fields necessary in order to submit an order to the relayer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiOrderConfigResponseSchema"},"example":{"senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","makerFee":"100000000000000","takerFee":"200000000000000"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/fee_recipients":{"get":{"description":"Retrieves a collection of all fee recipient addresses for a relayer. This endpoint should be [paginated](#section/Pagination).","operationId":"getFeeRecipients","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}},{"name":"page","in":"query","description":"The number of the page to request in the collection.","example":3,"schema":{"type":"number","default":1}},{"name":"perPage","in":"query","description":"The number of records to return per page.","example":10,"schema":{"type":"number","default":100}}],"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"A collection of all used fee recipient addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiFeeRecipientsResponseSchema"},"example":{"total":3,"page":1,"perPage":10,"records":["0x6eC92694ea172ebC430C30fa31De87620967A082","0x9e56625509c2f60af937f23b7b532600390e8c8b","0xa2b31dacf30a9c50ca473337c01d8a201ae33e32"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}},"/v2/order":{"post":{"description":"Submit a signed order to the relayer.","operationId":"postOrder","parameters":[{"name":"networkId","in":"query","description":"The id of the Ethereum network","example":42,"schema":{"type":"number","default":1}}],"requestBody":{"description":"A valid signed 0x order based on the schema.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/signedOrderSchema"},"example":{"makerAddress":"0x9e56625509c2f60af937f23b7b532600390e8c8b","takerAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","feeRecipientAddress":"0xb046140686d052fff581f63f8136cce132e857da","senderAddress":"0xa2b31dacf30a9c50ca473337c01d8a201ae33e32","makerAssetAmount":"10000000000000000","takerAssetAmount":"20000000000000000","makerFee":"100000000000000","takerFee":"200000000000000","expirationTimeSeconds":"1532560590","salt":"1532559225","makerAssetData":"0xf47261b04c32345ced77393b3530b1eed0f346429d","takerAssetData":"0x0257179264389b814a946f3e92105513705ca6b990","exchangeAddress":"0x12459c951127e0c374ff9105dda097662a027093","signature":"0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"}}}},"responses":{"200":{"headers":{"X-Rate-Limit-Limit":{"description":"The maximum number of requests you're permitted to make per hour.","schema":{"type":"integer"}},"X-Rate-Limit-Remaining":{"description":"The number of requests remaining in the current rate limit window.","schema":{"type":"integer"}},"X-Rate-Limit-Reset":{"description":"The time at which the current rate limit window resets in UTC epoch seconds.","schema":{"type":"integer"}}},"description":"OK","content":{}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/relayerApiErrorResponseSchema"},"example":{"code":100,"reason":"Validation failed","validationErrors":[{"field":"networkId","code":1006,"reason":"Network id 42 is not supported"}]}}}},"404":{"description":"Not found"},"429":{"description":"Too many requests - Rate limit exceeded"},"500":{"description":"Internal Server Error"},"501":{"description":"Not implemented."}}}}},"components":{"schemas":{"numberSchema":{"type":"string","pattern":"^\\d+(\\.\\d+)?$"},"addressSchema":{"type":"string","pattern":"^0x[0-9a-f]{40}$"},"hexSchema":{"type":"string","pattern":"^0x(([0-9a-f][0-9a-f])+)?$"},"orderHashSchema":{"type":"string","pattern":"^0x[0-9a-fA-F]{64}$"},"orderSchema":{"properties":{"makerAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAddress":{"$ref":"#/components/schemas/addressSchema"},"makerFee":{"$ref":"#/components/schemas/numberSchema"},"takerFee":{"$ref":"#/components/schemas/numberSchema"},"senderAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"takerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"salt":{"$ref":"#/components/schemas/numberSchema"},"exchangeAddress":{"$ref":"#/components/schemas/addressSchema"},"feeRecipientAddress":{"$ref":"#/components/schemas/addressSchema"},"expirationTimeSeconds":{"$ref":"#/components/schemas/numberSchema"}},"required":["makerAddress","takerAddress","makerFee","takerFee","senderAddress","makerAssetAmount","takerAssetAmount","makerAssetData","takerAssetData","salt","exchangeAddress","feeRecipientAddress","expirationTimeSeconds"],"type":"object"},"signedOrderSchema":{"allOf":[{"$ref":"#/components/schemas/orderSchema"},{"properties":{"signature":{"$ref":"#/components/schemas/hexSchema"}},"required":["signature"]}]},"signedOrdersSchema":{"type":"array","items":{"$ref":"#/components/schemas/signedOrderSchema"}},"ordersSchema":{"type":"array","items":{"$ref":"#/components/schemas/orderSchema"}},"paginatedCollectionSchema":{"type":"object","properties":{"total":{"type":"number"},"perPage":{"type":"number"},"page":{"type":"number"}},"required":["total","perPage","page"]},"relayerApiErrorResponseSchema":{"type":"object","properties":{"code":{"type":"integer","minimum":100,"maximum":103},"reason":{"type":"string"},"validationErrors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"integer","minimum":1000,"maximum":1006},"reason":{"type":"string"}},"required":["field","code","reason"]}}},"required":["code","reason"]},"relayerApiFeeRecipientsResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/addressSchema"}},"required":["records"]}]},"relayerApiOrderSchema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/orderSchema"},"metaData":{"type":"object"}},"required":["order","metaData"]},"relayerApiOrdersSchema":{"type":"array","items":{"$ref":"#/components/schemas/relayerApiOrderSchema"}},"relayerApiOrderConfigPayloadSchema":{"type":"object","properties":{"makerAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"takerAssetAmount":{"$ref":"#/components/schemas/numberSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"exchangeAddress":{"$ref":"#/components/schemas/addressSchema"},"expirationTimeSeconds":{"$ref":"#/components/schemas/numberSchema"}},"required":["makerAddress","takerAddress","makerAssetAmount","takerAssetAmount","makerAssetData","takerAssetData","exchangeAddress","expirationTimeSeconds"]},"relayerApiOrderConfigResponseSchema":{"type":"object","properties":{"makerFee":{"$ref":"#/components/schemas/numberSchema"},"takerFee":{"$ref":"#/components/schemas/numberSchema"},"feeRecipientAddress":{"$ref":"#/components/schemas/addressSchema"},"senderAddress":{"$ref":"#/components/schemas/addressSchema"}},"required":["makerFee","takerFee","feeRecipientAddress","senderAddress"]},"relayerApiOrderbookResponseSchema":{"type":"object","properties":{"bids":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"},"asks":{"$ref":"#/components/schemas/relayerApiOrdersResponseSchema"}},"required":["bids","asks"]},"relayerApiAssetDataPairsResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/relayerApiAssetDataPairsSchema"}},"required":["records"]}]},"relayerApiAssetDataTradeInfoSchema":{"type":"object","properties":{"assetData":{"$ref":"#/components/schemas/hexSchema"},"minAmount":{"$ref":"#/components/schemas/numberSchema"},"maxAmount":{"$ref":"#/components/schemas/numberSchema"},"precision":{"type":"number"}},"required":["assetData"]},"relayerApiOrdersChannelSubscribeSchema":{"type":"object","properties":{"type":{"enum":["subscribe"]},"channel":{"enum":["orders"]},"requestId":{"type":"string"},"payload":{"$ref":"#/components/schemas/relayerApiOrdersChannelSubscribePayload"}},"required":["type","channel","requestId"]},"relayerApiOrdersChannelSubscribePayload":{"type":"object","properties":{"makerAssetProxyId":{"$ref":"#/components/schemas/hexSchema"},"takerAssetProxyId":{"$ref":"#/components/schemas/hexSchema"},"networkId":{"type":"number"},"makerAssetAddress":{"$ref":"#/components/schemas/addressSchema"},"takerAssetAddress":{"$ref":"#/components/schemas/addressSchema"},"makerAssetData":{"$ref":"#/components/schemas/hexSchema"},"takerAssetData":{"$ref":"#/components/schemas/hexSchema"},"traderAssetData":{"$ref":"#/components/schemas/hexSchema"}}},"relayerApiOrdersChannelUpdateSchema":{"type":"object","properties":{"type":{"enum":["update"]},"channel":{"enum":["orders"]},"requestId":{"type":"string"},"payload":{"$ref":"#/components/schemas/relayerApiOrdersSchema"}},"required":["type","channel","requestId"]},"relayerApiOrdersResponseSchema":{"type":"object","allOf":[{"$ref":"#/components/schemas/paginatedCollectionSchema"},{"properties":{"records":{"$ref":"#/components/schemas/relayerApiOrdersSchema"}},"required":["records"]}]},"relayerApiAssetDataPairsSchema":{"type":"array","items":{"properties":{"assetDataA":{"$ref":"#/components/schemas/relayerApiAssetDataTradeInfoSchema"},"assetDataB":{"$ref":"#/components/schemas/relayerApiAssetDataTradeInfoSchema"}},"required":["assetDataA","assetDataB"],"type":"object"}}}}}
\ No newline at end of file diff --git a/packages/sra-api/src/parameters.ts b/packages/sra-api/src/parameters.ts index 10382894c..48ffb036d 100644 --- a/packages/sra-api/src/parameters.ts +++ b/packages/sra-api/src/parameters.ts @@ -7,6 +7,7 @@ export const paginationParameters: ParameterObject[] = [ example: 3, schema: { type: 'number', + default: 1, }, }, { @@ -16,6 +17,7 @@ export const paginationParameters: ParameterObject[] = [ example: 10, schema: { type: 'number', + default: 100, }, }, ]; @@ -27,10 +29,11 @@ export const networkdIdParameter: ParameterObject = { example: 42, schema: { type: 'number', + default: 1, }, }; export const generateParameters = (parameters: ParameterObject[], isPaginated: boolean = false): ParameterObject[] => { const optionalParameters = isPaginated ? paginationParameters : []; - return [networkdIdParameter, ...optionalParameters, ...parameters]; + return [...parameters, networkdIdParameter, ...optionalParameters]; }; diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 70dd7ddad..bf8a0f7b3 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -55,7 +55,7 @@ "@types/yargs": "^10.0.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -64,7 +64,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 3825526a8..3eb3d381f 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -63,7 +63,7 @@ "@types/sinon": "^2.2.2", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -73,7 +73,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2", + "typescript": "3.0.1", "webpack": "^3.1.0" }, "optionalDependencies": { diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index ce2a1a52d..769fdf371 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -43,7 +43,7 @@ "shx": "^0.2.2", "source-map-loader": "^0.1.6", "tslint": "5.11.0", - "typescript": "2.9.2", + "typescript": "3.0.1", "webpack": "^3.1.0", "webpack-node-externals": "^1.6.0" } diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 8de06399f..a20cf5621 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -34,10 +34,10 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/tslint-config/README.md", "devDependencies": { "@types/lodash": "4.14.104", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "lodash": "^4.17.5", diff --git a/packages/types/package.json b/packages/types/package.json index 9ee0bf0c9..8d5eb1123 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -24,11 +24,11 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/types/README.md", "devDependencies": { "@0xproject/tslint-config": "^1.0.5", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@types/node": "^8.0.53", diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index fa634420d..04480d093 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -62,8 +62,7 @@ export interface ValidatorSignature { export enum ExchangeContractErrs { OrderFillExpired = 'ORDER_FILL_EXPIRED', OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', - OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', - OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderCancelled = 'ORDER_CANCELLED', OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', diff --git a/packages/typescript-typings/package.json b/packages/typescript-typings/package.json index 75bafd496..0070df4fb 100644 --- a/packages/typescript-typings/package.json +++ b/packages/typescript-typings/package.json @@ -31,7 +31,7 @@ "popper.js": "1.14.3" }, "devDependencies": { - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "shx": "^0.2.2" }, "publishConfig": { diff --git a/packages/utils/package.json b/packages/utils/package.json index bccd03ae6..4568352e1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -32,13 +32,13 @@ "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "chai": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.4", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index fc52f9dd9..06450cda1 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -41,7 +41,7 @@ "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "ganache-core": "0xProject/ganache-core#monorepo-dep", "make-promises-safe": "^1.1.0", @@ -51,7 +51,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.9.2" + "typescript": "3.0.1" }, "dependencies": { "@0xproject/assert": "^1.0.5", diff --git a/packages/website/package.json b/packages/website/package.json index 85ea48821..ffabbce8b 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -84,7 +84,7 @@ "@types/redux": "^3.6.0", "awesome-typescript-loader": "^3.1.3", "copy-webpack-plugin": "^4.0.1", - "copyfiles": "^1.2.0", + "copyfiles": "^2.0.0", "css-loader": "0.23.x", "exports-loader": "0.6.x", "imports-loader": "0.6.x", @@ -98,7 +98,7 @@ "style-loader": "0.13.x", "tslint": "5.11.0", "tslint-config-0xproject": "^0.0.2", - "typescript": "2.9.2", + "typescript": "3.0.1", "uglifyjs-webpack-plugin": "^1.2.5", "webpack": "^3.1.0", "webpack-dev-middleware": "^1.10.0", diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 39bbd404c..32b07473c 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -247,12 +247,9 @@ export const utils = { } = { [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelAmountZero]: "Order cancel amount can't be 0", - [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: - 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderCancelled]: 'This order has been cancelled', [ExchangeContractErrs.OrderFillAmountZero]: "Order fill amount can't be 0", - [ExchangeContractErrs.OrderRemainingFillAmountZero]: - 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderRemainingFillAmountZero]: 'This order has already been completely filled', [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order. Please try filling a different amount.', [ExchangeContractErrs.InsufficientTakerBalance]: |