From efbfd08aa0152bf82d293fab0b073e1d650c9ab1 Mon Sep 17 00:00:00 2001
From: Amir Bandeali <abandeali1@gmail.com>
Date: Mon, 21 Jan 2019 21:32:59 -0800
Subject: Separate forwarder into own package

---
 .circleci/config.yml                               |    2 +
 .gitignore                                         |    2 +
 .prettierignore                                    |    2 +
 contracts/exchange-forwarder/CHANGELOG.json        |   74 ++
 contracts/exchange-forwarder/CHANGELOG.md          |   32 +
 contracts/exchange-forwarder/DEPLOYS.json          |   31 +
 contracts/exchange-forwarder/README.md             |   69 ++
 contracts/exchange-forwarder/compiler.json         |   22 +
 .../exchange-forwarder/contracts/src/Forwarder.sol |   50 +
 .../contracts/src/MixinAssets.sol                  |  143 +++
 .../contracts/src/MixinExchangeWrapper.sol         |  260 ++++
 .../contracts/src/MixinForwarderCore.sol           |  214 ++++
 .../exchange-forwarder/contracts/src/MixinWeth.sol |  113 ++
 .../contracts/src/interfaces/IAssets.sol           |   34 +
 .../contracts/src/interfaces/IForwarder.sol        |   30 +
 .../contracts/src/interfaces/IForwarderCore.sol    |   80 ++
 .../contracts/src/libs/LibConstants.sol            |   62 +
 .../contracts/src/libs/LibForwarderErrors.sol      |   34 +
 .../contracts/src/mixins/MAssets.sol               |   53 +
 .../contracts/src/mixins/MExchangeWrapper.sol      |   87 ++
 .../contracts/src/mixins/MWeth.sol                 |   41 +
 contracts/exchange-forwarder/package.json          |   86 ++
 .../exchange-forwarder/src/artifacts/index.ts      |    7 +
 contracts/exchange-forwarder/src/index.ts          |    3 +
 contracts/exchange-forwarder/src/wrappers/index.ts |    1 +
 contracts/exchange-forwarder/test/forwarder.ts     | 1279 ++++++++++++++++++++
 contracts/exchange-forwarder/test/global_hooks.ts  |   17 +
 .../test/utils/forwarder_wrapper.ts                |  126 ++
 contracts/exchange-forwarder/test/utils/index.ts   |    1 +
 contracts/exchange-forwarder/tsconfig.json         |   11 +
 contracts/exchange-forwarder/tslint.json           |    6 +
 contracts/extensions/compiler.json                 |    2 +-
 .../BalanceThresholdFilter.sol                     |   45 -
 .../MixinBalanceThresholdFilterCore.sol            |  135 ---
 .../MixinExchangeCalldata.sol                      |  103 --
 .../interfaces/IBalanceThresholdFilterCore.sol     |   55 -
 .../interfaces/IThresholdAsset.sol                 |   31 -
 .../mixins/MBalanceThresholdFilterCore.sol         |   54 -
 .../mixins/MExchangeCalldata.sol                   |   56 -
 .../contracts/DutchAuction/DutchAuction.sol        |  205 ----
 .../extensions/contracts/Forwarder/Forwarder.sol   |   50 -
 .../extensions/contracts/Forwarder/MixinAssets.sol |  143 ---
 .../contracts/Forwarder/MixinExchangeWrapper.sol   |  260 ----
 .../contracts/Forwarder/MixinForwarderCore.sol     |  214 ----
 .../extensions/contracts/Forwarder/MixinWeth.sol   |  113 --
 .../contracts/Forwarder/interfaces/IAssets.sol     |   34 -
 .../contracts/Forwarder/interfaces/IForwarder.sol  |   30 -
 .../Forwarder/interfaces/IForwarderCore.sol        |   80 --
 .../contracts/Forwarder/libs/LibConstants.sol      |   62 -
 .../Forwarder/libs/LibForwarderErrors.sol          |   34 -
 .../contracts/Forwarder/mixins/MAssets.sol         |   53 -
 .../Forwarder/mixins/MExchangeWrapper.sol          |   87 --
 .../contracts/Forwarder/mixins/MWeth.sol           |   41 -
 .../contracts/OrderMatcher/MixinAssets.sol         |  195 ---
 .../contracts/OrderMatcher/MixinMatchOrders.sol    |   86 --
 .../contracts/OrderMatcher/OrderMatcher.sol        |   38 -
 .../contracts/OrderMatcher/interfaces/IAssets.sol  |   43 -
 .../OrderMatcher/interfaces/IMatchOrders.sol       |   43 -
 .../OrderMatcher/interfaces/IOrderMatcher.sol      |   31 -
 .../contracts/OrderMatcher/libs/LibConstants.sol   |   56 -
 .../contracts/OrderMatcher/mixins/MAssets.sol      |   71 --
 .../contracts/OrderValidator/OrderValidator.sol    |  218 ----
 .../BalanceThresholdFilter.sol                     |   45 +
 .../MixinBalanceThresholdFilterCore.sol            |  135 +++
 .../MixinExchangeCalldata.sol                      |  103 ++
 .../interfaces/IBalanceThresholdFilterCore.sol     |   55 +
 .../interfaces/IThresholdAsset.sol                 |   31 +
 .../mixins/MBalanceThresholdFilterCore.sol         |   54 +
 .../mixins/MExchangeCalldata.sol                   |   56 +
 .../contracts/src/DutchAuction/DutchAuction.sol    |  205 ++++
 .../contracts/src/OrderMatcher/MixinAssets.sol     |  195 +++
 .../src/OrderMatcher/MixinMatchOrders.sol          |   86 ++
 .../contracts/src/OrderMatcher/OrderMatcher.sol    |   38 +
 .../src/OrderMatcher/interfaces/IAssets.sol        |   43 +
 .../src/OrderMatcher/interfaces/IMatchOrders.sol   |   43 +
 .../src/OrderMatcher/interfaces/IOrderMatcher.sol  |   31 +
 .../src/OrderMatcher/libs/LibConstants.sol         |   56 +
 .../contracts/src/OrderMatcher/mixins/MAssets.sol  |   71 ++
 .../src/OrderValidator/OrderValidator.sol          |  218 ++++
 contracts/extensions/package.json                  |    2 +-
 contracts/extensions/src/artifacts/index.ts        |    2 -
 contracts/extensions/src/wrappers/index.ts         |    1 -
 contracts/extensions/test/forwarder.ts             | 1279 --------------------
 .../extensions/test/utils/forwarder_wrapper.ts     |  126 --
 contracts/extensions/test/utils/index.ts           |    1 -
 contracts/extensions/tsconfig.json                 |    1 -
 contracts/utils/package.json                       |    2 +
 package.json                                       |    2 +-
 88 files changed, 4442 insertions(+), 4079 deletions(-)
 create mode 100644 contracts/exchange-forwarder/CHANGELOG.json
 create mode 100644 contracts/exchange-forwarder/CHANGELOG.md
 create mode 100644 contracts/exchange-forwarder/DEPLOYS.json
 create mode 100644 contracts/exchange-forwarder/README.md
 create mode 100644 contracts/exchange-forwarder/compiler.json
 create mode 100644 contracts/exchange-forwarder/contracts/src/Forwarder.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/MixinAssets.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/MixinForwarderCore.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/MixinWeth.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/interfaces/IAssets.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/interfaces/IForwarder.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/interfaces/IForwarderCore.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/libs/LibForwarderErrors.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/mixins/MAssets.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/mixins/MExchangeWrapper.sol
 create mode 100644 contracts/exchange-forwarder/contracts/src/mixins/MWeth.sol
 create mode 100644 contracts/exchange-forwarder/package.json
 create mode 100644 contracts/exchange-forwarder/src/artifacts/index.ts
 create mode 100644 contracts/exchange-forwarder/src/index.ts
 create mode 100644 contracts/exchange-forwarder/src/wrappers/index.ts
 create mode 100644 contracts/exchange-forwarder/test/forwarder.ts
 create mode 100644 contracts/exchange-forwarder/test/global_hooks.ts
 create mode 100644 contracts/exchange-forwarder/test/utils/forwarder_wrapper.ts
 create mode 100644 contracts/exchange-forwarder/test/utils/index.ts
 create mode 100644 contracts/exchange-forwarder/tsconfig.json
 create mode 100644 contracts/exchange-forwarder/tslint.json
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
 delete mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
 delete mode 100644 contracts/extensions/contracts/DutchAuction/DutchAuction.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/Forwarder.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/MixinAssets.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/MixinWeth.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
 delete mode 100644 contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
 delete mode 100644 contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol
 delete mode 100644 contracts/extensions/contracts/OrderValidator/OrderValidator.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
 create mode 100644 contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
 create mode 100644 contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol
 create mode 100644 contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol
 create mode 100644 contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol
 delete mode 100644 contracts/extensions/test/forwarder.ts
 delete mode 100644 contracts/extensions/test/utils/forwarder_wrapper.ts

diff --git a/.circleci/config.yml b/.circleci/config.yml
index cd6065fb9..248a308dc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -49,6 +49,7 @@ jobs:
             - run: yarn wsrun test:circleci @0x/contracts-extensions
             - run: yarn wsrun test:circleci @0x/contracts-asset-proxy
             - run: yarn wsrun test:circleci @0x/contracts-exchange
+            - run: yarn wsrun test:circleci @0x/contracts-exchange-forwarder
     test-contracts-geth:
         docker:
             - image: circleci/node:9-browsers
@@ -68,6 +69,7 @@ jobs:
             - run: yarn wsrun test:circleci @0x/contracts-extensions
             - run: yarn wsrun test:circleci @0x/contracts-asset-proxy
             - run: yarn wsrun test:circleci @0x/contracts-exchange
+            - run: yarn wsrun test:circleci @0x/contracts-exchange-forwarder
     test-publish:
         resource_class: medium+
         docker:
diff --git a/.gitignore b/.gitignore
index 997f8b2b2..f055a9398 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,6 +91,7 @@ contracts/exchange-libs/generated-artifacts/
 contracts/erc20/generated-artifacts/
 contracts/erc721/generated-artifacts/
 contracts/extensions/generated-artifacts/
+contracts/exchange-forwarder/generated-artifacts/
 packages/sol-tracing-utils/test/fixtures/artifacts/
 packages/metacoin/artifacts/
 
@@ -104,6 +105,7 @@ contracts/exchange-libs/generated-wrappers/
 contracts/erc20/generated-wrappers/
 contracts/erc721/generated-wrappers/
 contracts/extensions/generated-wrappers/
+contracts/exchange-forwarder/generated-wrappers/
 packages/metacoin/src/contract_wrappers
 
 # solc-bin in sol-compiler
diff --git a/.prettierignore b/.prettierignore
index 00d36575c..a72b5ed2a 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -16,6 +16,8 @@ lib
 /contracts/erc721/generated-artifacts
 /contracts/extensions/generated-wrappers
 /contracts/extensions/generated-artifacts
+/contracts/exchange-forwarder/generated-wrappers
+/contracts/exchange-forwarder/generated-artifacts
 /packages/abi-gen-wrappers/src/generated-wrappers
 /packages/contract-artifacts/artifacts
 /python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts
