aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers/src/contract_wrappers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contract-wrappers/src/contract_wrappers')
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts208
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts207
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts939
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts131
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts72
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts441
6 files changed, 1998 insertions, 0 deletions
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..f255ced62
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts
@@ -0,0 +1,208 @@
+import {
+ Artifact,
+ BlockParamLiteral,
+ ContractAbi,
+ FilterObject,
+ LogEntry,
+ LogWithDecodedArgs,
+ RawLog,
+} from '@0xproject/types';
+import { AbiDecoder, intervalUtils } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream';
+import * as _ from 'lodash';
+
+import {
+ BlockRange,
+ ContractEventArgs,
+ ContractEvents,
+ ContractWrappersError,
+ EventCallback,
+ IndexedFilterValues,
+ InternalContractWrappersError,
+} 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,
+ Token: ContractWrappersError.TokenContractDoesNotExist,
+ TokenRegistry: ContractWrappersError.TokenRegistryContractDoesNotExist,
+ TokenTransferProxy: ContractWrappersError.TokenTransferProxyContractDoesNotExist,
+ Exchange: ContractWrappersError.ExchangeContractDoesNotExist,
+};
+
+export class ContractWrapper {
+ protected _web3Wrapper: Web3Wrapper;
+ protected _networkId: number;
+ private _blockAndLogStreamerIfExists?: BlockAndLogStreamer;
+ private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer;
+ private _filters: { [filterToken: string]: FilterObject };
+ private _filterCallbacks: {
+ [filterToken: string]: EventCallback<ContractEventArgs>;
+ };
+ private _onLogAddedSubscriptionToken: string | undefined;
+ private _onLogRemovedSubscriptionToken: string | undefined;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number) {
+ this._web3Wrapper = web3Wrapper;
+ this._networkId = networkId;
+ 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>,
+ ): string {
+ const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
+ if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
+ this._startBlockAndLogStream();
+ }
+ 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 {
+ if (_.isUndefined(this._web3Wrapper.abiDecoder)) {
+ throw new Error(InternalContractWrappersError.NoAbiDecoder);
+ }
+ const logWithDecodedArgs = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop(log);
+ return logWithDecodedArgs;
+ }
+ protected async _getContractAbiAndAddressFromArtifactsAsync(
+ artifact: Artifact,
+ 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.contract_name]);
+ }
+ const abiAndAddress: [ContractAbi, string] = [artifact.abi, contractAddress];
+ return abiAndAddress;
+ }
+ protected _getContractAddress(artifact: Artifact, 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(): 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),
+ );
+ const catchAllLogFilter = {};
+ this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
+ this._blockAndLogStreamIntervalIfExists = intervalUtils.setAsyncExcludingInterval(
+ this._reconcileBlockAsync.bind(this),
+ constants.DEFAULT_BLOCK_POLLING_INTERVAL,
+ this._onReconcileBlockError.bind(this),
+ );
+ 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),
+ );
+ }
+ private _onReconcileBlockError(err: Error): void {
+ const filterTokens = _.keys(this._filterCallbacks);
+ _.each(filterTokens, filterToken => {
+ this._unsubscribe(filterToken, err);
+ });
+ }
+ 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/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts
new file mode 100644
index 000000000..1bd65270b
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts
@@ -0,0 +1,207 @@
+import { schemas } from '@0xproject/json-schemas';
+import { LogWithDecodedArgs } from '@0xproject/types';
+import { AbiDecoder, BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+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 { EtherTokenContract, EtherTokenContractEventArgs, EtherTokenEvents } from './generated/ether_token';
+import { TokenWrapper } from './token_wrapper';
+
+/**
+ * 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 {
+ private _etherTokenContractsByAddress: {
+ [address: string]: EtherTokenContract;
+ } = {};
+ private _tokenWrapper: TokenWrapper;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenWrapper: TokenWrapper) {
+ super(web3Wrapper, networkId);
+ this._tokenWrapper = tokenWrapper;
+ }
+ /**
+ * 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({
+ 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._tokenWrapper.getBalanceAsync(
+ normalizedEtherTokenAddress,
+ normalizedWithdrawerAddress,
+ );
+ assert.assert(
+ WETHBalanceInBaseUnits.gte(amountInWei),
+ ContractWrappersError.InsufficientWEthBalanceForWithdrawal,
+ );
+
+ const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress);
+ const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
+ 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 EtherTokenContractEventArgs>(
+ etherTokenAddress: string,
+ eventName: EtherTokenEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedEtherTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.EtherToken.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
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends EtherTokenContractEventArgs>(
+ etherTokenAddress: string,
+ eventName: EtherTokenEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ ): string {
+ assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
+ const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedEtherTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.EtherToken.abi,
+ callback,
+ );
+ 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();
+ }
+ /**
+ * 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;
+ }
+ private _invalidateContractInstance(): void {
+ this.unsubscribeAll();
+ this._etherTokenContractsByAddress = {};
+ }
+ private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
+ let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress];
+ if (!_.isUndefined(etherTokenContract)) {
+ return etherTokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.EtherToken,
+ etherTokenAddress,
+ );
+ const contractInstance = new EtherTokenContract(
+ 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..fc177dd42
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
@@ -0,0 +1,939 @@
+import { schemas } from '@0xproject/json-schemas';
+import { formatters, getOrderHashHex, OrderStateUtils } from '@0xproject/order-utils';
+import {
+ BlockParamLiteral,
+ DecodedLogArgs,
+ ECSignature,
+ ExchangeContractErrs,
+ LogEntry,
+ LogWithDecodedArgs,
+ Order,
+ OrderAddresses,
+ OrderState,
+ OrderValues,
+ SignedOrder,
+} from '@0xproject/types';
+import { AbiDecoder, BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { SimpleBalanceAndProxyAllowanceFetcher } from '../fetchers/simple_balance_and_proxy_allowance_fetcher';
+import { SimpleOrderFilledCancelledFetcher } from '../fetchers/simple_order_filled_cancelled_fetcher';
+import {
+ BlockRange,
+ EventCallback,
+ ExchangeContractErrCodes,
+ IndexedFilterValues,
+ MethodOpts,
+ OrderCancellationRequest,
+ OrderFillRequest,
+ OrderTransactionOpts,
+ ValidateOrderFillableOpts,
+} from '../types';
+import { assert } from '../utils/assert';
+import { decorators } from '../utils/decorators';
+import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator';
+import { OrderValidationUtils } from '../utils/order_validation_utils';
+import { utils } from '../utils/utils';
+
+import { ContractWrapper } from './contract_wrapper';
+import {
+ ExchangeContract,
+ ExchangeContractEventArgs,
+ ExchangeEvents,
+ LogErrorContractEventArgs,
+} from './generated/exchange';
+import { TokenWrapper } from './token_wrapper';
+const SHOULD_VALIDATE_BY_DEFAULT = true;
+
+interface ExchangeContractErrCodesToMsgs {
+ [exchangeContractErrCodes: number]: string;
+}
+
+/**
+ * This class includes all the functionality related to calling methods and subscribing to
+ * events of the 0x Exchange smart contract.
+ */
+export class ExchangeWrapper extends ContractWrapper {
+ private _exchangeContractIfExists?: ExchangeContract;
+ private _orderValidationUtils: OrderValidationUtils;
+ private _tokenWrapper: TokenWrapper;
+ private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = {
+ [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
+ [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
+ [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero,
+ [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero,
+ [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError,
+ [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError,
+ };
+ private _contractAddressIfExists?: string;
+ private _zrxContractAddressIfExists?: string;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ tokenWrapper: TokenWrapper,
+ contractAddressIfExists?: string,
+ zrxContractAddressIfExists?: string,
+ ) {
+ super(web3Wrapper, networkId);
+ this._tokenWrapper = tokenWrapper;
+ this._orderValidationUtils = new OrderValidationUtils(this);
+ this._contractAddressIfExists = contractAddressIfExists;
+ this._zrxContractAddressIfExists = zrxContractAddressIfExists;
+ }
+ /**
+ * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total
+ * amount that has been filled or cancelled. The remaining takerAmount can be calculated by
+ * subtracting the unavailable amount from the total order takerAmount.
+ * @param orderHash The hex encoded orderHash for which you would like to retrieve the
+ * unavailable takerAmount.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The amount of the order (in taker tokens) that has either been filled or cancelled.
+ */
+ public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> {
+ assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
+
+ const exchangeContract = await this._getExchangeContractAsync();
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync(
+ orderHash,
+ txData,
+ defaultBlock,
+ );
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount);
+ return unavailableTakerTokenAmount;
+ }
+ /**
+ * Retrieve the takerAmount of an order that has already been filled.
+ * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The amount of the order (in taker tokens) that has already been filled.
+ */
+ public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> {
+ assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
+
+ const exchangeContract = await this._getExchangeContractAsync();
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, txData, defaultBlock);
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits);
+ return fillAmountInBaseUnits;
+ }
+ /**
+ * Retrieve the takerAmount of an order that has been cancelled.
+ * @param orderHash The hex encoded orderHash for which you would like to retrieve the
+ * cancelled takerAmount.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The amount of the order (in taker tokens) that has been cancelled.
+ */
+ public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> {
+ assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
+
+ const exchangeContract = await this._getExchangeContractAsync();
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, txData, defaultBlock);
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits);
+ return cancelledAmountInBaseUnits;
+ }
+ /**
+ * Fills a signed order with an amount denominated in baseUnits of the taker token.
+ * Since the order in which transactions are included in the next block is indeterminate, race-conditions
+ * could arise where a users balance or allowance changes before the fillOrder executes. Because of this,
+ * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`.
+ * If false, the smart contract will not throw if the parties
+ * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check
+ * and causes the smart contract to throw (using all the gas supplied) instead.
+ * @param signedOrder An object that conforms to the SignedOrder interface.
+ * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that
+ * you wish to fill.
+ * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw
+ * if upon execution the tokens cannot be transferred.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Provider
+ * passed to 0x.js.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrderAsync(
+ signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ shouldThrowOnInsufficientBalanceOrAllowance: boolean,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+
+ const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(signedOrder);
+
+ const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync(
+ orderAddresses,
+ orderValues,
+ fillTakerTokenAmount,
+ shouldThrowOnInsufficientBalanceOrAllowance,
+ signedOrder.ecSignature.v,
+ signedOrder.ecSignature.r,
+ signedOrder.ecSignature.s,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
+ * If the fill amount is reached - it succeeds and does not fill the rest of the orders.
+ * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds.
+ * @param signedOrders The array of signedOrders that you would like to fill until
+ * takerTokenFillAmount is reached.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if
+ * upon execution any of the tokens cannot be transferred.
+ * If set to false, the call will continue to fill subsequent
+ * signedOrders even when some cannot be filled.
+ * @param takerAddress The user Ethereum address who would like to fill these
+ * orders. Must be available via the supplied Provider
+ * passed to 0x.js.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrdersUpToAsync(
+ signedOrders: SignedOrder[],
+ fillTakerTokenAmount: BigNumber,
+ shouldThrowOnInsufficientBalanceOrAllowance: boolean,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
+ assert.hasAtMostOneUniqueValue(
+ takerTokenAddresses,
+ ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed,
+ );
+ const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress);
+ assert.hasAtMostOneUniqueValue(
+ exchangeContractAddresses,
+ ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress,
+ );
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ let filledTakerTokenAmount = new BigNumber(0);
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ for (const signedOrder of signedOrders) {
+ const singleFilledTakerTokenAmount = await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount.minus(filledTakerTokenAmount),
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount);
+ if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) {
+ break;
+ }
+ }
+ }
+
+ if (_.isEmpty(signedOrders)) {
+ throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
+ }
+
+ const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
+ return [
+ ...formatters.getOrderAddressesAndValues(signedOrder),
+ signedOrder.ecSignature.v,
+ signedOrder.ecSignature.r,
+ signedOrder.ecSignature.s,
+ ];
+ });
+ // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
+ const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>(
+ orderAddressesValuesAndSignatureArray,
+ );
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync(
+ orderAddressesArray,
+ orderValuesArray,
+ fillTakerTokenAmount,
+ shouldThrowOnInsufficientBalanceOrAllowance,
+ vArray,
+ rArray,
+ sArray,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of fillOrderAsync.
+ * Executes multiple fills atomically in a single transaction.
+ * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even
+ * when earlier ones fail.
+ * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails.
+ * @param orderFillRequests An array of objects that conform to the
+ * OrderFillRequest interface.
+ * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw
+ * if upon execution any of the tokens cannot be
+ * transferred. If set to false, the call will continue to
+ * fill subsequent signedOrders even when some
+ * cannot be filled.
+ * @param takerAddress The user Ethereum address who would like to fill
+ * these orders. Must be available via the supplied
+ * Provider passed to 0x.js.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchFillOrdersAsync(
+ orderFillRequests: OrderFillRequest[],
+ shouldThrowOnInsufficientBalanceOrAllowance: boolean,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
+ const exchangeContractAddresses = _.map(
+ orderFillRequests,
+ orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
+ );
+ assert.hasAtMostOneUniqueValue(
+ exchangeContractAddresses,
+ ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress,
+ );
+ assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ for (const orderFillRequest of orderFillRequests) {
+ await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ orderFillRequest.signedOrder,
+ orderFillRequest.takerTokenFillAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+ }
+ if (_.isEmpty(orderFillRequests)) {
+ throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
+ }
+
+ const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => {
+ return [
+ ...formatters.getOrderAddressesAndValues(orderFillRequest.signedOrder),
+ orderFillRequest.takerTokenFillAmount,
+ orderFillRequest.signedOrder.ecSignature.v,
+ orderFillRequest.signedOrder.ecSignature.r,
+ orderFillRequest.signedOrder.ecSignature.s,
+ ];
+ });
+ // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
+ const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>(
+ orderAddressesValuesAmountsAndSignatureArray,
+ );
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
+ orderAddressesArray,
+ orderValuesArray,
+ fillTakerTokenAmounts,
+ shouldThrowOnInsufficientBalanceOrAllowance,
+ vArray,
+ rArray,
+ sArray,
+ {
+ 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. The
+ * signedOrder you wish to fill.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Provider passed to 0x.js.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async fillOrKillOrderAsync(
+ signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+
+ const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(signedOrder);
+ const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
+ orderAddresses,
+ orderValues,
+ fillTakerTokenAmount,
+ signedOrder.ecSignature.v,
+ signedOrder.ecSignature.r,
+ signedOrder.ecSignature.s,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically
+ * filled (each to the specified fillAmount) or aborted.
+ * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface.
+ * @param takerAddress The user Ethereum address who would like to fill there orders.
+ * Must be available via the supplied Provider passed to 0x.js.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchFillOrKillAsync(
+ orderFillRequests: OrderFillRequest[],
+ takerAddress: string,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
+ const exchangeContractAddresses = _.map(
+ orderFillRequests,
+ orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
+ );
+ assert.hasAtMostOneUniqueValue(
+ exchangeContractAddresses,
+ ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress,
+ );
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ if (_.isEmpty(orderFillRequests)) {
+ throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ for (const orderFillRequest of orderFillRequests) {
+ await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ orderFillRequest.signedOrder,
+ orderFillRequest.takerTokenFillAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+ }
+
+ const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => {
+ return [
+ ...formatters.getOrderAddressesAndValues(request.signedOrder),
+ request.takerTokenFillAmount,
+ request.signedOrder.ecSignature.v,
+ request.signedOrder.ecSignature.r,
+ request.signedOrder.ecSignature.s,
+ ];
+ });
+
+ // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
+ const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>(
+ orderAddressesValuesAndTakerTokenFillAmounts,
+ );
+ const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
+ orderAddresses,
+ orderValues,
+ fillTakerTokenAmounts,
+ vParams,
+ rParams,
+ sParams,
+ {
+ from: normalizedTakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Cancel a given fill amount of an order. Cancellations are cumulative.
+ * @param order An object that conforms to the Order or SignedOrder interface.
+ * The order you would like to cancel.
+ * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
+ * @param transactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async cancelOrderAsync(
+ order: Order | SignedOrder,
+ cancelTakerTokenAmount: BigNumber,
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount);
+ await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
+ const normalizedMakerAddress = order.maker.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ const orderHash = getOrderHashHex(order);
+ const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
+ OrderValidationUtils.validateCancelOrderThrowIfInvalid(
+ order,
+ cancelTakerTokenAmount,
+ unavailableTakerTokenAmount,
+ );
+ }
+
+ const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(order);
+ const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(
+ orderAddresses,
+ orderValues,
+ cancelTakerTokenAmount,
+ {
+ from: normalizedMakerAddress,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction.
+ * All orders must be from the same maker.
+ * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
+ * interface.
+ * @param transactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async batchCancelOrdersAsync(
+ orderCancellationRequests: OrderCancellationRequest[],
+ orderTransactionOpts: OrderTransactionOpts = {},
+ ): Promise<string> {
+ assert.doesConformToSchema(
+ 'orderCancellationRequests',
+ orderCancellationRequests,
+ schemas.orderCancellationRequestsSchema,
+ );
+ const exchangeContractAddresses = _.map(
+ orderCancellationRequests,
+ orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress,
+ );
+ assert.hasAtMostOneUniqueValue(
+ exchangeContractAddresses,
+ ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress,
+ );
+ const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker);
+ assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
+ const maker = makers[0];
+ await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
+ const normalizedMakerAddress = maker.toLowerCase();
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate)
+ ? SHOULD_VALIDATE_BY_DEFAULT
+ : orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ for (const orderCancellationRequest of orderCancellationRequests) {
+ const orderHash = getOrderHashHex(orderCancellationRequest.order);
+ const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
+ OrderValidationUtils.validateCancelOrderThrowIfInvalid(
+ orderCancellationRequest.order,
+ orderCancellationRequest.takerTokenCancelAmount,
+ unavailableTakerTokenAmount,
+ );
+ }
+ }
+ if (_.isEmpty(orderCancellationRequests)) {
+ throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => {
+ return [
+ ...formatters.getOrderAddressesAndValues(cancellationRequest.order),
+ cancellationRequest.takerTokenCancelAmount,
+ ];
+ });
+ // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
+ const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>(
+ orderAddressesValuesAndTakerTokenCancelAmounts,
+ );
+ const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(
+ orderAddresses,
+ orderValues,
+ cancelTakerTokenAmounts,
+ {
+ from: normalizedMakerAddress,
+ 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
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends ExchangeContractEventArgs>(
+ eventName: ExchangeEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ ): 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.abi,
+ callback,
+ );
+ 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 ExchangeContractEventArgs>(
+ 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.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;
+ }
+ /**
+ * Checks if order is still fillable and throws an error otherwise. Useful for orderbook
+ * pruning where you want to remove stale orders without knowing who the taker will be.
+ * @param signedOrder An object that conforms to the SignedOrder interface. The
+ * signedOrder you wish to validate.
+ * @param opts An object that conforms to the ValidateOrderFillableOpts
+ * interface. Allows specifying a specific fillTakerTokenAmount
+ * to validate for.
+ */
+ public async validateOrderFillableOrThrowAsync(
+ signedOrder: SignedOrder,
+ opts?: ValidateOrderFillableOpts,
+ ): Promise<void> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined;
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ await this._orderValidationUtils.validateOrderFillableOrThrowAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ zrxTokenAddress,
+ expectedFillTakerTokenAmount,
+ );
+ }
+ /**
+ * Checks if order fill will succeed and throws an error otherwise.
+ * @param signedOrder An object that conforms to the SignedOrder interface. The
+ * signedOrder you wish to fill.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Provider passed to 0x.js.
+ */
+ public async validateFillOrderThrowIfInvalidAsync(
+ signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ takerAddress: string,
+ ): Promise<void> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+ /**
+ * Checks if cancelling a given order will succeed and throws an informative error if it won't.
+ * @param order An object that conforms to the Order or SignedOrder interface.
+ * The order you would like to cancel.
+ * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
+ */
+ public async validateCancelOrderThrowIfInvalidAsync(
+ order: Order,
+ cancelTakerTokenAmount: BigNumber,
+ ): Promise<void> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount);
+ const orderHash = getOrderHashHex(order);
+ const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
+ OrderValidationUtils.validateCancelOrderThrowIfInvalid(
+ order,
+ cancelTakerTokenAmount,
+ unavailableTakerTokenAmount,
+ );
+ }
+ /**
+ * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't.
+ * @param signedOrder An object that conforms to the SignedOrder interface. The
+ * signedOrder you wish to fill.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Provider passed to 0x.js.
+ */
+ public async validateFillOrKillOrderThrowIfInvalidAsync(
+ signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber,
+ takerAddress: string,
+ ): Promise<void> {
+ assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const zrxTokenAddress = this.getZRXTokenAddress();
+ const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
+ await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
+ exchangeTradeEmulator,
+ signedOrder,
+ fillTakerTokenAmount,
+ normalizedTakerAddress,
+ zrxTokenAddress,
+ );
+ }
+ /**
+ * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing:
+ * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`.
+ * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or
+ * no decimals can only be filled in quantities and ratios that avoid large rounding errors.
+ * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill.
+ * @param takerTokenAmount The order size on the taker side
+ * @param makerTokenAmount The order size on the maker side
+ */
+ public async isRoundingErrorAsync(
+ fillTakerTokenAmount: BigNumber,
+ takerTokenAmount: BigNumber,
+ makerTokenAmount: BigNumber,
+ ): Promise<boolean> {
+ assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
+ assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount);
+ assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount);
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const isRoundingError = await exchangeInstance.isRoundingError.callAsync(
+ fillTakerTokenAmount,
+ takerTokenAmount,
+ makerTokenAmount,
+ );
+ return isRoundingError;
+ }
+ /**
+ * Checks if logs contain LogError, which is emitted by Exchange contract on transaction failure.
+ * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync`
+ */
+ public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | LogEntry>): void {
+ const errLog = _.find(logs, {
+ event: ExchangeEvents.LogError,
+ });
+ if (!_.isUndefined(errLog)) {
+ const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args;
+ const errCode = logArgs.errorId;
+ const errMessage = this._exchangeContractErrCodesToMsg[errCode];
+ throw new Error(errMessage);
+ }
+ }
+ /**
+ * Gets the latest OrderState of a signedOrder
+ * @param signedOrder The signedOrder
+ * @param stateLayer Optional, desired blockchain state layer (defaults to latest).
+ * @return OrderState of the signedOrder
+ */
+ public async getOrderStateAsync(
+ signedOrder: SignedOrder,
+ stateLayer: BlockParamLiteral = BlockParamLiteral.Latest,
+ ): Promise<OrderState> {
+ const simpleBalanceAndProxyAllowanceFetcher = new SimpleBalanceAndProxyAllowanceFetcher(
+ this._tokenWrapper,
+ stateLayer,
+ );
+ const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(this, stateLayer);
+ const orderStateUtils = new OrderStateUtils(
+ simpleBalanceAndProxyAllowanceFetcher,
+ simpleOrderFilledCancelledFetcher,
+ );
+ const orderState = orderStateUtils.getOrderStateAsync(signedOrder);
+ return orderState;
+ }
+ /**
+ * Returns the ZRX token address used by the exchange contract.
+ * @return Address of ZRX token
+ */
+ public getZRXTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ZRX, this._zrxContractAddressIfExists);
+ return contractAddress;
+ }
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ delete this._exchangeContractIfExists;
+ }
+ private async _isValidSignatureUsingContractCallAsync(
+ dataHex: string,
+ ecSignature: ECSignature,
+ signerAddressHex: string,
+ ): Promise<boolean> {
+ assert.isHexString('dataHex', dataHex);
+ assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema);
+ assert.isETHAddressHex('signerAddressHex', signerAddressHex);
+ const normalizedSignerAddress = signerAddressHex.toLowerCase();
+
+ const exchangeInstance = await this._getExchangeContractAsync();
+
+ const isValidSignature = await exchangeInstance.isValidSignature.callAsync(
+ normalizedSignerAddress,
+ dataHex,
+ ecSignature.v,
+ ecSignature.r,
+ ecSignature.s,
+ );
+ return isValidSignature;
+ }
+ private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> {
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(order);
+ const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues);
+ return orderHashHex;
+ }
+ 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/token_registry_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts
new file mode 100644
index 000000000..e75973658
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts
@@ -0,0 +1,131 @@
+import { Token } from '@0xproject/types';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { TokenMetadata } from '../types';
+import { assert } from '../utils/assert';
+import { constants } from '../utils/constants';
+
+import { ContractWrapper } from './contract_wrapper';
+import { TokenRegistryContract } from './generated/token_registry';
+
+/**
+ * This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
+ */
+export class TokenRegistryWrapper extends ContractWrapper {
+ private _tokenRegistryContractIfExists?: TokenRegistryContract;
+ private _contractAddressIfExists?: string;
+ private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined {
+ if (metadata[0] === constants.NULL_ADDRESS) {
+ return undefined;
+ }
+ const token = {
+ address: metadata[0],
+ name: metadata[1],
+ symbol: metadata[2],
+ decimals: metadata[3],
+ };
+ return token;
+ }
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ }
+ /**
+ * Retrieves all the tokens currently listed in the Token Registry smart contract
+ * @return An array of objects that conform to the Token interface.
+ */
+ public async getTokensAsync(): Promise<Token[]> {
+ const addresses = await this.getTokenAddressesAsync();
+ const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) =>
+ this.getTokenIfExistsAsync(address),
+ );
+ const tokens = await Promise.all(tokenPromises);
+ return tokens as Token[];
+ }
+ /**
+ * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract
+ * @return An array of token addresses.
+ */
+ public async getTokenAddressesAsync(): Promise<string[]> {
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
+ const lowerCaseAddresses = _.map(addresses, address => address.toLowerCase());
+ return lowerCaseAddresses;
+ }
+ /**
+ * Retrieves a token by address currently listed in the Token Registry smart contract
+ * @return An object that conforms to the Token interface or undefined if token not found.
+ */
+ public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> {
+ assert.isETHAddressHex('address', address);
+ const normalizedAddress = address.toLowerCase();
+
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(normalizedAddress);
+ const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
+ return token;
+ }
+ public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> {
+ assert.isString('symbol', symbol);
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol);
+ if (addressIfExists === constants.NULL_ADDRESS) {
+ return undefined;
+ }
+ return addressIfExists;
+ }
+ public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> {
+ assert.isString('name', name);
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
+ if (addressIfExists === constants.NULL_ADDRESS) {
+ return undefined;
+ }
+ return addressIfExists;
+ }
+ public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> {
+ assert.isString('symbol', symbol);
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
+ const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
+ return token;
+ }
+ public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> {
+ assert.isString('name', name);
+ const tokenRegistryContract = await this._getTokenRegistryContractAsync();
+ const metadata = await tokenRegistryContract.getTokenByName.callAsync(name);
+ const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
+ return token;
+ }
+ /**
+ * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the TokenRegistry contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.TokenRegistry, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ private _invalidateContractInstance(): void {
+ delete this._tokenRegistryContractIfExists;
+ }
+ private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> {
+ if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
+ return this._tokenRegistryContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.TokenRegistry,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new TokenRegistryContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._tokenRegistryContractIfExists = contractInstance;
+ return this._tokenRegistryContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts
new file mode 100644
index 000000000..02a2e19d0
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts
@@ -0,0 +1,72 @@
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { assert } from '../utils/assert';
+
+import { ContractWrapper } from './contract_wrapper';
+import { TokenTransferProxyContract } from './generated/token_transfer_proxy';
+
+/**
+ * This class includes the functionality related to interacting with the TokenTransferProxy contract.
+ */
+export class TokenTransferProxyWrapper extends ContractWrapper {
+ private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
+ private _contractAddressIfExists?: string;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ }
+ /**
+ * Check if the Exchange contract address is authorized by the TokenTransferProxy 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 tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
+ const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(
+ normalizedExchangeContractAddress,
+ );
+ return isAuthorized;
+ }
+ /**
+ * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract.
+ * @return The list of authorized addresses.
+ */
+ public async getAuthorizedAddressesAsync(): Promise<string[]> {
+ const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
+ const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
+ return authorizedAddresses;
+ }
+ /**
+ * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the TokenTransferProxy contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.TokenTransferProxy, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ private _invalidateContractInstance(): void {
+ delete this._tokenTransferProxyContractIfExists;
+ }
+ private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> {
+ if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
+ return this._tokenTransferProxyContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.TokenTransferProxy,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new TokenTransferProxyContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._tokenTransferProxyContractIfExists = contractInstance;
+ return this._tokenTransferProxyContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts
new file mode 100644
index 000000000..844318c79
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts
@@ -0,0 +1,441 @@
+import { schemas } from '@0xproject/json-schemas';
+import { LogWithDecodedArgs } from '@0xproject/types';
+import { AbiDecoder, BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+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 { TokenContract, TokenContractEventArgs, TokenEvents } from './generated/token';
+import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper';
+
+/**
+ * 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 Proxy smart contract.
+ */
+export class TokenWrapper extends ContractWrapper {
+ public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ private _tokenContractsByAddress: { [address: string]: TokenContract };
+ private _tokenTransferProxyWrapper: TokenTransferProxyWrapper;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenTransferProxyWrapper: TokenTransferProxyWrapper) {
+ super(web3Wrapper, networkId);
+ this._tokenContractsByAddress = {};
+ this._tokenTransferProxyWrapper = tokenTransferProxyWrapper;
+ }
+ /**
+ * 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);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, 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('spenderAddress', spenderAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedSpenderAddress = spenderAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const txHash = await tokenContract.approve.sendTransactionAsync(normalizedSpenderAddress, amountInBaseUnits, {
+ 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> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('spenderAddress', spenderAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedSpenderAddress = spenderAddress.toLowerCase();
+ const txHash = await this.setAllowanceAsync(
+ normalizedTokenAddress,
+ normalizedOwnerAddress,
+ normalizedSpenderAddress,
+ 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('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('spenderAddress', spenderAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedSpenderAddress = spenderAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let allowanceInBaseUnits = await tokenContract.allowance.callAsync(
+ normalizedOwnerAddress,
+ normalizedSpenderAddress,
+ txData,
+ 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> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+
+ const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
+ const allowanceInBaseUnits = await this.getAllowanceAsync(
+ normalizedTokenAddress,
+ normalizedOwnerAddress,
+ 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> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+
+ const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
+ const txHash = await this.setAllowanceAsync(
+ normalizedTokenAddress,
+ normalizedOwnerAddress,
+ 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> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const txHash = await this.setProxyAllowanceAsync(
+ normalizedTokenAddress,
+ normalizedOwnerAddress,
+ 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);
+ assert.isETHAddressHex('toAddress', toAddress);
+ await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedFromAddress = fromAddress.toLowerCase();
+ const normalizedToAddress = toAddress.toLowerCase();
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+
+ 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, {
+ 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('toAddress', toAddress);
+ assert.isETHAddressHex('fromAddress', fromAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ const normalizedToAddress = toAddress.toLowerCase();
+ const normalizedFromAddress = fromAddress.toLowerCase();
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+ assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
+
+ 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,
+ {
+ 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
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends TokenContractEventArgs>(
+ tokenAddress: string,
+ eventName: TokenEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ ): string {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.Token.abi,
+ callback,
+ );
+ 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 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 TokenContractEventArgs>(
+ tokenAddress: string,
+ eventName: TokenEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.Token.abi,
+ );
+ return logs;
+ }
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ this._tokenContractsByAddress = {};
+ }
+ private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress];
+ if (!_.isUndefined(tokenContract)) {
+ return tokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.Token,
+ normalizedTokenAddress,
+ );
+ const contractInstance = new TokenContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ tokenContract = contractInstance;
+ this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract;
+ return tokenContract;
+ }
+}