aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers/src
diff options
context:
space:
mode:
authorAlex Browne <stephenalexbrowne@gmail.com>2018-08-14 09:42:09 +0800
committerAlex Browne <stephenalexbrowne@gmail.com>2018-08-14 09:42:09 +0800
commit88766a02c7e6688e72d5c4c69ce68028b322f154 (patch)
treefa06552a80249e7998691b64df6b3b2827f9f947 /packages/contract-wrappers/src
parent8162394797342cef268cc8072fc860326974e269 (diff)
parentfadd292ecf367e42154856509d0ea0c20b23f2f1 (diff)
downloaddexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.gz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.bz2
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.lz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.xz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.zst
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.zip
Merge branch 'development'
Diffstat (limited to 'packages/contract-wrappers/src')
-rw-r--r--packages/contract-wrappers/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts11
-rw-r--r--packages/contract-wrappers/src/artifacts.ts25
-rw-r--r--packages/contract-wrappers/src/contract_wrappers.ts144
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts212
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts85
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts445
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts85
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts472
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts225
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts1123
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts220
-rw-r--r--packages/contract-wrappers/src/globals.d.ts6
-rw-r--r--packages/contract-wrappers/src/index.ts82
-rw-r--r--packages/contract-wrappers/src/monorepo_scripts/postpublish.ts8
-rw-r--r--packages/contract-wrappers/src/schemas/contract_wrappers_config_schema.ts5
-rw-r--r--packages/contract-wrappers/src/schemas/contract_wrappers_private_network_config_schema.ts36
-rw-r--r--packages/contract-wrappers/src/schemas/contract_wrappers_public_network_config_schema.ts44
-rw-r--r--packages/contract-wrappers/src/schemas/method_opts_schema.ts7
-rw-r--r--packages/contract-wrappers/src/schemas/order_tx_opts_schema.ts8
-rw-r--r--packages/contract-wrappers/src/schemas/tx_opts_schema.ts8
-rw-r--r--packages/contract-wrappers/src/types.ts190
-rw-r--r--packages/contract-wrappers/src/utils/assert.ts90
-rw-r--r--packages/contract-wrappers/src/utils/calldata_optimization_utils.ts44
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts15
-rw-r--r--packages/contract-wrappers/src/utils/decorators.ts96
-rw-r--r--packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts111
-rw-r--r--packages/contract-wrappers/src/utils/filter_utils.ts87
-rw-r--r--packages/contract-wrappers/src/utils/utils.ts11
28 files changed, 3895 insertions, 0 deletions
diff --git a/packages/contract-wrappers/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts b/packages/contract-wrappers/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts
new file mode 100644
index 000000000..1f139f1ef
--- /dev/null
+++ b/packages/contract-wrappers/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts
@@ -0,0 +1,11 @@
+import { BigNumber } from '@0xproject/utils';
+
+export abstract class AbstractBalanceAndProxyAllowanceLazyStore {
+ public abstract async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
+ public abstract async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
+ public abstract setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void;
+ public abstract deleteBalance(tokenAddress: string, userAddress: string): void;
+ public abstract setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void;
+ public abstract deleteProxyAllowance(tokenAddress: string, userAddress: string): void;
+ public abstract deleteAll(): void;
+}
diff --git a/packages/contract-wrappers/src/artifacts.ts b/packages/contract-wrappers/src/artifacts.ts
new file mode 100644
index 000000000..2481b311a
--- /dev/null
+++ b/packages/contract-wrappers/src/artifacts.ts
@@ -0,0 +1,25 @@
+import { ContractArtifact } from '@0xproject/sol-compiler';
+
+import * as DummyERC20Token from './artifacts/DummyERC20Token.json';
+import * as DummyERC721Token from './artifacts/DummyERC721Token.json';
+import * as ERC20Proxy from './artifacts/ERC20Proxy.json';
+import * as ERC20Token from './artifacts/ERC20Token.json';
+import * as ERC721Proxy from './artifacts/ERC721Proxy.json';
+import * as ERC721Token from './artifacts/ERC721Token.json';
+import * as Exchange from './artifacts/Exchange.json';
+import * as Forwarder from './artifacts/Forwarder.json';
+import * as EtherToken from './artifacts/WETH9.json';
+import * as ZRXToken from './artifacts/ZRXToken.json';
+
+export const artifacts = {
+ ZRXToken: (ZRXToken as any) as ContractArtifact,
+ DummyERC20Token: (DummyERC20Token as any) as ContractArtifact,
+ DummyERC721Token: (DummyERC721Token as any) as ContractArtifact,
+ ERC20Token: (ERC20Token as any) as ContractArtifact,
+ ERC721Token: (ERC721Token as any) as ContractArtifact,
+ Exchange: (Exchange as any) as ContractArtifact,
+ EtherToken: (EtherToken as any) as ContractArtifact,
+ ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
+ ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
+ Forwarder: (Forwarder as any) as ContractArtifact,
+};
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts
new file mode 100644
index 000000000..4277a0746
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers.ts
@@ -0,0 +1,144 @@
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { constants } from './utils/constants';
+
+import { artifacts } from './artifacts';
+import { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper';
+import { ERC20TokenWrapper } from './contract_wrappers/erc20_token_wrapper';
+import { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
+import { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper';
+import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
+import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
+import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
+import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema';
+import { contractWrappersPrivateNetworkConfigSchema } from './schemas/contract_wrappers_private_network_config_schema';
+import { contractWrappersPublicNetworkConfigSchema } from './schemas/contract_wrappers_public_network_config_schema';
+import { ContractWrappersConfig } from './types';
+import { assert } from './utils/assert';
+/**
+ * The ContractWrappers class contains smart contract wrappers helpful when building on 0x protocol.
+ */
+export class ContractWrappers {
+ /**
+ * An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract.
+ */
+ public exchange: ExchangeWrapper;
+ /**
+ * An instance of the ERC20TokenWrapper class containing methods for interacting with any ERC20 token smart contract.
+ */
+ public erc20Token: ERC20TokenWrapper;
+ /**
+ * An instance of the ERC721TokenWrapper class containing methods for interacting with any ERC721 token smart contract.
+ */
+ public erc721Token: ERC721TokenWrapper;
+ /**
+ * An instance of the EtherTokenWrapper class containing methods for interacting with the
+ * wrapped ETH ERC20 token smart contract.
+ */
+ public etherToken: EtherTokenWrapper;
+ /**
+ * An instance of the ERC20ProxyWrapper class containing methods for interacting with the
+ * erc20Proxy smart contract.
+ */
+ public erc20Proxy: ERC20ProxyWrapper;
+ /**
+ * An instance of the ERC721ProxyWrapper class containing methods for interacting with the
+ * erc721Proxy smart contract.
+ */
+ public erc721Proxy: ERC721ProxyWrapper;
+ /**
+ * An instance of the ForwarderWrapper class containing methods for interacting with any Forwarder smart contract.
+ */
+ public forwarder: ForwarderWrapper;
+
+ private _web3Wrapper: Web3Wrapper;
+ /**
+ * Instantiates a new ContractWrappers instance.
+ * @param provider The Provider instance you would like the 0x.js library to use for interacting with
+ * the Ethereum network.
+ * @param config The configuration object. Look up the type for the description.
+ * @return An instance of the ContractWrappers class.
+ */
+ constructor(provider: Provider, config: ContractWrappersConfig) {
+ assert.isWeb3Provider('provider', provider);
+ assert.doesConformToSchema('config', config, ContractWrappersConfigSchema, [
+ contractWrappersPrivateNetworkConfigSchema,
+ contractWrappersPublicNetworkConfigSchema,
+ ]);
+ const artifactJSONs = _.values(artifacts);
+ const abiArrays = _.map(artifactJSONs, artifact => artifact.compilerOutput.abi);
+ const txDefaults = {
+ gasPrice: config.gasPrice,
+ };
+ this._web3Wrapper = new Web3Wrapper(provider, txDefaults);
+ _.forEach(abiArrays, abi => {
+ this._web3Wrapper.abiDecoder.addABI(abi);
+ });
+ const blockPollingIntervalMs = _.isUndefined(config.blockPollingIntervalMs)
+ ? constants.DEFAULT_BLOCK_POLLING_INTERVAL
+ : config.blockPollingIntervalMs;
+ this.erc20Proxy = new ERC20ProxyWrapper(this._web3Wrapper, config.networkId, config.erc20ProxyContractAddress);
+ this.erc721Proxy = new ERC721ProxyWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ config.erc721ProxyContractAddress,
+ );
+ this.erc20Token = new ERC20TokenWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ this.erc20Proxy,
+ blockPollingIntervalMs,
+ );
+ this.erc721Token = new ERC721TokenWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ this.erc721Proxy,
+ blockPollingIntervalMs,
+ );
+ this.etherToken = new EtherTokenWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ this.erc20Token,
+ blockPollingIntervalMs,
+ );
+ this.exchange = new ExchangeWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ config.exchangeContractAddress,
+ config.zrxContractAddress,
+ blockPollingIntervalMs,
+ );
+ this.forwarder = new ForwarderWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ config.forwarderContractAddress,
+ config.zrxContractAddress,
+ );
+ }
+ /**
+ * Sets a new web3 provider for 0x.js. Updating the provider will stop all
+ * subscriptions so you will need to re-subscribe to all events relevant to your app after this call.
+ * @param provider The Web3Provider you would like the 0x.js library to use from now on.
+ * @param networkId The id of the network your provider is connected to
+ */
+ public setProvider(provider: Provider, networkId: number): void {
+ this._web3Wrapper.setProvider(provider);
+ (this.exchange as any)._invalidateContractInstances();
+ (this.exchange as any)._setNetworkId(networkId);
+ (this.erc20Token as any)._invalidateContractInstances();
+ (this.erc20Token as any)._setNetworkId(networkId);
+ (this.erc20Proxy as any)._invalidateContractInstance();
+ (this.erc20Proxy as any)._setNetworkId(networkId);
+ (this.etherToken as any)._invalidateContractInstance();
+ (this.etherToken as any)._setNetworkId(networkId);
+ }
+ /**
+ * Get the provider instance currently used by 0x.js
+ * @return Web3 provider instance
+ */
+ public getProvider(): Provider {
+ return this._web3Wrapper.getProvider();
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts
new file mode 100644
index 000000000..daf70253a
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts
@@ -0,0 +1,212 @@
+import { ContractArtifact } from '@0xproject/sol-compiler';
+import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { BlockParamLiteral, ContractAbi, FilterObject, LogEntry, LogWithDecodedArgs, RawLog } from 'ethereum-types';
+import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream';
+import * as _ from 'lodash';
+
+import {
+ BlockRange,
+ ContractEventArgs,
+ ContractEvents,
+ ContractWrappersError,
+ EventCallback,
+ IndexedFilterValues,
+} from '../types';
+import { constants } from '../utils/constants';
+import { filterUtils } from '../utils/filter_utils';
+
+const CONTRACT_NAME_TO_NOT_FOUND_ERROR: {
+ [contractName: string]: ContractWrappersError;
+} = {
+ ZRX: ContractWrappersError.ZRXContractDoesNotExist,
+ EtherToken: ContractWrappersError.EtherTokenContractDoesNotExist,
+ ERC20Token: ContractWrappersError.ERC20TokenContractDoesNotExist,
+ ERC20Proxy: ContractWrappersError.ERC20ProxyContractDoesNotExist,
+ ERC721Token: ContractWrappersError.ERC721TokenContractDoesNotExist,
+ ERC721Proxy: ContractWrappersError.ERC721ProxyContractDoesNotExist,
+ Exchange: ContractWrappersError.ExchangeContractDoesNotExist,
+};
+
+export abstract class ContractWrapper {
+ public abstract abi: ContractAbi;
+ protected _web3Wrapper: Web3Wrapper;
+ protected _networkId: number;
+ private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined;
+ private _blockPollingIntervalMs: number;
+ private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer;
+ private _filters: { [filterToken: string]: FilterObject };
+ private _filterCallbacks: {
+ [filterToken: string]: EventCallback<ContractEventArgs>;
+ };
+ private _onLogAddedSubscriptionToken: string | undefined;
+ private _onLogRemovedSubscriptionToken: string | undefined;
+ private static _onBlockAndLogStreamerError(isVerbose: boolean, err: Error): void {
+ // Since Blockstream errors are all recoverable, we simply log them if the verbose
+ // config is passed in.
+ if (isVerbose) {
+ logUtils.warn(err);
+ }
+ }
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, blockPollingIntervalMs?: number) {
+ this._web3Wrapper = web3Wrapper;
+ this._networkId = networkId;
+ this._blockPollingIntervalMs = _.isUndefined(blockPollingIntervalMs)
+ ? constants.DEFAULT_BLOCK_POLLING_INTERVAL
+ : blockPollingIntervalMs;
+ this._filters = {};
+ this._filterCallbacks = {};
+ this._blockAndLogStreamerIfExists = undefined;
+ this._onLogAddedSubscriptionToken = undefined;
+ this._onLogRemovedSubscriptionToken = undefined;
+ }
+ protected _unsubscribeAll(): void {
+ const filterTokens = _.keys(this._filterCallbacks);
+ _.each(filterTokens, filterToken => {
+ this._unsubscribe(filterToken);
+ });
+ }
+ protected _unsubscribe(filterToken: string, err?: Error): void {
+ if (_.isUndefined(this._filters[filterToken])) {
+ throw new Error(ContractWrappersError.SubscriptionNotFound);
+ }
+ if (!_.isUndefined(err)) {
+ const callback = this._filterCallbacks[filterToken];
+ callback(err, undefined);
+ }
+ delete this._filters[filterToken];
+ delete this._filterCallbacks[filterToken];
+ if (_.isEmpty(this._filters)) {
+ this._stopBlockAndLogStream();
+ }
+ }
+ protected _subscribe<ArgsType extends ContractEventArgs>(
+ address: string,
+ eventName: ContractEvents,
+ indexFilterValues: IndexedFilterValues,
+ abi: ContractAbi,
+ callback: EventCallback<ArgsType>,
+ isVerbose: boolean = false,
+ ): string {
+ const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
+ if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
+ this._startBlockAndLogStream(isVerbose);
+ }
+ const filterToken = filterUtils.generateUUID();
+ this._filters[filterToken] = filter;
+ this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>;
+ return filterToken;
+ }
+ protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
+ address: string,
+ eventName: ContractEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ abi: ContractAbi,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange);
+ const logs = await this._web3Wrapper.getLogsAsync(filter);
+ const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
+ return logsWithDecodedArguments;
+ }
+ protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
+ log: LogEntry,
+ ): LogWithDecodedArgs<ArgsType> | RawLog {
+ const abiDecoder = new AbiDecoder([this.abi]);
+ const logWithDecodedArgs = abiDecoder.tryToDecodeLogOrNoop(log);
+ return logWithDecodedArgs;
+ }
+ protected async _getContractAbiAndAddressFromArtifactsAsync(
+ artifact: ContractArtifact,
+ addressIfExists?: string,
+ ): Promise<[ContractAbi, string]> {
+ let contractAddress: string;
+ if (_.isUndefined(addressIfExists)) {
+ if (_.isUndefined(artifact.networks[this._networkId])) {
+ throw new Error(ContractWrappersError.ContractNotDeployedOnNetwork);
+ }
+ contractAddress = artifact.networks[this._networkId].address.toLowerCase();
+ } else {
+ contractAddress = addressIfExists;
+ }
+ const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
+ if (!doesContractExist) {
+ throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contractName]);
+ }
+ const abiAndAddress: [ContractAbi, string] = [artifact.compilerOutput.abi, contractAddress];
+ return abiAndAddress;
+ }
+ protected _getContractAddress(artifact: ContractArtifact, addressIfExists?: string): string {
+ if (_.isUndefined(addressIfExists)) {
+ const contractAddress = artifact.networks[this._networkId].address;
+ if (_.isUndefined(contractAddress)) {
+ throw new Error(ContractWrappersError.ExchangeContractDoesNotExist);
+ }
+ return contractAddress;
+ } else {
+ return addressIfExists;
+ }
+ }
+ private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: LogEntry): void {
+ _.forEach(this._filters, (filter: FilterObject, filterToken: string) => {
+ if (filterUtils.matchesFilter(log, filter)) {
+ const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
+ const logEvent = {
+ log: decodedLog,
+ isRemoved,
+ };
+ this._filterCallbacks[filterToken](null, logEvent);
+ }
+ });
+ }
+ private _startBlockAndLogStream(isVerbose: boolean): void {
+ if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
+ throw new Error(ContractWrappersError.SubscriptionAlreadyPresent);
+ }
+ this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
+ this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
+ this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
+ ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
+ );
+ const catchAllLogFilter = {};
+ this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
+ this._blockAndLogStreamIntervalIfExists = intervalUtils.setAsyncExcludingInterval(
+ this._reconcileBlockAsync.bind(this),
+ this._blockPollingIntervalMs,
+ ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
+ );
+ let isRemoved = false;
+ this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded(
+ this._onLogStateChanged.bind(this, isRemoved),
+ );
+ isRemoved = true;
+ this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved(
+ this._onLogStateChanged.bind(this, isRemoved),
+ );
+ }
+ // HACK: This should be a package-scoped method (which doesn't exist in TS)
+ // We don't want this method available in the public interface for all classes
+ // who inherit from ContractWrapper, and it is only used by the internal implementation
+ // of those higher classes.
+ // tslint:disable-next-line:no-unused-variable
+ private _setNetworkId(networkId: number): void {
+ this._networkId = networkId;
+ }
+ private _stopBlockAndLogStream(): void {
+ if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
+ throw new Error(ContractWrappersError.SubscriptionNotFound);
+ }
+ this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string);
+ this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string);
+ intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamIntervalIfExists as NodeJS.Timer);
+ delete this._blockAndLogStreamerIfExists;
+ }
+ private async _reconcileBlockAsync(): Promise<void> {
+ const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
+ // We need to coerce to Block type cause Web3.Block includes types for mempool blocks
+ if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
+ // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
+ await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block);
+ }
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts
new file mode 100644
index 000000000..821d1a8a2
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts
@@ -0,0 +1,85 @@
+import { AssetProxyId } from '@0xproject/types';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { assert } from '../utils/assert';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC20ProxyContract } from './generated/erc20_proxy';
+
+/**
+ * This class includes the functionality related to interacting with the ERC20Proxy contract.
+ */
+export class ERC20ProxyWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi;
+ private _erc20ProxyContractIfExists?: ERC20ProxyContract;
+ private _contractAddressIfExists?: string;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ }
+ /**
+ * Get the 4 bytes ID of this asset proxy
+ * @return Proxy id
+ */
+ public async getProxyIdAsync(): Promise<AssetProxyId> {
+ const ERC20ProxyContractInstance = await this._getERC20ProxyContractAsync();
+ const proxyId = (await ERC20ProxyContractInstance.getProxyId.callAsync()) as AssetProxyId;
+ return proxyId;
+ }
+ /**
+ * Check if the Exchange contract address is authorized by the ERC20Proxy contract.
+ * @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
+ * @return Whether the exchangeContractAddress is authorized.
+ */
+ public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
+ assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
+ const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase();
+ const ERC20ProxyContractInstance = await this._getERC20ProxyContractAsync();
+ const isAuthorized = await ERC20ProxyContractInstance.authorized.callAsync(normalizedExchangeContractAddress);
+ return isAuthorized;
+ }
+ /**
+ * Get the list of all Exchange contract addresses authorized by the ERC20Proxy contract.
+ * @return The list of authorized addresses.
+ */
+ public async getAuthorizedAddressesAsync(): Promise<string[]> {
+ const ERC20ProxyContractInstance = await this._getERC20ProxyContractAsync();
+ const authorizedAddresses = await ERC20ProxyContractInstance.getAuthorizedAddresses.callAsync();
+ return authorizedAddresses;
+ }
+ /**
+ * Retrieves the Ethereum address of the ERC20Proxy contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the ERC20Proxy contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ERC20Proxy, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstance(): void {
+ delete this._erc20ProxyContractIfExists;
+ }
+ private async _getERC20ProxyContractAsync(): Promise<ERC20ProxyContract> {
+ if (!_.isUndefined(this._erc20ProxyContractIfExists)) {
+ return this._erc20ProxyContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.ERC20Proxy,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new ERC20ProxyContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._erc20ProxyContractIfExists = contractInstance;
+ return this._erc20ProxyContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts
new file mode 100644
index 000000000..17bda5085
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts
@@ -0,0 +1,445 @@
+import { schemas } from '@0xproject/json-schemas';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { methodOptsSchema } from '../schemas/method_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import {
+ BlockRange,
+ ContractWrappersError,
+ EventCallback,
+ IndexedFilterValues,
+ MethodOpts,
+ TransactionOpts,
+} from '../types';
+import { assert } from '../utils/assert';
+import { constants } from '../utils/constants';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC20ProxyWrapper } from './erc20_proxy_wrapper';
+import { ERC20TokenContract, ERC20TokenEventArgs, ERC20TokenEvents } from './generated/erc20_token';
+
+const removeUndefinedProperties = _.pickBy;
+
+/**
+ * This class includes all the functionality related to interacting with ERC20 token contracts.
+ * All ERC20 method calls are supported, along with some convenience methods for getting/setting allowances
+ * to the 0x ERC20 Proxy smart contract.
+ */
+export class ERC20TokenWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.ERC20Token.compilerOutput.abi;
+ public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ private _tokenContractsByAddress: { [address: string]: ERC20TokenContract };
+ private _erc20ProxyWrapper: ERC20ProxyWrapper;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ erc20ProxyWrapper: ERC20ProxyWrapper,
+ blockPollingIntervalMs?: number,
+ ) {
+ super(web3Wrapper, networkId, blockPollingIntervalMs);
+ this._tokenContractsByAddress = {};
+ this._erc20ProxyWrapper = erc20ProxyWrapper;
+ }
+ /**
+ * Retrieves an owner's ERC20 token balance.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The owner's ERC20 token balance in base units.
+ */
+ public async getBalanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<BigNumber> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, methodOpts.defaultBlock);
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ balance = new BigNumber(balance);
+ return balance;
+ }
+ /**
+ * Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address.
+ * Equivalent to the ERC20 spec method `approve`.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
+ * for spenderAddress.
+ * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
+ * @param amountInBaseUnits The allowance amount you would like to set.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ spenderAddress: string,
+ amountInBaseUnits: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
+ assert.isETHAddressHex('spenderAddress', spenderAddress);
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedSpenderAddress = spenderAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const txHash = await tokenContract.approve.sendTransactionAsync(
+ normalizedSpenderAddress,
+ amountInBaseUnits,
+ removeUndefinedProperties({
+ from: normalizedOwnerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
+ * Equivalent to the ERC20 spec method `approve`.
+ * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
+ * allowances set to the max amount (e.g ZRX, WETH)
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
+ * for spenderAddress.
+ * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setUnlimitedAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ spenderAddress: string,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const txHash = await this.setAllowanceAsync(
+ tokenAddress,
+ ownerAddress,
+ spenderAddress,
+ this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ txOpts,
+ );
+ return txHash;
+ }
+ /**
+ * Retrieves the owners allowance in baseUnits set to the spender's address.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
+ * you would like to retrieve.
+ * @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
+ * @param methodOpts Optional arguments this method accepts.
+ */
+ public async getAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ spenderAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<BigNumber> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('spenderAddress', spenderAddress);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedSpenderAddress = spenderAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ let allowanceInBaseUnits = await tokenContract.allowance.callAsync(
+ normalizedOwnerAddress,
+ normalizedSpenderAddress,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
+ return allowanceInBaseUnits;
+ }
+ /**
+ * Retrieves the owner's allowance in baseUnits set to the 0x proxy contract.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving.
+ * @param methodOpts Optional arguments this method accepts.
+ */
+ public async getProxyAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<BigNumber> {
+ const proxyAddress = this._erc20ProxyWrapper.getContractAddress();
+ const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
+ return allowanceInBaseUnits;
+ }
+ /**
+ * Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf
+ * of an owner address.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
+ * for the Proxy contract.
+ * @param amountInBaseUnits The allowance amount specified in baseUnits.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setProxyAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ amountInBaseUnits: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const proxyAddress = this._erc20ProxyWrapper.getContractAddress();
+ const txHash = await this.setAllowanceAsync(
+ tokenAddress,
+ ownerAddress,
+ proxyAddress,
+ amountInBaseUnits,
+ txOpts,
+ );
+ return txHash;
+ }
+ /**
+ * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
+ * of an owner address.
+ * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
+ * allowances set to the max amount (e.g ZRX, WETH)
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
+ * for the Proxy contract.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setUnlimitedProxyAllowanceAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const txHash = await this.setProxyAllowanceAsync(
+ tokenAddress,
+ ownerAddress,
+ this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ txOpts,
+ );
+ return txHash;
+ }
+ /**
+ * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param fromAddress The hex encoded user Ethereum address that will send the funds.
+ * @param toAddress The hex encoded user Ethereum address that will receive the funds.
+ * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async transferAsync(
+ tokenAddress: string,
+ fromAddress: string,
+ toAddress: string,
+ amountInBaseUnits: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
+ assert.isETHAddressHex('toAddress', toAddress);
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedFromAddress = fromAddress.toLowerCase();
+ const normalizedToAddress = toAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress);
+ if (fromAddressBalance.lessThan(amountInBaseUnits)) {
+ throw new Error(ContractWrappersError.InsufficientBalanceForTransfer);
+ }
+
+ const txHash = await tokenContract.transfer.sendTransactionAsync(
+ normalizedToAddress,
+ amountInBaseUnits,
+ removeUndefinedProperties({
+ from: normalizedFromAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
+ * Requires the fromAddress to have sufficient funds and to have approved an allowance of
+ * `amountInBaseUnits` to `senderAddress`.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param fromAddress The hex encoded user Ethereum address whose funds are being sent.
+ * @param toAddress The hex encoded user Ethereum address that will receive the funds.
+ * @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The
+ * `fromAddress` must have set an allowance to the `senderAddress`
+ * before this call.
+ * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async transferFromAsync(
+ tokenAddress: string,
+ fromAddress: string,
+ toAddress: string,
+ senderAddress: string,
+ amountInBaseUnits: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('fromAddress', fromAddress);
+ assert.isETHAddressHex('toAddress', toAddress);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedToAddress = toAddress.toLowerCase();
+ const normalizedFromAddress = fromAddress.toLowerCase();
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const fromAddressAllowance = await this.getAllowanceAsync(
+ normalizedTokenAddress,
+ normalizedFromAddress,
+ normalizedSenderAddress,
+ );
+ if (fromAddressAllowance.lessThan(amountInBaseUnits)) {
+ throw new Error(ContractWrappersError.InsufficientAllowanceForTransfer);
+ }
+
+ const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress);
+ if (fromAddressBalance.lessThan(amountInBaseUnits)) {
+ throw new Error(ContractWrappersError.InsufficientBalanceForTransfer);
+ }
+
+ const txHash = await tokenContract.transferFrom.sendTransactionAsync(
+ normalizedFromAddress,
+ normalizedToAddress,
+ amountInBaseUnits,
+ removeUndefinedProperties({
+ from: normalizedSenderAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Subscribe to an event type emitted by the Token contract.
+ * @param tokenAddress The hex encoded address where the ERC20 token is deployed.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
+ * @param callback Callback that gets called when a log is added/removed
+ * @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered)
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends ERC20TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC20TokenEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ isVerbose: boolean = false,
+ ): string {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.doesBelongToStringEnum('eventName', eventName, ERC20TokenEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.ERC20Token.compilerOutput.abi,
+ callback,
+ isVerbose,
+ );
+ return subscriptionToken;
+ }
+ /**
+ * Cancel a subscription
+ * @param subscriptionToken Subscription token returned by `subscribe()`
+ */
+ public unsubscribe(subscriptionToken: string): void {
+ assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken);
+ this._unsubscribe(subscriptionToken);
+ }
+ /**
+ * Cancels all existing subscriptions
+ */
+ public unsubscribeAll(): void {
+ super._unsubscribeAll();
+ }
+ /**
+ * Gets historical logs without creating a subscription
+ * @param tokenAddress An address of the token that emitted the logs.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param blockRange Block range to get logs from.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
+ * @return Array of logs that match the parameters
+ */
+ public async getLogsAsync<ArgsType extends ERC20TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC20TokenEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.doesBelongToStringEnum('eventName', eventName, ERC20TokenEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.ERC20Token.compilerOutput.abi,
+ );
+ return logs;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ this._tokenContractsByAddress = {};
+ }
+ private async _getTokenContractAsync(tokenAddress: string): Promise<ERC20TokenContract> {
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress];
+ if (!_.isUndefined(tokenContract)) {
+ return tokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.ERC20Token,
+ normalizedTokenAddress,
+ );
+ const contractInstance = new ERC20TokenContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ tokenContract = contractInstance;
+ this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract;
+ return tokenContract;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts
new file mode 100644
index 000000000..38ecd4687
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts
@@ -0,0 +1,85 @@
+import { AssetProxyId } from '@0xproject/types';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { assert } from '../utils/assert';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC721ProxyContract } from './generated/erc721_proxy';
+
+/**
+ * This class includes the functionality related to interacting with the ERC721Proxy contract.
+ */
+export class ERC721ProxyWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi;
+ private _erc721ProxyContractIfExists?: ERC721ProxyContract;
+ private _contractAddressIfExists?: string;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ }
+ /**
+ * Get the 4 bytes ID of this asset proxy
+ * @return Proxy id
+ */
+ public async getProxyIdAsync(): Promise<AssetProxyId> {
+ const ERC721ProxyContractInstance = await this._getERC721ProxyContractAsync();
+ const proxyId = (await ERC721ProxyContractInstance.getProxyId.callAsync()) as AssetProxyId;
+ return proxyId;
+ }
+ /**
+ * Check if the Exchange contract address is authorized by the ERC721Proxy contract.
+ * @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
+ * @return Whether the exchangeContractAddress is authorized.
+ */
+ public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
+ assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
+ const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase();
+ const ERC721ProxyContractInstance = await this._getERC721ProxyContractAsync();
+ const isAuthorized = await ERC721ProxyContractInstance.authorized.callAsync(normalizedExchangeContractAddress);
+ return isAuthorized;
+ }
+ /**
+ * Get the list of all Exchange contract addresses authorized by the ERC721Proxy contract.
+ * @return The list of authorized addresses.
+ */
+ public async getAuthorizedAddressesAsync(): Promise<string[]> {
+ const ERC721ProxyContractInstance = await this._getERC721ProxyContractAsync();
+ const authorizedAddresses = await ERC721ProxyContractInstance.getAuthorizedAddresses.callAsync();
+ return authorizedAddresses;
+ }
+ /**
+ * Retrieves the Ethereum address of the ERC721Proxy contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the ERC721Proxy contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ERC721Proxy, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstance(): void {
+ delete this._erc721ProxyContractIfExists;
+ }
+ private async _getERC721ProxyContractAsync(): Promise<ERC721ProxyContract> {
+ if (!_.isUndefined(this._erc721ProxyContractIfExists)) {
+ return this._erc721ProxyContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.ERC721Proxy,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new ERC721ProxyContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._erc721ProxyContractIfExists = contractInstance;
+ return this._erc721ProxyContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts
new file mode 100644
index 000000000..7231e0bde
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts
@@ -0,0 +1,472 @@
+import { schemas } from '@0xproject/json-schemas';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { methodOptsSchema } from '../schemas/method_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import {
+ BlockRange,
+ ContractWrappersError,
+ EventCallback,
+ IndexedFilterValues,
+ MethodOpts,
+ TransactionOpts,
+} from '../types';
+import { assert } from '../utils/assert';
+import { constants } from '../utils/constants';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC721ProxyWrapper } from './erc721_proxy_wrapper';
+import { ERC721TokenContract, ERC721TokenEventArgs, ERC721TokenEvents } from './generated/erc721_token';
+
+const removeUndefinedProperties = _.pickBy;
+
+/**
+ * This class includes all the functionality related to interacting with ERC721 token contracts.
+ * All ERC721 method calls are supported, along with some convenience methods for getting/setting allowances
+ * to the 0x ERC721 Proxy smart contract.
+ */
+export class ERC721TokenWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.ERC721Token.compilerOutput.abi;
+ private _tokenContractsByAddress: { [address: string]: ERC721TokenContract };
+ private _erc721ProxyWrapper: ERC721ProxyWrapper;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ erc721ProxyWrapper: ERC721ProxyWrapper,
+ blockPollingIntervalMs?: number,
+ ) {
+ super(web3Wrapper, networkId, blockPollingIntervalMs);
+ this._tokenContractsByAddress = {};
+ this._erc721ProxyWrapper = erc721ProxyWrapper;
+ }
+ /**
+ * Count all NFTs assigned to an owner
+ * NFTs assigned to the zero address are considered invalid, and this function throws for queries about the zero address.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The number of NFTs owned by `ownerAddress`, possibly zero
+ */
+ public async getTokenCountAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<BigNumber> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, methodOpts.defaultBlock);
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ balance = new BigNumber(balance);
+ return balance;
+ }
+ /**
+ * Find the owner of an NFT
+ * NFTs assigned to zero address are considered invalid, and queries about them do throw.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The address of the owner of the NFT
+ */
+ public async getOwnerOfAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ methodOpts: MethodOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ try {
+ const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId, txData, methodOpts.defaultBlock);
+ return tokenOwner;
+ } catch (err) {
+ throw new Error(ContractWrappersError.ERC721OwnerNotFound);
+ }
+ }
+ /**
+ * Query if an address is an authorized operator for all NFT's of `ownerAddress`
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to check if approved.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise
+ */
+ public async isApprovedForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ operatorAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<boolean> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('operatorAddress', operatorAddress);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedOperatorAddress = operatorAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ const isApprovedForAll = await tokenContract.isApprovedForAll.callAsync(
+ normalizedOwnerAddress,
+ normalizedOperatorAddress,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return isApprovedForAll;
+ }
+ /**
+ * Query if 0x proxy is an authorized operator for all NFT's of `ownerAddress`
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise
+ */
+ public async isProxyApprovedForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<boolean> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const isProxyApprovedForAll = await this.isApprovedForAllAsync(
+ tokenAddress,
+ ownerAddress,
+ proxyAddress,
+ methodOpts,
+ );
+ return isProxyApprovedForAll;
+ }
+ /**
+ * Get the approved address for a single NFT. Returns undefined if no approval was set
+ * Throws if `_tokenId` is not a valid NFT
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The approved address for this NFT, or the undefined if there is none
+ */
+ public async getApprovedIfExistsAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ methodOpts: MethodOpts = {},
+ ): Promise<string | undefined> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+
+ const txData = {};
+ const approvedAddress = await tokenContract.getApproved.callAsync(tokenId, txData, methodOpts.defaultBlock);
+ if (approvedAddress === constants.NULL_ADDRESS) {
+ return undefined;
+ }
+ return approvedAddress;
+ }
+ /**
+ * Checks if 0x proxy is approved for a single NFT
+ * Throws if `_tokenId` is not a valid NFT
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if 0x proxy is approved
+ */
+ public async isProxyApprovedAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ methodOpts: MethodOpts = {},
+ ): Promise<boolean> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const approvedAddress = await this.getApprovedIfExistsAsync(tokenAddress, tokenId, methodOpts);
+ const isProxyApproved = approvedAddress === proxyAddress;
+ return isProxyApproved;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for.
+ * @param isApproved The boolean variable to set the approval to.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setApprovalForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ operatorAddress: string,
+ isApproved: boolean,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
+ assert.isETHAddressHex('operatorAddress', operatorAddress);
+ assert.isBoolean('isApproved', isApproved);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedOperatorAddress = operatorAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const txHash = await tokenContract.setApprovalForAll.sendTransactionAsync(
+ normalizedOperatorAddress,
+ isApproved,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: normalizedOwnerAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for.
+ * @param isApproved The boolean variable to set the approval to.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setProxyApprovalForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ isApproved: boolean,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const txHash = await this.setApprovalForAllAsync(tokenAddress, ownerAddress, proxyAddress, isApproved, txOpts);
+ return txHash;
+ }
+ /**
+ * Set or reaffirm the approved address for an NFT
+ * The zero address indicates there is no approved address. Throws unless `msg.sender` is the current NFT owner,
+ * or an authorized operator of the current owner.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the Approval event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param approvedAddress The hex encoded user Ethereum address you'd like to set approval for.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setApprovalAsync(
+ tokenAddress: string,
+ approvedAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('approvedAddress', approvedAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedApprovedAddress = approvedAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const tokenOwnerAddress = await tokenContract.ownerOf.callAsync(tokenId);
+ await assert.isSenderAddressAsync('tokenOwnerAddress', tokenOwnerAddress, this._web3Wrapper);
+ const txHash = await tokenContract.approve.sendTransactionAsync(
+ normalizedApprovedAddress,
+ tokenId,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: tokenOwnerAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Set or reaffirm 0x proxy as an approved address for an NFT
+ * Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the Approval event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setProxyApprovalAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const txHash = await this.setApprovalAsync(tokenAddress, proxyAddress, tokenId, txOpts);
+ return txHash;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param receiverAddress The hex encoded Ethereum address of the user to send the NFT to.
+ * @param senderAddress The hex encoded Ethereum address of the user to send the NFT to.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async transferFromAsync(
+ tokenAddress: string,
+ receiverAddress: string,
+ senderAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('receiverAddress', receiverAddress);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedReceiverAddress = receiverAddress.toLowerCase();
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const ownerAddress = await this.getOwnerOfAsync(tokenAddress, tokenId);
+ if (normalizedSenderAddress !== ownerAddress) {
+ const isApprovedForAll = await this.isApprovedForAllAsync(
+ normalizedTokenAddress,
+ ownerAddress,
+ normalizedSenderAddress,
+ );
+ if (!isApprovedForAll) {
+ const approvedAddress = await this.getApprovedIfExistsAsync(normalizedTokenAddress, tokenId);
+ if (approvedAddress !== normalizedSenderAddress) {
+ throw new Error(ContractWrappersError.ERC721NoApproval);
+ }
+ }
+ }
+ const txHash = await tokenContract.transferFrom.sendTransactionAsync(
+ ownerAddress,
+ normalizedReceiverAddress,
+ tokenId,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: normalizedSenderAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Subscribe to an event type emitted by the Token contract.
+ * @param tokenAddress The hex encoded address where the ERC721 token is deployed.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
+ * @param callback Callback that gets called when a log is added/removed
+ * @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered)
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends ERC721TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC721TokenEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ isVerbose: boolean = false,
+ ): string {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.ERC721Token.compilerOutput.abi,
+ callback,
+ isVerbose,
+ );
+ return subscriptionToken;
+ }
+ /**
+ * Cancel a subscription
+ * @param subscriptionToken Subscription token returned by `subscribe()`
+ */
+ public unsubscribe(subscriptionToken: string): void {
+ assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken);
+ this._unsubscribe(subscriptionToken);
+ }
+ /**
+ * Cancels all existing subscriptions
+ */
+ public unsubscribeAll(): void {
+ super._unsubscribeAll();
+ }
+ /**
+ * Gets historical logs without creating a subscription
+ * @param tokenAddress An address of the token that emitted the logs.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param blockRange Block range to get logs from.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
+ * @return Array of logs that match the parameters
+ */
+ public async getLogsAsync<ArgsType extends ERC721TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC721TokenEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.ERC721Token.compilerOutput.abi,
+ );
+ return logs;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ this._tokenContractsByAddress = {};
+ }
+ private async _getTokenContractAsync(tokenAddress: string): Promise<ERC721TokenContract> {
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress];
+ if (!_.isUndefined(tokenContract)) {
+ return tokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.ERC721Token,
+ normalizedTokenAddress,
+ );
+ const contractInstance = new ERC721TokenContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ tokenContract = contractInstance;
+ this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract;
+ return tokenContract;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts
new file mode 100644
index 000000000..5046d3667
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts
@@ -0,0 +1,225 @@
+import { schemas } from '@0xproject/json-schemas';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { BlockRange, ContractWrappersError, EventCallback, IndexedFilterValues, TransactionOpts } from '../types';
+import { assert } from '../utils/assert';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC20TokenWrapper } from './erc20_token_wrapper';
+import { WETH9Contract, WETH9EventArgs, WETH9Events } from './generated/weth9';
+
+const removeUndefinedProperties = _.pickBy;
+
+/**
+ * This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract.
+ * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back.
+ */
+export class EtherTokenWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.EtherToken.compilerOutput.abi;
+ private _etherTokenContractsByAddress: {
+ [address: string]: WETH9Contract;
+ } = {};
+ private _erc20TokenWrapper: ERC20TokenWrapper;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ erc20TokenWrapper: ERC20TokenWrapper,
+ blockPollingIntervalMs?: number,
+ ) {
+ super(web3Wrapper, networkId, blockPollingIntervalMs);
+ this._erc20TokenWrapper = erc20TokenWrapper;
+ }
+ /**
+ * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
+ * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
+ * for ETH.
+ * @param etherTokenAddress EtherToken address you wish to deposit into.
+ * @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
+ * @param depositor The hex encoded user Ethereum address that would like to make the deposit.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async depositAsync(
+ etherTokenAddress: string,
+ amountInWei: BigNumber,
+ depositor: string,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ assert.isValidBaseUnitAmount('amountInWei', amountInWei);
+ await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ const normalizedDepositorAddress = depositor.toLowerCase();
+
+ const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(normalizedDepositorAddress);
+ assert.assert(ethBalanceInWei.gte(amountInWei), ContractWrappersError.InsufficientEthBalanceForDeposit);
+
+ const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress);
+ const txHash = await wethContract.deposit.sendTransactionAsync(
+ removeUndefinedProperties({
+ from: normalizedDepositorAddress,
+ value: amountInWei,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
+ * equivalent number of wrapped ETH tokens.
+ * @param etherTokenAddress EtherToken address you wish to withdraw from.
+ * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
+ * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawal.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async withdrawAsync(
+ etherTokenAddress: string,
+ amountInWei: BigNumber,
+ withdrawer: string,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isValidBaseUnitAmount('amountInWei', amountInWei);
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ const normalizedWithdrawerAddress = withdrawer.toLowerCase();
+
+ const WETHBalanceInBaseUnits = await this._erc20TokenWrapper.getBalanceAsync(
+ normalizedEtherTokenAddress,
+ normalizedWithdrawerAddress,
+ );
+ assert.assert(
+ WETHBalanceInBaseUnits.gte(amountInWei),
+ ContractWrappersError.InsufficientWEthBalanceForWithdrawal,
+ );
+
+ const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress);
+ const txHash = await wethContract.withdraw.sendTransactionAsync(
+ amountInWei,
+ removeUndefinedProperties({
+ from: normalizedWithdrawerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Gets historical logs without creating a subscription
+ * @param etherTokenAddress An address of the ether token that emitted the logs.
+ * @param eventName The ether token contract event you would like to subscribe to.
+ * @param blockRange Block range to get logs from.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
+ * @return Array of logs that match the parameters
+ */
+ public async getLogsAsync<ArgsType extends WETH9EventArgs>(
+ etherTokenAddress: string,
+ eventName: WETH9Events,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, WETH9Events);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedEtherTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.EtherToken.compilerOutput.abi,
+ );
+ return logs;
+ }
+ /**
+ * Subscribe to an event type emitted by the Token contract.
+ * @param etherTokenAddress The hex encoded address where the ether token is deployed.
+ * @param eventName The ether token contract event you would like to subscribe to.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
+ * @param callback Callback that gets called when a log is added/removed
+ * @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered)
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends WETH9EventArgs>(
+ etherTokenAddress: string,
+ eventName: WETH9Events,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ isVerbose: boolean = false,
+ ): string {
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, WETH9Events);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedEtherTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.EtherToken.compilerOutput.abi,
+ callback,
+ isVerbose,
+ );
+ return subscriptionToken;
+ }
+ /**
+ * Cancel a subscription
+ * @param subscriptionToken Subscription token returned by `subscribe()`
+ */
+ public unsubscribe(subscriptionToken: string): void {
+ assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken);
+ this._unsubscribe(subscriptionToken);
+ }
+ /**
+ * Cancels all existing subscriptions
+ */
+ public unsubscribeAll(): void {
+ super._unsubscribeAll();
+ }
+ /**
+ * Retrieves the Ethereum address of the EtherToken contract deployed on the network
+ * that the user-passed web3 provider is connected to. If it's not Kovan, Ropsten, Rinkeby, Mainnet or TestRPC
+ * (networkId: 50), it will return undefined (e.g a private network).
+ * @returns The Ethereum address of the EtherToken contract or undefined.
+ */
+ public getContractAddressIfExists(): string | undefined {
+ const networkSpecificArtifact = artifacts.EtherToken.networks[this._networkId];
+ const contractAddressIfExists = _.isUndefined(networkSpecificArtifact)
+ ? undefined
+ : networkSpecificArtifact.address;
+ return contractAddressIfExists;
+ }
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstance(): void {
+ this.unsubscribeAll();
+ this._etherTokenContractsByAddress = {};
+ }
+ private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<WETH9Contract> {
+ let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress];
+ if (!_.isUndefined(etherTokenContract)) {
+ return etherTokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.EtherToken,
+ etherTokenAddress,
+ );
+ const contractInstance = new WETH9Contract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ etherTokenContract = contractInstance;
+ this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract;
+ return etherTokenContract;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
new file mode 100644
index 000000000..48bd00f90
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
@@ -0,0 +1,1123 @@
+import { schemas } from '@0xproject/json-schemas';
+import { assetDataUtils } from '@0xproject/order-utils';
+import { AssetProxyId, Order, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { methodOptsSchema } from '../schemas/method_opts_schema';
+import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import {
+ BlockRange,
+ EventCallback,
+ ExchangeWrapperError,
+ IndexedFilterValues,
+ MethodOpts,
+ OrderInfo,
+ OrderTransactionOpts,
+} from '../types';
+import { assert } from '../utils/assert';
+import { decorators } from '../utils/decorators';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated/exchange';
+
+/**
+ * This class includes all the functionality related to calling methods, sending transactions and subscribing to
+ * events of the 0x V2 Exchange smart contract.
+ */
+export class ExchangeWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.Exchange.compilerOutput.abi;
+ private _exchangeContractIfExists?: ExchangeContract;
+ private _contractAddressIfExists?: string;
+ private _zrxContractAddressIfExists?: string;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ contractAddressIfExists?: string,
+ zrxContractAddressIfExists?: string,
+ blockPollingIntervalMs?: number,
+ ) {
+ super(web3Wrapper, networkId, blockPollingIntervalMs);
+ this._contractAddressIfExists = contractAddressIfExists;
+ this._zrxContractAddressIfExists = zrxContractAddressIfExists;
+ }
+ /**
+ * Retrieve the address of an asset proxy by signature.
+ * @param proxyId The 4 bytes signature of an asset proxy
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The address of an asset proxy for a given signature
+ */
+ public async getAssetProxyBySignatureAsync(proxyId: AssetProxyId, methodOpts: MethodOpts = {}): Promise<string> {
+ assert.doesBelongToStringEnum('proxyId', proxyId, AssetProxyId);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeContract = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const assetProxy = await exchangeContract.getAssetProxy.callAsync(proxyId, txData, methodOpts.defaultBlock);
+ return assetProxy;
+ }
+ /**
+ * Retrieve the takerAssetAmount of an order that has already been filled.
+ * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAssetAmount.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The amount of the order (in taker asset base units) that has already been filled.
+ */
+ public async getFilledTakerAssetAmountAsync(orderHash: string, methodOpts: MethodOpts = {}): Promise<BigNumber> {
+ assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeContract = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const filledTakerAssetAmountInBaseUnits = await exchangeContract.filled.callAsync(
+ orderHash,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return filledTakerAssetAmountInBaseUnits;
+ }
+ /**
+ * Retrieve the exchange contract version
+ * @param methodOpts Optional arguments this method accepts.
+ * @return Version
+ */
+ public async getVersionAsync(methodOpts: MethodOpts = {}): Promise<string> {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeContract = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const version = await exchangeContract.VERSION.callAsync(txData, methodOpts.defaultBlock);
+ return version;
+ }
+ /**
+ * Retrieve the set order epoch for a given makerAddress & senderAddress pair.
+ * Orders can be bulk cancelled by setting the order epoch to a value lower then the salt value of orders one wishes to cancel.
+ * @param makerAddress Maker address
+ * @param senderAddress Sender address
+ * @param methodOpts Optional arguments this method accepts.
+ * @return Order epoch. Defaults to 0.
+ */
+ public async getOrderEpochAsync(
+ makerAddress: string,
+ senderAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<BigNumber> {
+ assert.isETHAddressHex('makerAddress', makerAddress);
+ assert.isETHAddressHex('senderAddress', senderAddress);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeContract = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const orderEpoch = await exchangeContract.orderEpoch.callAsync(
+ makerAddress,
+ senderAddress,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return orderEpoch;
+ }
+ /**
+ * Check if an order has been cancelled. Order cancellations are binary
+ * @param orderHash The hex encoded orderHash for which you would like to retrieve the cancelled takerAmount.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return Whether the order has been cancelled.
+ */
+ public async isCancelledAsync(orderHash: string, methodOpts: MethodOpts = {}): Promise<boolean> {
+ assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeContract = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const isCancelled = await exchangeContract.cancelled.callAsync(orderHash, txData, methodOpts.defaultBlock);
+ return isCancelled;
+ }
+ /**
+ * Fills a signed order with an amount denominated in baseUnits of the taker asset.
+ * @param signedOrder An object that conforms to the SignedOrder interface.
+ * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrderAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.fillOrder.callAsync(signedOrder, takerAssetFillAmount, signedOrder.signature, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+
+ const txHash = await exchangeInstance.fillOrder.sendTransactionAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * No-throw version of fillOrderAsync. This version will not throw if the fill fails. This allows the caller to save gas at the expense of not knowing the reason the fill failed.
+ * @param signedOrder An object that conforms to the SignedOrder interface.
+ * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrderNoThrowAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.fillOrderNoThrow.callAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ }
+ const txHash = await exchangeInstance.fillOrderNoThrow.sendTransactionAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled,
+ * the fill order is abandoned.
+ * @param signedOrder An object that conforms to the SignedOrder interface.
+ * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrKillOrderAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.fillOrKillOrder.callAsync(signedOrder, takerAssetFillAmount, signedOrder.signature, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Executes a 0x transaction. Transaction messages exist for the purpose of calling methods on the Exchange contract
+ * in the context of another address (see [ZEIP18](https://github.com/0xProject/ZEIPs/issues/18)).
+ * This is especially useful for implementing filter contracts.
+ * @param salt Salt
+ * @param signerAddress Signer address
+ * @param data Transaction data
+ * @param signature Signature
+ * @param senderAddress Sender address
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async executeTransactionAsync(
+ salt: BigNumber,
+ signerAddress: string,
+ data: string,
+ signature: string,
+ senderAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.isBigNumber('salt', salt);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ assert.isHexString('data', data);
+ assert.isHexString('signature', signature);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.executeTransaction.callAsync(salt, signerAddress, data, signature, {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.executeTransaction.sendTransactionAsync(
+ salt,
+ signerAddress,
+ data,
+ signature,
+ {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of fillOrderAsync. Executes multiple fills atomically in a single transaction.
+ * @param signedOrders An array of signed orders to fill.
+ * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchFillOrdersAsync(
+ signedOrders: SignedOrder[],
+ takerAssetFillAmounts: BigNumber[],
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ _.forEach(takerAssetFillAmounts, takerAssetFillAmount =>
+ assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount),
+ );
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.batchFillOrders.callAsync(signedOrders, takerAssetFillAmounts, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
+ signedOrders,
+ takerAssetFillAmounts,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Synchronously executes multiple calls to fillOrder until total amount of makerAsset is bought by taker.
+ * @param signedOrders An array of signed orders to fill.
+ * @param makerAssetFillAmount Maker asset fill amount.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async marketBuyOrdersAsync(
+ signedOrders: SignedOrder[],
+ makerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.marketBuyOrders.callAsync(signedOrders, makerAssetFillAmount, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.marketBuyOrders.sendTransactionAsync(
+ signedOrders,
+ makerAssetFillAmount,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Synchronously executes multiple calls to fillOrder until total amount of makerAsset is bought by taker.
+ * @param signedOrders An array of signed orders to fill.
+ * @param takerAssetFillAmount Taker asset fill amount.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async marketSellOrdersAsync(
+ signedOrders: SignedOrder[],
+ takerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.marketSellOrders.callAsync(signedOrders, takerAssetFillAmount, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.marketSellOrders.sendTransactionAsync(
+ signedOrders,
+ takerAssetFillAmount,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * No throw version of marketBuyOrdersAsync
+ * @param signedOrders An array of signed orders to fill.
+ * @param makerAssetFillAmount Maker asset fill amount.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async marketBuyOrdersNoThrowAsync(
+ signedOrders: SignedOrder[],
+ makerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.marketBuyOrdersNoThrow.callAsync(signedOrders, makerAssetFillAmount, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.marketBuyOrdersNoThrow.sendTransactionAsync(
+ signedOrders,
+ makerAssetFillAmount,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * No throw version of marketSellOrdersAsync
+ * @param signedOrders An array of signed orders to fill.
+ * @param takerAssetFillAmount Taker asset fill amount.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async marketSellOrdersNoThrowAsync(
+ signedOrders: SignedOrder[],
+ takerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.marketSellOrdersNoThrow.callAsync(signedOrders, takerAssetFillAmount, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.marketSellOrdersNoThrow.sendTransactionAsync(
+ signedOrders,
+ takerAssetFillAmount,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * No throw version of batchFillOrdersAsync
+ * @param signedOrders An array of signed orders to fill.
+ * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchFillOrdersNoThrowAsync(
+ signedOrders: SignedOrder[],
+ takerAssetFillAmounts: BigNumber[],
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ _.forEach(takerAssetFillAmounts, takerAssetFillAmount =>
+ assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount),
+ );
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.batchFillOrdersNoThrow.callAsync(signedOrders, takerAssetFillAmounts, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.batchFillOrdersNoThrow.sendTransactionAsync(
+ signedOrders,
+ takerAssetFillAmounts,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of fillOrKillOrderAsync. Executes multiple fills atomically in a single transaction.
+ * @param signedOrders An array of signed orders to fill.
+ * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill these orders. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchFillOrKillOrdersAsync(
+ signedOrders: SignedOrder[],
+ takerAssetFillAmounts: BigNumber[],
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ _.forEach(takerAssetFillAmounts, takerAssetFillAmount =>
+ assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount),
+ );
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const signatures = _.map(signedOrders, signedOrder => signedOrder.signature);
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.batchFillOrKillOrders.callAsync(signedOrders, takerAssetFillAmounts, signatures, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
+ signedOrders,
+ takerAssetFillAmounts,
+ signatures,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of cancelOrderAsync. Executes multiple cancels atomically in a single transaction.
+ * @param orders An array of orders to cancel.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchCancelOrdersAsync(
+ orders: Array<Order | SignedOrder>,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const makerAddresses = _.map(orders, order => order.makerAddress);
+ const makerAddress = makerAddresses[0];
+ await assert.isSenderAddressAsync('makerAddress', makerAddress, this._web3Wrapper);
+ const normalizedMakerAddress = makerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.batchCancelOrders.callAsync(orders, {
+ from: normalizedMakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(orders, {
+ from: normalizedMakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ return txHash;
+ }
+ /**
+ * 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 goes to the taker (whoever matched the two orders).
+ * @param leftSignedOrder First order to match.
+ * @param rightSignedOrder Second order to match.
+ * @param takerAddress The address that sends the transaction and gets the spread.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async matchOrdersAsync(
+ leftSignedOrder: SignedOrder,
+ rightSignedOrder: SignedOrder,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('leftSignedOrder', leftSignedOrder, schemas.signedOrderSchema);
+ assert.doesConformToSchema('rightSignedOrder', rightSignedOrder, schemas.signedOrderSchema);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ if (
+ rightSignedOrder.makerAssetData !== leftSignedOrder.takerAssetData ||
+ rightSignedOrder.takerAssetData !== leftSignedOrder.makerAssetData
+ ) {
+ throw new Error(ExchangeWrapperError.AssetDataMismatch);
+ } else {
+ // Smart contracts assigns the asset data from the left order to the right one so we can save gas on reducing the size of call data
+ rightSignedOrder.makerAssetData = '0x';
+ rightSignedOrder.takerAssetData = '0x';
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.matchOrders.callAsync(
+ leftSignedOrder,
+ rightSignedOrder,
+ leftSignedOrder.signature,
+ rightSignedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ }
+ const txHash = await exchangeInstance.matchOrders.sendTransactionAsync(
+ leftSignedOrder,
+ rightSignedOrder,
+ leftSignedOrder.signature,
+ rightSignedOrder.signature,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Approves a hash on-chain using any valid signature type.
+ * After presigning a hash, the preSign signature type will become valid for that hash and signer.
+ * @param hash Hash to pre-sign
+ * @param signerAddress Address that should have signed the given hash.
+ * @param signature Proof that the hash has been signed by signer.
+ * @param senderAddress Address that should send the transaction.
+ * @returns Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async preSignAsync(
+ hash: string,
+ signerAddress: string,
+ signature: string,
+ senderAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.isHexString('hash', hash);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ assert.isHexString('signature', signature);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedTakerAddress = senderAddress.toLowerCase();
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.preSign.callAsync(hash, signerAddress, signature, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.preSign.sendTransactionAsync(hash, signerAddress, signature, {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ return txHash;
+ }
+ /**
+ * Checks if the signature is valid.
+ * @param hash Hash to pre-sign
+ * @param signerAddress Address that should have signed the given hash.
+ * @param signature Proof that the hash has been signed by signer.
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns If the signature is valid
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async isValidSignatureAsync(
+ hash: string,
+ signerAddress: string,
+ signature: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<boolean> {
+ assert.isHexString('hash', hash);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ assert.isHexString('signature', signature);
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const isValidSignature = await exchangeInstance.isValidSignature.callAsync(
+ hash,
+ signerAddress,
+ signature,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return isValidSignature;
+ }
+ /**
+ * Checks if the validator is allowed by the signer.
+ * @param validatorAddress Address of a validator
+ * @param signerAddress Address of a signer
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns If the validator is allowed
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async isAllowedValidatorAsync(
+ signerAddress: string,
+ validatorAddress: string,
+ methodOpts: MethodOpts = {},
+ ): Promise<boolean> {
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ assert.isETHAddressHex('validatorAddress', validatorAddress);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedSignerAddress = signerAddress.toLowerCase();
+ const normalizedValidatorAddress = validatorAddress.toLowerCase();
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const isValidSignature = await exchangeInstance.allowedValidators.callAsync(
+ normalizedSignerAddress,
+ normalizedValidatorAddress,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return isValidSignature;
+ }
+ /**
+ * Check whether the hash is pre-signed on-chain.
+ * @param hash Hash to check if pre-signed
+ * @param signerAddress Address that should have signed the given hash.
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns Whether the hash is pre-signed.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async isPreSignedAsync(hash: string, signerAddress: string, methodOpts: MethodOpts = {}): Promise<boolean> {
+ assert.isHexString('hash', hash);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+
+ const txData = {};
+ const isPreSigned = await exchangeInstance.preSigned.callAsync(
+ hash,
+ signerAddress,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return isPreSigned;
+ }
+ /**
+ * Checks if transaction is already executed.
+ * @param transactionHash Transaction hash to check
+ * @param signerAddress Address that should have signed the given hash.
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns If transaction is already executed.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async isTransactionExecutedAsync(transactionHash: string, methodOpts: MethodOpts = {}): Promise<boolean> {
+ assert.isHexString('transactionHash', transactionHash);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const isExecuted = await exchangeInstance.transactions.callAsync(
+ transactionHash,
+ txData,
+ methodOpts.defaultBlock,
+ );
+ return isExecuted;
+ }
+ /**
+ * Get order info
+ * @param order Order
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns Order info
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async getOrderInfoAsync(order: Order | SignedOrder, methodOpts: MethodOpts = {}): Promise<OrderInfo> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const orderInfo = await exchangeInstance.getOrderInfo.callAsync(order, txData, methodOpts.defaultBlock);
+ return orderInfo;
+ }
+ /**
+ * Get order info for multiple orders
+ * @param orders Orders
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns Array of Order infos
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async getOrdersInfoAsync(
+ orders: Array<Order | SignedOrder>,
+ methodOpts: MethodOpts = {},
+ ): Promise<OrderInfo[]> {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const ordersInfo = await exchangeInstance.getOrdersInfo.callAsync(orders, txData, methodOpts.defaultBlock);
+ return ordersInfo;
+ }
+ /**
+ * Cancel a given order.
+ * @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel.
+ * @param transactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async cancelOrderAsync(
+ order: Order | SignedOrder,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ await assert.isSenderAddressAsync('order.maker', order.makerAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedMakerAddress = order.makerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.cancelOrder.callAsync(order, {
+ from: normalizedMakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(order, {
+ from: normalizedMakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ return txHash;
+ }
+ /**
+ * Sets the signature validator approval
+ * @param validatorAddress Validator contract address.
+ * @param isApproved Boolean value to set approval to.
+ * @param senderAddress Sender address.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async setSignatureValidatorApprovalAsync(
+ validatorAddress: string,
+ isApproved: boolean,
+ senderAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.isETHAddressHex('validatorAddress', validatorAddress);
+ assert.isBoolean('isApproved', isApproved);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.setSignatureValidatorApproval.callAsync(validatorAddress, isApproved, {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.setSignatureValidatorApproval.sendTransactionAsync(
+ validatorAddress,
+ isApproved,
+ {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
+ * and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress).
+ * @param targetOrderEpoch Target order epoch.
+ * @param senderAddress Address that should send the transaction.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async cancelOrdersUpToAsync(
+ targetOrderEpoch: BigNumber,
+ senderAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true },
+ ): Promise<string> {
+ assert.isBigNumber('targetOrderEpoch', targetOrderEpoch);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]);
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ if (orderTransactionOpts.shouldValidate) {
+ await exchangeInstance.cancelOrdersUpTo.callAsync(targetOrderEpoch, {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ }
+ const txHash = await exchangeInstance.cancelOrdersUpTo.sendTransactionAsync(targetOrderEpoch, {
+ from: normalizedSenderAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ });
+ return txHash;
+ }
+ /**
+ * Subscribe to an event type emitted by the Exchange contract.
+ * @param eventName The exchange contract event you would like to subscribe to.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
+ * @param callback Callback that gets called when a log is added/removed
+ * @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered)
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends ExchangeEventArgs>(
+ eventName: ExchangeEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ isVerbose: boolean = false,
+ ): string {
+ assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const exchangeContractAddress = this.getContractAddress();
+ const subscriptionToken = this._subscribe<ArgsType>(
+ exchangeContractAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.Exchange.compilerOutput.abi,
+ callback,
+ isVerbose,
+ );
+ return subscriptionToken;
+ }
+ /**
+ * Cancel a subscription
+ * @param subscriptionToken Subscription token returned by `subscribe()`
+ */
+ public unsubscribe(subscriptionToken: string): void {
+ this._unsubscribe(subscriptionToken);
+ }
+ /**
+ * Cancels all existing subscriptions
+ */
+ public unsubscribeAll(): void {
+ super._unsubscribeAll();
+ }
+ /**
+ * Gets historical logs without creating a subscription
+ * @param eventName The exchange contract event you would like to subscribe to.
+ * @param blockRange Block range to get logs from.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
+ * @return Array of logs that match the parameters
+ */
+ public async getLogsAsync<ArgsType extends ExchangeEventArgs>(
+ eventName: ExchangeEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const exchangeContractAddress = this.getContractAddress();
+ const logs = await this._getLogsAsync<ArgsType>(
+ exchangeContractAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.Exchange.compilerOutput.abi,
+ );
+ return logs;
+ }
+ /**
+ * Retrieves the Ethereum address of the Exchange contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the Exchange contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.Exchange, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the ZRX token address used by the exchange contract.
+ * @return Address of ZRX token
+ */
+ public getZRXTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the ZRX asset data used by the exchange contract.
+ * @return ZRX asset data
+ */
+ public getZRXAssetData(): string {
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress);
+ return zrxAssetData;
+ }
+ // tslint:disable:no-unused-variable
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ delete this._exchangeContractIfExists;
+ }
+ // tslint:enable:no-unused-variable
+ private async _getExchangeContractAsync(): Promise<ExchangeContract> {
+ if (!_.isUndefined(this._exchangeContractIfExists)) {
+ return this._exchangeContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.Exchange,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new ExchangeContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._exchangeContractIfExists = contractInstance;
+ return this._exchangeContractIfExists;
+ }
+} // tslint:disable:max-file-line-count
diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts
new file mode 100644
index 000000000..13ef0fe01
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts
@@ -0,0 +1,220 @@
+import { schemas } from '@0xproject/json-schemas';
+import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import { TransactionOpts } from '../types';
+import { assert } from '../utils/assert';
+import { calldataOptimizationUtils } from '../utils/calldata_optimization_utils';
+import { constants } from '../utils/constants';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ForwarderContract } from './generated/forwarder';
+
+/**
+ * This class includes the functionality related to interacting with the Forwarder contract.
+ */
+export class ForwarderWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.Forwarder.compilerOutput.abi;
+ private _forwarderContractIfExists?: ForwarderContract;
+ private _contractAddressIfExists?: string;
+ private _zrxContractAddressIfExists?: string;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ contractAddressIfExists?: string,
+ zrxContractAddressIfExists?: string,
+ ) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ this._zrxContractAddressIfExists = zrxContractAddressIfExists;
+ }
+ /**
+ * 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 signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
+ * All orders must specify WETH as the takerAsset
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param ethAmount The amount of eth to send with the transaction (in wei).
+ * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
+ * Used to purchase ZRX for primary order fees.
+ * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+ * Defaults to 0.
+ * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async marketSellOrdersWithEthAsync(
+ signedOrders: SignedOrder[],
+ takerAddress: string,
+ ethAmount: BigNumber,
+ signedFeeOrders: SignedOrder[] = [],
+ feePercentage: BigNumber = constants.ZERO_AMOUNT,
+ feeRecipientAddress: string = constants.NULL_ADDRESS,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ // type assertions
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.isBigNumber('ethAmount', ethAmount);
+ assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('feePercentage', feePercentage);
+ assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ // other assertions
+ assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
+ assert.feeOrdersCanBeUsedForForwarderContract(
+ signedFeeOrders,
+ this.getZRXTokenAddress(),
+ this.getEtherTokenAddress(),
+ );
+ // lowercase input addresses
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ // optimize orders
+ const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
+ const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
+ // send transaction
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync(
+ optimizedMarketOrders,
+ _.map(optimizedMarketOrders, order => order.signature),
+ optimizedFeeOrders,
+ _.map(optimizedFeeOrders, order => order.signature),
+ feePercentage,
+ feeRecipientAddress,
+ {
+ value: ethAmount,
+ from: normalizedTakerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Attempt to purchase makerAssetFillAmount of makerAsset by selling ethAmount provided with transaction.
+ * Any ZRX required to pay fees for primary orders will automatically be purchased by the contract.
+ * Any ETH not spent will be refunded to sender.
+ * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
+ * All orders must specify WETH as the takerAsset
+ * @param makerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param ethAmount The amount of eth to send with the transaction (in wei).
+ * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
+ * Used to purchase ZRX for primary order fees.
+ * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+ * Defaults to 0.
+ * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async marketBuyOrdersWithEthAsync(
+ signedOrders: SignedOrder[],
+ makerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ ethAmount: BigNumber,
+ signedFeeOrders: SignedOrder[] = [],
+ feePercentage: BigNumber = constants.ZERO_AMOUNT,
+ feeRecipientAddress: string = constants.NULL_ADDRESS,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ // type assertions
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.isBigNumber('ethAmount', ethAmount);
+ assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('feePercentage', feePercentage);
+ assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ // other assertions
+ assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
+ assert.feeOrdersCanBeUsedForForwarderContract(
+ signedFeeOrders,
+ this.getZRXTokenAddress(),
+ this.getEtherTokenAddress(),
+ );
+ // lowercase input addresses
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ // optimize orders
+ const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
+ const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
+ // send transaction
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync(
+ optimizedMarketOrders,
+ makerAssetFillAmount,
+ _.map(optimizedMarketOrders, order => order.signature),
+ optimizedFeeOrders,
+ _.map(optimizedFeeOrders, order => order.signature),
+ feePercentage,
+ feeRecipientAddress,
+ {
+ value: ethAmount,
+ from: normalizedTakerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Retrieves the Ethereum address of the Forwarder contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the Forwarder contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.Forwarder, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the ZRX token address used by the forwarder contract.
+ * @return Address of ZRX token
+ */
+ public getZRXTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the Ether token address used by the forwarder contract.
+ * @return Address of Ether token
+ */
+ public getEtherTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.EtherToken);
+ return contractAddress;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstance(): void {
+ delete this._forwarderContractIfExists;
+ }
+ private async _getForwarderContractAsync(): Promise<ForwarderContract> {
+ if (!_.isUndefined(this._forwarderContractIfExists)) {
+ return this._forwarderContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.Forwarder,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new ForwarderContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._forwarderContractIfExists = contractInstance;
+ return this._forwarderContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/globals.d.ts b/packages/contract-wrappers/src/globals.d.ts
new file mode 100644
index 000000000..94e63a32d
--- /dev/null
+++ b/packages/contract-wrappers/src/globals.d.ts
@@ -0,0 +1,6 @@
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts
new file mode 100644
index 000000000..1986e0004
--- /dev/null
+++ b/packages/contract-wrappers/src/index.ts
@@ -0,0 +1,82 @@
+export { ContractWrappers } from './contract_wrappers';
+export { ERC20TokenWrapper } from './contract_wrappers/erc20_token_wrapper';
+export { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper';
+export { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
+export { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
+export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper';
+export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
+export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
+
+export {
+ ContractWrappersError,
+ EventCallback,
+ ContractEvent,
+ Token,
+ IndexedFilterValues,
+ BlockRange,
+ OrderFillRequest,
+ ContractEventArgs,
+ ContractWrappersConfig,
+ MethodOpts,
+ OrderTransactionOpts,
+ TransactionOpts,
+ LogEvent,
+ DecodedLogEvent,
+ OnOrderStateChangeCallback,
+ OrderStatus,
+ OrderInfo,
+} from './types';
+
+export {
+ Order,
+ SignedOrder,
+ ECSignature,
+ OrderStateValid,
+ OrderStateInvalid,
+ OrderState,
+ AssetProxyId,
+} from '@0xproject/types';
+
+export {
+ BlockParamLiteral,
+ FilterObject,
+ BlockParam,
+ ContractEventArg,
+ LogWithDecodedArgs,
+ Provider,
+ TransactionReceipt,
+ TransactionReceiptWithDecodedLogs,
+} from 'ethereum-types';
+
+export {
+ WETH9Events,
+ WETH9WithdrawalEventArgs,
+ WETH9ApprovalEventArgs,
+ WETH9EventArgs,
+ WETH9DepositEventArgs,
+ WETH9TransferEventArgs,
+} from './contract_wrappers/generated/weth9';
+
+export {
+ ERC20TokenTransferEventArgs,
+ ERC20TokenApprovalEventArgs,
+ ERC20TokenEvents,
+ ERC20TokenEventArgs,
+} from './contract_wrappers/generated/erc20_token';
+
+export {
+ ERC721TokenApprovalEventArgs,
+ ERC721TokenApprovalForAllEventArgs,
+ ERC721TokenTransferEventArgs,
+ ERC721TokenEvents,
+ ERC721TokenEventArgs,
+} from './contract_wrappers/generated/erc721_token';
+
+export {
+ ExchangeCancelUpToEventArgs,
+ ExchangeAssetProxyRegisteredEventArgs,
+ ExchangeFillEventArgs,
+ ExchangeCancelEventArgs,
+ ExchangeEventArgs,
+ ExchangeEvents,
+} from './contract_wrappers/generated/exchange';
diff --git a/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts b/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts
new file mode 100644
index 000000000..dcb99d0f7
--- /dev/null
+++ b/packages/contract-wrappers/src/monorepo_scripts/postpublish.ts
@@ -0,0 +1,8 @@
+import { postpublishUtils } from '@0xproject/monorepo-scripts';
+
+import * as packageJSON from '../package.json';
+import * as tsConfigJSON from '../tsconfig.json';
+
+const cwd = `${__dirname}/..`;
+// tslint:disable-next-line:no-floating-promises
+postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/contract-wrappers/src/schemas/contract_wrappers_config_schema.ts b/packages/contract-wrappers/src/schemas/contract_wrappers_config_schema.ts
new file mode 100644
index 000000000..ac248b2d4
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/contract_wrappers_config_schema.ts
@@ -0,0 +1,5 @@
+export const ContractWrappersConfigSchema = {
+ id: '/ContractWrappersConfig',
+ oneOf: [{ $ref: '/ZeroExContractPrivateNetworkConfig' }, { $ref: '/ZeroExContractPublicNetworkConfig' }],
+ type: 'object',
+};
diff --git a/packages/contract-wrappers/src/schemas/contract_wrappers_private_network_config_schema.ts b/packages/contract-wrappers/src/schemas/contract_wrappers_private_network_config_schema.ts
new file mode 100644
index 000000000..904690ae7
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/contract_wrappers_private_network_config_schema.ts
@@ -0,0 +1,36 @@
+export const contractWrappersPrivateNetworkConfigSchema = {
+ id: '/ZeroExContractPrivateNetworkConfig',
+ properties: {
+ networkId: {
+ type: 'number',
+ minimum: 1,
+ },
+ gasPrice: { $ref: '/numberSchema' },
+ zrxContractAddress: { $ref: '/addressSchema' },
+ exchangeContractAddress: { $ref: '/addressSchema' },
+ erc20ProxyContractAddress: { $ref: '/addressSchema' },
+ erc721ProxyContractAddress: { $ref: '/addressSchema' },
+ blockPollingIntervalMs: { type: 'number' },
+ orderWatcherConfig: {
+ type: 'object',
+ properties: {
+ pollingIntervalMs: {
+ type: 'number',
+ minimum: 0,
+ },
+ numConfirmations: {
+ type: 'number',
+ minimum: 0,
+ },
+ },
+ },
+ },
+ type: 'object',
+ required: [
+ 'networkId',
+ 'zrxContractAddress',
+ 'exchangeContractAddress',
+ 'erc20ProxyContractAddress',
+ 'erc721ProxyContractAddress',
+ ],
+};
diff --git a/packages/contract-wrappers/src/schemas/contract_wrappers_public_network_config_schema.ts b/packages/contract-wrappers/src/schemas/contract_wrappers_public_network_config_schema.ts
new file mode 100644
index 000000000..5cd008ae0
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/contract_wrappers_public_network_config_schema.ts
@@ -0,0 +1,44 @@
+const networkNameToId: { [networkName: string]: number } = {
+ mainnet: 1,
+ ropsten: 3,
+ rinkeby: 4,
+ kovan: 42,
+ ganache: 50,
+};
+
+export const contractWrappersPublicNetworkConfigSchema = {
+ id: '/ZeroExContractPublicNetworkConfig',
+ properties: {
+ networkId: {
+ type: 'number',
+ enum: [
+ networkNameToId.mainnet,
+ networkNameToId.kovan,
+ networkNameToId.ropsten,
+ networkNameToId.rinkeby,
+ networkNameToId.ganache,
+ ],
+ },
+ gasPrice: { $ref: '/numberSchema' },
+ zrxContractAddress: { $ref: '/addressSchema' },
+ exchangeContractAddress: { $ref: '/addressSchema' },
+ erc20ProxyContractAddress: { $ref: '/addressSchema' },
+ erc721ProxyContractAddress: { $ref: '/addressSchema' },
+ blockPollingIntervalMs: { type: 'number' },
+ orderWatcherConfig: {
+ type: 'object',
+ properties: {
+ pollingIntervalMs: {
+ type: 'number',
+ minimum: 0,
+ },
+ numConfirmations: {
+ type: 'number',
+ minimum: 0,
+ },
+ },
+ },
+ },
+ type: 'object',
+ required: ['networkId'],
+};
diff --git a/packages/contract-wrappers/src/schemas/method_opts_schema.ts b/packages/contract-wrappers/src/schemas/method_opts_schema.ts
new file mode 100644
index 000000000..83003f818
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/method_opts_schema.ts
@@ -0,0 +1,7 @@
+export const methodOptsSchema = {
+ id: '/MethodOpts',
+ properties: {
+ defaultBlock: { $ref: '/blockParamSchema' },
+ },
+ type: 'object',
+};
diff --git a/packages/contract-wrappers/src/schemas/order_tx_opts_schema.ts b/packages/contract-wrappers/src/schemas/order_tx_opts_schema.ts
new file mode 100644
index 000000000..31ad759d5
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/order_tx_opts_schema.ts
@@ -0,0 +1,8 @@
+export const orderTxOptsSchema = {
+ id: '/OrderTxOpts',
+ allOf: [{ $ref: '/TxOpts' }],
+ properties: {
+ shouldValidate: { type: 'boolean' },
+ },
+ type: 'object',
+};
diff --git a/packages/contract-wrappers/src/schemas/tx_opts_schema.ts b/packages/contract-wrappers/src/schemas/tx_opts_schema.ts
new file mode 100644
index 000000000..83c819be2
--- /dev/null
+++ b/packages/contract-wrappers/src/schemas/tx_opts_schema.ts
@@ -0,0 +1,8 @@
+export const txOptsSchema = {
+ id: '/TxOpts',
+ properties: {
+ gasPrice: { $ref: '/numberSchema' },
+ gasLimit: { type: 'number' },
+ },
+ type: 'object',
+};
diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts
new file mode 100644
index 000000000..2b3cdc591
--- /dev/null
+++ b/packages/contract-wrappers/src/types.ts
@@ -0,0 +1,190 @@
+import { BigNumber } from '@0xproject/utils';
+
+import { OrderState, SignedOrder } from '@0xproject/types';
+import { BlockParam, ContractEventArg, DecodedLogArgs, LogEntryEvent, LogWithDecodedArgs } from 'ethereum-types';
+
+import { ERC20TokenEventArgs, ERC20TokenEvents } from './contract_wrappers/generated/erc20_token';
+import { ERC721TokenEventArgs, ERC721TokenEvents } from './contract_wrappers/generated/erc721_token';
+import { ExchangeEventArgs, ExchangeEvents } from './contract_wrappers/generated/exchange';
+import { WETH9EventArgs, WETH9Events } from './contract_wrappers/generated/weth9';
+
+export enum ExchangeWrapperError {
+ AssetDataMismatch = 'ASSET_DATA_MISMATCH',
+}
+
+export enum ContractWrappersError {
+ ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
+ ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST',
+ EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST',
+ ERC20ProxyContractDoesNotExist = 'ERC20_PROXY_CONTRACT_DOES_NOT_EXIST',
+ ERC721ProxyContractDoesNotExist = 'ERC721_PROXY_CONTRACT_DOES_NOT_EXIST',
+ ERC20TokenContractDoesNotExist = 'ERC20_TOKEN_CONTRACT_DOES_NOT_EXIST',
+ ERC721TokenContractDoesNotExist = 'ERC721_TOKEN_CONTRACT_DOES_NOT_EXIST',
+ ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
+ InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
+ InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
+ InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
+ InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
+ InvalidJump = 'INVALID_JUMP',
+ OutOfGas = 'OUT_OF_GAS',
+ SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
+ SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
+ ERC721OwnerNotFound = 'ERC_721_OWNER_NOT_FOUND',
+ ERC721NoApproval = 'ERC_721_NO_APPROVAL',
+}
+
+export enum InternalContractWrappersError {
+ NoAbiDecoder = 'NO_ABI_DECODER',
+}
+
+export type LogEvent = LogEntryEvent;
+export interface DecodedLogEvent<ArgsType extends DecodedLogArgs> {
+ isRemoved: boolean;
+ log: LogWithDecodedArgs<ArgsType>;
+}
+
+export type EventCallback<ArgsType extends DecodedLogArgs> = (
+ err: null | Error,
+ log?: DecodedLogEvent<ArgsType>,
+) => void;
+
+export interface ContractEvent {
+ logIndex: number;
+ transactionIndex: number;
+ transactionHash: string;
+ blockHash: string;
+ blockNumber: number;
+ address: string;
+ type: string;
+ event: string;
+ args: ContractEventArgs;
+}
+
+export type ContractEventArgs = ExchangeEventArgs | ERC20TokenEventArgs | ERC721TokenEventArgs | WETH9EventArgs;
+
+// [address, name, symbol, decimals, ipfsHash, swarmHash]
+export type TokenMetadata = [string, string, string, number, string, string];
+
+export interface Token {
+ name: string;
+ address: string;
+ symbol: string;
+ decimals: number;
+}
+
+export interface TxOpts {
+ from: string;
+ gas?: number;
+ value?: BigNumber;
+ gasPrice?: BigNumber;
+}
+
+export interface TokenAddressBySymbol {
+ [symbol: string]: string;
+}
+
+export type ContractEvents = ERC20TokenEvents | ERC721TokenEvents | ExchangeEvents | WETH9Events;
+
+export interface IndexedFilterValues {
+ [index: string]: ContractEventArg;
+}
+
+export interface BlockRange {
+ fromBlock: BlockParam;
+ toBlock: BlockParam;
+}
+
+export interface OrderFillRequest {
+ signedOrder: SignedOrder;
+ takerAssetFillAmount: BigNumber;
+}
+
+export type AsyncMethod = (...args: any[]) => Promise<any>;
+export type SyncMethod = (...args: any[]) => any;
+
+/**
+ * networkId: The id of the underlying ethereum network your provider is connected to. (1-mainnet, 3-ropsten, 4-rinkeby, 42-kovan, 50-testrpc)
+ * gasPrice: Gas price to use with every transaction
+ * exchangeContractAddress: The address of an exchange contract to use
+ * zrxContractAddress: The address of the ZRX contract to use
+ * erc20ProxyContractAddress: The address of the erc20 token transfer proxy contract to use
+ * erc721ProxyContractAddress: The address of the erc721 token transfer proxy contract to use
+ * forwarderContractAddress: The address of the forwarder contract to use
+ * orderWatcherConfig: All the configs related to the orderWatcher
+ * blockPollingIntervalMs: The interval to use for block polling in event watching methods (defaults to 1000)
+ */
+export interface ContractWrappersConfig {
+ networkId: number;
+ gasPrice?: BigNumber;
+ exchangeContractAddress?: string;
+ zrxContractAddress?: string;
+ erc20ProxyContractAddress?: string;
+ erc721ProxyContractAddress?: string;
+ forwarderContractAddress?: string;
+ blockPollingIntervalMs?: number;
+}
+
+/**
+ * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the
+ * supplied order maker has a sufficient allowance/balance to fill this amount of the order's
+ * takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient
+ * allowance/balance to fill the entire remaining order amount.
+ */
+export interface ValidateOrderFillableOpts {
+ expectedFillTakerTokenAmount?: BigNumber;
+}
+
+/**
+ * defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number
+ * let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the
+ * backing Ethereum node must keep the entire historical state of the chain (e.g setting `--pruning=archive`
+ * flag when running Parity).
+ */
+export interface MethodOpts {
+ defaultBlock?: BlockParam;
+}
+
+/**
+ * gasPrice: Gas price in Wei to use for a transaction
+ * gasLimit: The amount of gas to send with a transaction (in Gwei)
+ */
+export interface TransactionOpts {
+ gasPrice?: BigNumber;
+ gasLimit?: number;
+}
+
+/**
+ * shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before
+ * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default=true.
+ */
+export interface OrderTransactionOpts extends TransactionOpts {
+ shouldValidate?: boolean;
+}
+
+export enum TradeSide {
+ Maker = 'maker',
+ Taker = 'taker',
+}
+
+export enum TransferType {
+ Trade = 'trade',
+ Fee = 'fee',
+}
+
+export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void;
+
+export interface OrderInfo {
+ orderStatus: OrderStatus;
+ orderHash: string;
+ orderTakerAssetFilledAmount: BigNumber;
+}
+
+export enum OrderStatus {
+ INVALID = 0,
+ INVALID_MAKER_ASSET_AMOUNT,
+ INVALID_TAKER_ASSET_AMOUNT,
+ FILLABLE,
+ EXPIRED,
+ FULLY_FILLED,
+ CANCELLED,
+}
diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts
new file mode 100644
index 000000000..183642170
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/assert.ts
@@ -0,0 +1,90 @@
+import { assert as sharedAssert } from '@0xproject/assert';
+// HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here
+import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable
+import { assetDataUtils, isValidSignatureAsync } from '@0xproject/order-utils';
+import { ECSignature, Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable
+import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
+
+export const assert = {
+ ...sharedAssert,
+ async isValidSignatureAsync(
+ provider: Provider,
+ orderHash: string,
+ signature: string,
+ signerAddress: string,
+ ): Promise<void> {
+ const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress);
+ sharedAssert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
+ },
+ isValidSubscriptionToken(variableName: string, subscriptionToken: string): void {
+ const uuidRegex = new RegExp('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$');
+ const isValid = uuidRegex.test(subscriptionToken);
+ sharedAssert.assert(isValid, `Expected ${variableName} to be a valid subscription token`);
+ },
+ async isSenderAddressAsync(
+ variableName: string,
+ senderAddressHex: string,
+ web3Wrapper: Web3Wrapper,
+ ): Promise<void> {
+ sharedAssert.isETHAddressHex(variableName, senderAddressHex);
+ const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
+ sharedAssert.assert(
+ isSenderAddressAvailable,
+ `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
+ );
+ },
+ ordersCanBeUsedForForwarderContract(orders: Order[], etherTokenAddress: string): void {
+ sharedAssert.assert(!_.isEmpty(orders), 'Expected at least 1 signed order. Found no orders');
+ assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'makerAssetData');
+ assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
+ assert.allTakerAddressesAreNull(orders);
+ },
+ feeOrdersCanBeUsedForForwarderContract(orders: Order[], zrxTokenAddress: string, etherTokenAddress: string): void {
+ if (!_.isEmpty(orders)) {
+ assert.allMakerAssetDatasAreErc20Token(orders, zrxTokenAddress);
+ assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
+ }
+ },
+ allTakerAddressesAreNull(orders: Order[]): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'takerAddress', constants.NULL_ADDRESS);
+ },
+ allMakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(
+ orders,
+ 'makerAssetData',
+ assetDataUtils.encodeERC20AssetData(tokenAddress),
+ );
+ },
+ allTakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(
+ orders,
+ 'takerAssetData',
+ assetDataUtils.encodeERC20AssetData(tokenAddress),
+ );
+ },
+ /*
+ * Asserts that all the orders have the same value for the provided propertyName
+ * If the value parameter is provided, this asserts that all orders have the prope
+ */
+ ordersHaveAtMostOneUniqueValueForProperty(orders: Order[], propertyName: string, value?: any): void {
+ const allValues = _.map(orders, order => _.get(order, propertyName));
+ sharedAssert.hasAtMostOneUniqueValue(
+ allValues,
+ `Expected all orders to have the same ${propertyName} field. Found the following ${propertyName} values: ${JSON.stringify(
+ allValues,
+ )}`,
+ );
+ if (!_.isUndefined(value)) {
+ const firstValue = _.head(allValues);
+ sharedAssert.assert(
+ firstValue === value,
+ `Expected all orders to have a ${propertyName} field with value: ${value}. Found: ${firstValue}`,
+ );
+ }
+ },
+};
diff --git a/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts
new file mode 100644
index 000000000..3172cf531
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts
@@ -0,0 +1,44 @@
+import { SignedOrder } from '@0xproject/types';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
+
+export const calldataOptimizationUtils = {
+ /**
+ * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
+ * all makerAssetData are '0x' except for that of the first order, which retains its original value
+ * @param orders An array of SignedOrder objects
+ * @returns optimized orders
+ */
+ optimizeForwarderOrders(orders: SignedOrder[]): SignedOrder[] {
+ const optimizedOrders = _.map(orders, (order, index) =>
+ transformOrder(order, {
+ makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES,
+ takerAssetData: constants.NULL_BYTES,
+ }),
+ );
+ return optimizedOrders;
+ },
+ /**
+ * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
+ * all makerAssetData are '0x'
+ * @param orders An array of SignedOrder objects
+ * @returns optimized orders
+ */
+ optimizeForwarderFeeOrders(orders: SignedOrder[]): SignedOrder[] {
+ const optimizedOrders = _.map(orders, (order, index) =>
+ transformOrder(order, {
+ makerAssetData: constants.NULL_BYTES,
+ takerAssetData: constants.NULL_BYTES,
+ }),
+ );
+ return optimizedOrders;
+ },
+};
+
+const transformOrder = (order: SignedOrder, partialOrder: Partial<SignedOrder>) => {
+ return {
+ ...order,
+ ...partialOrder,
+ };
+};
diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts
new file mode 100644
index 000000000..2df11538c
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/constants.ts
@@ -0,0 +1,15 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const constants = {
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ NULL_BYTES: '0x',
+ TESTRPC_NETWORK_ID: 50,
+ INVALID_JUMP_PATTERN: 'invalid JUMP at',
+ REVERT: 'revert',
+ OUT_OF_GAS_PATTERN: 'out of gas',
+ INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
+ // tslint:disable-next-line:custom-no-magic-numbers
+ UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
+ DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
+ ZERO_AMOUNT: new BigNumber(0),
+};
diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts
new file mode 100644
index 000000000..6e77450e8
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/decorators.ts
@@ -0,0 +1,96 @@
+import { RevertReason } from '@0xproject/types';
+import * as _ from 'lodash';
+
+import { AsyncMethod, ContractWrappersError, SyncMethod } from '../types';
+
+import { constants } from './constants';
+
+type ErrorTransformer = (err: Error) => Error;
+
+const contractCallErrorTransformer = (error: Error) => {
+ if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
+ return new Error(ContractWrappersError.InvalidJump);
+ }
+ if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
+ return new Error(ContractWrappersError.OutOfGas);
+ }
+ if (_.includes(error.message, constants.REVERT)) {
+ const revertReason = error.message.split(constants.REVERT)[1].trim();
+ return new Error(revertReason);
+ }
+ return error;
+};
+
+const schemaErrorTransformer = (error: Error) => {
+ if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) {
+ const errMsg =
+ 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
+ return new Error(errMsg);
+ }
+ return error;
+};
+
+/**
+ * Source: https://stackoverflow.com/a/29837695/3546986
+ */
+const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
+ const asyncErrorHandlingDecorator = (
+ _target: object,
+ _key: string | symbol,
+ descriptor: TypedPropertyDescriptor<AsyncMethod>,
+ ) => {
+ const originalMethod = descriptor.value as AsyncMethod;
+
+ // Do not use arrow syntax here. Use a function expression in
+ // order to use the correct value of `this` in this method
+ // tslint:disable-next-line:only-arrow-functions
+ descriptor.value = async function(...args: any[]): Promise<any> {
+ try {
+ const result = await originalMethod.apply(this, args);
+ return result;
+ } catch (error) {
+ const transformedError = errorTransformer(error);
+ throw transformedError;
+ }
+ };
+
+ return descriptor;
+ };
+
+ return asyncErrorHandlingDecorator;
+};
+
+const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
+ const syncErrorHandlingDecorator = (
+ _target: object,
+ _key: string | symbol,
+ descriptor: TypedPropertyDescriptor<SyncMethod>,
+ ) => {
+ const originalMethod = descriptor.value as SyncMethod;
+
+ // Do not use arrow syntax here. Use a function expression in
+ // order to use the correct value of `this` in this method
+ // tslint:disable-next-line:only-arrow-functions
+ descriptor.value = function(...args: any[]): any {
+ try {
+ const result = originalMethod.apply(this, args);
+ return result;
+ } catch (error) {
+ const transformedError = errorTransformer(error);
+ throw transformedError;
+ }
+ };
+
+ return descriptor;
+ };
+
+ return syncErrorHandlingDecorator;
+};
+
+// _.flow(f, g) = f ∘ g
+const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
+
+export const decorators = {
+ asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
+ syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer),
+};
diff --git a/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts
new file mode 100644
index 000000000..279f2a796
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts
@@ -0,0 +1,111 @@
+import { ExchangeContractErrs } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+import { AbstractBalanceAndProxyAllowanceLazyStore } from '../abstract/abstract_balance_and_proxy_allowance_lazy_store';
+import { TradeSide, TransferType } from '../types';
+import { constants } from '../utils/constants';
+
+enum FailureReason {
+ Balance = 'balance',
+ ProxyAllowance = 'proxyAllowance',
+}
+
+const ERR_MSG_MAPPING = {
+ [FailureReason.Balance]: {
+ [TradeSide.Maker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
+ },
+ [TradeSide.Taker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
+ },
+ },
+ [FailureReason.ProxyAllowance]: {
+ [TradeSide.Maker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
+ },
+ [TradeSide.Taker]: {
+ [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
+ [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
+ },
+ },
+};
+
+export class ExchangeTransferSimulator {
+ private _store: AbstractBalanceAndProxyAllowanceLazyStore;
+ private static _throwValidationError(
+ failureReason: FailureReason,
+ tradeSide: TradeSide,
+ transferType: TransferType,
+ ): never {
+ const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
+ throw new Error(errMsg);
+ }
+ constructor(store: AbstractBalanceAndProxyAllowanceLazyStore) {
+ this._store = store;
+ }
+ /**
+ * Simulates transferFrom call performed by a proxy
+ * @param tokenAddress Address of the token to be transferred
+ * @param from Owner of the transferred tokens
+ * @param to Recipient of the transferred tokens
+ * @param amountInBaseUnits The amount of tokens being transferred
+ * @param tradeSide Is Maker/Taker transferring
+ * @param transferType Is it a fee payment or a value transfer
+ */
+ public async transferFromAsync(
+ tokenAddress: string,
+ from: string,
+ to: string,
+ amountInBaseUnits: BigNumber,
+ tradeSide: TradeSide,
+ transferType: TransferType,
+ ): Promise<void> {
+ // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
+ // allowances for the taker. We do however, want to increase the balance of the maker since the maker
+ // might be relying on those funds to fill subsequent orders or pay the order's fees.
+ if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
+ await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
+ return;
+ }
+ const balance = await this._store.getBalanceAsync(tokenAddress, from);
+ const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
+ if (proxyAllowance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
+ }
+ if (balance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
+ }
+ await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
+ await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
+ await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
+ }
+ private async _decreaseProxyAllowanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
+ if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
+ }
+ }
+ private async _increaseBalanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
+ this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
+ }
+ private async _decreaseBalanceAsync(
+ tokenAddress: string,
+ userAddress: string,
+ amountInBaseUnits: BigNumber,
+ ): Promise<void> {
+ const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
+ this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
+ }
+}
diff --git a/packages/contract-wrappers/src/utils/filter_utils.ts b/packages/contract-wrappers/src/utils/filter_utils.ts
new file mode 100644
index 000000000..0e73987f7
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/filter_utils.ts
@@ -0,0 +1,87 @@
+import { ConstructorAbi, ContractAbi, EventAbi, FallbackAbi, FilterObject, LogEntry, MethodAbi } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as jsSHA3 from 'js-sha3';
+import * as _ from 'lodash';
+import * as uuid from 'uuid/v4';
+
+import { BlockRange, ContractEvents, IndexedFilterValues } from '../types';
+
+const TOPIC_LENGTH = 32;
+
+export const filterUtils = {
+ generateUUID(): string {
+ return uuid();
+ },
+ getFilter(
+ address: string,
+ eventName: ContractEvents,
+ indexFilterValues: IndexedFilterValues,
+ abi: ContractAbi,
+ blockRange?: BlockRange,
+ ): FilterObject {
+ const eventAbi = _.find(abi, { name: eventName }) as EventAbi;
+ const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi);
+ const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
+ const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
+ const topics = [topicForEventSignature, ...topicsForIndexedArgs];
+ let filter: FilterObject = {
+ address,
+ topics,
+ };
+ if (!_.isUndefined(blockRange)) {
+ filter = {
+ ...blockRange,
+ ...filter,
+ };
+ }
+ return filter;
+ },
+ getEventSignatureFromAbiByName(eventAbi: EventAbi): string {
+ const types = _.map(eventAbi.inputs, 'type');
+ const signature = `${eventAbi.name}(${types.join(',')})`;
+ return signature;
+ },
+ getTopicsForIndexedArgs(abi: EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> {
+ const topics: Array<string | null> = [];
+ for (const eventInput of abi.inputs) {
+ if (!eventInput.indexed) {
+ continue;
+ }
+ if (_.isUndefined(indexFilterValues[eventInput.name])) {
+ // Null is a wildcard topic in a JSON-RPC call
+ topics.push(null);
+ } else {
+ const value = indexFilterValues[eventInput.name] as string;
+ const buffer = ethUtil.toBuffer(value);
+ const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
+ const topic = ethUtil.bufferToHex(paddedBuffer);
+ topics.push(topic);
+ }
+ }
+ return topics;
+ },
+ matchesFilter(log: LogEntry, filter: FilterObject): boolean {
+ if (!_.isUndefined(filter.address) && log.address !== filter.address) {
+ return false;
+ }
+ if (!_.isUndefined(filter.topics)) {
+ return filterUtils.doesMatchTopics(log.topics, filter.topics);
+ }
+ return true;
+ },
+ doesMatchTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
+ const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
+ const doesMatchTopics = _.every(matchesTopic);
+ return doesMatchTopics;
+ },
+ matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
+ if (_.isArray(filterTopic)) {
+ return _.includes(filterTopic, logTopic);
+ }
+ if (_.isString(filterTopic)) {
+ return filterTopic === logTopic;
+ }
+ // null topic is a wildcard
+ return true;
+ },
+};
diff --git a/packages/contract-wrappers/src/utils/utils.ts b/packages/contract-wrappers/src/utils/utils.ts
new file mode 100644
index 000000000..689a7ee0a
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/utils.ts
@@ -0,0 +1,11 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const utils = {
+ getCurrentUnixTimestampSec(): BigNumber {
+ const milisecondsInSecond = 1000;
+ return new BigNumber(Date.now() / milisecondsInSecond).round();
+ },
+ getCurrentUnixTimestampMs(): BigNumber {
+ return new BigNumber(Date.now());
+ },
+};