diff --git a/contracts/exchange-forwarder/CHANGELOG.json b/contracts/exchange-forwarder/CHANGELOG.json
new file mode 100644
index 000000000..920fef05a
--- /dev/null
+++ b/contracts/exchange-forwarder/CHANGELOG.json
@@ -0,0 +1,74 @@
+[
+    {
+        "version": "2.0.0",
+        "changes": [
+            {
+                "note": "Upgrade the bignumber.js to v8.0.2",
+                "pr": 1517
+            }
+        ]
+    },
+    {
+        "timestamp": 1547747677,
+        "version": "1.2.3",
+        "changes": [
+            {
+                "note": "Dependencies updated"
+            }
+        ]
+    },
+    {
+        "timestamp": 1547561734,
+        "version": "1.2.2",
+        "changes": [
+            {
+                "note": "Dependencies updated"
+            }
+        ]
+    },
+    {
+        "timestamp": 1547225310,
+        "version": "1.2.1",
+        "changes": [
+            {
+                "note": "Dependencies updated"
+            }
+        ]
+    },
+    {
+        "version": "1.2.0",
+        "changes": [
+            {
+                "note": "Added Dutch Auction Wrapper",
+                "pr": 1465
+            }
+        ],
+        "timestamp": 1547040760
+    },
+    {
+        "version": "1.1.0",
+        "changes": [
+            {
+                "note": "Added Balance Threshold Filter",
+                "pr": 1383
+            },
+            {
+                "note": "Add OrderMatcher",
+                "pr": 1117
+            },
+            {
+                "note": "Add OrderValidator",
+                "pr": 1464
+            }
+        ]
+    },
+    {
+        "timestamp": 1544741676,
+        "version": "1.0.2",
+        "changes": [
+            {
+                "note": "Dependencies updated"
+            }
+        ]
+    }
+]
diff --git a/contracts/exchange-forwarder/CHANGELOG.md b/contracts/exchange-forwarder/CHANGELOG.md
new file mode 100644
index 000000000..94ffdfb67
--- /dev/null
+++ b/contracts/exchange-forwarder/CHANGELOG.md
@@ -0,0 +1,32 @@
+<!--
+changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
+Edit the package's CHANGELOG.json file only.
+-->
+
+CHANGELOG
+
+## v1.2.3 - _January 17, 2019_
+
+    * Dependencies updated
+
+## v1.2.2 - _January 15, 2019_
+
+    * Dependencies updated
+
+## v1.2.1 - _January 11, 2019_
+
+    * Dependencies updated
+
+## v1.2.0 - _January 9, 2019_
+
+    * Added Dutch Auction Wrapper (#1465)
+
+## v1.1.0 - _Invalid date_
+
+    * Added Balance Threshold Filter (#1383)
+    * Add OrderMatcher (#1117)
+    * Add OrderValidator (#1464)
+
+## v1.0.2 - _December 13, 2018_
+
+    * Dependencies updated
diff --git a/contracts/exchange-forwarder/DEPLOYS.json b/contracts/exchange-forwarder/DEPLOYS.json
new file mode 100644
index 000000000..1a093bf77
--- /dev/null
+++ b/contracts/exchange-forwarder/DEPLOYS.json
@@ -0,0 +1,31 @@
+[
+    {
+        "name": "Forwarder",
+        "version": "1.1.0",
+        "changes": [
+            {
+                "note": "Round up when calculating remaining amounts in marketBuy functions",
+                "pr": 1162,
+                "networks": {
+                    "1": "0x5468a1dc173652ee28d249c271fa9933144746b1",
+                    "3": "0x2240dab907db71e64d3e0dba4800c83b5c502d4e",
+                    "42": "0x17992e4ffb22730138e4b62aaa6367fa9d3699a6"
+                }
+            }
+        ]
+    },
+    {
+        "name": "Forwarder",
+        "version": "1.0.0",
+        "changes": [
+            {
+                "note": "protocol v2 deploy",
+                "networks": {
+                    "1": "0x7afc2d5107af94c462a194d2c21b5bdd238709d6",
+                    "3": "0x3983e204b12b3c02fb0638caf2cd406a62e0ead3",
+                    "42": "0xd85e2fa7e7e252b27b01bf0d65c946959d2f45b8"
+                }
+            }
+        ]
+    }
+]
diff --git a/contracts/exchange-forwarder/README.md b/contracts/exchange-forwarder/README.md
new file mode 100644
index 000000000..820f6e78a
--- /dev/null
+++ b/contracts/exchange-forwarder/README.md
@@ -0,0 +1,69 @@
+## Contract extensions
+
+Smart contracts that implement extensions for the 0x protocol.
+
+## Usage
+
+Contract extensions of the protocol can be found in the [contracts](./contracts) directory. This directory contains contracts that interact with the 2.0.0 contracts and will be used in production, such as the [Forwarder](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarder-specification.md) contract.
+
+## Bug bounty
+
+A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty).
+
+## Contributing
+
+We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
+
+For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install Dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Build
+
+To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
+
+```bash
+PKG=@0x/contracts-extensions yarn build
+```
+
+Or continuously rebuild on change:
+
+```bash
+PKG=@0x/contracts-extensions yarn watch
+```
+
+### Clean
+
+```bash
+yarn clean
+```
+
+### Lint
+
+```bash
+yarn lint
+```
+
+### Run Tests
+
+```bash
+yarn test
+```
+
+#### Testing options
+
+Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md).
diff --git a/contracts/exchange-forwarder/compiler.json b/contracts/exchange-forwarder/compiler.json
new file mode 100644
index 000000000..b0237a519
--- /dev/null
+++ b/contracts/exchange-forwarder/compiler.json
@@ -0,0 +1,22 @@
+{
+    "artifactsDir": "./generated-artifacts",
+    "contractsDir": "./contracts",
+    "compilerSettings": {
+        "optimizer": {
+            "enabled": true,
+            "runs": 1000000
+        },
+        "outputSelection": {
+            "*": {
+                "*": [
+                    "abi",
+                    "evm.bytecode.object",
+                    "evm.bytecode.sourceMap",
+                    "evm.deployedBytecode.object",
+                    "evm.deployedBytecode.sourceMap"
+                ]
+            }
+        }
+    },
+    "contracts": ["Forwarder"]
+}
diff --git a/contracts/exchange-forwarder/contracts/src/Forwarder.sol b/contracts/exchange-forwarder/contracts/src/Forwarder.sol
new file mode 100644
index 000000000..94dec40ed
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/Forwarder.sol
@@ -0,0 +1,50 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./MixinWeth.sol";
+import "./MixinForwarderCore.sol";
+import "./libs/LibConstants.sol";
+import "./MixinAssets.sol";
+import "./MixinExchangeWrapper.sol";
+
+
+// solhint-disable no-empty-blocks
+contract Forwarder is
+    LibConstants,
+    MixinWeth,
+    MixinAssets,
+    MixinExchangeWrapper,
+    MixinForwarderCore
+{
+    constructor (
+        address _exchange,
+        bytes memory _zrxAssetData,
+        bytes memory _wethAssetData
+    )
+        public
+        LibConstants(
+            _exchange,
+            _zrxAssetData,
+            _wethAssetData
+        )
+        MixinForwarderCore()
+    {}
+}
diff --git a/contracts/exchange-forwarder/contracts/src/MixinAssets.sol b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol
new file mode 100644
index 000000000..330586ed9
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol
@@ -0,0 +1,143 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-utils/contracts/src/Ownable.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
+import "./libs/LibConstants.sol";
+import "./mixins/MAssets.sol";
+
+
+contract MixinAssets is
+    Ownable,
+    LibConstants,
+    MAssets
+{
+    using LibBytes for bytes;
+
+    bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
+
+    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
+    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+    ///      used to withdraw assets that were accidentally sent to this contract.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of ERC20 token to withdraw.
+    function withdrawAsset(
+        bytes assetData,
+        uint256 amount
+    )
+        external
+        onlyOwner
+    {
+        transferAssetToSender(assetData, amount);
+    }
+
+    /// @dev Transfers given amount of asset to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferAssetToSender(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        bytes4 proxyId = assetData.readBytes4(0);
+
+        if (proxyId == ERC20_DATA_ID) {
+            transferERC20Token(assetData, amount);
+        } else if (proxyId == ERC721_DATA_ID) {
+            transferERC721Token(assetData, amount);
+        } else {
+            revert("UNSUPPORTED_ASSET_PROXY");
+        }
+    }
+
+    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        address token = assetData.readAddress(16);
+
+        // Transfer tokens.
+        // We do a raw call so we can check the success separate
+        // from the return data.
+        bool success = token.call(abi.encodeWithSelector(
+            ERC20_TRANSFER_SELECTOR,
+            msg.sender,
+            amount
+        ));
+        require(
+            success,
+            "TRANSFER_FAILED"
+        );
+        
+        // Check return data.
+        // If there is no return data, we assume the token incorrectly
+        // does not return a bool. In this case we expect it to revert
+        // on failure, which was handled above.
+        // If the token does return data, we require that it is a single
+        // value that evaluates to true.
+        assembly {
+            if returndatasize {
+                success := 0
+                if eq(returndatasize, 32) {
+                    // First 64 bytes of memory are reserved scratch space
+                    returndatacopy(0, 0, 32)
+                    success := mload(0)
+                }
+            }
+        }
+        require(
+            success,
+            "TRANSFER_FAILED"
+        );
+    }
+
+    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        require(
+            amount == 1,
+            "INVALID_AMOUNT"
+        );
+        // Decode asset data.
+        address token = assetData.readAddress(16);
+        uint256 tokenId = assetData.readUint256(36);
+
+        // Perform transfer.
+        IERC721Token(token).transferFrom(
+            address(this),
+            msg.sender,
+            tokenId
+        );
+    }
+}
diff --git a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol
new file mode 100644
index 000000000..c970c40cf
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol
@@ -0,0 +1,260 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibConstants.sol";
+import "./mixins/MExchangeWrapper.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibAbiEncoder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
+
+
+contract MixinExchangeWrapper is
+    LibAbiEncoder,
+    LibFillResults,
+    LibMath,
+    LibConstants,
+    MExchangeWrapper
+{
+    /// @dev Fills the input order.
+    ///      Returns false if the transaction would otherwise revert.
+    /// @param order Order struct containing order specifications.
+    /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+    /// @param signature Proof that order has been created by maker.
+    /// @return Amounts filled and fees paid by maker and taker.
+    function fillOrderNoThrow(
+        LibOrder.Order memory order,
+        uint256 takerAssetFillAmount,
+        bytes memory signature
+    )
+        internal
+        returns (FillResults memory fillResults)
+    {
+        // ABI encode calldata for `fillOrder`
+        bytes memory fillOrderCalldata = abiEncodeFillOrder(
+            order,
+            takerAssetFillAmount,
+            signature
+        );
+
+        address exchange = address(EXCHANGE);
+
+        // Call `fillOrder` and handle any exceptions gracefully
+        assembly {
+            let success := call(
+                gas,                                // forward all gas
+                exchange,                           // call address of Exchange contract
+                0,                                  // transfer 0 wei
+                add(fillOrderCalldata, 32),         // pointer to start of input (skip array length in first 32 bytes)
+                mload(fillOrderCalldata),           // length of input
+                fillOrderCalldata,                  // write output over input
+                128                                 // output size is 128 bytes
+            )
+            if success {
+                mstore(fillResults, mload(fillOrderCalldata))
+                mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32)))
+                mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64)))
+                mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96)))
+            }
+        }
+        // fillResults values will be 0 by default if call was unsuccessful
+        return fillResults;
+    }
+
+    /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker.
+    ///      Returns false if the transaction would otherwise revert.
+    /// @param orders Array of order specifications.
+    /// @param wethSellAmount Desired amount of WETH to sell.
+    /// @param signatures Proofs that orders have been signed by makers.
+    /// @return Amounts filled and fees paid by makers and taker.
+    function marketSellWeth(
+        LibOrder.Order[] memory orders,
+        uint256 wethSellAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (FillResults memory totalFillResults)
+    {
+        bytes memory makerAssetData = orders[0].makerAssetData;
+        bytes memory wethAssetData = WETH_ASSET_DATA;
+
+        uint256 ordersLength = orders.length;
+        for (uint256 i = 0; i != ordersLength; i++) {
+
+            // We assume that asset being bought by taker is the same for each order.
+            // We assume that asset being sold by taker is WETH for each order.
+            orders[i].makerAssetData = makerAssetData;
+            orders[i].takerAssetData = wethAssetData;
+
+            // Calculate the remaining amount of WETH to sell
+            uint256 remainingTakerAssetFillAmount = safeSub(wethSellAmount, totalFillResults.takerAssetFilledAmount);
+
+            // Attempt to sell the remaining amount of WETH
+            FillResults memory singleFillResults = fillOrderNoThrow(
+                orders[i],
+                remainingTakerAssetFillAmount,
+                signatures[i]
+            );
+
+            // Update amounts filled and fees paid by maker and taker
+            addFillResults(totalFillResults, singleFillResults);
+
+            // Stop execution if the entire amount of takerAsset has been sold
+            if (totalFillResults.takerAssetFilledAmount >= wethSellAmount) {
+                break;
+            }
+        }
+        return totalFillResults;
+    }
+
+    /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
+    ///      Returns false if the transaction would otherwise revert.
+    ///      The asset being sold by taker must always be WETH.
+    /// @param orders Array of order specifications.
+    /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+    /// @param signatures Proofs that orders have been signed by makers.
+    /// @return Amounts filled and fees paid by makers and taker.
+    function marketBuyExactAmountWithWeth(
+        LibOrder.Order[] memory orders,
+        uint256 makerAssetFillAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (FillResults memory totalFillResults)
+    {
+        bytes memory makerAssetData = orders[0].makerAssetData;
+        bytes memory wethAssetData = WETH_ASSET_DATA;
+
+        uint256 ordersLength = orders.length;
+        for (uint256 i = 0; i != ordersLength; i++) {
+
+            // We assume that asset being bought by taker is the same for each order.
+            // We assume that asset being sold by taker is WETH for each order.
+            orders[i].makerAssetData = makerAssetData;
+            orders[i].takerAssetData = wethAssetData;
+
+            // Calculate the remaining amount of makerAsset to buy
+            uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
+
+            // Convert the remaining amount of makerAsset to buy into remaining amount
+            // of takerAsset to sell, assuming entire amount can be sold in the current order.
+            // We round up because the exchange rate computed by fillOrder rounds in favor
+            // of the Maker. In this case we want to overestimate the amount of takerAsset.
+            uint256 remainingTakerAssetFillAmount = getPartialAmountCeil(
+                orders[i].takerAssetAmount,
+                orders[i].makerAssetAmount,
+                remainingMakerAssetFillAmount
+            );
+
+            // Attempt to sell the remaining amount of takerAsset
+            FillResults memory singleFillResults = fillOrderNoThrow(
+                orders[i],
+                remainingTakerAssetFillAmount,
+                signatures[i]
+            );
+
+            // Update amounts filled and fees paid by maker and taker
+            addFillResults(totalFillResults, singleFillResults);
+
+            // Stop execution if the entire amount of makerAsset has been bought
+            uint256 makerAssetFilledAmount = totalFillResults.makerAssetFilledAmount;
+            if (makerAssetFilledAmount >= makerAssetFillAmount) {
+                break;
+            }
+        }
+
+        require(
+            makerAssetFilledAmount >= makerAssetFillAmount,
+            "COMPLETE_FILL_FAILED"
+        );
+        return totalFillResults;
+    }
+
+    /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee
+    ///      that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues).
+    ///      It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX
+    ///      as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases.
+    ///      The asset being sold by taker must always be WETH. 
+    /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset.
+    /// @param zrxBuyAmount Desired amount of ZRX to buy.
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @return totalFillResults Amounts filled and fees paid by maker and taker.
+    function marketBuyExactZrxWithWeth(
+        LibOrder.Order[] memory orders,
+        uint256 zrxBuyAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (FillResults memory totalFillResults)
+    {
+        // Do nothing if zrxBuyAmount == 0
+        if (zrxBuyAmount == 0) {
+            return totalFillResults;
+        }
+
+        bytes memory zrxAssetData = ZRX_ASSET_DATA;
+        bytes memory wethAssetData = WETH_ASSET_DATA;
+        uint256 zrxPurchased = 0;
+
+        uint256 ordersLength = orders.length;
+        for (uint256 i = 0; i != ordersLength; i++) {
+
+            // All of these are ZRX/WETH, so we can drop the respective assetData from calldata.
+            orders[i].makerAssetData = zrxAssetData;
+            orders[i].takerAssetData = wethAssetData;
+
+            // Calculate the remaining amount of ZRX to buy.
+            uint256 remainingZrxBuyAmount = safeSub(zrxBuyAmount, zrxPurchased);
+
+            // Convert the remaining amount of ZRX to buy into remaining amount
+            // of WETH to sell, assuming entire amount can be sold in the current order.
+            // We round up because the exchange rate computed by fillOrder rounds in favor
+            // of the Maker. In this case we want to overestimate the amount of takerAsset.
+            uint256 remainingWethSellAmount = getPartialAmountCeil(
+                orders[i].takerAssetAmount,
+                safeSub(orders[i].makerAssetAmount, orders[i].takerFee),  // our exchange rate after fees 
+                remainingZrxBuyAmount
+            );
+
+            // Attempt to sell the remaining amount of WETH.
+            FillResults memory singleFillResult = fillOrderNoThrow(
+                orders[i],
+                remainingWethSellAmount,
+                signatures[i]
+            );
+
+            // Update amounts filled and fees paid by maker and taker.
+            addFillResults(totalFillResults, singleFillResult);
+            zrxPurchased = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid);
+
+            // Stop execution if the entire amount of ZRX has been bought.
+            if (zrxPurchased >= zrxBuyAmount) {
+                break;
+            }
+        }
+
+        require(
+            zrxPurchased >= zrxBuyAmount,
+            "COMPLETE_FILL_FAILED"
+        );
+        return totalFillResults;
+    }
+}
diff --git a/contracts/exchange-forwarder/contracts/src/MixinForwarderCore.sol b/contracts/exchange-forwarder/contracts/src/MixinForwarderCore.sol
new file mode 100644
index 000000000..b65e1f7ce
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/MixinForwarderCore.sol
@@ -0,0 +1,214 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibConstants.sol";
+import "./mixins/MWeth.sol";
+import "./mixins/MAssets.sol";
+import "./mixins/MExchangeWrapper.sol";
+import "./interfaces/IForwarderCore.sol";
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
+
+
+contract MixinForwarderCore is
+    LibFillResults,
+    LibMath,
+    LibConstants,
+    MWeth,
+    MAssets,
+    MExchangeWrapper,
+    IForwarderCore
+{
+    using LibBytes for bytes;
+
+    /// @dev Constructor approves ERC20 proxy to transfer ZRX and WETH on this contract's behalf.
+    constructor ()
+        public
+    {
+        address proxyAddress = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
+        require(
+            proxyAddress != address(0),
+            "UNREGISTERED_ASSET_PROXY"
+        );
+        ETHER_TOKEN.approve(proxyAddress, MAX_UINT);
+        ZRX_TOKEN.approve(proxyAddress, MAX_UINT);
+    }
+
+    /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
+    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
+    ///      5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
+    ///      Any ETH not spent will be refunded to sender.
+    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
+    /// @param feeSignatures Proofs that feeOrders have been created by makers.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
+    function marketSellOrdersWithEth(
+        LibOrder.Order[] memory orders,
+        bytes[] memory signatures,
+        LibOrder.Order[] memory feeOrders,
+        bytes[] memory feeSignatures,
+        uint256  feePercentage,
+        address feeRecipient
+    )
+        public
+        payable
+        returns (
+            FillResults memory orderFillResults,
+            FillResults memory feeOrderFillResults
+        )
+    {
+        // Convert ETH to WETH.
+        convertEthToWeth();
+
+        uint256 wethSellAmount;
+        uint256 zrxBuyAmount;
+        uint256 makerAssetAmountPurchased;
+        if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
+            // Calculate amount of WETH that won't be spent on ETH fees.
+            wethSellAmount = getPartialAmountFloor(
+                PERCENTAGE_DENOMINATOR,
+                safeAdd(PERCENTAGE_DENOMINATOR, feePercentage),
+                msg.value
+            );
+            // Market sell available WETH.
+            // ZRX fees are paid with this contract's balance.
+            orderFillResults = marketSellWeth(
+                orders,
+                wethSellAmount,
+                signatures
+            );
+            // The fee amount must be deducted from the amount transfered back to sender.
+            makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid);
+        } else {
+            // 5% of WETH is reserved for filling feeOrders and paying feeRecipient.
+            wethSellAmount = getPartialAmountFloor(
+                MAX_WETH_FILL_PERCENTAGE,
+                PERCENTAGE_DENOMINATOR,
+                msg.value
+            );
+            // Market sell 95% of WETH.
+            // ZRX fees are payed with this contract's balance.
+            orderFillResults = marketSellWeth(
+                orders,
+                wethSellAmount,
+                signatures
+            );
+            // Buy back all ZRX spent on fees.
+            zrxBuyAmount = orderFillResults.takerFeePaid;
+            feeOrderFillResults = marketBuyExactZrxWithWeth(
+                feeOrders,
+                zrxBuyAmount,
+                feeSignatures
+            );
+            makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
+        }
+
+        // Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
+        // Refund remaining ETH to msg.sender.
+        transferEthFeeAndRefund(
+            orderFillResults.takerAssetFilledAmount,
+            feeOrderFillResults.takerAssetFilledAmount,
+            feePercentage,
+            feeRecipient
+        );
+
+        // Transfer purchased assets to msg.sender.
+        transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased);
+    }
+
+    /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction.
+    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
+    ///      Any ETH not spent will be refunded to sender.
+    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
+    /// @param makerAssetFillAmount Desired amount of makerAsset to purchase.
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
+    /// @param feeSignatures Proofs that feeOrders have been created by makers.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
+    function marketBuyOrdersWithEth(
+        LibOrder.Order[] memory orders,
+        uint256 makerAssetFillAmount,
+        bytes[] memory signatures,
+        LibOrder.Order[] memory feeOrders,
+        bytes[] memory feeSignatures,
+        uint256  feePercentage,
+        address feeRecipient
+    )
+        public
+        payable
+        returns (
+            FillResults memory orderFillResults,
+            FillResults memory feeOrderFillResults
+        )
+    {
+        // Convert ETH to WETH.
+        convertEthToWeth();
+
+        uint256 zrxBuyAmount;
+        uint256 makerAssetAmountPurchased;
+        if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
+            // If the makerAsset is ZRX, it is not necessary to pay fees out of this
+            // contracts's ZRX balance because fees are factored into the price of the order.
+            orderFillResults = marketBuyExactZrxWithWeth(
+                orders,
+                makerAssetFillAmount,
+                signatures
+            );
+            // The fee amount must be deducted from the amount transfered back to sender.
+            makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid);
+        } else {
+            // Attemp to purchase desired amount of makerAsset.
+            // ZRX fees are payed with this contract's balance.
+            orderFillResults = marketBuyExactAmountWithWeth(
+                orders,
+                makerAssetFillAmount,
+                signatures
+            );
+            // Buy back all ZRX spent on fees.
+            zrxBuyAmount = orderFillResults.takerFeePaid;
+            feeOrderFillResults = marketBuyExactZrxWithWeth(
+                feeOrders,
+                zrxBuyAmount,
+                feeSignatures
+            );
+            makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
+        }
+
+        // Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
+        // Refund remaining ETH to msg.sender.
+        transferEthFeeAndRefund(
+            orderFillResults.takerAssetFilledAmount,
+            feeOrderFillResults.takerAssetFilledAmount,
+            feePercentage,
+            feeRecipient
+        );
+
+        // Transfer purchased assets to msg.sender.
+        transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased);
+    }
+}
diff --git a/contracts/exchange-forwarder/contracts/src/MixinWeth.sol b/contracts/exchange-forwarder/contracts/src/MixinWeth.sol
new file mode 100644
index 000000000..029924019
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/MixinWeth.sol
@@ -0,0 +1,113 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
+import "./libs/LibConstants.sol";
+import "./mixins/MWeth.sol";
+
+
+contract MixinWeth is
+    LibMath,
+    LibConstants,
+    MWeth
+{
+    /// @dev Default payabale function, this allows us to withdraw WETH
+    function ()
+        public
+        payable
+    {
+        require(
+            msg.sender == address(ETHER_TOKEN),
+            "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY"
+        );
+    }
+
+    /// @dev Converts message call's ETH value into WETH.
+    function convertEthToWeth()
+        internal
+    {
+        require(
+            msg.value > 0,
+            "INVALID_MSG_VALUE"
+        );
+        ETHER_TOKEN.deposit.value(msg.value)();
+    }
+
+    /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient.
+    ///      Refunds any excess ETH to msg.sender.
+    /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders.
+    /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    function transferEthFeeAndRefund(
+        uint256 wethSoldExcludingFeeOrders,
+        uint256 wethSoldForZrx,
+        uint256 feePercentage,
+        address feeRecipient
+    )
+        internal
+    {
+        // Ensure feePercentage is less than 5%.
+        require(
+            feePercentage <= MAX_FEE_PERCENTAGE,
+            "FEE_PERCENTAGE_TOO_LARGE"
+        );
+
+        // Ensure that no extra WETH owned by this contract has been sold.
+        uint256 wethSold = safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx);
+        require(
+            wethSold <= msg.value,
+            "OVERSOLD_WETH"
+        );
+
+        // Calculate amount of WETH that hasn't been sold.
+        uint256 wethRemaining = safeSub(msg.value, wethSold);
+
+        // Calculate ETH fee to pay to feeRecipient.
+        uint256 ethFee = getPartialAmountFloor(
+            feePercentage,
+            PERCENTAGE_DENOMINATOR,
+            wethSoldExcludingFeeOrders
+        );
+
+        // Ensure fee is less than amount of WETH remaining.
+        require(
+            ethFee <= wethRemaining,
+            "INSUFFICIENT_ETH_REMAINING"
+        );
+    
+        // Do nothing if no WETH remaining
+        if (wethRemaining > 0) {
+            // Convert remaining WETH to ETH
+            ETHER_TOKEN.withdraw(wethRemaining);
+
+            // Pay ETH to feeRecipient
+            if (ethFee > 0) {
+                feeRecipient.transfer(ethFee);
+            }
+
+            // Refund remaining ETH to msg.sender.
+            uint256 ethRefund = safeSub(wethRemaining, ethFee);
+            if (ethRefund > 0) {
+                msg.sender.transfer(ethRefund);
+            }
+        }
+    }
+}
diff --git a/contracts/exchange-forwarder/contracts/src/interfaces/IAssets.sol b/contracts/exchange-forwarder/contracts/src/interfaces/IAssets.sol
new file mode 100644
index 000000000..cebfd3706
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/interfaces/IAssets.sol
@@ -0,0 +1,34 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IAssets {
+
+    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
+    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+    ///      used to withdraw assets that were accidentally sent to this contract.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of ERC20 token to withdraw.
+    function withdrawAsset(
+        bytes assetData,
+        uint256 amount
+    )
+        external;
+}
diff --git a/contracts/exchange-forwarder/contracts/src/interfaces/IForwarder.sol b/contracts/exchange-forwarder/contracts/src/interfaces/IForwarder.sol
new file mode 100644
index 000000000..6ce8a1d31
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/interfaces/IForwarder.sol
@@ -0,0 +1,30 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./IForwarderCore.sol";
+import "./IAssets.sol";
+
+
+// solhint-disable no-empty-blocks
+contract IForwarder is
+    IForwarderCore,
+    IAssets
+{}
diff --git a/contracts/exchange-forwarder/contracts/src/interfaces/IForwarderCore.sol b/contracts/exchange-forwarder/contracts/src/interfaces/IForwarderCore.sol
new file mode 100644
index 000000000..00009d3d5
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/interfaces/IForwarderCore.sol
@@ -0,0 +1,80 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
+
+
+contract IForwarderCore {
+
+    /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
+    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
+    ///      5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
+    ///      Any ETH not spent will be refunded to sender.
+    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
+    /// @param feeSignatures Proofs that feeOrders have been created by makers.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
+    function marketSellOrdersWithEth(
+        LibOrder.Order[] memory orders,
+        bytes[] memory signatures,
+        LibOrder.Order[] memory feeOrders,
+        bytes[] memory feeSignatures,
+        uint256  feePercentage,
+        address feeRecipient
+    )
+        public
+        payable
+        returns (
+            LibFillResults.FillResults memory orderFillResults,
+            LibFillResults.FillResults memory feeOrderFillResults
+        );
+
+    /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction.
+    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
+    ///      Any ETH not spent will be refunded to sender.
+    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
+    /// @param makerAssetFillAmount Desired amount of makerAsset to purchase.
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
+    /// @param feeSignatures Proofs that feeOrders have been created by makers.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
+    function marketBuyOrdersWithEth(
+        LibOrder.Order[] memory orders,
+        uint256 makerAssetFillAmount,
+        bytes[] memory signatures,
+        LibOrder.Order[] memory feeOrders,
+        bytes[] memory feeSignatures,
+        uint256  feePercentage,
+        address feeRecipient
+    )
+        public
+        payable
+        returns (
+            LibFillResults.FillResults memory orderFillResults,
+            LibFillResults.FillResults memory feeOrderFillResults
+        );
+}
diff --git a/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol b/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol
new file mode 100644
index 000000000..3ff2a03e5
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol
@@ -0,0 +1,62 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+
+
+contract LibConstants {
+
+    using LibBytes for bytes;
+
+    bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
+    bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
+    uint256 constant internal MAX_UINT = 2**256 - 1;
+    uint256 constant internal PERCENTAGE_DENOMINATOR = 10**18; 
+    uint256 constant internal MAX_FEE_PERCENTAGE = 5 * PERCENTAGE_DENOMINATOR / 100;         // 5%
+    uint256 constant internal MAX_WETH_FILL_PERCENTAGE = 95 * PERCENTAGE_DENOMINATOR / 100;  // 95%
+ 
+     // solhint-disable var-name-mixedcase
+    IExchange internal EXCHANGE;
+    IEtherToken internal ETHER_TOKEN;
+    IERC20Token internal ZRX_TOKEN;
+    bytes internal ZRX_ASSET_DATA;
+    bytes internal WETH_ASSET_DATA;
+    // solhint-enable var-name-mixedcase
+
+    constructor (
+        address _exchange,
+        bytes memory _zrxAssetData,
+        bytes memory _wethAssetData
+    )
+        public
+    {
+        EXCHANGE = IExchange(_exchange);
+        ZRX_ASSET_DATA = _zrxAssetData;
+        WETH_ASSET_DATA = _wethAssetData;
+
+        address etherToken = _wethAssetData.readAddress(16);
+        address zrxToken = _zrxAssetData.readAddress(16);
+        ETHER_TOKEN = IEtherToken(etherToken);
+        ZRX_TOKEN = IERC20Token(zrxToken);
+    }
+}
diff --git a/contracts/exchange-forwarder/contracts/src/libs/LibForwarderErrors.sol b/contracts/exchange-forwarder/contracts/src/libs/LibForwarderErrors.sol
new file mode 100644
index 000000000..7a95b78a0
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/libs/LibForwarderErrors.sol
@@ -0,0 +1,34 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+// solhint-disable
+pragma solidity ^0.4.24;
+
+
+/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
+contract LibForwarderErrors {
+    string constant FEE_PERCENTAGE_TOO_LARGE = "FEE_PROPORTION_TOO_LARGE";                        // Provided fee percentage greater than 5%.
+    string constant INSUFFICIENT_ETH_REMAINING = "INSUFFICIENT_ETH_REMAINING";                    // Not enough ETH remaining to pay feeRecipient.
+    string constant OVERSOLD_WETH = "OVERSOLD_WETH";                                              // More WETH sold than provided with current message call.
+    string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED";                                // Desired purchase amount not completely filled (required for ZRX fees only).
+    string constant TRANSFER_FAILED = "TRANSFER_FAILED";                                          // Asset transfer failed.
+    string constant UNSUPPORTED_ASSET_PROXY = "UNSUPPORTED_ASSET_PROXY";                          // Proxy in assetData not supported.
+    string constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY = "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY";  // Fallback function may only be used for WETH withdrawals.
+    string constant INVALID_MSG_VALUE = "INVALID_MSG_VALUE";                                      // msg.value must be greater than 0.
+    string constant INVALID_AMOUNT = "INVALID_AMOUNT";                                            // Amount must equal 1.
+}
diff --git a/contracts/exchange-forwarder/contracts/src/mixins/MAssets.sol b/contracts/exchange-forwarder/contracts/src/mixins/MAssets.sol
new file mode 100644
index 000000000..1757b37fb
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/mixins/MAssets.sol
@@ -0,0 +1,53 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../interfaces/IAssets.sol";
+
+
+contract MAssets is
+    IAssets
+{
+    /// @dev Transfers given amount of asset to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferAssetToSender(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+}
diff --git a/contracts/exchange-forwarder/contracts/src/mixins/MExchangeWrapper.sol b/contracts/exchange-forwarder/contracts/src/mixins/MExchangeWrapper.sol
new file mode 100644
index 000000000..7439e8259
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/mixins/MExchangeWrapper.sol
@@ -0,0 +1,87 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
+
+
+contract MExchangeWrapper {
+
+    /// @dev Fills the input order.
+    ///      Returns false if the transaction would otherwise revert.
+    /// @param order Order struct containing order specifications.
+    /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+    /// @param signature Proof that order has been created by maker.
+    /// @return Amounts filled and fees paid by maker and taker.
+    function fillOrderNoThrow(
+        LibOrder.Order memory order,
+        uint256 takerAssetFillAmount,
+        bytes memory signature
+    )
+        internal
+        returns (LibFillResults.FillResults memory fillResults);
+
+    /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker.
+    ///      Returns false if the transaction would otherwise revert.
+    /// @param orders Array of order specifications.
+    /// @param wethSellAmount Desired amount of WETH to sell.
+    /// @param signatures Proofs that orders have been signed by makers.
+    /// @return Amounts filled and fees paid by makers and taker.
+    function marketSellWeth(
+        LibOrder.Order[] memory orders,
+        uint256 wethSellAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (LibFillResults.FillResults memory totalFillResults);
+
+    /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
+    ///      Returns false if the transaction would otherwise revert.
+    ///      The asset being sold by taker must always be WETH.
+    /// @param orders Array of order specifications.
+    /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+    /// @param signatures Proofs that orders have been signed by makers.
+    /// @return Amounts filled and fees paid by makers and taker.
+    function marketBuyExactAmountWithWeth(
+        LibOrder.Order[] memory orders,
+        uint256 makerAssetFillAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (LibFillResults.FillResults memory totalFillResults);
+
+    /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee
+    ///      that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues).
+    ///      It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX
+    ///      as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases.
+    ///      The asset being sold by taker must always be WETH. 
+    /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset.
+    /// @param zrxBuyAmount Desired amount of ZRX to buy.
+    /// @param signatures Proofs that orders have been created by makers.
+    /// @return totalFillResults Amounts filled and fees paid by maker and taker.
+    function marketBuyExactZrxWithWeth(
+        LibOrder.Order[] memory orders,
+        uint256 zrxBuyAmount,
+        bytes[] memory signatures
+    )
+        internal
+        returns (LibFillResults.FillResults memory totalFillResults);
+}
diff --git a/contracts/exchange-forwarder/contracts/src/mixins/MWeth.sol b/contracts/exchange-forwarder/contracts/src/mixins/MWeth.sol
new file mode 100644
index 000000000..15d66942f
--- /dev/null
+++ b/contracts/exchange-forwarder/contracts/src/mixins/MWeth.sol
@@ -0,0 +1,41 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract MWeth {
+
+    /// @dev Converts message call's ETH value into WETH.
+    function convertEthToWeth()
+        internal;
+
+    /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient.
+    ///      Refunds any excess ETH to msg.sender.
+    /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders.
+    /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees.
+    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+    /// @param feeRecipient Address that will receive ETH when orders are filled.
+    function transferEthFeeAndRefund(
+        uint256 wethSoldExcludingFeeOrders,
+        uint256 wethSoldForZrx,
+        uint256 feePercentage,
+        address feeRecipient
+    )
+        internal;
+}
diff --git a/contracts/exchange-forwarder/package.json b/contracts/exchange-forwarder/package.json
new file mode 100644
index 000000000..3383df990
--- /dev/null
+++ b/contracts/exchange-forwarder/package.json
@@ -0,0 +1,86 @@
+{
+    "name": "@0x/contracts-exchange-forwarder",
+    "version": "1.2.3",
+    "engines": {
+        "node": ">=6.12"
+    },
+    "description": "Smart contract extensions of 0x protocol",
+    "main": "lib/src/index.js",
+    "directories": {
+        "test": "test"
+    },
+    "scripts": {
+        "build": "yarn pre_build && tsc -b",
+        "build:ci": "yarn build",
+        "pre_build": "run-s compile generate_contract_wrappers",
+        "test": "yarn run_mocha",
+        "rebuild_and_test": "run-s build test",
+        "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
+        "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
+        "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
+        "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
+        "compile": "sol-compiler",
+        "watch": "sol-compiler -w",
+        "clean": "shx rm -rf lib generated-artifacts generated-wrappers",
+        "generate_contract_wrappers": "abi-gen --abis  ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
+        "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
+        "coverage:report:text": "istanbul report text",
+        "coverage:report:html": "istanbul report html && open coverage/index.html",
+        "profiler:report:html": "istanbul report html && open coverage/index.html",
+        "coverage:report:lcov": "istanbul report lcov",
+        "test:circleci": "yarn test",
+        "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
+    },
+    "config": {
+        "abis": "generated-artifacts/@(Forwarder).json"
+    },
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/0xProject/0x-monorepo.git"
+    },
+    "license": "Apache-2.0",
+    "bugs": {
+        "url": "https://github.com/0xProject/0x-monorepo/issues"
+    },
+    "homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
+    "devDependencies": {
+        "@0x/abi-gen": "^1.0.22",
+        "@0x/contract-wrappers": "^5.0.1",
+        "@0x/contracts-test-utils": "^2.0.1",
+        "@0x/dev-utils": "^1.0.24",
+        "@0x/sol-compiler": "^2.0.2",
+        "@0x/tslint-config": "^2.0.2",
+        "@types/lodash": "4.14.104",
+        "@types/node": "*",
+        "chai": "^4.0.1",
+        "chai-as-promised": "^7.1.0",
+        "chai-bignumber": "^3.0.0",
+        "dirty-chai": "^2.0.1",
+        "make-promises-safe": "^1.1.0",
+        "mocha": "^4.1.0",
+        "npm-run-all": "^4.1.2",
+        "shx": "^0.2.2",
+        "solhint": "^1.4.1",
+        "tslint": "5.11.0",
+        "typescript": "3.0.1"
+    },
+    "dependencies": {
+        "@0x/base-contract": "^3.0.13",
+        "@0x/contracts-asset-proxy": "^2.2.3",
+        "@0x/contracts-exchange-libs": "^1.0.6",
+        "@0x/contracts-exchange": "^2.2.3",
+        "@0x/contracts-erc20": "^1.0.6",
+        "@0x/contracts-erc721": "^1.0.6",
+        "@0x/contracts-utils": "^1.0.6",
+        "@0x/order-utils": "^3.1.2",
+        "@0x/types": "^1.5.2",
+        "@0x/typescript-typings": "^3.0.8",
+        "@0x/utils": "^3.0.1",
+        "@0x/web3-wrapper": "^3.2.4",
+        "ethereum-types": "^1.1.6",
+        "lodash": "^4.17.5"
+    },
+    "publishConfig": {
+        "access": "public"
+    }
+}
diff --git a/contracts/exchange-forwarder/src/artifacts/index.ts b/contracts/exchange-forwarder/src/artifacts/index.ts
new file mode 100644
index 000000000..f15dac49a
--- /dev/null
+++ b/contracts/exchange-forwarder/src/artifacts/index.ts
@@ -0,0 +1,7 @@
+import { ContractArtifact } from 'ethereum-types';
+
+import * as Forwarder from '../../generated-artifacts/Forwarder.json';
+
+export const artifacts = {
+    Forwarder: Forwarder as ContractArtifact,
+};
diff --git a/contracts/exchange-forwarder/src/index.ts b/contracts/exchange-forwarder/src/index.ts
new file mode 100644
index 000000000..ba813e7ca
--- /dev/null
+++ b/contracts/exchange-forwarder/src/index.ts
@@ -0,0 +1,3 @@
+export * from './artifacts';
+export * from './wrappers';
+export * from '../test/utils';
diff --git a/contracts/exchange-forwarder/src/wrappers/index.ts b/contracts/exchange-forwarder/src/wrappers/index.ts
new file mode 100644
index 000000000..89dff9e74
--- /dev/null
+++ b/contracts/exchange-forwarder/src/wrappers/index.ts
@@ -0,0 +1 @@
+export * from '../../generated-wrappers/forwarder';
diff --git a/contracts/exchange-forwarder/test/forwarder.ts b/contracts/exchange-forwarder/test/forwarder.ts
new file mode 100644
index 000000000..24c219563
--- /dev/null
+++ b/contracts/exchange-forwarder/test/forwarder.ts
@@ -0,0 +1,1279 @@
+import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
+import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
+import { DummyERC721TokenContract } from '@0x/contracts-erc721';
+import { artifacts as exchangeArtifacts, ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange';
+import {
+    chaiSetup,
+    constants,
+    ContractName,
+    ERC20BalancesByOwner,
+    expectContractCreationFailedAsync,
+    expectTransactionFailedAsync,
+    OrderFactory,
+    provider,
+    sendTransactionResult,
+    txDefaults,
+    web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason, SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+
+import { artifacts, ForwarderContract, ForwarderWrapper } from '../src';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+const DECIMALS_DEFAULT = 18;
+const MAX_WETH_FILL_PERCENTAGE = 95;
+
+describe(ContractName.Forwarder, () => {
+    let makerAddress: string;
+    let owner: string;
+    let takerAddress: string;
+    let feeRecipientAddress: string;
+    let defaultMakerAssetAddress: string;
+    let zrxAssetData: string;
+    let wethAssetData: string;
+
+    let weth: DummyERC20TokenContract;
+    let zrxToken: DummyERC20TokenContract;
+    let erc20TokenA: DummyERC20TokenContract;
+    let erc721Token: DummyERC721TokenContract;
+    let forwarderContract: ForwarderContract;
+    let wethContract: WETH9Contract;
+    let forwarderWrapper: ForwarderWrapper;
+    let exchangeWrapper: ExchangeWrapper;
+
+    let orderWithoutFee: SignedOrder;
+    let orderWithFee: SignedOrder;
+    let feeOrder: SignedOrder;
+    let orderFactory: OrderFactory;
+    let erc20Wrapper: ERC20Wrapper;
+    let erc20Balances: ERC20BalancesByOwner;
+    let tx: TransactionReceiptWithDecodedLogs;
+
+    let erc721MakerAssetIds: BigNumber[];
+    let takerEthBalanceBefore: BigNumber;
+    let feePercentage: BigNumber;
+    let gasPrice: BigNumber;
+
+    before(async () => {
+        await blockchainLifecycle.startAsync();
+        const accounts = await web3Wrapper.getAvailableAddressesAsync();
+        const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+
+        const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
+        const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
+        gasPrice = new BigNumber(transaction.gasPrice);
+
+        const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+        erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+
+        const numDummyErc20ToDeploy = 3;
+        [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+            numDummyErc20ToDeploy,
+            constants.DUMMY_TOKEN_DECIMALS,
+        );
+        const erc20Proxy = await erc20Wrapper.deployProxyAsync();
+        await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+        [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+        const erc721Proxy = await erc721Wrapper.deployProxyAsync();
+        await erc721Wrapper.setBalancesAndAllowancesAsync();
+        const erc721Balances = await erc721Wrapper.getBalancesAsync();
+        erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
+
+        wethContract = await WETH9Contract.deployFrom0xArtifactAsync(erc20Artifacts.WETH9, provider, txDefaults);
+        weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider);
+        erc20Wrapper.addDummyTokenContract(weth);
+
+        wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address);
+        zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+        const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+            exchangeArtifacts.Exchange,
+            provider,
+            txDefaults,
+            zrxAssetData,
+        );
+        exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider);
+        await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+        await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
+
+        await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
+            from: owner,
+        });
+        await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
+            from: owner,
+        });
+
+        defaultMakerAssetAddress = erc20TokenA.address;
+        const defaultTakerAssetAddress = wethContract.address;
+        const defaultOrderParams = {
+            exchangeAddress: exchangeInstance.address,
+            makerAddress,
+            feeRecipientAddress,
+            makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
+            takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
+            makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
+            takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
+            makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
+        };
+        const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+        orderFactory = new OrderFactory(privateKey, defaultOrderParams);
+
+        const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync(
+            artifacts.Forwarder,
+            provider,
+            txDefaults,
+            exchangeInstance.address,
+            zrxAssetData,
+            wethAssetData,
+        );
+        forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider);
+        forwarderWrapper = new ForwarderWrapper(forwarderContract, provider);
+        const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18);
+        await web3Wrapper.awaitTransactionSuccessAsync(
+            await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount),
+        );
+        erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address);
+    });
+    after(async () => {
+        await blockchainLifecycle.revertAsync();
+    });
+    beforeEach(async () => {
+        await blockchainLifecycle.startAsync();
+        erc20Balances = await erc20Wrapper.getBalancesAsync();
+        takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+        orderWithoutFee = await orderFactory.newSignedOrderAsync();
+        feeOrder = await orderFactory.newSignedOrderAsync({
+            makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
+            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+        });
+        orderWithFee = await orderFactory.newSignedOrderAsync({
+            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+        });
+    });
+    afterEach(async () => {
+        await blockchainLifecycle.revertAsync();
+    });
+
+    describe('constructor', () => {
+        it('should revert if assetProxy is unregistered', async () => {
+            const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+                exchangeArtifacts.Exchange,
+                provider,
+                txDefaults,
+                zrxAssetData,
+            );
+            return expectContractCreationFailedAsync(
+                (ForwarderContract.deployFrom0xArtifactAsync(
+                    artifacts.Forwarder,
+                    provider,
+                    txDefaults,
+                    exchangeInstance.address,
+                    zrxAssetData,
+                    wethAssetData,
+                ) as any) as sendTransactionResult,
+                RevertReason.UnregisteredAssetProxy,
+            );
+        });
+    });
+    describe('marketSellOrdersWithEth without extra fees', () => {
+        it('should fill a single order', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+                ethValue,
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const makerAssetFillAmount = primaryTakerAssetFillAmount
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fill multiple orders', async () => {
+            const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+            const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+                ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+            );
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+                ethValue,
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const firstTakerAssetFillAmount = ordersWithoutFee[0].takerAssetAmount;
+            const secondTakerAssetFillAmount = primaryTakerAssetFillAmount.minus(firstTakerAssetFillAmount);
+
+            const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+                ordersWithoutFee[1].makerAssetAmount
+                    .times(secondTakerAssetFillAmount)
+                    .dividedToIntegerBy(ordersWithoutFee[1].takerAssetAmount),
+            );
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fill the order and pay ZRX fees from a single feeOrder', async () => {
+            const ordersWithFee = [orderWithFee];
+            const feeOrders = [feeOrder];
+            const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+                ethValue,
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const makerAssetFillAmount = primaryTakerAssetFillAmount
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            const feeAmount = ForwarderWrapper.getPercentageOfValue(
+                orderWithFee.takerFee.dividedToIntegerBy(2),
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+            const totalEthSpent = primaryTakerAssetFillAmount
+                .plus(wethSpentOnFeeOrders)
+                .plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fill the orders and pay ZRX from multiple feeOrders', async () => {
+            const ordersWithFee = [orderWithFee];
+            const ethValue = orderWithFee.takerAssetAmount;
+            const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+            const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+            const takerAssetAmount = feeOrder.takerAssetAmount
+                .times(makerAssetAmount)
+                .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
+            const firstFeeOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetData,
+                makerAssetAmount,
+                takerAssetAmount,
+            });
+            const secondFeeOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetData,
+                makerAssetAmount,
+                takerAssetAmount,
+            });
+            const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+                ethValue,
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const makerAssetFillAmount = primaryTakerAssetFillAmount
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            const feeAmount = ForwarderWrapper.getPercentageOfValue(orderWithFee.takerFee, MAX_WETH_FILL_PERCENTAGE);
+            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+            const totalEthSpent = primaryTakerAssetFillAmount
+                .plus(wethSpentOnFeeOrders)
+                .plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fill the order when token is ZRX with fees', async () => {
+            orderWithFee = await orderFactory.newSignedOrderAsync({
+                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
+                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            });
+            const ordersWithFee = [orderWithFee];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+            const takerFeePaid = orderWithFee.takerFee.dividedToIntegerBy(2);
+            const makerFeePaid = orderWithFee.makerFee.dividedToIntegerBy(2);
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount).minus(makerFeePaid),
+            );
+            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount).minus(takerFeePaid),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(ethValue),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[forwarderContract.address][zrxToken.address],
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = orderWithoutFee.takerAssetAmount.times(2);
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const totalEthSpent = orderWithoutFee.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+        });
+        it('should revert if ZRX cannot be fully repurchased', async () => {
+            orderWithFee = await orderFactory.newSignedOrderAsync({
+                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
+            });
+            const ordersWithFee = [orderWithFee];
+            feeOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
+                makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            });
+            const feeOrders = [feeOrder];
+            const ethValue = orderWithFee.takerAssetAmount;
+            return expectTransactionFailedAsync(
+                forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+                    value: ethValue,
+                    from: takerAddress,
+                }),
+                RevertReason.CompleteFillFailed,
+            );
+        });
+        it('should not fill orders with different makerAssetData than the first order', async () => {
+            const makerAssetId = erc721MakerAssetIds[0];
+            const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber(1),
+                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+            });
+            const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
+            const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
+
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const totalEthSpent = erc20SignedOrder.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+        });
+    });
+    describe('marketSellOrdersWithEth with extra fees', () => {
+        it('should fill the order and send fee to feeRecipient', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+
+            const baseFeePercentage = 2;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+            const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+                { feePercentage, feeRecipient: feeRecipientAddress },
+            );
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+                ethValue,
+                MAX_WETH_FILL_PERCENTAGE,
+            );
+            const makerAssetFillAmount = primaryTakerAssetFillAmount
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fail if the fee is set too high', async () => {
+            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+            const baseFeePercentage = 6;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage);
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            await expectTransactionFailedAsync(
+                forwarderWrapper.marketSellOrdersWithEthAsync(
+                    ordersWithoutFee,
+                    feeOrders,
+                    { from: takerAddress, value: ethValue, gasPrice },
+                    { feePercentage, feeRecipient: feeRecipientAddress },
+                ),
+                RevertReason.FeePercentageTooLarge,
+            );
+        });
+        it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+            const baseFeePercentage = 5;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+            const ordersWithFee = [orderWithFee];
+            const feeOrders = [feeOrder];
+            await expectTransactionFailedAsync(
+                forwarderWrapper.marketSellOrdersWithEthAsync(
+                    ordersWithFee,
+                    feeOrders,
+                    { from: takerAddress, value: ethValue, gasPrice },
+                    { feePercentage, feeRecipient: feeRecipientAddress },
+                ),
+                RevertReason.InsufficientEthRemaining,
+            );
+        });
+    });
+    describe('marketBuyOrdersWithEth without extra fees', () => {
+        it('should buy the exact amount of makerAsset in a single order', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should buy the exact amount of makerAsset in multiple orders', async () => {
+            const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+            const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+                ordersWithoutFee[1].makerAssetAmount.dividedToIntegerBy(2),
+            );
+            const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+                ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+            );
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should buy the exact amount of makerAsset and return excess ETH', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount;
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ethValue.dividedToIntegerBy(2);
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should buy the exact amount of makerAsset and pay ZRX from feeOrders', async () => {
+            const ordersWithFee = [orderWithFee];
+            const feeOrders = [feeOrder];
+            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithFee.takerAssetAmount;
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+            const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+            const totalEthSpent = primaryTakerAssetFillAmount
+                .plus(wethSpentOnFeeOrders)
+                .plus(gasPrice.times(tx.gasUsed));
+
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should buy slightly greater than makerAssetAmount when buying ZRX', async () => {
+            orderWithFee = await orderFactory.newSignedOrderAsync({
+                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
+                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            });
+            const ordersWithFee = [orderWithFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithFee.takerAssetAmount;
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders(
+                makerAssetFillAmount,
+                ordersWithFee,
+            );
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            const makerAssetFilledAmount = orderWithFee.makerAssetAmount
+                .times(primaryTakerAssetFillAmount)
+                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+            const takerFeePaid = orderWithFee.takerFee
+                .times(primaryTakerAssetFillAmount)
+                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+            const makerFeePaid = orderWithFee.makerFee
+                .times(primaryTakerAssetFillAmount)
+                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+            const totalZrxPurchased = makerAssetFilledAmount.minus(takerFeePaid);
+            // Up to 1 wei worth of ZRX will be overbought per order
+            const maxOverboughtZrx = new BigNumber(1)
+                .times(orderWithFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+
+            expect(totalZrxPurchased).to.be.bignumber.gte(makerAssetFillAmount);
+            expect(totalZrxPurchased).to.be.bignumber.lte(makerAssetFillAmount.plus(maxOverboughtZrx));
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFilledAmount).minus(makerFeePaid),
+            );
+            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[forwarderContract.address][zrxToken.address],
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(4);
+            return expectTransactionFailedAsync(
+                forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                    value: ethValue,
+                    from: takerAddress,
+                }),
+                RevertReason.CompleteFillFailed,
+            );
+        });
+        it('should buy an ERC721 asset from a single order', async () => {
+            const makerAssetId = erc721MakerAssetIds[0];
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber(1),
+                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = new BigNumber(1);
+            const ethValue = orderWithFee.takerAssetAmount;
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                from: takerAddress,
+                value: ethValue,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            expect(newOwner).to.be.bignumber.equal(takerAddress);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should revert if buying an ERC721 asset when later orders contain different makerAssetData', async () => {
+            const makerAssetId = erc721MakerAssetIds[0];
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber(1),
+                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+            });
+            const differentMakerAssetDataOrder = await orderFactory.newSignedOrderAsync();
+            const ordersWithoutFee = [orderWithoutFee, differentMakerAssetDataOrder];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = new BigNumber(1).plus(differentMakerAssetDataOrder.makerAssetAmount);
+            const ethValue = orderWithFee.takerAssetAmount;
+            return expectTransactionFailedAsync(
+                forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                    value: ethValue,
+                    from: takerAddress,
+                }),
+                RevertReason.CompleteFillFailed,
+            );
+        });
+        it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
+            const makerAssetId = erc721MakerAssetIds[0];
+            orderWithFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber(1),
+                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            });
+            const ordersWithFee = [orderWithFee];
+            const feeOrders = [feeOrder];
+            const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+            const feeAmount = orderWithFee.takerFee;
+            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+            const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+
+            expect(newOwner).to.be.bignumber.equal(takerAddress);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => {
+            const makerAssetId = erc721MakerAssetIds[0];
+            orderWithFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber(1),
+                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+            });
+            const ordersWithFee = [orderWithFee];
+            const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+            const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+            const takerAssetAmount = feeOrder.takerAssetAmount
+                .times(makerAssetAmount)
+                .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
+            const firstFeeOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetData,
+                makerAssetAmount,
+                takerAssetAmount,
+            });
+            const secondFeeOrder = await orderFactory.newSignedOrderAsync({
+                makerAssetData,
+                makerAssetAmount,
+                takerAssetAmount,
+            });
+            const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+            const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+            const feeAmount = orderWithFee.takerFee;
+            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+            const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+
+            expect(newOwner).to.be.bignumber.equal(takerAddress);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => {
+            // The 0x Protocol contracts round the exchange rate in favor of the Maker.
+            // In this case, the taker must round up how much they're going to spend, which
+            // in turn increases the amount of MakerAsset being purchased.
+            // Example:
+            //  The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T.
+            //  For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset.
+            //  To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset.
+            //  However, the Taker can only spend whole units.
+            //  Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset.
+            //  Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset.
+            //
+            //  The forwarding contract will opt for the second option, which overbuys, to ensure the taker
+            //  receives at least the amount of MakerAsset they requested.
+            //
+            // Construct test case using values from example above
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber('30'),
+                takerAssetAmount: new BigNumber('20'),
+                makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
+                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+                makerFee: new BigNumber(0),
+                takerFee: new BigNumber(0),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const desiredMakerAssetFillAmount = new BigNumber('5');
+            const makerAssetFillAmount = new BigNumber('6');
+            const ethValue = new BigNumber('4');
+            // Execute test case
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                desiredMakerAssetFillAmount,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+            );
+            // Fetch end balances and construct expected outputs
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            // Validate test case
+            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => {
+            // See the test case above for a detailed description of this case.
+            // The difference here is that the MakerAsset is ZRX. We expect the same result as above,
+            // but this tests a different code path.
+            //
+            // Construct test case using values from example above
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber('30'),
+                takerAssetAmount: new BigNumber('20'),
+                makerAssetData: zrxAssetData,
+                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+                makerFee: new BigNumber(0),
+                takerFee: new BigNumber(0),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const desiredMakerAssetFillAmount = new BigNumber('5');
+            const makerAssetFillAmount = new BigNumber('6');
+            const ethValue = new BigNumber('4');
+            // Execute test case
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                desiredMakerAssetFillAmount,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+            );
+            // Fetch end balances and construct expected outputs
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            // Validate test case
+            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => {
+            // Order taken from a transaction on mainnet that failed due to a rounding error.
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber('268166666666666666666'),
+                takerAssetAmount: new BigNumber('219090625878836371'),
+                makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
+                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+                makerFee: new BigNumber(0),
+                takerFee: new BigNumber(0),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            // The taker will receive more than the desired amount of makerAsset due to rounding
+            const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
+            const ethValue = new BigNumber('4084971271824171');
+            const makerAssetFillAmount = ethValue
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            // Execute test case
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                desiredMakerAssetFillAmount,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+            );
+            // Fetch end balances and construct expected outputs
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            // Validate test case
+            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => {
+            // Order taken from a transaction on mainnet that failed due to a rounding error.
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber('268166666666666666666'),
+                takerAssetAmount: new BigNumber('219090625878836371'),
+                makerAssetData: zrxAssetData,
+                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+                makerFee: new BigNumber(0),
+                takerFee: new BigNumber(0),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            // The taker will receive more than the desired amount of makerAsset due to rounding
+            const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
+            const ethValue = new BigNumber('4084971271824171');
+            const makerAssetFillAmount = ethValue
+                .times(orderWithoutFee.makerAssetAmount)
+                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+            // Execute test case
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                desiredMakerAssetFillAmount,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+            );
+            // Fetch end balances and construct expected outputs
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            // Validate test case
+            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => {
+            // An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1.
+            // Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1
+            // We have since changed this to be Ceil(desiredMakerAmount * exchangeRate)
+            // These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer.
+            //
+            // This test verifies that `ceil` is sufficient:
+            //  Let TakerAssetAmount = MakerAssetAmount * 2
+            //  -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2
+            //  .: desiredMakerAmount * exchangeRate is an integer.
+            //
+            // Construct test case using values from example above
+            orderWithoutFee = await orderFactory.newSignedOrderAsync({
+                makerAssetAmount: new BigNumber('30'),
+                takerAssetAmount: new BigNumber('60'),
+                makerAssetData: zrxAssetData,
+                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+                makerFee: new BigNumber(0),
+                takerFee: new BigNumber(0),
+            });
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = new BigNumber('5');
+            const ethValue = new BigNumber('10');
+            // Execute test case
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+                value: ethValue,
+                from: takerAddress,
+            });
+            // Fetch end balances and construct expected outputs
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            const primaryTakerAssetFillAmount = ethValue;
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+            // Validate test case
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+    });
+    describe('marketBuyOrdersWithEth with extra fees', () => {
+        it('should buy an asset and send fee to feeRecipient', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount;
+
+            const baseFeePercentage = 2;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+            const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+                ordersWithoutFee,
+                feeOrders,
+                makerAssetFillAmount,
+                {
+                    value: ethValue,
+                    from: takerAddress,
+                },
+                { feePercentage, feeRecipient: feeRecipientAddress },
+            );
+            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+            const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+
+            const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+            const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+            const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
+
+            expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+            );
+            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+            );
+            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+            );
+            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+                constants.ZERO_AMOUNT,
+            );
+            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+        });
+        it('should fail if the fee is set too high', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount;
+
+            const baseFeePercentage = 6;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+            await expectTransactionFailedAsync(
+                forwarderWrapper.marketBuyOrdersWithEthAsync(
+                    ordersWithoutFee,
+                    feeOrders,
+                    makerAssetFillAmount,
+                    {
+                        value: ethValue,
+                        from: takerAddress,
+                    },
+                    { feePercentage, feeRecipient: feeRecipientAddress },
+                ),
+                RevertReason.FeePercentageTooLarge,
+            );
+        });
+        it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+            const ordersWithoutFee = [orderWithoutFee];
+            const feeOrders: SignedOrder[] = [];
+            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+            const baseFeePercentage = 2;
+            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+            await expectTransactionFailedAsync(
+                forwarderWrapper.marketBuyOrdersWithEthAsync(
+                    ordersWithoutFee,
+                    feeOrders,
+                    makerAssetFillAmount,
+                    {
+                        value: ethValue,
+                        from: takerAddress,
+                    },
+                    { feePercentage, feeRecipient: feeRecipientAddress },
+                ),
+                RevertReason.InsufficientEthRemaining,
+            );
+        });
+    });
+    describe('withdrawAsset', () => {
+        it('should allow owner to withdraw ERC20 tokens', async () => {
+            const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address];
+            await forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: owner });
+            const newBalances = await erc20Wrapper.getBalancesAsync();
+            expect(newBalances[owner][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[owner][zrxToken.address].plus(zrxWithdrawAmount),
+            );
+            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+                erc20Balances[forwarderContract.address][zrxToken.address].minus(zrxWithdrawAmount),
+            );
+        });
+        it('should revert if not called by owner', async () => {
+            const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address];
+            await expectTransactionFailedAsync(
+                forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: makerAddress }),
+                RevertReason.OnlyContractOwner,
+            );
+        });
+    });
+});
+// tslint:disable:max-file-line-count
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/exchange-forwarder/test/global_hooks.ts b/contracts/exchange-forwarder/test/global_hooks.ts
new file mode 100644
index 000000000..f8ace376a
--- /dev/null
+++ b/contracts/exchange-forwarder/test/global_hooks.ts
@@ -0,0 +1,17 @@
+import { env, EnvVars } from '@0x/dev-utils';
+
+import { coverage, profiler, provider } from '@0x/contracts-test-utils';
+before('start web3 provider', () => {
+    provider.start();
+});
+after('generate coverage report', async () => {
+    if (env.parseBoolean(EnvVars.SolidityCoverage)) {
+        const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
+        await coverageSubprovider.writeCoverageAsync();
+    }
+    if (env.parseBoolean(EnvVars.SolidityProfiler)) {
+        const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
+        await profilerSubprovider.writeProfilerOutputAsync();
+    }
+    provider.stop();
+});
diff --git a/contracts/exchange-forwarder/test/utils/forwarder_wrapper.ts b/contracts/exchange-forwarder/test/utils/forwarder_wrapper.ts
new file mode 100644
index 000000000..207106852
--- /dev/null
+++ b/contracts/exchange-forwarder/test/utils/forwarder_wrapper.ts
@@ -0,0 +1,126 @@
+import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
+import { artifacts as erc721Artifacts } from '@0x/contracts-erc721';
+import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
+import { constants, formatters, LogDecoder, MarketSellOrders } from '@0x/contracts-test-utils';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { ForwarderContract } from '../../generated-wrappers/forwarder';
+import { artifacts } from '../../src/artifacts';
+
+export class ForwarderWrapper {
+    private readonly _web3Wrapper: Web3Wrapper;
+    private readonly _forwarderContract: ForwarderContract;
+    private readonly _logDecoder: LogDecoder;
+    public static getPercentageOfValue(value: BigNumber, percentage: number): BigNumber {
+        const numerator = constants.PERCENTAGE_DENOMINATOR.times(percentage).dividedToIntegerBy(100);
+        const newValue = value.times(numerator).dividedToIntegerBy(constants.PERCENTAGE_DENOMINATOR);
+        return newValue;
+    }
+    public static getWethForFeeOrders(feeAmount: BigNumber, feeOrders: SignedOrder[]): BigNumber {
+        let wethAmount = new BigNumber(0);
+        let remainingFeeAmount = feeAmount;
+        _.forEach(feeOrders, feeOrder => {
+            const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
+            if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
+                wethAmount = wethAmount.plus(
+                    feeOrder.takerAssetAmount
+                        .times(remainingFeeAmount)
+                        .dividedBy(feeAvailable)
+                        .integerValue(BigNumber.ROUND_CEIL),
+                );
+                remainingFeeAmount = new BigNumber(0);
+            } else if (!remainingFeeAmount.isZero()) {
+                wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
+                remainingFeeAmount = remainingFeeAmount.minus(feeAvailable);
+            }
+        });
+        return wethAmount;
+    }
+    private static _createOptimizedOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+        _.forEach(signedOrders, (signedOrder, index) => {
+            signedOrder.takerAssetData = constants.NULL_BYTES;
+            if (index > 0) {
+                signedOrder.makerAssetData = constants.NULL_BYTES;
+            }
+        });
+        const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
+        return params;
+    }
+    private static _createOptimizedZrxOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+        _.forEach(signedOrders, signedOrder => {
+            signedOrder.makerAssetData = constants.NULL_BYTES;
+            signedOrder.takerAssetData = constants.NULL_BYTES;
+        });
+        const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
+        return params;
+    }
+    constructor(contractInstance: ForwarderContract, provider: Provider) {
+        this._forwarderContract = contractInstance;
+        this._web3Wrapper = new Web3Wrapper(provider);
+        this._logDecoder = new LogDecoder(this._web3Wrapper, {
+            ...artifacts,
+            ...erc20Artifacts,
+            ...erc721Artifacts,
+            ...exchangeArtifacts,
+        });
+    }
+    public async marketSellOrdersWithEthAsync(
+        orders: SignedOrder[],
+        feeOrders: SignedOrder[],
+        txData: TxDataPayable,
+        opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
+    ): Promise<TransactionReceiptWithDecodedLogs> {
+        const params = ForwarderWrapper._createOptimizedOrders(orders);
+        const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
+        const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
+        const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
+        const txHash = await this._forwarderContract.marketSellOrdersWithEth.sendTransactionAsync(
+            params.orders,
+            params.signatures,
+            feeParams.orders,
+            feeParams.signatures,
+            feePercentage,
+            feeRecipient,
+            txData,
+        );
+        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+        return tx;
+    }
+    public async marketBuyOrdersWithEthAsync(
+        orders: SignedOrder[],
+        feeOrders: SignedOrder[],
+        makerAssetFillAmount: BigNumber,
+        txData: TxDataPayable,
+        opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
+    ): Promise<TransactionReceiptWithDecodedLogs> {
+        const params = ForwarderWrapper._createOptimizedOrders(orders);
+        const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
+        const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
+        const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
+        const txHash = await this._forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync(
+            params.orders,
+            makerAssetFillAmount,
+            params.signatures,
+            feeParams.orders,
+            feeParams.signatures,
+            feePercentage,
+            feeRecipient,
+            txData,
+        );
+        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+        return tx;
+    }
+    public async withdrawAssetAsync(
+        assetData: string,
+        amount: BigNumber,
+        txData: TxDataPayable,
+    ): Promise<TransactionReceiptWithDecodedLogs> {
+        const txHash = await this._forwarderContract.withdrawAsset.sendTransactionAsync(assetData, amount, txData);
+        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+        return tx;
+    }
+}
diff --git a/contracts/exchange-forwarder/test/utils/index.ts b/contracts/exchange-forwarder/test/utils/index.ts
new file mode 100644
index 000000000..d290ec75d
--- /dev/null
+++ b/contracts/exchange-forwarder/test/utils/index.ts
@@ -0,0 +1 @@
+export * from './forwarder_wrapper';
diff --git a/contracts/exchange-forwarder/tsconfig.json b/contracts/exchange-forwarder/tsconfig.json
new file mode 100644
index 000000000..cd80b5a5f
--- /dev/null
+++ b/contracts/exchange-forwarder/tsconfig.json
@@ -0,0 +1,11 @@
+{
+    "extends": "../../tsconfig",
+    "compilerOptions": {
+        "outDir": "lib",
+        "rootDir": ".",
+        "resolveJsonModule": true
+    },
+    "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
+    "files": ["./generated-artifacts/Forwarder.json"],
+    "exclude": ["./deploy/solc/solc_bin"]
+}
diff --git a/contracts/exchange-forwarder/tslint.json b/contracts/exchange-forwarder/tslint.json
new file mode 100644
index 000000000..1bb3ac2a2
--- /dev/null
+++ b/contracts/exchange-forwarder/tslint.json
@@ -0,0 +1,6 @@
+{
+    "extends": ["@0x/tslint-config"],
+    "rules": {
+        "custom-no-magic-numbers": false
+    }
+}
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json
index 2bb468724..1dfaf7a05 100644
--- a/contracts/extensions/compiler.json
+++ b/contracts/extensions/compiler.json
@@ -18,5 +18,5 @@
             }
         }
     },
-    "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder", "OrderMatcher", "OrderValidator"]
+    "contracts": ["BalanceThresholdFilter", "DutchAuction", "OrderMatcher", "OrderValidator"]
 }
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
deleted file mode 100644
index b6e14525d..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity 0.4.24;
-
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-import "./interfaces/IThresholdAsset.sol";
-import "./MixinBalanceThresholdFilterCore.sol";
-
-
-contract BalanceThresholdFilter is
-    MixinBalanceThresholdFilterCore
-{
-
-    /// @dev Constructs BalanceThresholdFilter.
-    /// @param exchange Address of 0x exchange.
-    /// @param thresholdAsset The asset that must be held by makers/takers.
-    /// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers.
-    constructor(
-        address exchange,
-        address thresholdAsset,
-        uint256 balanceThreshold
-    )
-        public
-    {
-        EXCHANGE = IExchange(exchange);
-        THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
-        BALANCE_THRESHOLD = balanceThreshold;
-    }
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
deleted file mode 100644
index 68dabafeb..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "./mixins/MBalanceThresholdFilterCore.sol";
-import "./MixinExchangeCalldata.sol";
-
-
-contract MixinBalanceThresholdFilterCore is
-    MBalanceThresholdFilterCore,
-    MixinExchangeCalldata,
-    LibOrder,
-    LibExchangeSelectors
-{
-
-    /// @dev Executes an Exchange transaction iff the maker and taker meet 
-    ///      the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR 
-    ///      the exchange function is a cancellation.
-    ///      Supported Exchange functions:
-    ///          batchFillOrders
-    ///          batchFillOrdersNoThrow
-    ///          batchFillOrKillOrders
-    ///          fillOrder
-    ///          fillOrderNoThrow
-    ///          fillOrKillOrder
-    ///          marketBuyOrders
-    ///          marketBuyOrdersNoThrow
-    ///          marketSellOrders
-    ///          marketSellOrdersNoThrow
-    ///          matchOrders
-    ///          cancelOrder
-    ///          batchCancelOrders
-    ///          cancelOrdersUpTo
-    ///     Trying to call any other exchange function will throw.
-    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
-    /// @param signerAddress Address of transaction signer.
-    /// @param signedExchangeTransaction AbiV2 encoded calldata.
-    /// @param signature Proof of signer transaction by signer.
-    function executeTransaction(
-        uint256 salt,
-        address signerAddress,
-        bytes signedExchangeTransaction,
-        bytes signature
-    ) 
-        external
-    {
-        // Get accounts whose balances must be validated
-        address[] memory addressesToValidate = getAddressesToValidate(signerAddress);
-
-        // Validate account balances
-        uint256 balanceThreshold = BALANCE_THRESHOLD;
-        IThresholdAsset thresholdAsset = THRESHOLD_ASSET;
-        for (uint256 i = 0; i < addressesToValidate.length; ++i) {
-            uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]);
-            require(
-                addressBalance >= balanceThreshold,
-                "AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"
-            );
-        }
-        emit ValidatedAddresses(addressesToValidate);
-        
-        // All addresses are valid. Execute exchange function.
-        EXCHANGE.executeTransaction(
-            salt,
-            signerAddress,
-            signedExchangeTransaction,
-            signature
-        );
-    }
-
-    /// @dev Constructs an array of addresses to be validated.
-    ///      Addresses depend on which Exchange function is to be called
-    ///      (defined by `signedExchangeTransaction` above).
-    /// @param signerAddress Address of transaction signer.
-    /// @return addressesToValidate Array of addresses to validate.
-    function getAddressesToValidate(address signerAddress)
-        internal pure
-        returns (address[] memory addressesToValidate)
-    {
-        bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0));
-        // solhint-disable expression-indent
-        if (
-            exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR              ||
-            exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR     ||
-            exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR      ||
-            exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR              ||
-            exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR     ||
-            exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR             ||
-            exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
-        ) {
-            addressesToValidate = loadMakerAddressesFromOrderArray(0);
-            addressesToValidate = addressesToValidate.append(signerAddress);
-        } else if (
-            exchangeFunctionSelector == FILL_ORDER_SELECTOR             ||
-            exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR    ||
-            exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR
-        ) {
-            address makerAddress = loadMakerAddressFromOrder(0);
-            addressesToValidate = addressesToValidate.append(makerAddress);
-            addressesToValidate = addressesToValidate.append(signerAddress);
-        } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) {
-            address leftMakerAddress = loadMakerAddressFromOrder(0);
-            addressesToValidate = addressesToValidate.append(leftMakerAddress);
-            address rightMakerAddress = loadMakerAddressFromOrder(1);
-            addressesToValidate = addressesToValidate.append(rightMakerAddress);
-            addressesToValidate = addressesToValidate.append(signerAddress);
-        } else if (
-            exchangeFunctionSelector != CANCEL_ORDER_SELECTOR           &&
-            exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR    &&
-            exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR
-        ) {
-            revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR");
-        }
-        // solhint-enable expression-indent
-        return addressesToValidate;
-    }
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
deleted file mode 100644
index f2c0caa57..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
+++ /dev/null
@@ -1,103 +0,0 @@
-    
-    /*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "./mixins/MExchangeCalldata.sol";
-import "@0x/contracts-utils/contracts/src/LibAddressArray.sol";
-
-
-contract MixinExchangeCalldata is 
-    MExchangeCalldata
-{
-
-    using LibAddressArray for address[];
-
-    /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
-    ///      which is accessed through `signedExchangeTransaction`.
-    /// @param offset  Offset into the Exchange calldata.
-    /// @return value  Corresponding 32 byte value stored at `offset`.
-    function exchangeCalldataload(uint256 offset)
-        internal pure
-        returns (bytes32 value)
-    {
-        assembly {
-            // Pointer to exchange transaction
-            // 0x04 for calldata selector
-            // 0x40 to access `signedExchangeTransaction`, which is the third parameter
-            let exchangeTxPtr := calldataload(0x44)
-
-            // Offset into Exchange calldata
-            // We compute this by adding 0x24 to the `exchangeTxPtr` computed above.
-            // 0x04 for calldata selector
-            // 0x20 for length field of `signedExchangeTransaction`
-            let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset))
-            value := calldataload(exchangeCalldataOffset)
-        }
-        return value;
-    }
-
-    /// @dev Convenience function that skips the 4 byte selector when loading
-    ///      from the embedded Exchange calldata.
-    /// @param offset  Offset into the Exchange calldata (minus the 4 byte selector)
-    /// @return value  Corresponding 32 byte value stored at `offset` + 4.
-    function loadExchangeData(uint256 offset)
-        internal pure
-        returns (bytes32 value)
-    {
-        value = exchangeCalldataload(offset + 4);
-        return value;
-    }
-
-    /// @dev Extracts the maker address from an order stored in the Exchange calldata
-    ///      (which is embedded in `signedExchangeTransaction`).
-    /// @param orderParamIndex  Index of the order in the Exchange function's signature.
-    /// @return makerAddress The extracted maker address.
-    function loadMakerAddressFromOrder(uint256 orderParamIndex)
-        internal pure
-        returns (address makerAddress)
-    {
-        uint256 orderOffsetInBytes = orderParamIndex * 32;
-        uint256 orderPtr = uint256(loadExchangeData(orderOffsetInBytes));
-        makerAddress = address(loadExchangeData(orderPtr));
-        return makerAddress;
-    }
-
-    /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
-    ///      (which is embedded in `signedExchangeTransaction`).
-    /// @param orderArrayParamIndex  Index of the order array in the Exchange function's signature
-    /// @return makerAddresses The extracted maker addresses.
-    function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
-        internal pure
-        returns (address[] makerAddresses)
-    {
-        uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32;
-        uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayOffsetInBytes));
-        uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr));
-        uint256 orderArrayLengthInBytes = orderArrayLength * 32;
-        uint256 orderArrayElementPtr = orderArrayPtr + 32;
-        uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes;
-        for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) {
-            uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset));
-            address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr));
-            makerAddresses = makerAddresses.append(makerAddress);
-        }
-        return makerAddresses;
-    }
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
deleted file mode 100644
index 4a1bf1fb2..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
+++ /dev/null
@@ -1,55 +0,0 @@
-
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract IBalanceThresholdFilterCore {
-    
-    /// @dev Executes an Exchange transaction iff the maker and taker meet 
-    ///      the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR 
-    ///      the exchange function is a cancellation.
-    ///      Supported Exchange functions:
-    ///         - batchFillOrders
-    ///         - batchFillOrdersNoThrow
-    ///         - batchFillOrKillOrders
-    ///         - fillOrder
-    ///         - fillOrderNoThrow
-    ///         - fillOrKillOrder
-    ///         - marketBuyOrders
-    ///         - marketBuyOrdersNoThrow
-    ///         - marketSellOrders
-    ///         - marketSellOrdersNoThrow
-    ///         - matchOrders
-    ///         - cancelOrder
-    ///         - batchCancelOrders
-    ///         - cancelOrdersUpTo
-    ///     Trying to call any other exchange function will throw.
-    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
-    /// @param signerAddress Address of transaction signer.
-    /// @param signedExchangeTransaction AbiV2 encoded calldata.
-    /// @param signature Proof of signer transaction by signer.
-    function executeTransaction(
-        uint256 salt,
-        address signerAddress,
-        bytes signedExchangeTransaction,
-        bytes signature
-    ) 
-        external;
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
deleted file mode 100644
index f78f9c2de..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract IThresholdAsset {
-
-    /// @param _owner The address from which the balance will be retrieved
-    /// @return Balance of owner
-    function balanceOf(address _owner)
-        external
-        view
-        returns (uint256);
-
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
deleted file mode 100644
index 1c840136b..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-import "../interfaces/IThresholdAsset.sol";
-import "../interfaces/IBalanceThresholdFilterCore.sol";
-
-
-contract MBalanceThresholdFilterCore is
-    IBalanceThresholdFilterCore
-{
-
-    // Points to 0x exchange contract
-    // solhint-disable var-name-mixedcase
-    IExchange internal EXCHANGE;
-
-    // The asset that must be held by makers/takers
-    IThresholdAsset internal THRESHOLD_ASSET;
-
-    // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers
-    uint256 internal BALANCE_THRESHOLD;
-    // solhint-enable var-name-mixedcase
-
-    // Addresses that hold at least `BALANCE_THRESHOLD` of `THRESHOLD_ASSET`
-    event ValidatedAddresses (
-        address[] addresses
-    );
-
-    /// @dev Constructs an array of addresses to be validated.
-    ///      Addresses depend on which Exchange function is to be called
-    ///      (defined by `signedExchangeTransaction` above).
-    /// @param signerAddress Address of transaction signer.
-    /// @return addressesToValidate Array of addresses to validate.
-    function getAddressesToValidate(address signerAddress)
-        internal pure
-        returns (address[] memory addressesToValidate);
-}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
deleted file mode 100644
index 40536d820..000000000
--- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
+++ /dev/null
@@ -1,56 +0,0 @@
-    
-    /*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract MExchangeCalldata {
-
-    /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
-    ///      which is accessed through `signedExchangeTransaction`.
-    /// @param offset  Offset into the Exchange calldata.
-    /// @return value  Corresponding 32 byte value stored at `offset`.
-    function exchangeCalldataload(uint256 offset)
-        internal pure
-        returns (bytes32 value);
-
-    /// @dev Convenience function that skips the 4 byte selector when loading
-    ///      from the embedded Exchange calldata.
-    /// @param offset  Offset into the Exchange calldata (minus the 4 byte selector)
-    /// @return value  Corresponding 32 byte value stored at `offset` + 4.
-    function loadExchangeData(uint256 offset)
-        internal pure
-        returns (bytes32 value);
-
-    /// @dev Extracts the maker address from an order stored in the Exchange calldata
-    ///      (which is embedded in `signedExchangeTransaction`).
-    /// @param orderParamIndex  Index of the order in the Exchange function's signature.
-    /// @return makerAddress The extracted maker address.
-    function loadMakerAddressFromOrder(uint256 orderParamIndex)
-        internal pure
-        returns (address makerAddress);
-
-    /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
-    ///      (which is embedded in `signedExchangeTransaction`).
-    /// @param orderArrayParamIndex  Index of the order array in the Exchange function's signature
-    /// @return makerAddresses The extracted maker addresses.
-    function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
-        internal pure
-        returns (address[] makerAddresses);
-}
diff --git a/contracts/extensions/contracts/DutchAuction/DutchAuction.sol b/contracts/extensions/contracts/DutchAuction/DutchAuction.sol
deleted file mode 100644
index 7631880c0..000000000
--- a/contracts/extensions/contracts/DutchAuction/DutchAuction.sol
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity 0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-import "@0x/contracts-utils/contracts/src/SafeMath.sol";
-
-
-contract DutchAuction is
-    SafeMath
-{
-    using LibBytes for bytes;
-
-    // solhint-disable var-name-mixedcase
-    IExchange internal EXCHANGE;
-
-    struct AuctionDetails {
-        uint256 beginTimeSeconds;    // Auction begin unix timestamp: sellOrder.makerAssetData
-        uint256 endTimeSeconds;      // Auction end unix timestamp: sellOrder.expiryTimeSeconds
-        uint256 beginAmount;         // Auction begin amount: sellOrder.makerAssetData
-        uint256 endAmount;           // Auction end amount: sellOrder.takerAssetAmount
-        uint256 currentAmount;       // Calculated amount given block.timestamp
-        uint256 currentTimeSeconds;  // block.timestamp
-    }
-
-    constructor (address _exchange)
-        public
-    {
-        EXCHANGE = IExchange(_exchange);
-    }
-
-    /// @dev Matches the buy and sell orders at an amount given the following: the current block time, the auction
-    ///      start time and the auction begin amount. The sell order is a an order at the lowest amount
-    ///      at the end of the auction. Excess from the match is transferred to the seller.
-    ///      Over time the price moves from beginAmount to endAmount given the current block.timestamp.
-    ///      sellOrder.expiryTimeSeconds is the end time of the auction.
-    ///      sellOrder.takerAssetAmount is the end amount of the auction (lowest possible amount).
-    ///      sellOrder.makerAssetData is the ABI encoded Asset Proxy data with the following data appended
-    ///      buyOrder.makerAssetData is the buyers bid on the auction, must meet the amount for the current block timestamp
-    ///      (uint256 beginTimeSeconds, uint256 beginAmount).
-    ///      This function reverts in the following scenarios:
-    ///         * Auction has not started (auctionDetails.currentTimeSeconds < auctionDetails.beginTimeSeconds)
-    ///         * Auction has expired (auctionDetails.endTimeSeconds < auctionDetails.currentTimeSeconds)
-    ///         * Amount is invalid: Buy order amount is too low (buyOrder.makerAssetAmount < auctionDetails.currentAmount)
-    ///         * Amount is invalid: Invalid begin amount (auctionDetails.beginAmount > auctionDetails.endAmount)
-    ///         * Any failure in the 0x Match Orders
-    /// @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
-    /// @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
-    /// @param buySignature Proof that order was created by the buyer.
-    /// @param sellSignature Proof that order was created by the seller.
-    /// @return matchedFillResults amounts filled and fees paid by maker and taker of matched orders.
-    function matchOrders(
-        LibOrder.Order memory buyOrder,
-        LibOrder.Order memory sellOrder,
-        bytes memory buySignature,
-        bytes memory sellSignature
-    )
-        public
-        returns (LibFillResults.MatchedFillResults memory matchedFillResults)
-    {
-        AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder);
-        // Ensure the auction has not yet started
-        require(
-            auctionDetails.currentTimeSeconds >= auctionDetails.beginTimeSeconds,
-            "AUCTION_NOT_STARTED"
-        );
-        // Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early
-        require(
-            sellOrder.expirationTimeSeconds > auctionDetails.currentTimeSeconds,
-            "AUCTION_EXPIRED"
-        );
-        // Validate the buyer amount is greater than the current auction amount
-        require(
-            buyOrder.makerAssetAmount >= auctionDetails.currentAmount,
-            "INVALID_AMOUNT"
-        );
-        // Match orders, maximally filling `buyOrder`
-        matchedFillResults = EXCHANGE.matchOrders(
-            buyOrder,
-            sellOrder,
-            buySignature,
-            sellSignature
-        );
-        // The difference in sellOrder.takerAssetAmount and current amount is given as spread to the matcher
-        // This may include additional spread from the buyOrder.makerAssetAmount and the currentAmount.
-        // e.g currentAmount is 30, sellOrder.takerAssetAmount is 10 and buyOrder.makerAssetamount is 40.
-        // 10 (40-30) is returned to the buyer, 20 (30-10) sent to the seller and 10 has previously
-        // been transferred to the seller during matchOrders
-        uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
-        if (leftMakerAssetSpreadAmount > 0) {
-            // ERC20 Asset data itself is encoded as follows:
-            //
-            // | Area     | Offset | Length  | Contents                            |
-            // |----------|--------|---------|-------------------------------------|
-            // | Header   | 0      | 4       | function selector                   |
-            // | Params   |        | 1 * 32  | function parameters:                |
-            // |          | 4      | 12      |   1. token address padding          |
-            // |          | 16     | 20      |   2. token address                  |
-            bytes memory assetData = sellOrder.takerAssetData;
-            address token = assetData.readAddress(16);
-            // Calculate the excess from the buy order. This can occur if the buyer sends in a higher
-            // amount than the calculated current amount
-            uint256 buyerExcessAmount = safeSub(buyOrder.makerAssetAmount, auctionDetails.currentAmount);
-            uint256 sellerExcessAmount = safeSub(leftMakerAssetSpreadAmount, buyerExcessAmount);
-            // Return the difference between auctionDetails.currentAmount and sellOrder.takerAssetAmount
-            // to the seller
-            if (sellerExcessAmount > 0) {
-                IERC20Token(token).transfer(sellOrder.makerAddress, sellerExcessAmount);
-            }
-            // Return the difference between buyOrder.makerAssetAmount and auctionDetails.currentAmount
-            // to the buyer
-            if (buyerExcessAmount > 0) {
-                IERC20Token(token).transfer(buyOrder.makerAddress, buyerExcessAmount);
-            }
-        }
-        return matchedFillResults;
-    }
-
-    /// @dev Calculates the Auction Details for the given order
-    /// @param order The sell order
-    /// @return AuctionDetails
-    function getAuctionDetails(
-        LibOrder.Order memory order
-    )
-        public
-        returns (AuctionDetails memory auctionDetails)
-    {
-        uint256 makerAssetDataLength = order.makerAssetData.length;
-        // It is unknown the encoded data of makerAssetData, we assume the last 64 bytes
-        // are the Auction Details encoding.
-        // Auction Details is encoded as follows:
-        //
-        // | Area     | Offset | Length  | Contents                            |
-        // |----------|--------|---------|-------------------------------------|
-        // | Params   |        | 2 * 32  | parameters:                         |
-        // |          | -64    | 32      |   1. auction begin unix timestamp   |
-        // |          | -32    | 32      |   2. auction begin begin amount     |
-        // ERC20 asset data length is 4+32, 64 for auction details results in min length 100
-        require(
-            makerAssetDataLength >= 100,
-            "INVALID_ASSET_DATA"
-        );
-        uint256 auctionBeginTimeSeconds = order.makerAssetData.readUint256(makerAssetDataLength - 64);
-        uint256 auctionBeginAmount = order.makerAssetData.readUint256(makerAssetDataLength - 32);
-        // Ensure the auction has a valid begin time
-        require(
-            order.expirationTimeSeconds > auctionBeginTimeSeconds,
-            "INVALID_BEGIN_TIME"
-        );
-        uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds;
-        // Ensure the auction goes from high to low
-        uint256 minAmount = order.takerAssetAmount;
-        require(
-            auctionBeginAmount > minAmount,
-            "INVALID_AMOUNT"
-        );
-        uint256 amountDelta = auctionBeginAmount-minAmount;
-        // solhint-disable-next-line not-rely-on-time
-        uint256 timestamp = block.timestamp;
-        auctionDetails.beginTimeSeconds = auctionBeginTimeSeconds;
-        auctionDetails.endTimeSeconds = order.expirationTimeSeconds;
-        auctionDetails.beginAmount = auctionBeginAmount;
-        auctionDetails.endAmount = minAmount;
-        auctionDetails.currentTimeSeconds = timestamp;
-
-        uint256 remainingDurationSeconds = order.expirationTimeSeconds-timestamp;
-        if (timestamp < auctionBeginTimeSeconds) {
-            // If the auction has not yet begun the current amount is the auctionBeginAmount
-            auctionDetails.currentAmount = auctionBeginAmount;
-        } else if (timestamp >= order.expirationTimeSeconds) {
-            // If the auction has ended the current amount is the minAmount.
-            // Auction end time is guaranteed by 0x Exchange due to the order expiration
-            auctionDetails.currentAmount = minAmount;
-        } else {
-            auctionDetails.currentAmount = safeAdd(
-                minAmount,
-                safeDiv(
-                    safeMul(remainingDurationSeconds, amountDelta),
-                    auctionDurationSeconds
-                )
-            );
-        }
-        return auctionDetails;
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/Forwarder.sol b/contracts/extensions/contracts/Forwarder/Forwarder.sol
deleted file mode 100644
index 94dec40ed..000000000
--- a/contracts/extensions/contracts/Forwarder/Forwarder.sol
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity 0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "./MixinWeth.sol";
-import "./MixinForwarderCore.sol";
-import "./libs/LibConstants.sol";
-import "./MixinAssets.sol";
-import "./MixinExchangeWrapper.sol";
-
-
-// solhint-disable no-empty-blocks
-contract Forwarder is
-    LibConstants,
-    MixinWeth,
-    MixinAssets,
-    MixinExchangeWrapper,
-    MixinForwarderCore
-{
-    constructor (
-        address _exchange,
-        bytes memory _zrxAssetData,
-        bytes memory _wethAssetData
-    )
-        public
-        LibConstants(
-            _exchange,
-            _zrxAssetData,
-            _wethAssetData
-        )
-        MixinForwarderCore()
-    {}
-}
diff --git a/contracts/extensions/contracts/Forwarder/MixinAssets.sol b/contracts/extensions/contracts/Forwarder/MixinAssets.sol
deleted file mode 100644
index 330586ed9..000000000
--- a/contracts/extensions/contracts/Forwarder/MixinAssets.sol
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-import "@0x/contracts-utils/contracts/src/Ownable.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
-import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
-import "./libs/LibConstants.sol";
-import "./mixins/MAssets.sol";
-
-
-contract MixinAssets is
-    Ownable,
-    LibConstants,
-    MAssets
-{
-    using LibBytes for bytes;
-
-    bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
-
-    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
-    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
-    ///      used to withdraw assets that were accidentally sent to this contract.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of ERC20 token to withdraw.
-    function withdrawAsset(
-        bytes assetData,
-        uint256 amount
-    )
-        external
-        onlyOwner
-    {
-        transferAssetToSender(assetData, amount);
-    }
-
-    /// @dev Transfers given amount of asset to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferAssetToSender(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        bytes4 proxyId = assetData.readBytes4(0);
-
-        if (proxyId == ERC20_DATA_ID) {
-            transferERC20Token(assetData, amount);
-        } else if (proxyId == ERC721_DATA_ID) {
-            transferERC721Token(assetData, amount);
-        } else {
-            revert("UNSUPPORTED_ASSET_PROXY");
-        }
-    }
-
-    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        address token = assetData.readAddress(16);
-
-        // Transfer tokens.
-        // We do a raw call so we can check the success separate
-        // from the return data.
-        bool success = token.call(abi.encodeWithSelector(
-            ERC20_TRANSFER_SELECTOR,
-            msg.sender,
-            amount
-        ));
-        require(
-            success,
-            "TRANSFER_FAILED"
-        );
-        
-        // Check return data.
-        // If there is no return data, we assume the token incorrectly
-        // does not return a bool. In this case we expect it to revert
-        // on failure, which was handled above.
-        // If the token does return data, we require that it is a single
-        // value that evaluates to true.
-        assembly {
-            if returndatasize {
-                success := 0
-                if eq(returndatasize, 32) {
-                    // First 64 bytes of memory are reserved scratch space
-                    returndatacopy(0, 0, 32)
-                    success := mload(0)
-                }
-            }
-        }
-        require(
-            success,
-            "TRANSFER_FAILED"
-        );
-    }
-
-    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        require(
-            amount == 1,
-            "INVALID_AMOUNT"
-        );
-        // Decode asset data.
-        address token = assetData.readAddress(16);
-        uint256 tokenId = assetData.readUint256(36);
-
-        // Perform transfer.
-        IERC721Token(token).transferFrom(
-            address(this),
-            msg.sender,
-            tokenId
-        );
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
deleted file mode 100644
index c970c40cf..000000000
--- a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "./libs/LibConstants.sol";
-import "./mixins/MExchangeWrapper.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibAbiEncoder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
-
-
-contract MixinExchangeWrapper is
-    LibAbiEncoder,
-    LibFillResults,
-    LibMath,
-    LibConstants,
-    MExchangeWrapper
-{
-    /// @dev Fills the input order.
-    ///      Returns false if the transaction would otherwise revert.
-    /// @param order Order struct containing order specifications.
-    /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
-    /// @param signature Proof that order has been created by maker.
-    /// @return Amounts filled and fees paid by maker and taker.
-    function fillOrderNoThrow(
-        LibOrder.Order memory order,
-        uint256 takerAssetFillAmount,
-        bytes memory signature
-    )
-        internal
-        returns (FillResults memory fillResults)
-    {
-        // ABI encode calldata for `fillOrder`
-        bytes memory fillOrderCalldata = abiEncodeFillOrder(
-            order,
-            takerAssetFillAmount,
-            signature
-        );
-
-        address exchange = address(EXCHANGE);
-
-        // Call `fillOrder` and handle any exceptions gracefully
-        assembly {
-            let success := call(
-                gas,                                // forward all gas
-                exchange,                           // call address of Exchange contract
-                0,                                  // transfer 0 wei
-                add(fillOrderCalldata, 32),         // pointer to start of input (skip array length in first 32 bytes)
-                mload(fillOrderCalldata),           // length of input
-                fillOrderCalldata,                  // write output over input
-                128                                 // output size is 128 bytes
-            )
-            if success {
-                mstore(fillResults, mload(fillOrderCalldata))
-                mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32)))
-                mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64)))
-                mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96)))
-            }
-        }
-        // fillResults values will be 0 by default if call was unsuccessful
-        return fillResults;
-    }
-
-    /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker.
-    ///      Returns false if the transaction would otherwise revert.
-    /// @param orders Array of order specifications.
-    /// @param wethSellAmount Desired amount of WETH to sell.
-    /// @param signatures Proofs that orders have been signed by makers.
-    /// @return Amounts filled and fees paid by makers and taker.
-    function marketSellWeth(
-        LibOrder.Order[] memory orders,
-        uint256 wethSellAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (FillResults memory totalFillResults)
-    {
-        bytes memory makerAssetData = orders[0].makerAssetData;
-        bytes memory wethAssetData = WETH_ASSET_DATA;
-
-        uint256 ordersLength = orders.length;
-        for (uint256 i = 0; i != ordersLength; i++) {
-
-            // We assume that asset being bought by taker is the same for each order.
-            // We assume that asset being sold by taker is WETH for each order.
-            orders[i].makerAssetData = makerAssetData;
-            orders[i].takerAssetData = wethAssetData;
-
-            // Calculate the remaining amount of WETH to sell
-            uint256 remainingTakerAssetFillAmount = safeSub(wethSellAmount, totalFillResults.takerAssetFilledAmount);
-
-            // Attempt to sell the remaining amount of WETH
-            FillResults memory singleFillResults = fillOrderNoThrow(
-                orders[i],
-                remainingTakerAssetFillAmount,
-                signatures[i]
-            );
-
-            // Update amounts filled and fees paid by maker and taker
-            addFillResults(totalFillResults, singleFillResults);
-
-            // Stop execution if the entire amount of takerAsset has been sold
-            if (totalFillResults.takerAssetFilledAmount >= wethSellAmount) {
-                break;
-            }
-        }
-        return totalFillResults;
-    }
-
-    /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
-    ///      Returns false if the transaction would otherwise revert.
-    ///      The asset being sold by taker must always be WETH.
-    /// @param orders Array of order specifications.
-    /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
-    /// @param signatures Proofs that orders have been signed by makers.
-    /// @return Amounts filled and fees paid by makers and taker.
-    function marketBuyExactAmountWithWeth(
-        LibOrder.Order[] memory orders,
-        uint256 makerAssetFillAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (FillResults memory totalFillResults)
-    {
-        bytes memory makerAssetData = orders[0].makerAssetData;
-        bytes memory wethAssetData = WETH_ASSET_DATA;
-
-        uint256 ordersLength = orders.length;
-        for (uint256 i = 0; i != ordersLength; i++) {
-
-            // We assume that asset being bought by taker is the same for each order.
-            // We assume that asset being sold by taker is WETH for each order.
-            orders[i].makerAssetData = makerAssetData;
-            orders[i].takerAssetData = wethAssetData;
-
-            // Calculate the remaining amount of makerAsset to buy
-            uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
-
-            // Convert the remaining amount of makerAsset to buy into remaining amount
-            // of takerAsset to sell, assuming entire amount can be sold in the current order.
-            // We round up because the exchange rate computed by fillOrder rounds in favor
-            // of the Maker. In this case we want to overestimate the amount of takerAsset.
-            uint256 remainingTakerAssetFillAmount = getPartialAmountCeil(
-                orders[i].takerAssetAmount,
-                orders[i].makerAssetAmount,
-                remainingMakerAssetFillAmount
-            );
-
-            // Attempt to sell the remaining amount of takerAsset
-            FillResults memory singleFillResults = fillOrderNoThrow(
-                orders[i],
-                remainingTakerAssetFillAmount,
-                signatures[i]
-            );
-
-            // Update amounts filled and fees paid by maker and taker
-            addFillResults(totalFillResults, singleFillResults);
-
-            // Stop execution if the entire amount of makerAsset has been bought
-            uint256 makerAssetFilledAmount = totalFillResults.makerAssetFilledAmount;
-            if (makerAssetFilledAmount >= makerAssetFillAmount) {
-                break;
-            }
-        }
-
-        require(
-            makerAssetFilledAmount >= makerAssetFillAmount,
-            "COMPLETE_FILL_FAILED"
-        );
-        return totalFillResults;
-    }
-
-    /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee
-    ///      that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues).
-    ///      It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX
-    ///      as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases.
-    ///      The asset being sold by taker must always be WETH. 
-    /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset.
-    /// @param zrxBuyAmount Desired amount of ZRX to buy.
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @return totalFillResults Amounts filled and fees paid by maker and taker.
-    function marketBuyExactZrxWithWeth(
-        LibOrder.Order[] memory orders,
-        uint256 zrxBuyAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (FillResults memory totalFillResults)
-    {
-        // Do nothing if zrxBuyAmount == 0
-        if (zrxBuyAmount == 0) {
-            return totalFillResults;
-        }
-
-        bytes memory zrxAssetData = ZRX_ASSET_DATA;
-        bytes memory wethAssetData = WETH_ASSET_DATA;
-        uint256 zrxPurchased = 0;
-
-        uint256 ordersLength = orders.length;
-        for (uint256 i = 0; i != ordersLength; i++) {
-
-            // All of these are ZRX/WETH, so we can drop the respective assetData from calldata.
-            orders[i].makerAssetData = zrxAssetData;
-            orders[i].takerAssetData = wethAssetData;
-
-            // Calculate the remaining amount of ZRX to buy.
-            uint256 remainingZrxBuyAmount = safeSub(zrxBuyAmount, zrxPurchased);
-
-            // Convert the remaining amount of ZRX to buy into remaining amount
-            // of WETH to sell, assuming entire amount can be sold in the current order.
-            // We round up because the exchange rate computed by fillOrder rounds in favor
-            // of the Maker. In this case we want to overestimate the amount of takerAsset.
-            uint256 remainingWethSellAmount = getPartialAmountCeil(
-                orders[i].takerAssetAmount,
-                safeSub(orders[i].makerAssetAmount, orders[i].takerFee),  // our exchange rate after fees 
-                remainingZrxBuyAmount
-            );
-
-            // Attempt to sell the remaining amount of WETH.
-            FillResults memory singleFillResult = fillOrderNoThrow(
-                orders[i],
-                remainingWethSellAmount,
-                signatures[i]
-            );
-
-            // Update amounts filled and fees paid by maker and taker.
-            addFillResults(totalFillResults, singleFillResult);
-            zrxPurchased = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid);
-
-            // Stop execution if the entire amount of ZRX has been bought.
-            if (zrxPurchased >= zrxBuyAmount) {
-                break;
-            }
-        }
-
-        require(
-            zrxPurchased >= zrxBuyAmount,
-            "COMPLETE_FILL_FAILED"
-        );
-        return totalFillResults;
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol b/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
deleted file mode 100644
index b65e1f7ce..000000000
--- a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "./libs/LibConstants.sol";
-import "./mixins/MWeth.sol";
-import "./mixins/MAssets.sol";
-import "./mixins/MExchangeWrapper.sol";
-import "./interfaces/IForwarderCore.sol";
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
-
-
-contract MixinForwarderCore is
-    LibFillResults,
-    LibMath,
-    LibConstants,
-    MWeth,
-    MAssets,
-    MExchangeWrapper,
-    IForwarderCore
-{
-    using LibBytes for bytes;
-
-    /// @dev Constructor approves ERC20 proxy to transfer ZRX and WETH on this contract's behalf.
-    constructor ()
-        public
-    {
-        address proxyAddress = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
-        require(
-            proxyAddress != address(0),
-            "UNREGISTERED_ASSET_PROXY"
-        );
-        ETHER_TOKEN.approve(proxyAddress, MAX_UINT);
-        ZRX_TOKEN.approve(proxyAddress, MAX_UINT);
-    }
-
-    /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
-    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
-    ///      5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
-    ///      Any ETH not spent will be refunded to sender.
-    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
-    /// @param feeSignatures Proofs that feeOrders have been created by makers.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
-    function marketSellOrdersWithEth(
-        LibOrder.Order[] memory orders,
-        bytes[] memory signatures,
-        LibOrder.Order[] memory feeOrders,
-        bytes[] memory feeSignatures,
-        uint256  feePercentage,
-        address feeRecipient
-    )
-        public
-        payable
-        returns (
-            FillResults memory orderFillResults,
-            FillResults memory feeOrderFillResults
-        )
-    {
-        // Convert ETH to WETH.
-        convertEthToWeth();
-
-        uint256 wethSellAmount;
-        uint256 zrxBuyAmount;
-        uint256 makerAssetAmountPurchased;
-        if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
-            // Calculate amount of WETH that won't be spent on ETH fees.
-            wethSellAmount = getPartialAmountFloor(
-                PERCENTAGE_DENOMINATOR,
-                safeAdd(PERCENTAGE_DENOMINATOR, feePercentage),
-                msg.value
-            );
-            // Market sell available WETH.
-            // ZRX fees are paid with this contract's balance.
-            orderFillResults = marketSellWeth(
-                orders,
-                wethSellAmount,
-                signatures
-            );
-            // The fee amount must be deducted from the amount transfered back to sender.
-            makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid);
-        } else {
-            // 5% of WETH is reserved for filling feeOrders and paying feeRecipient.
-            wethSellAmount = getPartialAmountFloor(
-                MAX_WETH_FILL_PERCENTAGE,
-                PERCENTAGE_DENOMINATOR,
-                msg.value
-            );
-            // Market sell 95% of WETH.
-            // ZRX fees are payed with this contract's balance.
-            orderFillResults = marketSellWeth(
-                orders,
-                wethSellAmount,
-                signatures
-            );
-            // Buy back all ZRX spent on fees.
-            zrxBuyAmount = orderFillResults.takerFeePaid;
-            feeOrderFillResults = marketBuyExactZrxWithWeth(
-                feeOrders,
-                zrxBuyAmount,
-                feeSignatures
-            );
-            makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
-        }
-
-        // Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
-        // Refund remaining ETH to msg.sender.
-        transferEthFeeAndRefund(
-            orderFillResults.takerAssetFilledAmount,
-            feeOrderFillResults.takerAssetFilledAmount,
-            feePercentage,
-            feeRecipient
-        );
-
-        // Transfer purchased assets to msg.sender.
-        transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased);
-    }
-
-    /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction.
-    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
-    ///      Any ETH not spent will be refunded to sender.
-    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
-    /// @param makerAssetFillAmount Desired amount of makerAsset to purchase.
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
-    /// @param feeSignatures Proofs that feeOrders have been created by makers.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
-    function marketBuyOrdersWithEth(
-        LibOrder.Order[] memory orders,
-        uint256 makerAssetFillAmount,
-        bytes[] memory signatures,
-        LibOrder.Order[] memory feeOrders,
-        bytes[] memory feeSignatures,
-        uint256  feePercentage,
-        address feeRecipient
-    )
-        public
-        payable
-        returns (
-            FillResults memory orderFillResults,
-            FillResults memory feeOrderFillResults
-        )
-    {
-        // Convert ETH to WETH.
-        convertEthToWeth();
-
-        uint256 zrxBuyAmount;
-        uint256 makerAssetAmountPurchased;
-        if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
-            // If the makerAsset is ZRX, it is not necessary to pay fees out of this
-            // contracts's ZRX balance because fees are factored into the price of the order.
-            orderFillResults = marketBuyExactZrxWithWeth(
-                orders,
-                makerAssetFillAmount,
-                signatures
-            );
-            // The fee amount must be deducted from the amount transfered back to sender.
-            makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid);
-        } else {
-            // Attemp to purchase desired amount of makerAsset.
-            // ZRX fees are payed with this contract's balance.
-            orderFillResults = marketBuyExactAmountWithWeth(
-                orders,
-                makerAssetFillAmount,
-                signatures
-            );
-            // Buy back all ZRX spent on fees.
-            zrxBuyAmount = orderFillResults.takerFeePaid;
-            feeOrderFillResults = marketBuyExactZrxWithWeth(
-                feeOrders,
-                zrxBuyAmount,
-                feeSignatures
-            );
-            makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
-        }
-
-        // Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
-        // Refund remaining ETH to msg.sender.
-        transferEthFeeAndRefund(
-            orderFillResults.takerAssetFilledAmount,
-            feeOrderFillResults.takerAssetFilledAmount,
-            feePercentage,
-            feeRecipient
-        );
-
-        // Transfer purchased assets to msg.sender.
-        transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased);
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/MixinWeth.sol b/contracts/extensions/contracts/Forwarder/MixinWeth.sol
deleted file mode 100644
index 029924019..000000000
--- a/contracts/extensions/contracts/Forwarder/MixinWeth.sol
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
-import "./libs/LibConstants.sol";
-import "./mixins/MWeth.sol";
-
-
-contract MixinWeth is
-    LibMath,
-    LibConstants,
-    MWeth
-{
-    /// @dev Default payabale function, this allows us to withdraw WETH
-    function ()
-        public
-        payable
-    {
-        require(
-            msg.sender == address(ETHER_TOKEN),
-            "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY"
-        );
-    }
-
-    /// @dev Converts message call's ETH value into WETH.
-    function convertEthToWeth()
-        internal
-    {
-        require(
-            msg.value > 0,
-            "INVALID_MSG_VALUE"
-        );
-        ETHER_TOKEN.deposit.value(msg.value)();
-    }
-
-    /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient.
-    ///      Refunds any excess ETH to msg.sender.
-    /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders.
-    /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    function transferEthFeeAndRefund(
-        uint256 wethSoldExcludingFeeOrders,
-        uint256 wethSoldForZrx,
-        uint256 feePercentage,
-        address feeRecipient
-    )
-        internal
-    {
-        // Ensure feePercentage is less than 5%.
-        require(
-            feePercentage <= MAX_FEE_PERCENTAGE,
-            "FEE_PERCENTAGE_TOO_LARGE"
-        );
-
-        // Ensure that no extra WETH owned by this contract has been sold.
-        uint256 wethSold = safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx);
-        require(
-            wethSold <= msg.value,
-            "OVERSOLD_WETH"
-        );
-
-        // Calculate amount of WETH that hasn't been sold.
-        uint256 wethRemaining = safeSub(msg.value, wethSold);
-
-        // Calculate ETH fee to pay to feeRecipient.
-        uint256 ethFee = getPartialAmountFloor(
-            feePercentage,
-            PERCENTAGE_DENOMINATOR,
-            wethSoldExcludingFeeOrders
-        );
-
-        // Ensure fee is less than amount of WETH remaining.
-        require(
-            ethFee <= wethRemaining,
-            "INSUFFICIENT_ETH_REMAINING"
-        );
-    
-        // Do nothing if no WETH remaining
-        if (wethRemaining > 0) {
-            // Convert remaining WETH to ETH
-            ETHER_TOKEN.withdraw(wethRemaining);
-
-            // Pay ETH to feeRecipient
-            if (ethFee > 0) {
-                feeRecipient.transfer(ethFee);
-            }
-
-            // Refund remaining ETH to msg.sender.
-            uint256 ethRefund = safeSub(wethRemaining, ethFee);
-            if (ethRefund > 0) {
-                msg.sender.transfer(ethRefund);
-            }
-        }
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol b/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
deleted file mode 100644
index cebfd3706..000000000
--- a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract IAssets {
-
-    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
-    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
-    ///      used to withdraw assets that were accidentally sent to this contract.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of ERC20 token to withdraw.
-    function withdrawAsset(
-        bytes assetData,
-        uint256 amount
-    )
-        external;
-}
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
deleted file mode 100644
index 6ce8a1d31..000000000
--- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "./IForwarderCore.sol";
-import "./IAssets.sol";
-
-
-// solhint-disable no-empty-blocks
-contract IForwarder is
-    IForwarderCore,
-    IAssets
-{}
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
deleted file mode 100644
index 00009d3d5..000000000
--- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
-
-
-contract IForwarderCore {
-
-    /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
-    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
-    ///      5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
-    ///      Any ETH not spent will be refunded to sender.
-    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
-    /// @param feeSignatures Proofs that feeOrders have been created by makers.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
-    function marketSellOrdersWithEth(
-        LibOrder.Order[] memory orders,
-        bytes[] memory signatures,
-        LibOrder.Order[] memory feeOrders,
-        bytes[] memory feeSignatures,
-        uint256  feePercentage,
-        address feeRecipient
-    )
-        public
-        payable
-        returns (
-            LibFillResults.FillResults memory orderFillResults,
-            LibFillResults.FillResults memory feeOrderFillResults
-        );
-
-    /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction.
-    ///      Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
-    ///      Any ETH not spent will be refunded to sender.
-    /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. 
-    /// @param makerAssetFillAmount Desired amount of makerAsset to purchase.
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees.
-    /// @param feeSignatures Proofs that feeOrders have been created by makers.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    /// @return Amounts filled and fees paid by maker and taker for both sets of orders.
-    function marketBuyOrdersWithEth(
-        LibOrder.Order[] memory orders,
-        uint256 makerAssetFillAmount,
-        bytes[] memory signatures,
-        LibOrder.Order[] memory feeOrders,
-        bytes[] memory feeSignatures,
-        uint256  feePercentage,
-        address feeRecipient
-    )
-        public
-        payable
-        returns (
-            LibFillResults.FillResults memory orderFillResults,
-            LibFillResults.FillResults memory feeOrderFillResults
-        );
-}
diff --git a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol b/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
deleted file mode 100644
index 3ff2a03e5..000000000
--- a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
-
-
-contract LibConstants {
-
-    using LibBytes for bytes;
-
-    bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
-    bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
-    uint256 constant internal MAX_UINT = 2**256 - 1;
-    uint256 constant internal PERCENTAGE_DENOMINATOR = 10**18; 
-    uint256 constant internal MAX_FEE_PERCENTAGE = 5 * PERCENTAGE_DENOMINATOR / 100;         // 5%
-    uint256 constant internal MAX_WETH_FILL_PERCENTAGE = 95 * PERCENTAGE_DENOMINATOR / 100;  // 95%
- 
-     // solhint-disable var-name-mixedcase
-    IExchange internal EXCHANGE;
-    IEtherToken internal ETHER_TOKEN;
-    IERC20Token internal ZRX_TOKEN;
-    bytes internal ZRX_ASSET_DATA;
-    bytes internal WETH_ASSET_DATA;
-    // solhint-enable var-name-mixedcase
-
-    constructor (
-        address _exchange,
-        bytes memory _zrxAssetData,
-        bytes memory _wethAssetData
-    )
-        public
-    {
-        EXCHANGE = IExchange(_exchange);
-        ZRX_ASSET_DATA = _zrxAssetData;
-        WETH_ASSET_DATA = _wethAssetData;
-
-        address etherToken = _wethAssetData.readAddress(16);
-        address zrxToken = _zrxAssetData.readAddress(16);
-        ETHER_TOKEN = IEtherToken(etherToken);
-        ZRX_TOKEN = IERC20Token(zrxToken);
-    }
-}
diff --git a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol b/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
deleted file mode 100644
index 7a95b78a0..000000000
--- a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-// solhint-disable
-pragma solidity ^0.4.24;
-
-
-/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
-contract LibForwarderErrors {
-    string constant FEE_PERCENTAGE_TOO_LARGE = "FEE_PROPORTION_TOO_LARGE";                        // Provided fee percentage greater than 5%.
-    string constant INSUFFICIENT_ETH_REMAINING = "INSUFFICIENT_ETH_REMAINING";                    // Not enough ETH remaining to pay feeRecipient.
-    string constant OVERSOLD_WETH = "OVERSOLD_WETH";                                              // More WETH sold than provided with current message call.
-    string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED";                                // Desired purchase amount not completely filled (required for ZRX fees only).
-    string constant TRANSFER_FAILED = "TRANSFER_FAILED";                                          // Asset transfer failed.
-    string constant UNSUPPORTED_ASSET_PROXY = "UNSUPPORTED_ASSET_PROXY";                          // Proxy in assetData not supported.
-    string constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY = "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY";  // Fallback function may only be used for WETH withdrawals.
-    string constant INVALID_MSG_VALUE = "INVALID_MSG_VALUE";                                      // msg.value must be greater than 0.
-    string constant INVALID_AMOUNT = "INVALID_AMOUNT";                                            // Amount must equal 1.
-}
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol b/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
deleted file mode 100644
index 1757b37fb..000000000
--- a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "../interfaces/IAssets.sol";
-
-
-contract MAssets is
-    IAssets
-{
-    /// @dev Transfers given amount of asset to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferAssetToSender(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-}
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
deleted file mode 100644
index 7439e8259..000000000
--- a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
-
-
-contract MExchangeWrapper {
-
-    /// @dev Fills the input order.
-    ///      Returns false if the transaction would otherwise revert.
-    /// @param order Order struct containing order specifications.
-    /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
-    /// @param signature Proof that order has been created by maker.
-    /// @return Amounts filled and fees paid by maker and taker.
-    function fillOrderNoThrow(
-        LibOrder.Order memory order,
-        uint256 takerAssetFillAmount,
-        bytes memory signature
-    )
-        internal
-        returns (LibFillResults.FillResults memory fillResults);
-
-    /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker.
-    ///      Returns false if the transaction would otherwise revert.
-    /// @param orders Array of order specifications.
-    /// @param wethSellAmount Desired amount of WETH to sell.
-    /// @param signatures Proofs that orders have been signed by makers.
-    /// @return Amounts filled and fees paid by makers and taker.
-    function marketSellWeth(
-        LibOrder.Order[] memory orders,
-        uint256 wethSellAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (LibFillResults.FillResults memory totalFillResults);
-
-    /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
-    ///      Returns false if the transaction would otherwise revert.
-    ///      The asset being sold by taker must always be WETH.
-    /// @param orders Array of order specifications.
-    /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
-    /// @param signatures Proofs that orders have been signed by makers.
-    /// @return Amounts filled and fees paid by makers and taker.
-    function marketBuyExactAmountWithWeth(
-        LibOrder.Order[] memory orders,
-        uint256 makerAssetFillAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (LibFillResults.FillResults memory totalFillResults);
-
-    /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee
-    ///      that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues).
-    ///      It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX
-    ///      as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases.
-    ///      The asset being sold by taker must always be WETH. 
-    /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset.
-    /// @param zrxBuyAmount Desired amount of ZRX to buy.
-    /// @param signatures Proofs that orders have been created by makers.
-    /// @return totalFillResults Amounts filled and fees paid by maker and taker.
-    function marketBuyExactZrxWithWeth(
-        LibOrder.Order[] memory orders,
-        uint256 zrxBuyAmount,
-        bytes[] memory signatures
-    )
-        internal
-        returns (LibFillResults.FillResults memory totalFillResults);
-}
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol b/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
deleted file mode 100644
index 15d66942f..000000000
--- a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract MWeth {
-
-    /// @dev Converts message call's ETH value into WETH.
-    function convertEthToWeth()
-        internal;
-
-    /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient.
-    ///      Refunds any excess ETH to msg.sender.
-    /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders.
-    /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees.
-    /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
-    /// @param feeRecipient Address that will receive ETH when orders are filled.
-    function transferEthFeeAndRefund(
-        uint256 wethSoldExcludingFeeOrders,
-        uint256 wethSoldForZrx,
-        uint256 feePercentage,
-        address feeRecipient
-    )
-        internal;
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol b/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
deleted file mode 100644
index 65b36ea65..000000000
--- a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-import "@0x/contracts-utils/contracts/src/Ownable.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
-import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
-import "./mixins/MAssets.sol";
-import "./libs/LibConstants.sol";
-
-
-contract MixinAssets is
-    MAssets,
-    Ownable,
-    LibConstants
-{
-    using LibBytes for bytes;
-
-    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
-    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
-    ///      used to withdraw assets that were accidentally sent to this contract.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to withdraw.
-    function withdrawAsset(
-        bytes assetData,
-        uint256 amount
-    )
-        external
-        onlyOwner
-    {
-        transferAssetToSender(assetData, amount);
-    }
-
-    /// @dev Approves or disapproves an AssetProxy to spend asset.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveAssetProxy(
-        bytes assetData,
-        uint256 amount
-    )
-        external
-        onlyOwner
-    {
-        bytes4 proxyId = assetData.readBytes4(0);
-
-        if (proxyId == ERC20_DATA_ID) {
-            approveERC20Token(assetData, amount);
-        } else if (proxyId == ERC721_DATA_ID) {
-            approveERC721Token(assetData, amount);
-        } else {
-            revert("UNSUPPORTED_ASSET_PROXY");
-        }
-    }
-
-    /// @dev Transfers given amount of asset to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferAssetToSender(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        bytes4 proxyId = assetData.readBytes4(0);
-
-        if (proxyId == ERC20_DATA_ID) {
-            transferERC20Token(assetData, amount);
-        } else if (proxyId == ERC721_DATA_ID) {
-            transferERC721Token(assetData, amount);
-        } else {
-            revert("UNSUPPORTED_ASSET_PROXY");
-        }
-    }
-
-    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        // 4 byte id + 12 0 bytes before ABI encoded token address.
-        address token = assetData.readAddress(16);
-
-        // Transfer tokens.
-        // We do a raw call so we can check the success separate
-        // from the return data.
-        bool success = token.call(abi.encodeWithSelector(
-            ERC20_TRANSFER_SELECTOR,
-            msg.sender,
-            amount
-        ));
-        require(
-            success,
-            "TRANSFER_FAILED"
-        );
-        
-        // Check return data.
-        // If there is no return data, we assume the token incorrectly
-        // does not return a bool. In this case we expect it to revert
-        // on failure, which was handled above.
-        // If the token does return data, we require that it is a single
-        // value that evaluates to true.
-        assembly {
-            if returndatasize {
-                success := 0
-                if eq(returndatasize, 32) {
-                    // First 64 bytes of memory are reserved scratch space
-                    returndatacopy(0, 0, 32)
-                    success := mload(0)
-                }
-            }
-        }
-        require(
-            success,
-            "TRANSFER_FAILED"
-        );
-    }
-
-    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        require(
-            amount == 1,
-            "INVALID_AMOUNT"
-        );
-        // Decode asset data.
-        // 4 byte id + 12 0 bytes before ABI encoded token address.
-        address token = assetData.readAddress(16);
-        // 4 byte id + 32 byte ABI encoded token address before token id.
-        uint256 tokenId = assetData.readUint256(36);
-
-        // Perform transfer.
-        IERC721Token(token).transferFrom(
-            address(this),
-            msg.sender,
-            tokenId
-        );
-    }
-
-    /// @dev Sets approval for ERC20 AssetProxy.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        address token = assetData.readAddress(16);
-        require(
-            IERC20Token(token).approve(ERC20_PROXY_ADDRESS, amount),
-            "APPROVAL_FAILED"
-        );
-    }
-
-    /// @dev Sets approval for ERC721 AssetProxy.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal
-    {
-        address token = assetData.readAddress(16);
-        bool approval = amount >= 1;
-        IERC721Token(token).setApprovalForAll(ERC721_PROXY_ADDRESS, approval);
-    }
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol b/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
deleted file mode 100644
index baf41f650..000000000
--- a/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "./libs/LibConstants.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
-import "@0x/contracts-utils/contracts/src/Ownable.sol";
-
-
-contract MixinMatchOrders is
-    Ownable,
-    LibConstants
-{
-    /// @dev Match two complementary orders that have a profitable spread.
-    ///      Each order is filled at their respective price point. However, the calculations are
-    ///      carried out as though the orders are both being filled at the right order's price point.
-    ///      The profit made by the left order is then used to fill the right order as much as possible.
-    ///      This results in a spread being taken in terms of both assets. The spread is held within this contract.
-    /// @param leftOrder First order to match.
-    /// @param rightOrder Second order to match.
-    /// @param leftSignature Proof that order was created by the left maker.
-    /// @param rightSignature Proof that order was created by the right maker.
-    function matchOrders(
-        LibOrder.Order memory leftOrder,
-        LibOrder.Order memory rightOrder,
-        bytes memory leftSignature,
-        bytes memory rightSignature
-    )
-        public
-        onlyOwner
-    {
-        // Match orders, maximally filling `leftOrder`
-        LibFillResults.MatchedFillResults memory matchedFillResults = EXCHANGE.matchOrders(
-            leftOrder,
-            rightOrder,
-            leftSignature,
-            rightSignature
-        );
-
-        uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
-        uint256 rightOrderTakerAssetAmount = rightOrder.takerAssetAmount;
-
-        // Do not attempt to call `fillOrder` if no spread was taken or `rightOrder` has been completely filled
-        if (leftMakerAssetSpreadAmount == 0 || matchedFillResults.right.takerAssetFilledAmount == rightOrderTakerAssetAmount) {
-            return;
-        }
-
-        // The `assetData` fields of the `rightOrder` could have been null for the `matchOrders` call. We reassign them before calling `fillOrder`.
-        rightOrder.makerAssetData = leftOrder.takerAssetData;
-        rightOrder.takerAssetData = leftOrder.makerAssetData;
-
-        // Query `rightOrder` info to check if it has been completely filled
-        // We need to make this check in case the `rightOrder` was partially filled before the `matchOrders` call
-        LibOrder.OrderInfo memory orderInfo = EXCHANGE.getOrderInfo(rightOrder);
-
-        // Do not attempt to call `fillOrder` if order has been completely filled
-        if (orderInfo.orderTakerAssetFilledAmount == rightOrderTakerAssetAmount) {
-            return;
-        }
-
-        // We do not need to pass in a signature since it was already validated in the `matchOrders` call
-        EXCHANGE.fillOrder(
-            rightOrder,
-            leftMakerAssetSpreadAmount,
-            ""
-        );
-    }
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol b/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
deleted file mode 100644
index 89a7f3c55..000000000
--- a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity 0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-utils/contracts/src/Ownable.sol";
-import "./libs/LibConstants.sol";
-import "./MixinMatchOrders.sol";
-import "./MixinAssets.sol";
-
-
-// solhint-disable no-empty-blocks
-contract OrderMatcher is
-    MixinMatchOrders,
-    MixinAssets
-{
-    constructor (address _exchange)
-        public
-        LibConstants(_exchange)
-        Ownable()
-    {}
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol
deleted file mode 100644
index 9fcc0023a..000000000
--- a/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-
-contract IAssets {
-
-    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
-    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
-    ///      used to withdraw assets that were accidentally sent to this contract.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to withdraw.
-    function withdrawAsset(
-        bytes assetData,
-        uint256 amount
-    )
-        external;
-
-    /// @dev Approves or disapproves an AssetProxy to spend asset.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveAssetProxy(
-        bytes assetData,
-        uint256 amount
-    )
-        external;
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol
deleted file mode 100644
index 7e4a88df8..000000000
--- a/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-
-
-contract IMatchOrders {
-
-    /// @dev Match two complementary orders that have a profitable spread.
-    ///      Each order is filled at their respective price point. However, the calculations are
-    ///      carried out as though the orders are both being filled at the right order's price point.
-    ///      The profit made by the left order is then used to fill the right order as much as possible.
-    ///      This results in a spread being taken in terms of both assets. The spread is held within this contract.
-    /// @param leftOrder First order to match.
-    /// @param rightOrder Second order to match.
-    /// @param leftSignature Proof that order was created by the left maker.
-    /// @param rightSignature Proof that order was created by the right maker.
-    function matchOrders(
-        LibOrder.Order memory leftOrder,
-        LibOrder.Order memory rightOrder,
-        bytes memory leftSignature,
-        bytes memory rightSignature
-    )
-        public;
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol
deleted file mode 100644
index 5e5125dea..000000000
--- a/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contract-utils/contracts/src/interfaces/IOwnable.sol";
-import "./IMatchOrders.sol";
-import "./IAssets.sol";
-
-
-// solhint-disable no-empty-blocks
-contract IOrderMatcher is
-    IOwnable,
-    IMatchOrders,
-    IAssets
-{}
diff --git a/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol b/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
deleted file mode 100644
index 77b72398f..000000000
--- a/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-
-
-contract LibConstants {
-
-    // bytes4(keccak256("transfer(address,uint256)"))
-    bytes4 constant internal ERC20_TRANSFER_SELECTOR = 0xa9059cbb;
-    // bytes4(keccak256("ERC20Token(address)"))
-    bytes4 constant internal ERC20_DATA_ID = 0xf47261b0;
-    // bytes4(keccak256("ERC721Token(address,uint256)"))
-    bytes4 constant internal ERC721_DATA_ID = 0x02571792;
- 
-     // solhint-disable var-name-mixedcase
-    IExchange internal EXCHANGE;
-    address internal ERC20_PROXY_ADDRESS;
-    address internal ERC721_PROXY_ADDRESS;
-    // solhint-enable var-name-mixedcase
-
-    constructor (address _exchange)
-        public
-    {
-        EXCHANGE = IExchange(_exchange);
-
-        ERC20_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
-        require(
-            ERC20_PROXY_ADDRESS != address(0),
-            "UNREGISTERED_ASSET_PROXY"
-        );
-
-        ERC721_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC721_DATA_ID);
-        require(
-            ERC721_PROXY_ADDRESS != address(0),
-            "UNREGISTERED_ASSET_PROXY"
-        );
-    }
-}
diff --git a/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol b/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol
deleted file mode 100644
index 6ea7a3290..000000000
--- a/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity ^0.4.24;
-
-import "../interfaces/IAssets.sol";
-
-
-contract MAssets is
-    IAssets
-{
-    /// @dev Transfers given amount of asset to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferAssetToSender(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to transfer to sender.
-    function transferERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Sets approval for ERC20 AssetProxy.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveERC20Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-
-    /// @dev Sets approval for ERC721 AssetProxy.
-    /// @param assetData Byte array encoded for the respective asset proxy.
-    /// @param amount Amount of asset to approve for respective proxy.
-    function approveERC721Token(
-        bytes memory assetData,
-        uint256 amount
-    )
-        internal;
-}
diff --git a/contracts/extensions/contracts/OrderValidator/OrderValidator.sol b/contracts/extensions/contracts/OrderValidator/OrderValidator.sol
deleted file mode 100644
index 3297a980f..000000000
--- a/contracts/extensions/contracts/OrderValidator/OrderValidator.sol
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
-
-  Copyright 2018 ZeroEx Intl.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-*/
-
-pragma solidity 0.4.24;
-pragma experimental ABIEncoderV2;
-
-import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
-import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
-import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
-import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
-import "@0x/contracts-utils/contracts/src/LibBytes.sol";
-
-
-contract OrderValidator {
-
-    using LibBytes for bytes;
-
-    bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
-    bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
-
-    struct TraderInfo {
-        uint256 makerBalance;       // Maker's balance of makerAsset
-        uint256 makerAllowance;     // Maker's allowance to corresponding AssetProxy
-        uint256 takerBalance;       // Taker's balance of takerAsset
-        uint256 takerAllowance;     // Taker's allowance to corresponding AssetProxy
-        uint256 makerZrxBalance;    // Maker's balance of ZRX
-        uint256 makerZrxAllowance;  // Maker's allowance of ZRX to ERC20Proxy
-        uint256 takerZrxBalance;    // Taker's balance of ZRX
-        uint256 takerZrxAllowance;  // Taker's allowance of ZRX to ERC20Proxy
-    }
-
-    // solhint-disable var-name-mixedcase
-    IExchange internal EXCHANGE;
-    bytes internal ZRX_ASSET_DATA;
-    // solhint-enable var-name-mixedcase
-
-    constructor (address _exchange, bytes memory _zrxAssetData)
-        public
-    {
-        EXCHANGE = IExchange(_exchange);
-        ZRX_ASSET_DATA = _zrxAssetData;
-    }
-
-    /// @dev Fetches information for order and maker/taker of order.
-    /// @param order The order structure.
-    /// @param takerAddress Address that will be filling the order.
-    /// @return OrderInfo and TraderInfo instances for given order.
-    function getOrderAndTraderInfo(LibOrder.Order memory order, address takerAddress)
-        public
-        view
-        returns (LibOrder.OrderInfo memory orderInfo, TraderInfo memory traderInfo)
-    {
-        orderInfo = EXCHANGE.getOrderInfo(order);
-        traderInfo = getTraderInfo(order, takerAddress);
-        return (orderInfo, traderInfo);
-    }
-
-    /// @dev Fetches information for all passed in orders and the makers/takers of each order.
-    /// @param orders Array of order specifications.
-    /// @param takerAddresses Array of taker addresses corresponding to each order.
-    /// @return Arrays of OrderInfo and TraderInfo instances that correspond to each order.
-    function getOrdersAndTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
-        public
-        view
-        returns (LibOrder.OrderInfo[] memory ordersInfo, TraderInfo[] memory tradersInfo)
-    {
-        ordersInfo = EXCHANGE.getOrdersInfo(orders);
-        tradersInfo = getTradersInfo(orders, takerAddresses);
-        return (ordersInfo, tradersInfo);
-    }
-
-    /// @dev Fetches balance and allowances for maker and taker of order.
-    /// @param order The order structure.
-    /// @param takerAddress Address that will be filling the order.
-    /// @return Balances and allowances of maker and taker of order.
-    function getTraderInfo(LibOrder.Order memory order, address takerAddress)
-        public
-        view
-        returns (TraderInfo memory traderInfo)
-    {
-        (traderInfo.makerBalance, traderInfo.makerAllowance) = getBalanceAndAllowance(order.makerAddress, order.makerAssetData);
-        (traderInfo.takerBalance, traderInfo.takerAllowance) = getBalanceAndAllowance(takerAddress, order.takerAssetData);
-        bytes memory zrxAssetData = ZRX_ASSET_DATA;
-        (traderInfo.makerZrxBalance, traderInfo.makerZrxAllowance) = getBalanceAndAllowance(order.makerAddress, zrxAssetData);
-        (traderInfo.takerZrxBalance, traderInfo.takerZrxAllowance) = getBalanceAndAllowance(takerAddress, zrxAssetData);
-        return traderInfo;
-    }
-
-    /// @dev Fetches balances and allowances of maker and taker for each provided order.
-    /// @param orders Array of order specifications.
-    /// @param takerAddresses Array of taker addresses corresponding to each order.
-    /// @return Array of balances and allowances for maker and taker of each order.
-    function getTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
-        public
-        view
-        returns (TraderInfo[] memory)
-    {
-        uint256 ordersLength = orders.length;
-        TraderInfo[] memory tradersInfo = new TraderInfo[](ordersLength);
-        for (uint256 i = 0; i != ordersLength; i++) {
-            tradersInfo[i] = getTraderInfo(orders[i], takerAddresses[i]);
-        }
-        return tradersInfo;
-    }
-
-    /// @dev Fetches token balances and allowances of an address to given assetProxy. Supports ERC20 and ERC721.
-    /// @param target Address to fetch balances and allowances of.
-    /// @param assetData Encoded data that can be decoded by a specified proxy contract when transferring asset.
-    /// @return Balance of asset and allowance set to given proxy of asset.
-    ///         For ERC721 tokens, these values will always be 1 or 0.
-    function getBalanceAndAllowance(address target, bytes memory assetData)
-        public
-        view
-        returns (uint256 balance, uint256 allowance)
-    {
-        bytes4 assetProxyId = assetData.readBytes4(0);
-        address token = assetData.readAddress(16);
-        address assetProxy = EXCHANGE.getAssetProxy(assetProxyId);
-
-        if (assetProxyId == ERC20_DATA_ID) {
-            // Query balance
-            balance = IERC20Token(token).balanceOf(target);
-
-            // Query allowance
-            allowance = IERC20Token(token).allowance(target, assetProxy);
-        } else if (assetProxyId == ERC721_DATA_ID) {
-            uint256 tokenId = assetData.readUint256(36);
-
-            // Query owner of tokenId
-            address owner = getERC721TokenOwner(token, tokenId);
-
-            // Set balance to 1 if tokenId is owned by target
-            balance = target == owner ? 1 : 0;
-
-            // Check if ERC721Proxy is approved to spend tokenId
-            bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy);
-            
-            // Set alowance to 1 if ERC721Proxy is approved to spend tokenId
-            allowance = isApproved ? 1 : 0;
-        } else {
-            revert("UNSUPPORTED_ASSET_PROXY");
-        }
-        return (balance, allowance);
-    }
-
-    /// @dev Fetches token balances and allowances of an address for each given assetProxy. Supports ERC20 and ERC721.
-    /// @param target Address to fetch balances and allowances of.
-    /// @param assetData Array of encoded byte arrays that can be decoded by a specified proxy contract when transferring asset.
-    /// @return Balances and allowances of assets.
-    ///         For ERC721 tokens, these values will always be 1 or 0.
-    function getBalancesAndAllowances(address target, bytes[] memory assetData)
-        public
-        view
-        returns (uint256[] memory, uint256[] memory)
-    {
-        uint256 length = assetData.length;
-        uint256[] memory balances = new uint256[](length);
-        uint256[] memory allowances = new uint256[](length);
-        for (uint256 i = 0; i != length; i++) {
-            (balances[i], allowances[i]) = getBalanceAndAllowance(target, assetData[i]);
-        }
-        return (balances, allowances);
-    }
-
-    /// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token.
-    /// @param token Address of ERC721 token.
-    /// @param tokenId The identifier for the specific NFT.
-    /// @return Owner of tokenId or null address if unowned.
-    function getERC721TokenOwner(address token, uint256 tokenId)
-        public
-        view
-        returns (address owner)
-    {
-        assembly {
-            // load free memory pointer
-            let cdStart := mload(64)
-
-            // bytes4(keccak256(ownerOf(uint256))) = 0x6352211e
-            mstore(cdStart, 0x6352211e00000000000000000000000000000000000000000000000000000000)
-            mstore(add(cdStart, 4), tokenId)
-
-            // staticcall `ownerOf(tokenId)`
-            // `ownerOf` will revert if tokenId is not owned
-            let success := staticcall(
-                gas,      // forward all gas
-                token,    // call token contract
-                cdStart,  // start of calldata
-                36,       // length of input is 36 bytes
-                cdStart,  // write output over input
-                32        // size of output is 32 bytes
-            )
-
-            // Success implies that tokenId is owned
-            // Copy owner from return data if successful
-            if success {
-                owner := mload(cdStart)
-            }    
-        }
-
-        // Owner initialized to address(0), no need to modify if call is unsuccessful
-        return owner;
-    }
-}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol
new file mode 100644
index 000000000..b6e14525d
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol
@@ -0,0 +1,45 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "./interfaces/IThresholdAsset.sol";
+import "./MixinBalanceThresholdFilterCore.sol";
+
+
+contract BalanceThresholdFilter is
+    MixinBalanceThresholdFilterCore
+{
+
+    /// @dev Constructs BalanceThresholdFilter.
+    /// @param exchange Address of 0x exchange.
+    /// @param thresholdAsset The asset that must be held by makers/takers.
+    /// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers.
+    constructor(
+        address exchange,
+        address thresholdAsset,
+        uint256 balanceThreshold
+    )
+        public
+    {
+        EXCHANGE = IExchange(exchange);
+        THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
+        BALANCE_THRESHOLD = balanceThreshold;
+    }
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..68dabafeb
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
@@ -0,0 +1,135 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "./mixins/MBalanceThresholdFilterCore.sol";
+import "./MixinExchangeCalldata.sol";
+
+
+contract MixinBalanceThresholdFilterCore is
+    MBalanceThresholdFilterCore,
+    MixinExchangeCalldata,
+    LibOrder,
+    LibExchangeSelectors
+{
+
+    /// @dev Executes an Exchange transaction iff the maker and taker meet 
+    ///      the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR 
+    ///      the exchange function is a cancellation.
+    ///      Supported Exchange functions:
+    ///          batchFillOrders
+    ///          batchFillOrdersNoThrow
+    ///          batchFillOrKillOrders
+    ///          fillOrder
+    ///          fillOrderNoThrow
+    ///          fillOrKillOrder
+    ///          marketBuyOrders
+    ///          marketBuyOrdersNoThrow
+    ///          marketSellOrders
+    ///          marketSellOrdersNoThrow
+    ///          matchOrders
+    ///          cancelOrder
+    ///          batchCancelOrders
+    ///          cancelOrdersUpTo
+    ///     Trying to call any other exchange function will throw.
+    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+    /// @param signerAddress Address of transaction signer.
+    /// @param signedExchangeTransaction AbiV2 encoded calldata.
+    /// @param signature Proof of signer transaction by signer.
+    function executeTransaction(
+        uint256 salt,
+        address signerAddress,
+        bytes signedExchangeTransaction,
+        bytes signature
+    ) 
+        external
+    {
+        // Get accounts whose balances must be validated
+        address[] memory addressesToValidate = getAddressesToValidate(signerAddress);
+
+        // Validate account balances
+        uint256 balanceThreshold = BALANCE_THRESHOLD;
+        IThresholdAsset thresholdAsset = THRESHOLD_ASSET;
+        for (uint256 i = 0; i < addressesToValidate.length; ++i) {
+            uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]);
+            require(
+                addressBalance >= balanceThreshold,
+                "AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"
+            );
+        }
+        emit ValidatedAddresses(addressesToValidate);
+        
+        // All addresses are valid. Execute exchange function.
+        EXCHANGE.executeTransaction(
+            salt,
+            signerAddress,
+            signedExchangeTransaction,
+            signature
+        );
+    }
+
+    /// @dev Constructs an array of addresses to be validated.
+    ///      Addresses depend on which Exchange function is to be called
+    ///      (defined by `signedExchangeTransaction` above).
+    /// @param signerAddress Address of transaction signer.
+    /// @return addressesToValidate Array of addresses to validate.
+    function getAddressesToValidate(address signerAddress)
+        internal pure
+        returns (address[] memory addressesToValidate)
+    {
+        bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0));
+        // solhint-disable expression-indent
+        if (
+            exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR              ||
+            exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR     ||
+            exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR      ||
+            exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR              ||
+            exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR     ||
+            exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR             ||
+            exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
+        ) {
+            addressesToValidate = loadMakerAddressesFromOrderArray(0);
+            addressesToValidate = addressesToValidate.append(signerAddress);
+        } else if (
+            exchangeFunctionSelector == FILL_ORDER_SELECTOR             ||
+            exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR    ||
+            exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR
+        ) {
+            address makerAddress = loadMakerAddressFromOrder(0);
+            addressesToValidate = addressesToValidate.append(makerAddress);
+            addressesToValidate = addressesToValidate.append(signerAddress);
+        } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) {
+            address leftMakerAddress = loadMakerAddressFromOrder(0);
+            addressesToValidate = addressesToValidate.append(leftMakerAddress);
+            address rightMakerAddress = loadMakerAddressFromOrder(1);
+            addressesToValidate = addressesToValidate.append(rightMakerAddress);
+            addressesToValidate = addressesToValidate.append(signerAddress);
+        } else if (
+            exchangeFunctionSelector != CANCEL_ORDER_SELECTOR           &&
+            exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR    &&
+            exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR
+        ) {
+            revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR");
+        }
+        // solhint-enable expression-indent
+        return addressesToValidate;
+    }
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol
new file mode 100644
index 000000000..f2c0caa57
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol
@@ -0,0 +1,103 @@
+    
+    /*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "./mixins/MExchangeCalldata.sol";
+import "@0x/contracts-utils/contracts/src/LibAddressArray.sol";
+
+
+contract MixinExchangeCalldata is 
+    MExchangeCalldata
+{
+
+    using LibAddressArray for address[];
+
+    /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
+    ///      which is accessed through `signedExchangeTransaction`.
+    /// @param offset  Offset into the Exchange calldata.
+    /// @return value  Corresponding 32 byte value stored at `offset`.
+    function exchangeCalldataload(uint256 offset)
+        internal pure
+        returns (bytes32 value)
+    {
+        assembly {
+            // Pointer to exchange transaction
+            // 0x04 for calldata selector
+            // 0x40 to access `signedExchangeTransaction`, which is the third parameter
+            let exchangeTxPtr := calldataload(0x44)
+
+            // Offset into Exchange calldata
+            // We compute this by adding 0x24 to the `exchangeTxPtr` computed above.
+            // 0x04 for calldata selector
+            // 0x20 for length field of `signedExchangeTransaction`
+            let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset))
+            value := calldataload(exchangeCalldataOffset)
+        }
+        return value;
+    }
+
+    /// @dev Convenience function that skips the 4 byte selector when loading
+    ///      from the embedded Exchange calldata.
+    /// @param offset  Offset into the Exchange calldata (minus the 4 byte selector)
+    /// @return value  Corresponding 32 byte value stored at `offset` + 4.
+    function loadExchangeData(uint256 offset)
+        internal pure
+        returns (bytes32 value)
+    {
+        value = exchangeCalldataload(offset + 4);
+        return value;
+    }
+
+    /// @dev Extracts the maker address from an order stored in the Exchange calldata
+    ///      (which is embedded in `signedExchangeTransaction`).
+    /// @param orderParamIndex  Index of the order in the Exchange function's signature.
+    /// @return makerAddress The extracted maker address.
+    function loadMakerAddressFromOrder(uint256 orderParamIndex)
+        internal pure
+        returns (address makerAddress)
+    {
+        uint256 orderOffsetInBytes = orderParamIndex * 32;
+        uint256 orderPtr = uint256(loadExchangeData(orderOffsetInBytes));
+        makerAddress = address(loadExchangeData(orderPtr));
+        return makerAddress;
+    }
+
+    /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
+    ///      (which is embedded in `signedExchangeTransaction`).
+    /// @param orderArrayParamIndex  Index of the order array in the Exchange function's signature
+    /// @return makerAddresses The extracted maker addresses.
+    function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
+        internal pure
+        returns (address[] makerAddresses)
+    {
+        uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32;
+        uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayOffsetInBytes));
+        uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr));
+        uint256 orderArrayLengthInBytes = orderArrayLength * 32;
+        uint256 orderArrayElementPtr = orderArrayPtr + 32;
+        uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes;
+        for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) {
+            uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset));
+            address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr));
+            makerAddresses = makerAddresses.append(makerAddress);
+        }
+        return makerAddresses;
+    }
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..4a1bf1fb2
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
@@ -0,0 +1,55 @@
+
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IBalanceThresholdFilterCore {
+    
+    /// @dev Executes an Exchange transaction iff the maker and taker meet 
+    ///      the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR 
+    ///      the exchange function is a cancellation.
+    ///      Supported Exchange functions:
+    ///         - batchFillOrders
+    ///         - batchFillOrdersNoThrow
+    ///         - batchFillOrKillOrders
+    ///         - fillOrder
+    ///         - fillOrderNoThrow
+    ///         - fillOrKillOrder
+    ///         - marketBuyOrders
+    ///         - marketBuyOrdersNoThrow
+    ///         - marketSellOrders
+    ///         - marketSellOrdersNoThrow
+    ///         - matchOrders
+    ///         - cancelOrder
+    ///         - batchCancelOrders
+    ///         - cancelOrdersUpTo
+    ///     Trying to call any other exchange function will throw.
+    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+    /// @param signerAddress Address of transaction signer.
+    /// @param signedExchangeTransaction AbiV2 encoded calldata.
+    /// @param signature Proof of signer transaction by signer.
+    function executeTransaction(
+        uint256 salt,
+        address signerAddress,
+        bytes signedExchangeTransaction,
+        bytes signature
+    ) 
+        external;
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
new file mode 100644
index 000000000..f78f9c2de
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
@@ -0,0 +1,31 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IThresholdAsset {
+
+    /// @param _owner The address from which the balance will be retrieved
+    /// @return Balance of owner
+    function balanceOf(address _owner)
+        external
+        view
+        returns (uint256);
+
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..1c840136b
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
@@ -0,0 +1,54 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "../interfaces/IThresholdAsset.sol";
+import "../interfaces/IBalanceThresholdFilterCore.sol";
+
+
+contract MBalanceThresholdFilterCore is
+    IBalanceThresholdFilterCore
+{
+
+    // Points to 0x exchange contract
+    // solhint-disable var-name-mixedcase
+    IExchange internal EXCHANGE;
+
+    // The asset that must be held by makers/takers
+    IThresholdAsset internal THRESHOLD_ASSET;
+
+    // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers
+    uint256 internal BALANCE_THRESHOLD;
+    // solhint-enable var-name-mixedcase
+
+    // Addresses that hold at least `BALANCE_THRESHOLD` of `THRESHOLD_ASSET`
+    event ValidatedAddresses (
+        address[] addresses
+    );
+
+    /// @dev Constructs an array of addresses to be validated.
+    ///      Addresses depend on which Exchange function is to be called
+    ///      (defined by `signedExchangeTransaction` above).
+    /// @param signerAddress Address of transaction signer.
+    /// @return addressesToValidate Array of addresses to validate.
+    function getAddressesToValidate(address signerAddress)
+        internal pure
+        returns (address[] memory addressesToValidate);
+}
diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
new file mode 100644
index 000000000..40536d820
--- /dev/null
+++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
@@ -0,0 +1,56 @@
+    
+    /*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract MExchangeCalldata {
+
+    /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
+    ///      which is accessed through `signedExchangeTransaction`.
+    /// @param offset  Offset into the Exchange calldata.
+    /// @return value  Corresponding 32 byte value stored at `offset`.
+    function exchangeCalldataload(uint256 offset)
+        internal pure
+        returns (bytes32 value);
+
+    /// @dev Convenience function that skips the 4 byte selector when loading
+    ///      from the embedded Exchange calldata.
+    /// @param offset  Offset into the Exchange calldata (minus the 4 byte selector)
+    /// @return value  Corresponding 32 byte value stored at `offset` + 4.
+    function loadExchangeData(uint256 offset)
+        internal pure
+        returns (bytes32 value);
+
+    /// @dev Extracts the maker address from an order stored in the Exchange calldata
+    ///      (which is embedded in `signedExchangeTransaction`).
+    /// @param orderParamIndex  Index of the order in the Exchange function's signature.
+    /// @return makerAddress The extracted maker address.
+    function loadMakerAddressFromOrder(uint256 orderParamIndex)
+        internal pure
+        returns (address makerAddress);
+
+    /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
+    ///      (which is embedded in `signedExchangeTransaction`).
+    /// @param orderArrayParamIndex  Index of the order array in the Exchange function's signature
+    /// @return makerAddresses The extracted maker addresses.
+    function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
+        internal pure
+        returns (address[] makerAddresses);
+}
diff --git a/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
new file mode 100644
index 000000000..7631880c0
--- /dev/null
+++ b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
@@ -0,0 +1,205 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-utils/contracts/src/SafeMath.sol";
+
+
+contract DutchAuction is
+    SafeMath
+{
+    using LibBytes for bytes;
+
+    // solhint-disable var-name-mixedcase
+    IExchange internal EXCHANGE;
+
+    struct AuctionDetails {
+        uint256 beginTimeSeconds;    // Auction begin unix timestamp: sellOrder.makerAssetData
+        uint256 endTimeSeconds;      // Auction end unix timestamp: sellOrder.expiryTimeSeconds
+        uint256 beginAmount;         // Auction begin amount: sellOrder.makerAssetData
+        uint256 endAmount;           // Auction end amount: sellOrder.takerAssetAmount
+        uint256 currentAmount;       // Calculated amount given block.timestamp
+        uint256 currentTimeSeconds;  // block.timestamp
+    }
+
+    constructor (address _exchange)
+        public
+    {
+        EXCHANGE = IExchange(_exchange);
+    }
+
+    /// @dev Matches the buy and sell orders at an amount given the following: the current block time, the auction
+    ///      start time and the auction begin amount. The sell order is a an order at the lowest amount
+    ///      at the end of the auction. Excess from the match is transferred to the seller.
+    ///      Over time the price moves from beginAmount to endAmount given the current block.timestamp.
+    ///      sellOrder.expiryTimeSeconds is the end time of the auction.
+    ///      sellOrder.takerAssetAmount is the end amount of the auction (lowest possible amount).
+    ///      sellOrder.makerAssetData is the ABI encoded Asset Proxy data with the following data appended
+    ///      buyOrder.makerAssetData is the buyers bid on the auction, must meet the amount for the current block timestamp
+    ///      (uint256 beginTimeSeconds, uint256 beginAmount).
+    ///      This function reverts in the following scenarios:
+    ///         * Auction has not started (auctionDetails.currentTimeSeconds < auctionDetails.beginTimeSeconds)
+    ///         * Auction has expired (auctionDetails.endTimeSeconds < auctionDetails.currentTimeSeconds)
+    ///         * Amount is invalid: Buy order amount is too low (buyOrder.makerAssetAmount < auctionDetails.currentAmount)
+    ///         * Amount is invalid: Invalid begin amount (auctionDetails.beginAmount > auctionDetails.endAmount)
+    ///         * Any failure in the 0x Match Orders
+    /// @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
+    /// @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
+    /// @param buySignature Proof that order was created by the buyer.
+    /// @param sellSignature Proof that order was created by the seller.
+    /// @return matchedFillResults amounts filled and fees paid by maker and taker of matched orders.
+    function matchOrders(
+        LibOrder.Order memory buyOrder,
+        LibOrder.Order memory sellOrder,
+        bytes memory buySignature,
+        bytes memory sellSignature
+    )
+        public
+        returns (LibFillResults.MatchedFillResults memory matchedFillResults)
+    {
+        AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder);
+        // Ensure the auction has not yet started
+        require(
+            auctionDetails.currentTimeSeconds >= auctionDetails.beginTimeSeconds,
+            "AUCTION_NOT_STARTED"
+        );
+        // Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early
+        require(
+            sellOrder.expirationTimeSeconds > auctionDetails.currentTimeSeconds,
+            "AUCTION_EXPIRED"
+        );
+        // Validate the buyer amount is greater than the current auction amount
+        require(
+            buyOrder.makerAssetAmount >= auctionDetails.currentAmount,
+            "INVALID_AMOUNT"
+        );
+        // Match orders, maximally filling `buyOrder`
+        matchedFillResults = EXCHANGE.matchOrders(
+            buyOrder,
+            sellOrder,
+            buySignature,
+            sellSignature
+        );
+        // The difference in sellOrder.takerAssetAmount and current amount is given as spread to the matcher
+        // This may include additional spread from the buyOrder.makerAssetAmount and the currentAmount.
+        // e.g currentAmount is 30, sellOrder.takerAssetAmount is 10 and buyOrder.makerAssetamount is 40.
+        // 10 (40-30) is returned to the buyer, 20 (30-10) sent to the seller and 10 has previously
+        // been transferred to the seller during matchOrders
+        uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
+        if (leftMakerAssetSpreadAmount > 0) {
+            // ERC20 Asset data itself is encoded as follows:
+            //
+            // | Area     | Offset | Length  | Contents                            |
+            // |----------|--------|---------|-------------------------------------|
+            // | Header   | 0      | 4       | function selector                   |
+            // | Params   |        | 1 * 32  | function parameters:                |
+            // |          | 4      | 12      |   1. token address padding          |
+            // |          | 16     | 20      |   2. token address                  |
+            bytes memory assetData = sellOrder.takerAssetData;
+            address token = assetData.readAddress(16);
+            // Calculate the excess from the buy order. This can occur if the buyer sends in a higher
+            // amount than the calculated current amount
+            uint256 buyerExcessAmount = safeSub(buyOrder.makerAssetAmount, auctionDetails.currentAmount);
+            uint256 sellerExcessAmount = safeSub(leftMakerAssetSpreadAmount, buyerExcessAmount);
+            // Return the difference between auctionDetails.currentAmount and sellOrder.takerAssetAmount
+            // to the seller
+            if (sellerExcessAmount > 0) {
+                IERC20Token(token).transfer(sellOrder.makerAddress, sellerExcessAmount);
+            }
+            // Return the difference between buyOrder.makerAssetAmount and auctionDetails.currentAmount
+            // to the buyer
+            if (buyerExcessAmount > 0) {
+                IERC20Token(token).transfer(buyOrder.makerAddress, buyerExcessAmount);
+            }
+        }
+        return matchedFillResults;
+    }
+
+    /// @dev Calculates the Auction Details for the given order
+    /// @param order The sell order
+    /// @return AuctionDetails
+    function getAuctionDetails(
+        LibOrder.Order memory order
+    )
+        public
+        returns (AuctionDetails memory auctionDetails)
+    {
+        uint256 makerAssetDataLength = order.makerAssetData.length;
+        // It is unknown the encoded data of makerAssetData, we assume the last 64 bytes
+        // are the Auction Details encoding.
+        // Auction Details is encoded as follows:
+        //
+        // | Area     | Offset | Length  | Contents                            |
+        // |----------|--------|---------|-------------------------------------|
+        // | Params   |        | 2 * 32  | parameters:                         |
+        // |          | -64    | 32      |   1. auction begin unix timestamp   |
+        // |          | -32    | 32      |   2. auction begin begin amount     |
+        // ERC20 asset data length is 4+32, 64 for auction details results in min length 100
+        require(
+            makerAssetDataLength >= 100,
+            "INVALID_ASSET_DATA"
+        );
+        uint256 auctionBeginTimeSeconds = order.makerAssetData.readUint256(makerAssetDataLength - 64);
+        uint256 auctionBeginAmount = order.makerAssetData.readUint256(makerAssetDataLength - 32);
+        // Ensure the auction has a valid begin time
+        require(
+            order.expirationTimeSeconds > auctionBeginTimeSeconds,
+            "INVALID_BEGIN_TIME"
+        );
+        uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds;
+        // Ensure the auction goes from high to low
+        uint256 minAmount = order.takerAssetAmount;
+        require(
+            auctionBeginAmount > minAmount,
+            "INVALID_AMOUNT"
+        );
+        uint256 amountDelta = auctionBeginAmount-minAmount;
+        // solhint-disable-next-line not-rely-on-time
+        uint256 timestamp = block.timestamp;
+        auctionDetails.beginTimeSeconds = auctionBeginTimeSeconds;
+        auctionDetails.endTimeSeconds = order.expirationTimeSeconds;
+        auctionDetails.beginAmount = auctionBeginAmount;
+        auctionDetails.endAmount = minAmount;
+        auctionDetails.currentTimeSeconds = timestamp;
+
+        uint256 remainingDurationSeconds = order.expirationTimeSeconds-timestamp;
+        if (timestamp < auctionBeginTimeSeconds) {
+            // If the auction has not yet begun the current amount is the auctionBeginAmount
+            auctionDetails.currentAmount = auctionBeginAmount;
+        } else if (timestamp >= order.expirationTimeSeconds) {
+            // If the auction has ended the current amount is the minAmount.
+            // Auction end time is guaranteed by 0x Exchange due to the order expiration
+            auctionDetails.currentAmount = minAmount;
+        } else {
+            auctionDetails.currentAmount = safeAdd(
+                minAmount,
+                safeDiv(
+                    safeMul(remainingDurationSeconds, amountDelta),
+                    auctionDurationSeconds
+                )
+            );
+        }
+        return auctionDetails;
+    }
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol
new file mode 100644
index 000000000..65b36ea65
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol
@@ -0,0 +1,195 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-utils/contracts/src/Ownable.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
+import "./mixins/MAssets.sol";
+import "./libs/LibConstants.sol";
+
+
+contract MixinAssets is
+    MAssets,
+    Ownable,
+    LibConstants
+{
+    using LibBytes for bytes;
+
+    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
+    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+    ///      used to withdraw assets that were accidentally sent to this contract.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to withdraw.
+    function withdrawAsset(
+        bytes assetData,
+        uint256 amount
+    )
+        external
+        onlyOwner
+    {
+        transferAssetToSender(assetData, amount);
+    }
+
+    /// @dev Approves or disapproves an AssetProxy to spend asset.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveAssetProxy(
+        bytes assetData,
+        uint256 amount
+    )
+        external
+        onlyOwner
+    {
+        bytes4 proxyId = assetData.readBytes4(0);
+
+        if (proxyId == ERC20_DATA_ID) {
+            approveERC20Token(assetData, amount);
+        } else if (proxyId == ERC721_DATA_ID) {
+            approveERC721Token(assetData, amount);
+        } else {
+            revert("UNSUPPORTED_ASSET_PROXY");
+        }
+    }
+
+    /// @dev Transfers given amount of asset to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferAssetToSender(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        bytes4 proxyId = assetData.readBytes4(0);
+
+        if (proxyId == ERC20_DATA_ID) {
+            transferERC20Token(assetData, amount);
+        } else if (proxyId == ERC721_DATA_ID) {
+            transferERC721Token(assetData, amount);
+        } else {
+            revert("UNSUPPORTED_ASSET_PROXY");
+        }
+    }
+
+    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        // 4 byte id + 12 0 bytes before ABI encoded token address.
+        address token = assetData.readAddress(16);
+
+        // Transfer tokens.
+        // We do a raw call so we can check the success separate
+        // from the return data.
+        bool success = token.call(abi.encodeWithSelector(
+            ERC20_TRANSFER_SELECTOR,
+            msg.sender,
+            amount
+        ));
+        require(
+            success,
+            "TRANSFER_FAILED"
+        );
+        
+        // Check return data.
+        // If there is no return data, we assume the token incorrectly
+        // does not return a bool. In this case we expect it to revert
+        // on failure, which was handled above.
+        // If the token does return data, we require that it is a single
+        // value that evaluates to true.
+        assembly {
+            if returndatasize {
+                success := 0
+                if eq(returndatasize, 32) {
+                    // First 64 bytes of memory are reserved scratch space
+                    returndatacopy(0, 0, 32)
+                    success := mload(0)
+                }
+            }
+        }
+        require(
+            success,
+            "TRANSFER_FAILED"
+        );
+    }
+
+    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        require(
+            amount == 1,
+            "INVALID_AMOUNT"
+        );
+        // Decode asset data.
+        // 4 byte id + 12 0 bytes before ABI encoded token address.
+        address token = assetData.readAddress(16);
+        // 4 byte id + 32 byte ABI encoded token address before token id.
+        uint256 tokenId = assetData.readUint256(36);
+
+        // Perform transfer.
+        IERC721Token(token).transferFrom(
+            address(this),
+            msg.sender,
+            tokenId
+        );
+    }
+
+    /// @dev Sets approval for ERC20 AssetProxy.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        address token = assetData.readAddress(16);
+        require(
+            IERC20Token(token).approve(ERC20_PROXY_ADDRESS, amount),
+            "APPROVAL_FAILED"
+        );
+    }
+
+    /// @dev Sets approval for ERC721 AssetProxy.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal
+    {
+        address token = assetData.readAddress(16);
+        bool approval = amount >= 1;
+        IERC721Token(token).setApprovalForAll(ERC721_PROXY_ADDRESS, approval);
+    }
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol b/contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol
new file mode 100644
index 000000000..baf41f650
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol
@@ -0,0 +1,86 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibConstants.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
+import "@0x/contracts-utils/contracts/src/Ownable.sol";
+
+
+contract MixinMatchOrders is
+    Ownable,
+    LibConstants
+{
+    /// @dev Match two complementary orders that have a profitable spread.
+    ///      Each order is filled at their respective price point. However, the calculations are
+    ///      carried out as though the orders are both being filled at the right order's price point.
+    ///      The profit made by the left order is then used to fill the right order as much as possible.
+    ///      This results in a spread being taken in terms of both assets. The spread is held within this contract.
+    /// @param leftOrder First order to match.
+    /// @param rightOrder Second order to match.
+    /// @param leftSignature Proof that order was created by the left maker.
+    /// @param rightSignature Proof that order was created by the right maker.
+    function matchOrders(
+        LibOrder.Order memory leftOrder,
+        LibOrder.Order memory rightOrder,
+        bytes memory leftSignature,
+        bytes memory rightSignature
+    )
+        public
+        onlyOwner
+    {
+        // Match orders, maximally filling `leftOrder`
+        LibFillResults.MatchedFillResults memory matchedFillResults = EXCHANGE.matchOrders(
+            leftOrder,
+            rightOrder,
+            leftSignature,
+            rightSignature
+        );
+
+        uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
+        uint256 rightOrderTakerAssetAmount = rightOrder.takerAssetAmount;
+
+        // Do not attempt to call `fillOrder` if no spread was taken or `rightOrder` has been completely filled
+        if (leftMakerAssetSpreadAmount == 0 || matchedFillResults.right.takerAssetFilledAmount == rightOrderTakerAssetAmount) {
+            return;
+        }
+
+        // The `assetData` fields of the `rightOrder` could have been null for the `matchOrders` call. We reassign them before calling `fillOrder`.
+        rightOrder.makerAssetData = leftOrder.takerAssetData;
+        rightOrder.takerAssetData = leftOrder.makerAssetData;
+
+        // Query `rightOrder` info to check if it has been completely filled
+        // We need to make this check in case the `rightOrder` was partially filled before the `matchOrders` call
+        LibOrder.OrderInfo memory orderInfo = EXCHANGE.getOrderInfo(rightOrder);
+
+        // Do not attempt to call `fillOrder` if order has been completely filled
+        if (orderInfo.orderTakerAssetFilledAmount == rightOrderTakerAssetAmount) {
+            return;
+        }
+
+        // We do not need to pass in a signature since it was already validated in the `matchOrders` call
+        EXCHANGE.fillOrder(
+            rightOrder,
+            leftMakerAssetSpreadAmount,
+            ""
+        );
+    }
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol b/contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol
new file mode 100644
index 000000000..89a7f3c55
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol
@@ -0,0 +1,38 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-utils/contracts/src/Ownable.sol";
+import "./libs/LibConstants.sol";
+import "./MixinMatchOrders.sol";
+import "./MixinAssets.sol";
+
+
+// solhint-disable no-empty-blocks
+contract OrderMatcher is
+    MixinMatchOrders,
+    MixinAssets
+{
+    constructor (address _exchange)
+        public
+        LibConstants(_exchange)
+        Ownable()
+    {}
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol
new file mode 100644
index 000000000..9fcc0023a
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol
@@ -0,0 +1,43 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IAssets {
+
+    /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to 
+    ///      function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+    ///      used to withdraw assets that were accidentally sent to this contract.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to withdraw.
+    function withdrawAsset(
+        bytes assetData,
+        uint256 amount
+    )
+        external;
+
+    /// @dev Approves or disapproves an AssetProxy to spend asset.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveAssetProxy(
+        bytes assetData,
+        uint256 amount
+    )
+        external;
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol
new file mode 100644
index 000000000..7e4a88df8
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol
@@ -0,0 +1,43 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+
+
+contract IMatchOrders {
+
+    /// @dev Match two complementary orders that have a profitable spread.
+    ///      Each order is filled at their respective price point. However, the calculations are
+    ///      carried out as though the orders are both being filled at the right order's price point.
+    ///      The profit made by the left order is then used to fill the right order as much as possible.
+    ///      This results in a spread being taken in terms of both assets. The spread is held within this contract.
+    /// @param leftOrder First order to match.
+    /// @param rightOrder Second order to match.
+    /// @param leftSignature Proof that order was created by the left maker.
+    /// @param rightSignature Proof that order was created by the right maker.
+    function matchOrders(
+        LibOrder.Order memory leftOrder,
+        LibOrder.Order memory rightOrder,
+        bytes memory leftSignature,
+        bytes memory rightSignature
+    )
+        public;
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol
new file mode 100644
index 000000000..5e5125dea
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol
@@ -0,0 +1,31 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contract-utils/contracts/src/interfaces/IOwnable.sol";
+import "./IMatchOrders.sol";
+import "./IAssets.sol";
+
+
+// solhint-disable no-empty-blocks
+contract IOrderMatcher is
+    IOwnable,
+    IMatchOrders,
+    IAssets
+{}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol b/contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol
new file mode 100644
index 000000000..77b72398f
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol
@@ -0,0 +1,56 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+
+
+contract LibConstants {
+
+    // bytes4(keccak256("transfer(address,uint256)"))
+    bytes4 constant internal ERC20_TRANSFER_SELECTOR = 0xa9059cbb;
+    // bytes4(keccak256("ERC20Token(address)"))
+    bytes4 constant internal ERC20_DATA_ID = 0xf47261b0;
+    // bytes4(keccak256("ERC721Token(address,uint256)"))
+    bytes4 constant internal ERC721_DATA_ID = 0x02571792;
+ 
+     // solhint-disable var-name-mixedcase
+    IExchange internal EXCHANGE;
+    address internal ERC20_PROXY_ADDRESS;
+    address internal ERC721_PROXY_ADDRESS;
+    // solhint-enable var-name-mixedcase
+
+    constructor (address _exchange)
+        public
+    {
+        EXCHANGE = IExchange(_exchange);
+
+        ERC20_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
+        require(
+            ERC20_PROXY_ADDRESS != address(0),
+            "UNREGISTERED_ASSET_PROXY"
+        );
+
+        ERC721_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC721_DATA_ID);
+        require(
+            ERC721_PROXY_ADDRESS != address(0),
+            "UNREGISTERED_ASSET_PROXY"
+        );
+    }
+}
diff --git a/contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol
new file mode 100644
index 000000000..6ea7a3290
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol
@@ -0,0 +1,71 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../interfaces/IAssets.sol";
+
+
+contract MAssets is
+    IAssets
+{
+    /// @dev Transfers given amount of asset to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferAssetToSender(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to transfer to sender.
+    function transferERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Sets approval for ERC20 AssetProxy.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveERC20Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+
+    /// @dev Sets approval for ERC721 AssetProxy.
+    /// @param assetData Byte array encoded for the respective asset proxy.
+    /// @param amount Amount of asset to approve for respective proxy.
+    function approveERC721Token(
+        bytes memory assetData,
+        uint256 amount
+    )
+        internal;
+}
diff --git a/contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol b/contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol
new file mode 100644
index 000000000..3297a980f
--- /dev/null
+++ b/contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol
@@ -0,0 +1,218 @@
+/*
+
+  Copyright 2018 ZeroEx Intl.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+
+
+contract OrderValidator {
+
+    using LibBytes for bytes;
+
+    bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
+    bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
+
+    struct TraderInfo {
+        uint256 makerBalance;       // Maker's balance of makerAsset
+        uint256 makerAllowance;     // Maker's allowance to corresponding AssetProxy
+        uint256 takerBalance;       // Taker's balance of takerAsset
+        uint256 takerAllowance;     // Taker's allowance to corresponding AssetProxy
+        uint256 makerZrxBalance;    // Maker's balance of ZRX
+        uint256 makerZrxAllowance;  // Maker's allowance of ZRX to ERC20Proxy
+        uint256 takerZrxBalance;    // Taker's balance of ZRX
+        uint256 takerZrxAllowance;  // Taker's allowance of ZRX to ERC20Proxy
+    }
+
+    // solhint-disable var-name-mixedcase
+    IExchange internal EXCHANGE;
+    bytes internal ZRX_ASSET_DATA;
+    // solhint-enable var-name-mixedcase
+
+    constructor (address _exchange, bytes memory _zrxAssetData)
+        public
+    {
+        EXCHANGE = IExchange(_exchange);
+        ZRX_ASSET_DATA = _zrxAssetData;
+    }
+
+    /// @dev Fetches information for order and maker/taker of order.
+    /// @param order The order structure.
+    /// @param takerAddress Address that will be filling the order.
+    /// @return OrderInfo and TraderInfo instances for given order.
+    function getOrderAndTraderInfo(LibOrder.Order memory order, address takerAddress)
+        public
+        view
+        returns (LibOrder.OrderInfo memory orderInfo, TraderInfo memory traderInfo)
+    {
+        orderInfo = EXCHANGE.getOrderInfo(order);
+        traderInfo = getTraderInfo(order, takerAddress);
+        return (orderInfo, traderInfo);
+    }
+
+    /// @dev Fetches information for all passed in orders and the makers/takers of each order.
+    /// @param orders Array of order specifications.
+    /// @param takerAddresses Array of taker addresses corresponding to each order.
+    /// @return Arrays of OrderInfo and TraderInfo instances that correspond to each order.
+    function getOrdersAndTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
+        public
+        view
+        returns (LibOrder.OrderInfo[] memory ordersInfo, TraderInfo[] memory tradersInfo)
+    {
+        ordersInfo = EXCHANGE.getOrdersInfo(orders);
+        tradersInfo = getTradersInfo(orders, takerAddresses);
+        return (ordersInfo, tradersInfo);
+    }
+
+    /// @dev Fetches balance and allowances for maker and taker of order.
+    /// @param order The order structure.
+    /// @param takerAddress Address that will be filling the order.
+    /// @return Balances and allowances of maker and taker of order.
+    function getTraderInfo(LibOrder.Order memory order, address takerAddress)
+        public
+        view
+        returns (TraderInfo memory traderInfo)
+    {
+        (traderInfo.makerBalance, traderInfo.makerAllowance) = getBalanceAndAllowance(order.makerAddress, order.makerAssetData);
+        (traderInfo.takerBalance, traderInfo.takerAllowance) = getBalanceAndAllowance(takerAddress, order.takerAssetData);
+        bytes memory zrxAssetData = ZRX_ASSET_DATA;
+        (traderInfo.makerZrxBalance, traderInfo.makerZrxAllowance) = getBalanceAndAllowance(order.makerAddress, zrxAssetData);
+        (traderInfo.takerZrxBalance, traderInfo.takerZrxAllowance) = getBalanceAndAllowance(takerAddress, zrxAssetData);
+        return traderInfo;
+    }
+
+    /// @dev Fetches balances and allowances of maker and taker for each provided order.
+    /// @param orders Array of order specifications.
+    /// @param takerAddresses Array of taker addresses corresponding to each order.
+    /// @return Array of balances and allowances for maker and taker of each order.
+    function getTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
+        public
+        view
+        returns (TraderInfo[] memory)
+    {
+        uint256 ordersLength = orders.length;
+        TraderInfo[] memory tradersInfo = new TraderInfo[](ordersLength);
+        for (uint256 i = 0; i != ordersLength; i++) {
+            tradersInfo[i] = getTraderInfo(orders[i], takerAddresses[i]);
+        }
+        return tradersInfo;
+    }
+
+    /// @dev Fetches token balances and allowances of an address to given assetProxy. Supports ERC20 and ERC721.
+    /// @param target Address to fetch balances and allowances of.
+    /// @param assetData Encoded data that can be decoded by a specified proxy contract when transferring asset.
+    /// @return Balance of asset and allowance set to given proxy of asset.
+    ///         For ERC721 tokens, these values will always be 1 or 0.
+    function getBalanceAndAllowance(address target, bytes memory assetData)
+        public
+        view
+        returns (uint256 balance, uint256 allowance)
+    {
+        bytes4 assetProxyId = assetData.readBytes4(0);
+        address token = assetData.readAddress(16);
+        address assetProxy = EXCHANGE.getAssetProxy(assetProxyId);
+
+        if (assetProxyId == ERC20_DATA_ID) {
+            // Query balance
+            balance = IERC20Token(token).balanceOf(target);
+
+            // Query allowance
+            allowance = IERC20Token(token).allowance(target, assetProxy);
+        } else if (assetProxyId == ERC721_DATA_ID) {
+            uint256 tokenId = assetData.readUint256(36);
+
+            // Query owner of tokenId
+            address owner = getERC721TokenOwner(token, tokenId);
+
+            // Set balance to 1 if tokenId is owned by target
+            balance = target == owner ? 1 : 0;
+
+            // Check if ERC721Proxy is approved to spend tokenId
+            bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy);
+            
+            // Set alowance to 1 if ERC721Proxy is approved to spend tokenId
+            allowance = isApproved ? 1 : 0;
+        } else {
+            revert("UNSUPPORTED_ASSET_PROXY");
+        }
+        return (balance, allowance);
+    }
+
+    /// @dev Fetches token balances and allowances of an address for each given assetProxy. Supports ERC20 and ERC721.
+    /// @param target Address to fetch balances and allowances of.
+    /// @param assetData Array of encoded byte arrays that can be decoded by a specified proxy contract when transferring asset.
+    /// @return Balances and allowances of assets.
+    ///         For ERC721 tokens, these values will always be 1 or 0.
+    function getBalancesAndAllowances(address target, bytes[] memory assetData)
+        public
+        view
+        returns (uint256[] memory, uint256[] memory)
+    {
+        uint256 length = assetData.length;
+        uint256[] memory balances = new uint256[](length);
+        uint256[] memory allowances = new uint256[](length);
+        for (uint256 i = 0; i != length; i++) {
+            (balances[i], allowances[i]) = getBalanceAndAllowance(target, assetData[i]);
+        }
+        return (balances, allowances);
+    }
+
+    /// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token.
+    /// @param token Address of ERC721 token.
+    /// @param tokenId The identifier for the specific NFT.
+    /// @return Owner of tokenId or null address if unowned.
+    function getERC721TokenOwner(address token, uint256 tokenId)
+        public
+        view
+        returns (address owner)
+    {
+        assembly {
+            // load free memory pointer
+            let cdStart := mload(64)
+
+            // bytes4(keccak256(ownerOf(uint256))) = 0x6352211e
+            mstore(cdStart, 0x6352211e00000000000000000000000000000000000000000000000000000000)
+            mstore(add(cdStart, 4), tokenId)
+
+            // staticcall `ownerOf(tokenId)`
+            // `ownerOf` will revert if tokenId is not owned
+            let success := staticcall(
+                gas,      // forward all gas
+                token,    // call token contract
+                cdStart,  // start of calldata
+                36,       // length of input is 36 bytes
+                cdStart,  // write output over input
+                32        // size of output is 32 bytes
+            )
+
+            // Success implies that tokenId is owned
+            // Copy owner from return data if successful
+            if success {
+                owner := mload(cdStart)
+            }    
+        }
+
+        // Owner initialized to address(0), no need to modify if call is unsuccessful
+        return owner;
+    }
+}
diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json
index 4395d1fac..e4adf6350 100644
--- a/contracts/extensions/package.json
+++ b/contracts/extensions/package.json
@@ -32,7 +32,7 @@
         "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
     },
     "config": {
-        "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder|OrderMatcher|OrderValidator).json"
+        "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|OrderMatcher|OrderValidator).json"
     },
     "repository": {
         "type": "git",
diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts
index 329d0f3f3..f2aee4908 100644
--- a/contracts/extensions/src/artifacts/index.ts
+++ b/contracts/extensions/src/artifacts/index.ts
@@ -2,14 +2,12 @@ import { ContractArtifact } from 'ethereum-types';
 
 import * as BalanceThresholdFilter from '../../generated-artifacts/BalanceThresholdFilter.json';
 import * as DutchAuction from '../../generated-artifacts/DutchAuction.json';
-import * as Forwarder from '../../generated-artifacts/Forwarder.json';
 import * as OrderMatcher from '../../generated-artifacts/OrderMatcher.json';
 import * as OrderValidator from '../../generated-artifacts/OrderValidator.json';
 
 export const artifacts = {
     BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact,
     DutchAuction: DutchAuction as ContractArtifact,
-    Forwarder: Forwarder as ContractArtifact,
     OrderMatcher: OrderMatcher as ContractArtifact,
     OrderValidator: OrderValidator as ContractArtifact,
 };
diff --git a/contracts/extensions/src/wrappers/index.ts b/contracts/extensions/src/wrappers/index.ts
index 65aec3ccd..eed198524 100644
--- a/contracts/extensions/src/wrappers/index.ts
+++ b/contracts/extensions/src/wrappers/index.ts
@@ -1,5 +1,4 @@
 export * from '../../generated-wrappers/balance_threshold_filter';
 export * from '../../generated-wrappers/dutch_auction';
-export * from '../../generated-wrappers/forwarder';
 export * from '../../generated-wrappers/order_matcher';
 export * from '../../generated-wrappers/order_validator';
diff --git a/contracts/extensions/test/forwarder.ts b/contracts/extensions/test/forwarder.ts
deleted file mode 100644
index 24c219563..000000000
--- a/contracts/extensions/test/forwarder.ts
+++ /dev/null
@@ -1,1279 +0,0 @@
-import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
-import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
-import { DummyERC721TokenContract } from '@0x/contracts-erc721';
-import { artifacts as exchangeArtifacts, ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange';
-import {
-    chaiSetup,
-    constants,
-    ContractName,
-    ERC20BalancesByOwner,
-    expectContractCreationFailedAsync,
-    expectTransactionFailedAsync,
-    OrderFactory,
-    provider,
-    sendTransactionResult,
-    txDefaults,
-    web3Wrapper,
-} from '@0x/contracts-test-utils';
-import { BlockchainLifecycle } from '@0x/dev-utils';
-import { assetDataUtils } from '@0x/order-utils';
-import { RevertReason, SignedOrder } from '@0x/types';
-import { BigNumber } from '@0x/utils';
-import { Web3Wrapper } from '@0x/web3-wrapper';
-import * as chai from 'chai';
-import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
-
-import { artifacts, ForwarderContract, ForwarderWrapper } from '../src';
-
-chaiSetup.configure();
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-const DECIMALS_DEFAULT = 18;
-const MAX_WETH_FILL_PERCENTAGE = 95;
-
-describe(ContractName.Forwarder, () => {
-    let makerAddress: string;
-    let owner: string;
-    let takerAddress: string;
-    let feeRecipientAddress: string;
-    let defaultMakerAssetAddress: string;
-    let zrxAssetData: string;
-    let wethAssetData: string;
-
-    let weth: DummyERC20TokenContract;
-    let zrxToken: DummyERC20TokenContract;
-    let erc20TokenA: DummyERC20TokenContract;
-    let erc721Token: DummyERC721TokenContract;
-    let forwarderContract: ForwarderContract;
-    let wethContract: WETH9Contract;
-    let forwarderWrapper: ForwarderWrapper;
-    let exchangeWrapper: ExchangeWrapper;
-
-    let orderWithoutFee: SignedOrder;
-    let orderWithFee: SignedOrder;
-    let feeOrder: SignedOrder;
-    let orderFactory: OrderFactory;
-    let erc20Wrapper: ERC20Wrapper;
-    let erc20Balances: ERC20BalancesByOwner;
-    let tx: TransactionReceiptWithDecodedLogs;
-
-    let erc721MakerAssetIds: BigNumber[];
-    let takerEthBalanceBefore: BigNumber;
-    let feePercentage: BigNumber;
-    let gasPrice: BigNumber;
-
-    before(async () => {
-        await blockchainLifecycle.startAsync();
-        const accounts = await web3Wrapper.getAvailableAddressesAsync();
-        const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
-
-        const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
-        const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
-        gasPrice = new BigNumber(transaction.gasPrice);
-
-        const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
-        erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
-
-        const numDummyErc20ToDeploy = 3;
-        [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
-            numDummyErc20ToDeploy,
-            constants.DUMMY_TOKEN_DECIMALS,
-        );
-        const erc20Proxy = await erc20Wrapper.deployProxyAsync();
-        await erc20Wrapper.setBalancesAndAllowancesAsync();
-
-        [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
-        const erc721Proxy = await erc721Wrapper.deployProxyAsync();
-        await erc721Wrapper.setBalancesAndAllowancesAsync();
-        const erc721Balances = await erc721Wrapper.getBalancesAsync();
-        erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
-
-        wethContract = await WETH9Contract.deployFrom0xArtifactAsync(erc20Artifacts.WETH9, provider, txDefaults);
-        weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider);
-        erc20Wrapper.addDummyTokenContract(weth);
-
-        wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address);
-        zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
-        const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
-            exchangeArtifacts.Exchange,
-            provider,
-            txDefaults,
-            zrxAssetData,
-        );
-        exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider);
-        await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
-        await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
-
-        await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
-            from: owner,
-        });
-        await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
-            from: owner,
-        });
-
-        defaultMakerAssetAddress = erc20TokenA.address;
-        const defaultTakerAssetAddress = wethContract.address;
-        const defaultOrderParams = {
-            exchangeAddress: exchangeInstance.address,
-            makerAddress,
-            feeRecipientAddress,
-            makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
-            takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
-            makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
-            takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
-            makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
-        };
-        const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
-        orderFactory = new OrderFactory(privateKey, defaultOrderParams);
-
-        const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync(
-            artifacts.Forwarder,
-            provider,
-            txDefaults,
-            exchangeInstance.address,
-            zrxAssetData,
-            wethAssetData,
-        );
-        forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider);
-        forwarderWrapper = new ForwarderWrapper(forwarderContract, provider);
-        const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18);
-        await web3Wrapper.awaitTransactionSuccessAsync(
-            await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount),
-        );
-        erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address);
-    });
-    after(async () => {
-        await blockchainLifecycle.revertAsync();
-    });
-    beforeEach(async () => {
-        await blockchainLifecycle.startAsync();
-        erc20Balances = await erc20Wrapper.getBalancesAsync();
-        takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-        orderWithoutFee = await orderFactory.newSignedOrderAsync();
-        feeOrder = await orderFactory.newSignedOrderAsync({
-            makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
-            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-        });
-        orderWithFee = await orderFactory.newSignedOrderAsync({
-            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-        });
-    });
-    afterEach(async () => {
-        await blockchainLifecycle.revertAsync();
-    });
-
-    describe('constructor', () => {
-        it('should revert if assetProxy is unregistered', async () => {
-            const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
-                exchangeArtifacts.Exchange,
-                provider,
-                txDefaults,
-                zrxAssetData,
-            );
-            return expectContractCreationFailedAsync(
-                (ForwarderContract.deployFrom0xArtifactAsync(
-                    artifacts.Forwarder,
-                    provider,
-                    txDefaults,
-                    exchangeInstance.address,
-                    zrxAssetData,
-                    wethAssetData,
-                ) as any) as sendTransactionResult,
-                RevertReason.UnregisteredAssetProxy,
-            );
-        });
-    });
-    describe('marketSellOrdersWithEth without extra fees', () => {
-        it('should fill a single order', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
-                ethValue,
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const makerAssetFillAmount = primaryTakerAssetFillAmount
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fill multiple orders', async () => {
-            const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
-            const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
-                ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
-            );
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
-                ethValue,
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const firstTakerAssetFillAmount = ordersWithoutFee[0].takerAssetAmount;
-            const secondTakerAssetFillAmount = primaryTakerAssetFillAmount.minus(firstTakerAssetFillAmount);
-
-            const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
-                ordersWithoutFee[1].makerAssetAmount
-                    .times(secondTakerAssetFillAmount)
-                    .dividedToIntegerBy(ordersWithoutFee[1].takerAssetAmount),
-            );
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fill the order and pay ZRX fees from a single feeOrder', async () => {
-            const ordersWithFee = [orderWithFee];
-            const feeOrders = [feeOrder];
-            const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
-                ethValue,
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const makerAssetFillAmount = primaryTakerAssetFillAmount
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            const feeAmount = ForwarderWrapper.getPercentageOfValue(
-                orderWithFee.takerFee.dividedToIntegerBy(2),
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
-            const totalEthSpent = primaryTakerAssetFillAmount
-                .plus(wethSpentOnFeeOrders)
-                .plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fill the orders and pay ZRX from multiple feeOrders', async () => {
-            const ordersWithFee = [orderWithFee];
-            const ethValue = orderWithFee.takerAssetAmount;
-            const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
-            const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
-            const takerAssetAmount = feeOrder.takerAssetAmount
-                .times(makerAssetAmount)
-                .dividedToIntegerBy(feeOrder.makerAssetAmount);
-
-            const firstFeeOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetData,
-                makerAssetAmount,
-                takerAssetAmount,
-            });
-            const secondFeeOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetData,
-                makerAssetAmount,
-                takerAssetAmount,
-            });
-            const feeOrders = [firstFeeOrder, secondFeeOrder];
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
-                ethValue,
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const makerAssetFillAmount = primaryTakerAssetFillAmount
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            const feeAmount = ForwarderWrapper.getPercentageOfValue(orderWithFee.takerFee, MAX_WETH_FILL_PERCENTAGE);
-            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
-            const totalEthSpent = primaryTakerAssetFillAmount
-                .plus(wethSpentOnFeeOrders)
-                .plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fill the order when token is ZRX with fees', async () => {
-            orderWithFee = await orderFactory.newSignedOrderAsync({
-                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
-                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            });
-            const ordersWithFee = [orderWithFee];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
-            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
-            const takerFeePaid = orderWithFee.takerFee.dividedToIntegerBy(2);
-            const makerFeePaid = orderWithFee.makerFee.dividedToIntegerBy(2);
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount).minus(makerFeePaid),
-            );
-            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount).minus(takerFeePaid),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(ethValue),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[forwarderContract.address][zrxToken.address],
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = orderWithoutFee.takerAssetAmount.times(2);
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const totalEthSpent = orderWithoutFee.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-        });
-        it('should revert if ZRX cannot be fully repurchased', async () => {
-            orderWithFee = await orderFactory.newSignedOrderAsync({
-                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
-            });
-            const ordersWithFee = [orderWithFee];
-            feeOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
-                makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            });
-            const feeOrders = [feeOrder];
-            const ethValue = orderWithFee.takerAssetAmount;
-            return expectTransactionFailedAsync(
-                forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
-                    value: ethValue,
-                    from: takerAddress,
-                }),
-                RevertReason.CompleteFillFailed,
-            );
-        });
-        it('should not fill orders with different makerAssetData than the first order', async () => {
-            const makerAssetId = erc721MakerAssetIds[0];
-            const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber(1),
-                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
-            });
-            const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
-            const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
-
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const totalEthSpent = erc20SignedOrder.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-        });
-    });
-    describe('marketSellOrdersWithEth with extra fees', () => {
-        it('should fill the order and send fee to feeRecipient', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
-
-            const baseFeePercentage = 2;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
-            const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
-            tx = await forwarderWrapper.marketSellOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-                { feePercentage, feeRecipient: feeRecipientAddress },
-            );
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
-                ethValue,
-                MAX_WETH_FILL_PERCENTAGE,
-            );
-            const makerAssetFillAmount = primaryTakerAssetFillAmount
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fail if the fee is set too high', async () => {
-            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
-            const baseFeePercentage = 6;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage);
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            await expectTransactionFailedAsync(
-                forwarderWrapper.marketSellOrdersWithEthAsync(
-                    ordersWithoutFee,
-                    feeOrders,
-                    { from: takerAddress, value: ethValue, gasPrice },
-                    { feePercentage, feeRecipient: feeRecipientAddress },
-                ),
-                RevertReason.FeePercentageTooLarge,
-            );
-        });
-        it('should fail if there is not enough ETH remaining to pay the fee', async () => {
-            const ethValue = orderWithoutFee.takerAssetAmount.div(2);
-            const baseFeePercentage = 5;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
-            const ordersWithFee = [orderWithFee];
-            const feeOrders = [feeOrder];
-            await expectTransactionFailedAsync(
-                forwarderWrapper.marketSellOrdersWithEthAsync(
-                    ordersWithFee,
-                    feeOrders,
-                    { from: takerAddress, value: ethValue, gasPrice },
-                    { feePercentage, feeRecipient: feeRecipientAddress },
-                ),
-                RevertReason.InsufficientEthRemaining,
-            );
-        });
-    });
-    describe('marketBuyOrdersWithEth without extra fees', () => {
-        it('should buy the exact amount of makerAsset in a single order', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should buy the exact amount of makerAsset in multiple orders', async () => {
-            const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
-            const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
-                ordersWithoutFee[1].makerAssetAmount.dividedToIntegerBy(2),
-            );
-            const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
-                ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
-            );
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should buy the exact amount of makerAsset and return excess ETH', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount;
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ethValue.dividedToIntegerBy(2);
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should buy the exact amount of makerAsset and pay ZRX from feeOrders', async () => {
-            const ordersWithFee = [orderWithFee];
-            const feeOrders = [feeOrder];
-            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithFee.takerAssetAmount;
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
-            const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
-            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
-            const totalEthSpent = primaryTakerAssetFillAmount
-                .plus(wethSpentOnFeeOrders)
-                .plus(gasPrice.times(tx.gasUsed));
-
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should buy slightly greater than makerAssetAmount when buying ZRX', async () => {
-            orderWithFee = await orderFactory.newSignedOrderAsync({
-                makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
-                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            });
-            const ordersWithFee = [orderWithFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithFee.takerAssetAmount;
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders(
-                makerAssetFillAmount,
-                ordersWithFee,
-            );
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            const makerAssetFilledAmount = orderWithFee.makerAssetAmount
-                .times(primaryTakerAssetFillAmount)
-                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
-            const takerFeePaid = orderWithFee.takerFee
-                .times(primaryTakerAssetFillAmount)
-                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
-            const makerFeePaid = orderWithFee.makerFee
-                .times(primaryTakerAssetFillAmount)
-                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
-            const totalZrxPurchased = makerAssetFilledAmount.minus(takerFeePaid);
-            // Up to 1 wei worth of ZRX will be overbought per order
-            const maxOverboughtZrx = new BigNumber(1)
-                .times(orderWithFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithFee.takerAssetAmount);
-
-            expect(totalZrxPurchased).to.be.bignumber.gte(makerAssetFillAmount);
-            expect(totalZrxPurchased).to.be.bignumber.lte(makerAssetFillAmount.plus(maxOverboughtZrx));
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFilledAmount).minus(makerFeePaid),
-            );
-            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[forwarderContract.address][zrxToken.address],
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(4);
-            return expectTransactionFailedAsync(
-                forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                    value: ethValue,
-                    from: takerAddress,
-                }),
-                RevertReason.CompleteFillFailed,
-            );
-        });
-        it('should buy an ERC721 asset from a single order', async () => {
-            const makerAssetId = erc721MakerAssetIds[0];
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber(1),
-                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = new BigNumber(1);
-            const ethValue = orderWithFee.takerAssetAmount;
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                from: takerAddress,
-                value: ethValue,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            expect(newOwner).to.be.bignumber.equal(takerAddress);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should revert if buying an ERC721 asset when later orders contain different makerAssetData', async () => {
-            const makerAssetId = erc721MakerAssetIds[0];
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber(1),
-                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
-            });
-            const differentMakerAssetDataOrder = await orderFactory.newSignedOrderAsync();
-            const ordersWithoutFee = [orderWithoutFee, differentMakerAssetDataOrder];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = new BigNumber(1).plus(differentMakerAssetDataOrder.makerAssetAmount);
-            const ethValue = orderWithFee.takerAssetAmount;
-            return expectTransactionFailedAsync(
-                forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                    value: ethValue,
-                    from: takerAddress,
-                }),
-                RevertReason.CompleteFillFailed,
-            );
-        });
-        it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
-            const makerAssetId = erc721MakerAssetIds[0];
-            orderWithFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber(1),
-                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
-                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            });
-            const ordersWithFee = [orderWithFee];
-            const feeOrders = [feeOrder];
-            const makerAssetFillAmount = orderWithFee.makerAssetAmount;
-            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
-            const feeAmount = orderWithFee.takerFee;
-            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
-            const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
-
-            expect(newOwner).to.be.bignumber.equal(takerAddress);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => {
-            const makerAssetId = erc721MakerAssetIds[0];
-            orderWithFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber(1),
-                makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
-                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
-            });
-            const ordersWithFee = [orderWithFee];
-            const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
-            const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
-            const takerAssetAmount = feeOrder.takerAssetAmount
-                .times(makerAssetAmount)
-                .dividedToIntegerBy(feeOrder.makerAssetAmount);
-
-            const firstFeeOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetData,
-                makerAssetAmount,
-                takerAssetAmount,
-            });
-            const secondFeeOrder = await orderFactory.newSignedOrderAsync({
-                makerAssetData,
-                makerAssetAmount,
-                takerAssetAmount,
-            });
-            const feeOrders = [firstFeeOrder, secondFeeOrder];
-
-            const makerAssetFillAmount = orderWithFee.makerAssetAmount;
-            const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
-            const feeAmount = orderWithFee.takerFee;
-            const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
-            const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
-
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
-
-            expect(newOwner).to.be.bignumber.equal(takerAddress);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => {
-            // The 0x Protocol contracts round the exchange rate in favor of the Maker.
-            // In this case, the taker must round up how much they're going to spend, which
-            // in turn increases the amount of MakerAsset being purchased.
-            // Example:
-            //  The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T.
-            //  For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset.
-            //  To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset.
-            //  However, the Taker can only spend whole units.
-            //  Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset.
-            //  Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset.
-            //
-            //  The forwarding contract will opt for the second option, which overbuys, to ensure the taker
-            //  receives at least the amount of MakerAsset they requested.
-            //
-            // Construct test case using values from example above
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber('30'),
-                takerAssetAmount: new BigNumber('20'),
-                makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
-                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
-                makerFee: new BigNumber(0),
-                takerFee: new BigNumber(0),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const desiredMakerAssetFillAmount = new BigNumber('5');
-            const makerAssetFillAmount = new BigNumber('6');
-            const ethValue = new BigNumber('4');
-            // Execute test case
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                desiredMakerAssetFillAmount,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-            );
-            // Fetch end balances and construct expected outputs
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            // Validate test case
-            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => {
-            // See the test case above for a detailed description of this case.
-            // The difference here is that the MakerAsset is ZRX. We expect the same result as above,
-            // but this tests a different code path.
-            //
-            // Construct test case using values from example above
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber('30'),
-                takerAssetAmount: new BigNumber('20'),
-                makerAssetData: zrxAssetData,
-                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
-                makerFee: new BigNumber(0),
-                takerFee: new BigNumber(0),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const desiredMakerAssetFillAmount = new BigNumber('5');
-            const makerAssetFillAmount = new BigNumber('6');
-            const ethValue = new BigNumber('4');
-            // Execute test case
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                desiredMakerAssetFillAmount,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-            );
-            // Fetch end balances and construct expected outputs
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            // Validate test case
-            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => {
-            // Order taken from a transaction on mainnet that failed due to a rounding error.
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber('268166666666666666666'),
-                takerAssetAmount: new BigNumber('219090625878836371'),
-                makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
-                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
-                makerFee: new BigNumber(0),
-                takerFee: new BigNumber(0),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            // The taker will receive more than the desired amount of makerAsset due to rounding
-            const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
-            const ethValue = new BigNumber('4084971271824171');
-            const makerAssetFillAmount = ethValue
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            // Execute test case
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                desiredMakerAssetFillAmount,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-            );
-            // Fetch end balances and construct expected outputs
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            // Validate test case
-            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => {
-            // Order taken from a transaction on mainnet that failed due to a rounding error.
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber('268166666666666666666'),
-                takerAssetAmount: new BigNumber('219090625878836371'),
-                makerAssetData: zrxAssetData,
-                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
-                makerFee: new BigNumber(0),
-                takerFee: new BigNumber(0),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            // The taker will receive more than the desired amount of makerAsset due to rounding
-            const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
-            const ethValue = new BigNumber('4084971271824171');
-            const makerAssetFillAmount = ethValue
-                .times(orderWithoutFee.makerAssetAmount)
-                .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
-            // Execute test case
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                desiredMakerAssetFillAmount,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-            );
-            // Fetch end balances and construct expected outputs
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            // Validate test case
-            expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => {
-            // An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1.
-            // Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1
-            // We have since changed this to be Ceil(desiredMakerAmount * exchangeRate)
-            // These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer.
-            //
-            // This test verifies that `ceil` is sufficient:
-            //  Let TakerAssetAmount = MakerAssetAmount * 2
-            //  -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2
-            //  .: desiredMakerAmount * exchangeRate is an integer.
-            //
-            // Construct test case using values from example above
-            orderWithoutFee = await orderFactory.newSignedOrderAsync({
-                makerAssetAmount: new BigNumber('30'),
-                takerAssetAmount: new BigNumber('60'),
-                makerAssetData: zrxAssetData,
-                takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
-                makerFee: new BigNumber(0),
-                takerFee: new BigNumber(0),
-            });
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = new BigNumber('5');
-            const ethValue = new BigNumber('10');
-            // Execute test case
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
-                value: ethValue,
-                from: takerAddress,
-            });
-            // Fetch end balances and construct expected outputs
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            const primaryTakerAssetFillAmount = ethValue;
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
-            // Validate test case
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-    });
-    describe('marketBuyOrdersWithEth with extra fees', () => {
-        it('should buy an asset and send fee to feeRecipient', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount;
-
-            const baseFeePercentage = 2;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
-            const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
-            tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
-                ordersWithoutFee,
-                feeOrders,
-                makerAssetFillAmount,
-                {
-                    value: ethValue,
-                    from: takerAddress,
-                },
-                { feePercentage, feeRecipient: feeRecipientAddress },
-            );
-            const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
-            const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
-            const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-
-            const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
-            const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
-            const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
-
-            expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
-            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
-            expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
-            );
-            expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
-            );
-            expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
-                erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
-            );
-            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-            expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
-                constants.ZERO_AMOUNT,
-            );
-            expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
-        });
-        it('should fail if the fee is set too high', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount;
-
-            const baseFeePercentage = 6;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
-            await expectTransactionFailedAsync(
-                forwarderWrapper.marketBuyOrdersWithEthAsync(
-                    ordersWithoutFee,
-                    feeOrders,
-                    makerAssetFillAmount,
-                    {
-                        value: ethValue,
-                        from: takerAddress,
-                    },
-                    { feePercentage, feeRecipient: feeRecipientAddress },
-                ),
-                RevertReason.FeePercentageTooLarge,
-            );
-        });
-        it('should fail if there is not enough ETH remaining to pay the fee', async () => {
-            const ordersWithoutFee = [orderWithoutFee];
-            const feeOrders: SignedOrder[] = [];
-            const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
-            const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
-
-            const baseFeePercentage = 2;
-            feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
-            await expectTransactionFailedAsync(
-                forwarderWrapper.marketBuyOrdersWithEthAsync(
-                    ordersWithoutFee,
-                    feeOrders,
-                    makerAssetFillAmount,
-                    {
-                        value: ethValue,
-                        from: takerAddress,
-                    },
-                    { feePercentage, feeRecipient: feeRecipientAddress },
-                ),
-                RevertReason.InsufficientEthRemaining,
-            );
-        });
-    });
-    describe('withdrawAsset', () => {
-        it('should allow owner to withdraw ERC20 tokens', async () => {
-            const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address];
-            await forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: owner });
-            const newBalances = await erc20Wrapper.getBalancesAsync();
-            expect(newBalances[owner][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[owner][zrxToken.address].plus(zrxWithdrawAmount),
-            );
-            expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
-                erc20Balances[forwarderContract.address][zrxToken.address].minus(zrxWithdrawAmount),
-            );
-        });
-        it('should revert if not called by owner', async () => {
-            const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address];
-            await expectTransactionFailedAsync(
-                forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: makerAddress }),
-                RevertReason.OnlyContractOwner,
-            );
-        });
-    });
-});
-// tslint:disable:max-file-line-count
-// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/extensions/test/utils/forwarder_wrapper.ts b/contracts/extensions/test/utils/forwarder_wrapper.ts
deleted file mode 100644
index 207106852..000000000
--- a/contracts/extensions/test/utils/forwarder_wrapper.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
-import { artifacts as erc721Artifacts } from '@0x/contracts-erc721';
-import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
-import { constants, formatters, LogDecoder, MarketSellOrders } from '@0x/contracts-test-utils';
-import { SignedOrder } from '@0x/types';
-import { BigNumber } from '@0x/utils';
-import { Web3Wrapper } from '@0x/web3-wrapper';
-import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types';
-import * as _ from 'lodash';
-
-import { ForwarderContract } from '../../generated-wrappers/forwarder';
-import { artifacts } from '../../src/artifacts';
-
-export class ForwarderWrapper {
-    private readonly _web3Wrapper: Web3Wrapper;
-    private readonly _forwarderContract: ForwarderContract;
-    private readonly _logDecoder: LogDecoder;
-    public static getPercentageOfValue(value: BigNumber, percentage: number): BigNumber {
-        const numerator = constants.PERCENTAGE_DENOMINATOR.times(percentage).dividedToIntegerBy(100);
-        const newValue = value.times(numerator).dividedToIntegerBy(constants.PERCENTAGE_DENOMINATOR);
-        return newValue;
-    }
-    public static getWethForFeeOrders(feeAmount: BigNumber, feeOrders: SignedOrder[]): BigNumber {
-        let wethAmount = new BigNumber(0);
-        let remainingFeeAmount = feeAmount;
-        _.forEach(feeOrders, feeOrder => {
-            const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
-            if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
-                wethAmount = wethAmount.plus(
-                    feeOrder.takerAssetAmount
-                        .times(remainingFeeAmount)
-                        .dividedBy(feeAvailable)
-                        .integerValue(BigNumber.ROUND_CEIL),
-                );
-                remainingFeeAmount = new BigNumber(0);
-            } else if (!remainingFeeAmount.isZero()) {
-                wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
-                remainingFeeAmount = remainingFeeAmount.minus(feeAvailable);
-            }
-        });
-        return wethAmount;
-    }
-    private static _createOptimizedOrders(signedOrders: SignedOrder[]): MarketSellOrders {
-        _.forEach(signedOrders, (signedOrder, index) => {
-            signedOrder.takerAssetData = constants.NULL_BYTES;
-            if (index > 0) {
-                signedOrder.makerAssetData = constants.NULL_BYTES;
-            }
-        });
-        const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
-        return params;
-    }
-    private static _createOptimizedZrxOrders(signedOrders: SignedOrder[]): MarketSellOrders {
-        _.forEach(signedOrders, signedOrder => {
-            signedOrder.makerAssetData = constants.NULL_BYTES;
-            signedOrder.takerAssetData = constants.NULL_BYTES;
-        });
-        const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
-        return params;
-    }
-    constructor(contractInstance: ForwarderContract, provider: Provider) {
-        this._forwarderContract = contractInstance;
-        this._web3Wrapper = new Web3Wrapper(provider);
-        this._logDecoder = new LogDecoder(this._web3Wrapper, {
-            ...artifacts,
-            ...erc20Artifacts,
-            ...erc721Artifacts,
-            ...exchangeArtifacts,
-        });
-    }
-    public async marketSellOrdersWithEthAsync(
-        orders: SignedOrder[],
-        feeOrders: SignedOrder[],
-        txData: TxDataPayable,
-        opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
-    ): Promise<TransactionReceiptWithDecodedLogs> {
-        const params = ForwarderWrapper._createOptimizedOrders(orders);
-        const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
-        const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
-        const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
-        const txHash = await this._forwarderContract.marketSellOrdersWithEth.sendTransactionAsync(
-            params.orders,
-            params.signatures,
-            feeParams.orders,
-            feeParams.signatures,
-            feePercentage,
-            feeRecipient,
-            txData,
-        );
-        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
-        return tx;
-    }
-    public async marketBuyOrdersWithEthAsync(
-        orders: SignedOrder[],
-        feeOrders: SignedOrder[],
-        makerAssetFillAmount: BigNumber,
-        txData: TxDataPayable,
-        opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
-    ): Promise<TransactionReceiptWithDecodedLogs> {
-        const params = ForwarderWrapper._createOptimizedOrders(orders);
-        const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
-        const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
-        const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
-        const txHash = await this._forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync(
-            params.orders,
-            makerAssetFillAmount,
-            params.signatures,
-            feeParams.orders,
-            feeParams.signatures,
-            feePercentage,
-            feeRecipient,
-            txData,
-        );
-        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
-        return tx;
-    }
-    public async withdrawAssetAsync(
-        assetData: string,
-        amount: BigNumber,
-        txData: TxDataPayable,
-    ): Promise<TransactionReceiptWithDecodedLogs> {
-        const txHash = await this._forwarderContract.withdrawAsset.sendTransactionAsync(assetData, amount, txData);
-        const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
-        return tx;
-    }
-}
diff --git a/contracts/extensions/test/utils/index.ts b/contracts/extensions/test/utils/index.ts
index d5d529c11..6ba9a2300 100644
--- a/contracts/extensions/test/utils/index.ts
+++ b/contracts/extensions/test/utils/index.ts
@@ -1,3 +1,2 @@
 export * from './balance_threshold_wrapper';
 export * from './dutch_auction_test_wrapper';
-export * from './forwarder_wrapper';
diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json
index ed9b4fbe1..0c06c6902 100644
--- a/contracts/extensions/tsconfig.json
+++ b/contracts/extensions/tsconfig.json
@@ -9,7 +9,6 @@
     "files": [
         "./generated-artifacts/BalanceThresholdFilter.json",
         "./generated-artifacts/DutchAuction.json",
-        "./generated-artifacts/Forwarder.json",
         "./generated-artifacts/OrderMatcher.json",
         "./generated-artifacts/OrderValidator.json"
     ],
diff --git a/contracts/utils/package.json b/contracts/utils/package.json
index a4800fec9..0b14ec7cf 100644
--- a/contracts/utils/package.json
+++ b/contracts/utils/package.json
@@ -51,6 +51,7 @@
         "@0x/tslint-config": "^2.0.2",
         "@types/lodash": "4.14.104",
         "@types/node": "*",
+        "@types/bn.js": "^4.11.0",
         "chai": "^4.0.1",
         "chai-as-promised": "^7.1.0",
         "chai-bignumber": "^3.0.0",
@@ -70,6 +71,7 @@
         "@0x/typescript-typings": "^3.0.8",
         "@0x/utils": "^3.0.1",
         "@0x/web3-wrapper": "^3.2.4",
+        "bn.js": "^4.11.8",
         "ethereum-types": "^1.1.6",
         "ethereumjs-util": "^5.1.1",
         "lodash": "^4.17.5"
diff --git a/package.json b/package.json
index 2e201f467..62b4c1c36 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
         "lint": "wsrun lint $PKG --fast-exit --parallel --exclude-missing"
     },
     "config": {
-        "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-exchange @0x/contracts-exchange-libs @0x/contracts-extensions @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils",
+        "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-extensions @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils",
         "mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic",
         "packagesWithDocPages": "0x.js connect json-schemas subproviders web3-wrapper contract-wrappers order-utils order-watcher sol-compiler sol-coverage sol-profiler sol-trace ethereum-types asset-buyer migrations"
     },
-- 
cgit v1.2.3