aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/asset-buyer/CHANGELOG.json4
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts43
-rw-r--r--packages/asset-buyer/src/types.ts2
-rw-r--r--packages/contract-wrappers/CHANGELOG.json8
-rw-r--r--packages/contract-wrappers/src/index.ts1
-rw-r--r--packages/contract-wrappers/src/types.ts5
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts1
-rw-r--r--packages/contract-wrappers/src/utils/decorators.ts14
-rw-r--r--packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol12
-rw-r--r--packages/contracts/test/extensions/forwarder.ts265
-rw-r--r--packages/contracts/test/utils/forwarder_wrapper.ts9
-rw-r--r--packages/instant/src/components/buy_button.tsx2
-rw-r--r--packages/instant/src/components/buy_order_state_button.tsx23
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx8
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx4
-rw-r--r--packages/instant/src/constants.ts1
-rw-r--r--packages/instant/src/containers/selected_asset_button.tsx39
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_state_button.tsx20
-rw-r--r--packages/instant/src/containers/selected_asset_retry_button.tsx4
-rw-r--r--packages/instant/src/index.umd.ts3
-rw-r--r--packages/instant/src/redux/actions.ts4
-rw-r--r--packages/instant/src/redux/reducer.ts2
-rw-r--r--packages/instant/src/types.ts1
-rw-r--r--packages/instant/test/util/asset.test.ts47
-rw-r--r--packages/json-schemas/CHANGELOG.json10
-rw-r--r--packages/json-schemas/schemas/order_cancel_schema.json2
-rw-r--r--packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json2
-rw-r--r--packages/json-schemas/schemas/order_fill_requests_schema.json2
-rw-r--r--packages/json-schemas/schemas/order_schema.json12
-rw-r--r--packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json4
-rw-r--r--packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json6
-rw-r--r--packages/json-schemas/schemas/relayer_api_order_config_response_schema.json4
-rw-r--r--packages/json-schemas/schemas/whole_number_schema.json5
-rw-r--r--packages/json-schemas/schemas/zero_ex_transaction_schema.json2
-rw-r--r--packages/json-schemas/src/schemas.ts2
-rw-r--r--packages/json-schemas/test/schema_test.ts12
-rw-r--r--packages/json-schemas/tsconfig.json3
-rw-r--r--packages/monorepo-scripts/CHANGELOG.json4
-rw-r--r--packages/monorepo-scripts/src/doc_gen_configs.ts1
-rw-r--r--packages/order-utils/test/asset_data_utils_test.ts31
-rw-r--r--packages/web3-wrapper/CHANGELOG.json10
-rw-r--r--packages/web3-wrapper/src/web3_wrapper.ts11
-rw-r--r--packages/website/md/docs/asset_buyer/installation.md4
-rw-r--r--packages/website/md/docs/asset_buyer/usage.md6
-rw-r--r--packages/website/md/docs/json_schemas/2/schemas.md58
-rw-r--r--packages/website/md/docs/json_schemas/3/schemas.md41
-rw-r--r--packages/website/ts/blockchain.ts2
-rw-r--r--packages/website/ts/components/portal/portal.tsx19
-rw-r--r--packages/website/ts/containers/json_schemas_documentation.ts17
49 files changed, 649 insertions, 143 deletions
diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json
index 5ebe2aa24..7ebcd8c2f 100644
--- a/packages/asset-buyer/CHANGELOG.json
+++ b/packages/asset-buyer/CHANGELOG.json
@@ -25,6 +25,10 @@
{
"note": "Add missing types to public interface",
"pr": 1139
+ },
+ {
+ "note": "Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy",
+ "pr": 1147
}
],
"timestamp": 1539871071
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts
index 2fe8f6deb..2f9a3108e 100644
--- a/packages/asset-buyer/src/asset_buyer.ts
+++ b/packages/asset-buyer/src/asset_buyer.ts
@@ -1,4 +1,4 @@
-import { ContractWrappers } from '@0x/contract-wrappers';
+import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers';
import { schemas } from '@0x/json-schemas';
import { SignedOrder } from '@0x/order-utils';
import { ObjectMap } from '@0x/types';
@@ -210,21 +210,32 @@ export class AssetBuyer {
throw new Error(AssetBuyerError.NoAddressAvailable);
}
}
- // if no ethAmount is provided, default to the worst ethAmount from buyQuote
- const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
- orders,
- assetBuyAmount,
- finalTakerAddress,
- ethAmount || worstCaseQuoteInfo.totalEthAmount,
- feeOrders,
- feePercentage,
- feeRecipient,
- {
- gasLimit,
- gasPrice,
- },
- );
- return txHash;
+ try {
+ // if no ethAmount is provided, default to the worst ethAmount from buyQuote
+ const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
+ orders,
+ assetBuyAmount,
+ finalTakerAddress,
+ ethAmount || worstCaseQuoteInfo.totalEthAmount,
+ feeOrders,
+ feePercentage,
+ feeRecipient,
+ {
+ gasLimit,
+ gasPrice,
+ shouldValidate: true,
+ },
+ );
+ return txHash;
+ } catch (err) {
+ if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
+ throw new Error(AssetBuyerError.SignatureRequestDenied);
+ } else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) {
+ throw new Error(AssetBuyerError.TransactionValueTooLow);
+ } else {
+ throw err;
+ }
+ }
}
/**
* Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts
index 0d8e732d7..f15e7e934 100644
--- a/packages/asset-buyer/src/types.ts
+++ b/packages/asset-buyer/src/types.ts
@@ -112,6 +112,8 @@ export enum AssetBuyerError {
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE',
AssetUnavailable = 'ASSET_UNAVAILABLE',
+ SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
+ TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW',
}
export interface OrdersAndFillableAmounts {
diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json
index f66be3f0a..c3d986b4a 100644
--- a/packages/contract-wrappers/CHANGELOG.json
+++ b/packages/contract-wrappers/CHANGELOG.json
@@ -37,6 +37,14 @@
"note":
"Removed ContractNotFound errors. Checking for this error was somewhat ineffecient. Relevant methods/functions now return the default error from web3-wrapper, which we feel provides enough information.",
"pr": 1105
+ },
+ {
+ "note": "Add `ForwarderWrapperError` to public interface",
+ "pr": 1147
+ },
+ {
+ "note": "Add `ContractWrapperError.SignatureRequestDenied` to public interface",
+ "pr": 1147
}
],
"timestamp": 1539871071
diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts
index a38bd122b..d66ff5c9c 100644
--- a/packages/contract-wrappers/src/index.ts
+++ b/packages/contract-wrappers/src/index.ts
@@ -39,6 +39,7 @@ export { TransactionEncoder } from './utils/transaction_encoder';
export {
ContractWrappersError,
+ ForwarderWrapperError,
IndexedFilterValues,
BlockRange,
ContractWrappersConfig,
diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts
index 60750179f..5a5bdd530 100644
--- a/packages/contract-wrappers/src/types.ts
+++ b/packages/contract-wrappers/src/types.ts
@@ -18,6 +18,10 @@ export enum ExchangeWrapperError {
AssetDataMismatch = 'ASSET_DATA_MISMATCH',
}
+export enum ForwarderWrapperError {
+ CompleteFillFailed = 'COMPLETE_FILL_FAILED',
+}
+
export enum ContractWrappersError {
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
@@ -30,6 +34,7 @@ export enum ContractWrappersError {
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
ERC721OwnerNotFound = 'ERC_721_OWNER_NOT_FOUND',
ERC721NoApproval = 'ERC_721_NO_APPROVAL',
+ SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
}
export enum InternalContractWrappersError {
diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts
index 9ff61f62a..c587ba526 100644
--- a/packages/contract-wrappers/src/utils/constants.ts
+++ b/packages/contract-wrappers/src/utils/constants.ts
@@ -14,4 +14,5 @@ export const constants = {
ZERO_AMOUNT: new BigNumber(0),
ONE_AMOUNT: new BigNumber(1),
ETHER_TOKEN_DECIMALS: 18,
+ USER_DENIED_SIGNATURE_PATTERN: 'User denied transaction signature',
};
diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts
index e17246015..a4207ae4c 100644
--- a/packages/contract-wrappers/src/utils/decorators.ts
+++ b/packages/contract-wrappers/src/utils/decorators.ts
@@ -29,6 +29,14 @@ const schemaErrorTransformer = (error: Error) => {
return error;
};
+const signatureRequestErrorTransformer = (error: Error) => {
+ if (_.includes(error.message, constants.USER_DENIED_SIGNATURE_PATTERN)) {
+ const errMsg = ContractWrappersError.SignatureRequestDenied;
+ return new Error(errMsg);
+ }
+ return error;
+};
+
/**
* Source: https://stackoverflow.com/a/29837695/3546986
*/
@@ -87,7 +95,11 @@ const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
};
// _.flow(f, g) = f ∘ g
-const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
+const zeroExErrorTransformer = _.flow(
+ schemaErrorTransformer,
+ contractCallErrorTransformer,
+ signatureRequestErrorTransformer,
+);
export const decorators = {
asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
diff --git a/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol b/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol
index fea9a53c2..4991c0ea5 100644
--- a/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol
+++ b/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol
@@ -155,8 +155,10 @@ contract MixinExchangeWrapper is
uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
// Convert the remaining amount of makerAsset to buy into remaining amount
- // of takerAsset to sell, assuming entire amount can be sold in the current order
- uint256 remainingTakerAssetFillAmount = getPartialAmountFloor(
+ // of takerAsset to sell, assuming entire amount can be sold in the current order.
+ // We round up because the exchange rate computed by fillOrder rounds in favor
+ // of the Maker. In this case we want to overestimate the amount of takerAsset.
+ uint256 remainingTakerAssetFillAmount = getPartialAmountCeil(
orders[i].takerAssetAmount,
orders[i].makerAssetAmount,
remainingMakerAssetFillAmount
@@ -224,7 +226,9 @@ contract MixinExchangeWrapper is
// Convert the remaining amount of ZRX to buy into remaining amount
// of WETH to sell, assuming entire amount can be sold in the current order.
- uint256 remainingWethSellAmount = getPartialAmountFloor(
+ // We round up because the exchange rate computed by fillOrder rounds in favor
+ // of the Maker. In this case we want to overestimate the amount of takerAsset.
+ uint256 remainingWethSellAmount = getPartialAmountCeil(
orders[i].takerAssetAmount,
safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees
remainingZrxBuyAmount
@@ -233,7 +237,7 @@ contract MixinExchangeWrapper is
// Attempt to sell the remaining amount of WETH.
FillResults memory singleFillResult = fillOrderNoThrow(
orders[i],
- safeAdd(remainingWethSellAmount, 1), // we add 1 wei to the fill amount to make up for rounding errors
+ remainingWethSellAmount,
signatures[i]
);
diff --git a/packages/contracts/test/extensions/forwarder.ts b/packages/contracts/test/extensions/forwarder.ts
index b76624fa9..c006be0fe 100644
--- a/packages/contracts/test/extensions/forwarder.ts
+++ b/packages/contracts/test/extensions/forwarder.ts
@@ -45,6 +45,7 @@ describe(ContractName.Forwarder, () => {
let weth: DummyERC20TokenContract;
let zrxToken: DummyERC20TokenContract;
+ let erc20TokenA: DummyERC20TokenContract;
let erc721Token: DummyERC721TokenContract;
let forwarderContract: ForwarderContract;
let wethContract: WETH9Contract;
@@ -77,7 +78,6 @@ describe(ContractName.Forwarder, () => {
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
const numDummyErc20ToDeploy = 3;
- let erc20TokenA;
[erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
@@ -902,6 +902,269 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
+ it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => {
+ // The 0x Protocol contracts round the exchange rate in favor of the Maker.
+ // In this case, the taker must round up how much they're going to spend, which
+ // in turn increases the amount of MakerAsset being purchased.
+ // Example:
+ // The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T.
+ // For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset.
+ // To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset.
+ // However, the Taker can only spend whole units.
+ // Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset.
+ // Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset.
+ //
+ // The forwarding contract will opt for the second option, which overbuys, to ensure the taker
+ // receives at least the amount of MakerAsset they requested.
+ //
+ // Construct test case using values from example above
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber('30'),
+ takerAssetAmount: new BigNumber('20'),
+ makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const desiredMakerAssetFillAmount = new BigNumber('5');
+ const makerAssetFillAmount = new BigNumber('6');
+ const ethValue = new BigNumber('4');
+ // Execute test case
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ desiredMakerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ );
+ // Fetch end balances and construct expected outputs
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ // Validate test case
+ expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => {
+ // See the test case above for a detailed description of this case.
+ // The difference here is that the MakerAsset is ZRX. We expect the same result as above,
+ // but this tests a different code path.
+ //
+ // Construct test case using values from example above
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber('30'),
+ takerAssetAmount: new BigNumber('20'),
+ makerAssetData: zrxAssetData,
+ takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const desiredMakerAssetFillAmount = new BigNumber('5');
+ const makerAssetFillAmount = new BigNumber('6');
+ const ethValue = new BigNumber('4');
+ // Execute test case
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ desiredMakerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ );
+ // Fetch end balances and construct expected outputs
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ // Validate test case
+ expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => {
+ // Order taken from a transaction on mainnet that failed due to a rounding error.
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber('268166666666666666666'),
+ takerAssetAmount: new BigNumber('219090625878836371'),
+ makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ // The taker will receive more than the desired amount of makerAsset due to rounding
+ const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
+ const ethValue = new BigNumber('4084971271824171');
+ const makerAssetFillAmount = ethValue
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ // Execute test case
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ desiredMakerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ );
+ // Fetch end balances and construct expected outputs
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ // Validate test case
+ expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => {
+ // Order taken from a transaction on mainnet that failed due to a rounding error.
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber('268166666666666666666'),
+ takerAssetAmount: new BigNumber('219090625878836371'),
+ makerAssetData: zrxAssetData,
+ takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ // The taker will receive more than the desired amount of makerAsset due to rounding
+ const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
+ const ethValue = new BigNumber('4084971271824171');
+ const makerAssetFillAmount = ethValue
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ // Execute test case
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ desiredMakerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ );
+ // Fetch end balances and construct expected outputs
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ // Validate test case
+ expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => {
+ // An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1.
+ // Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1
+ // We have since changed this to be Ceil(desiredMakerAmount * exchangeRate)
+ // These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer.
+ //
+ // This test verifies that `ceil` is sufficient:
+ // Let TakerAssetAmount = MakerAssetAmount * 2
+ // -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2
+ // .: desiredMakerAmount * exchangeRate is an integer.
+ //
+ // Construct test case using values from example above
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber('30'),
+ takerAssetAmount: new BigNumber('60'),
+ makerAssetData: zrxAssetData,
+ takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = new BigNumber('5');
+ const ethValue = new BigNumber('10');
+ // Execute test case
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ // Fetch end balances and construct expected outputs
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ // Validate test case
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
});
describe('marketBuyOrdersWithEth with extra fees', () => {
it('should buy an asset and send fee to feeRecipient', async () => {
diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts
index f1a64d47d..a0bfcfe1d 100644
--- a/packages/contracts/test/utils/forwarder_wrapper.ts
+++ b/packages/contracts/test/utils/forwarder_wrapper.ts
@@ -26,9 +26,12 @@ export class ForwarderWrapper {
_.forEach(feeOrders, feeOrder => {
const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
- wethAmount = wethAmount
- .plus(feeOrder.takerAssetAmount.times(remainingFeeAmount).dividedToIntegerBy(feeAvailable))
- .plus(1);
+ wethAmount = wethAmount.plus(
+ feeOrder.takerAssetAmount
+ .times(remainingFeeAmount)
+ .dividedBy(feeAvailable)
+ .ceil(),
+ );
remainingFeeAmount = new BigNumber(0);
} else if (!remainingFeeAmount.isZero()) {
wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 1afd216d8..9c42f3d87 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -23,7 +23,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
- const shouldDisableButton = _.isUndefined(this.props.buyQuote);
+ const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
return (
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
diff --git a/packages/instant/src/components/buy_order_state_button.tsx b/packages/instant/src/components/buy_order_state_button.tsx
new file mode 100644
index 000000000..5bc965c7d
--- /dev/null
+++ b/packages/instant/src/components/buy_order_state_button.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+
+import { PlacingOrderButton } from '../components/placing_order_button';
+import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button';
+import { SelectedAssetRetryButton } from '../containers/selected_asset_retry_button';
+import { SelectedAssetViewTransactionButton } from '../containers/selected_asset_view_transaction_button';
+import { AsyncProcessState } from '../types';
+
+export interface BuyOrderStateButtonProps {
+ buyOrderProcessingState: AsyncProcessState;
+}
+
+export const BuyOrderStateButton: React.StatelessComponent<BuyOrderStateButtonProps> = props => {
+ if (props.buyOrderProcessingState === AsyncProcessState.FAILURE) {
+ return <SelectedAssetRetryButton />;
+ } else if (props.buyOrderProcessingState === AsyncProcessState.SUCCESS) {
+ return <SelectedAssetViewTransactionButton />;
+ } else if (props.buyOrderProcessingState === AsyncProcessState.PENDING) {
+ return <PlacingOrderButton />;
+ }
+
+ return <SelectedAssetBuyButton />;
+};
diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx
index 142e74dae..ffa5a8250 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -31,7 +31,7 @@ export interface ZeroExInstantOptionalProps {
}
export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
- public store: Store;
+ private readonly _store: Store;
private static _mergeInitialStateWithProps(props: ZeroExInstantProps, state: State = INITIAL_STATE): State {
// Create merged object such that properties in props override default settings
const optionalPropsWithDefaults: ZeroExInstantOptionalProps = {
@@ -58,14 +58,14 @@ export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
}
constructor(props: ZeroExInstantProps) {
super(props);
- this.store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
+ this._store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchAndDispatchToStore(this.store);
+ asyncData.fetchAndDispatchToStore(this._store);
}
public render(): React.ReactNode {
return (
- <Provider store={this.store}>
+ <Provider store={this._store}>
<SelectedAssetThemeProvider>
<ZeroExInstantContainer />
</SelectedAssetThemeProvider>
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index 088d0a93e..1d17ed12a 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details';
import { LatestError } from '../containers/latest_error';
-import { SelectedAssetButton } from '../containers/selected_asset_button';
+import { SelectedAssetBuyOrderStateButton } from '../containers/selected_asset_buy_order_state_button';
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
import { ColorOption } from '../style/theme';
@@ -27,7 +27,7 @@ export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantConta
<SelectedAssetInstantHeading />
<LatestBuyQuoteOrderDetails />
<Container padding="20px" width="100%">
- <SelectedAssetButton />
+ <SelectedAssetBuyOrderStateButton />
</Container>
</Flex>
</Container>
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
index 7f4c5a058..31491c80a 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -1,3 +1,4 @@
import { BigNumber } from '@0x/utils';
export const BIG_NUMBER_ZERO = new BigNumber(0);
export const ethDecimals = 18;
+export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
diff --git a/packages/instant/src/containers/selected_asset_button.tsx b/packages/instant/src/containers/selected_asset_button.tsx
deleted file mode 100644
index d368d05e1..000000000
--- a/packages/instant/src/containers/selected_asset_button.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import * as _ from 'lodash';
-import * as React from 'react';
-import { connect } from 'react-redux';
-
-import { SecondaryButton } from '../components/secondary_button';
-import { State } from '../redux/reducer';
-import { AsyncProcessState, OrderState } from '../types';
-
-import { PlacingOrderButton } from '../components/placing_order_button';
-
-import { SelectedAssetBuyButton } from './selected_asset_buy_button';
-import { SelectedAssetRetryButton } from './selected_asset_retry_button';
-import { SelectedAssetViewTransactionButton } from './selected_asset_view_transaction_button';
-
-interface ConnectedState {
- buyOrderState: OrderState;
-}
-export interface SelectedAssetButtonProps {}
-const mapStateToProps = (state: State, _ownProps: SelectedAssetButtonProps): ConnectedState => ({
- buyOrderState: state.buyOrderState,
-});
-
-const SelectedAssetButtonPresentationComponent: React.StatelessComponent<{
- buyOrderState: OrderState;
-}> = props => {
- if (props.buyOrderState.processState === AsyncProcessState.FAILURE) {
- return <SelectedAssetRetryButton />;
- } else if (props.buyOrderState.processState === AsyncProcessState.SUCCESS) {
- return <SelectedAssetViewTransactionButton />;
- } else if (props.buyOrderState.processState === AsyncProcessState.PENDING) {
- return <PlacingOrderButton />;
- }
-
- return <SelectedAssetBuyButton />;
-};
-
-export const SelectedAssetButton: React.ComponentClass<SelectedAssetButtonProps> = connect(mapStateToProps)(
- SelectedAssetButtonPresentationComponent,
-);
diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx b/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx
new file mode 100644
index 000000000..f3efbb5d2
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx
@@ -0,0 +1,20 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+
+import { State } from '../redux/reducer';
+import { AsyncProcessState } from '../types';
+
+import { BuyOrderStateButton } from '../components/buy_order_state_button';
+
+interface ConnectedState {
+ buyOrderProcessingState: AsyncProcessState;
+}
+export interface SelectedAssetButtonProps {}
+const mapStateToProps = (state: State, _ownProps: SelectedAssetButtonProps): ConnectedState => ({
+ buyOrderProcessingState: state.buyOrderState.processState,
+});
+
+export const SelectedAssetBuyOrderStateButton: React.ComponentClass<SelectedAssetButtonProps> = connect(
+ mapStateToProps,
+)(BuyOrderStateButton);
diff --git a/packages/instant/src/containers/selected_asset_retry_button.tsx b/packages/instant/src/containers/selected_asset_retry_button.tsx
index 66177551d..b2b140be6 100644
--- a/packages/instant/src/containers/selected_asset_retry_button.tsx
+++ b/packages/instant/src/containers/selected_asset_retry_button.tsx
@@ -17,10 +17,10 @@ const mapDispatchToProps = (
dispatch: Dispatch<Action>,
_ownProps: SelectedAssetRetryButtonProps,
): ConnectedDispatch => ({
- onClick: () => dispatch(actions.clearBuyQuoteAndSelectedAssetAmount()),
+ onClick: () => dispatch(actions.resetAmount()),
});
export const SelectedAssetRetryButton: React.ComponentClass<SelectedAssetRetryButtonProps> = connect(
- null,
+ undefined,
mapDispatchToProps,
)(RetryButton);
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index fe78ce401..f648b37f2 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -1,8 +1,9 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
+import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR } from './constants';
import { ZeroExInstant, ZeroExInstantProps } from './index';
-export const render = (props: ZeroExInstantProps, selector: string = '#zeroExInstantContainer') => {
+export const render = (props: ZeroExInstantProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
ReactDOM.render(React.createElement(ZeroExInstant, props), document.querySelector(selector));
};
diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts
index 1360634d7..9c084ab3c 100644
--- a/packages/instant/src/redux/actions.ts
+++ b/packages/instant/src/redux/actions.ts
@@ -31,7 +31,7 @@ export enum ActionTypes {
SET_ERROR = 'SET_ERROR',
HIDE_ERROR = 'HIDE_ERROR',
CLEAR_ERROR = 'CLEAR_ERROR',
- CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT = 'CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT',
+ RESET_AMOUNT = 'RESET_AMOUNT',
}
export const actions = {
@@ -45,5 +45,5 @@ export const actions = {
setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error),
hideError: () => createAction(ActionTypes.HIDE_ERROR),
clearError: () => createAction(ActionTypes.CLEAR_ERROR),
- clearBuyQuoteAndSelectedAssetAmount: () => createAction(ActionTypes.CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT),
+ resetAmount: () => createAction(ActionTypes.RESET_AMOUNT),
};
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index 1aedcbcc4..c6a05ac52 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -101,7 +101,7 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
...state,
selectedAsset: newSelectedAsset,
};
- case ActionTypes.CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT:
+ case ActionTypes.RESET_AMOUNT:
return {
...state,
latestBuyQuote: undefined,
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index b7b16f4d7..c5521c63c 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -55,6 +55,7 @@ export interface ERC721Asset {
assetData: string;
metaData: ERC721AssetMetaData;
}
+
export interface Asset {
assetData: string;
metaData: AssetMetaData;
diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts
new file mode 100644
index 000000000..c7db7eba7
--- /dev/null
+++ b/packages/instant/test/util/asset.test.ts
@@ -0,0 +1,47 @@
+import { AssetProxyId, ObjectMap } from '@0x/types';
+
+import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types';
+import { assetUtils } from '../../src/util/asset';
+
+const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
+const ZRX_ASSET_DATA_KOVAN = '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa';
+const ZRX_META_DATA: ERC20AssetMetaData = {
+ assetProxyId: AssetProxyId.ERC20,
+ symbol: 'zrx',
+ decimals: 18,
+};
+const ZRX_ASSET: Asset = {
+ assetData: ZRX_ASSET_DATA,
+ metaData: ZRX_META_DATA,
+};
+const META_DATA_MAP: ObjectMap<AssetMetaData> = {
+ [ZRX_ASSET_DATA]: ZRX_META_DATA,
+};
+
+describe('assetDataUtil', () => {
+ describe('bestNameForAsset', () => {
+ it('should return default string if assetData is undefined', () => {
+ expect(assetUtils.bestNameForAsset(undefined, 'xyz')).toEqual('xyz');
+ });
+ it('should return ZRX for ZRX assetData', () => {
+ expect(assetUtils.bestNameForAsset(ZRX_ASSET, 'mah default')).toEqual('ZRX');
+ });
+ });
+ describe('getMetaDataOrThrow', () => {
+ it('should return the metaData for the supplied mainnet asset data', () => {
+ expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA, META_DATA_MAP, Network.Mainnet)).toEqual(
+ ZRX_META_DATA,
+ );
+ });
+ it('should return the metaData for the supplied non-mainnet asset data', () => {
+ expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA_KOVAN, META_DATA_MAP, Network.Kovan)).toEqual(
+ ZRX_META_DATA,
+ );
+ });
+ it('should throw if the metaData for the asset is not available', () => {
+ expect(() =>
+ assetUtils.getMetaDataOrThrow('asset data we dont have', META_DATA_MAP, Network.Mainnet),
+ ).toThrowError(ZeroExInstantError.AssetMetaDataNotAvailable);
+ });
+ });
+});
diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json
index 8a88df6e2..3bd6552e3 100644
--- a/packages/json-schemas/CHANGELOG.json
+++ b/packages/json-schemas/CHANGELOG.json
@@ -1,5 +1,15 @@
[
{
+ "version": "2.0.1",
+ "changes": [
+ {
+ "note":
+ "Improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal amounts",
+ "pr": 1173
+ }
+ ]
+ },
+ {
"version": "2.0.0",
"changes": [
{
diff --git a/packages/json-schemas/schemas/order_cancel_schema.json b/packages/json-schemas/schemas/order_cancel_schema.json
index 09d4068c6..8d0999941 100644
--- a/packages/json-schemas/schemas/order_cancel_schema.json
+++ b/packages/json-schemas/schemas/order_cancel_schema.json
@@ -4,7 +4,7 @@
"items": {
"properties": {
"order": { "$ref": "/orderSchema" },
- "takerTokenCancelAmount": { "$ref": "/numberSchema" }
+ "takerTokenCancelAmount": { "$ref": "/wholeNumberSchema" }
},
"required": ["order", "takerTokenCancelAmount"],
"type": "object"
diff --git a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json
index c9549c72e..73bbf20bb 100644
--- a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json
+++ b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json
@@ -4,7 +4,7 @@
"items": {
"properties": {
"signedOrder": { "$ref": "/signedOrderSchema" },
- "fillTakerAmount": { "$ref": "/numberSchema" }
+ "fillTakerAmount": { "$ref": "/wholeNumberSchema" }
},
"required": ["signedOrder", "fillTakerAmount"],
"type": "object"
diff --git a/packages/json-schemas/schemas/order_fill_requests_schema.json b/packages/json-schemas/schemas/order_fill_requests_schema.json
index 98325653b..d06fb19a2 100644
--- a/packages/json-schemas/schemas/order_fill_requests_schema.json
+++ b/packages/json-schemas/schemas/order_fill_requests_schema.json
@@ -4,7 +4,7 @@
"items": {
"properties": {
"signedOrder": { "$ref": "/signedOrderSchema" },
- "takerTokenFillAmount": { "$ref": "/numberSchema" }
+ "takerTokenFillAmount": { "$ref": "/wholeNumberSchema" }
},
"required": ["signedOrder", "takerTokenFillAmount"],
"type": "object"
diff --git a/packages/json-schemas/schemas/order_schema.json b/packages/json-schemas/schemas/order_schema.json
index de2c8c7c2..c70b9e2dd 100644
--- a/packages/json-schemas/schemas/order_schema.json
+++ b/packages/json-schemas/schemas/order_schema.json
@@ -3,17 +3,17 @@
"properties": {
"makerAddress": { "$ref": "/addressSchema" },
"takerAddress": { "$ref": "/addressSchema" },
- "makerFee": { "$ref": "/numberSchema" },
- "takerFee": { "$ref": "/numberSchema" },
+ "makerFee": { "$ref": "/wholeNumberSchema" },
+ "takerFee": { "$ref": "/wholeNumberSchema" },
"senderAddress": { "$ref": "/addressSchema" },
- "makerAssetAmount": { "$ref": "/numberSchema" },
- "takerAssetAmount": { "$ref": "/numberSchema" },
+ "makerAssetAmount": { "$ref": "/wholeNumberSchema" },
+ "takerAssetAmount": { "$ref": "/wholeNumberSchema" },
"makerAssetData": { "$ref": "/hexSchema" },
"takerAssetData": { "$ref": "/hexSchema" },
- "salt": { "$ref": "/numberSchema" },
+ "salt": { "$ref": "/wholeNumberSchema" },
"exchangeAddress": { "$ref": "/addressSchema" },
"feeRecipientAddress": { "$ref": "/addressSchema" },
- "expirationTimeSeconds": { "$ref": "/numberSchema" }
+ "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
},
"required": [
"makerAddress",
diff --git a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json b/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json
index 0ab9b444f..e0f274c5d 100644
--- a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json
+++ b/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json
@@ -3,8 +3,8 @@
"type": "object",
"properties": {
"assetData": { "$ref": "/hexSchema" },
- "minAmount": { "$ref": "/numberSchema" },
- "maxAmount": { "$ref": "/numberSchema" },
+ "minAmount": { "$ref": "/wholeNumberSchema" },
+ "maxAmount": { "$ref": "/wholeNumberSchema" },
"precision": { "type": "number" }
},
"required": ["assetData"]
diff --git a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json b/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json
index bc95b61ef..f4583fc62 100644
--- a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json
+++ b/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json
@@ -4,12 +4,12 @@
"properties": {
"makerAddress": { "$ref": "/addressSchema" },
"takerAddress": { "$ref": "/addressSchema" },
- "makerAssetAmount": { "$ref": "/numberSchema" },
- "takerAssetAmount": { "$ref": "/numberSchema" },
+ "makerAssetAmount": { "$ref": "/wholeNumberSchema" },
+ "takerAssetAmount": { "$ref": "/wholeNumberSchema" },
"makerAssetData": { "$ref": "/hexSchema" },
"takerAssetData": { "$ref": "/hexSchema" },
"exchangeAddress": { "$ref": "/addressSchema" },
- "expirationTimeSeconds": { "$ref": "/numberSchema" }
+ "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
},
"required": [
"makerAddress",
diff --git a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json b/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json
index 0742febdf..8193861e1 100644
--- a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json
+++ b/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json
@@ -2,8 +2,8 @@
"id": "/relayerApiOrderConfigResponseSchema",
"type": "object",
"properties": {
- "makerFee": { "$ref": "/numberSchema" },
- "takerFee": { "$ref": "/numberSchema" },
+ "makerFee": { "$ref": "/wholeNumberSchema" },
+ "takerFee": { "$ref": "/wholeNumberSchema" },
"feeRecipientAddress": { "$ref": "/addressSchema" },
"senderAddress": { "$ref": "/addressSchema" }
},
diff --git a/packages/json-schemas/schemas/whole_number_schema.json b/packages/json-schemas/schemas/whole_number_schema.json
new file mode 100644
index 000000000..944ce820e
--- /dev/null
+++ b/packages/json-schemas/schemas/whole_number_schema.json
@@ -0,0 +1,5 @@
+{
+ "id": "/wholeNumberSchema",
+ "type": "string",
+ "pattern": "^\\d+$"
+}
diff --git a/packages/json-schemas/schemas/zero_ex_transaction_schema.json b/packages/json-schemas/schemas/zero_ex_transaction_schema.json
index ad3b11583..0c714f14d 100644
--- a/packages/json-schemas/schemas/zero_ex_transaction_schema.json
+++ b/packages/json-schemas/schemas/zero_ex_transaction_schema.json
@@ -3,7 +3,7 @@
"properties": {
"data": { "$ref": "/hexSchema" },
"signerAddress": { "$ref": "/addressSchema" },
- "salt": { "$ref": "/numberSchema" }
+ "salt": { "$ref": "/wholeNumberSchema" }
},
"required": ["data", "salt", "signerAddress"],
"type": "object"
diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts
index 8318df617..8ece5de75 100644
--- a/packages/json-schemas/src/schemas.ts
+++ b/packages/json-schemas/src/schemas.ts
@@ -34,6 +34,7 @@ import * as signedOrderSchema from '../schemas/signed_order_schema.json';
import * as signedOrdersSchema from '../schemas/signed_orders_schema.json';
import * as tokenSchema from '../schemas/token_schema.json';
import * as txDataSchema from '../schemas/tx_data_schema.json';
+import * as wholeNumberSchema from '../schemas/whole_number_schema.json';
import * as zeroExTransactionSchema from '../schemas/zero_ex_transaction_schema.json';
export const schemas = {
@@ -74,4 +75,5 @@ export const schemas = {
relayerApiOrdersResponseSchema,
relayerApiAssetDataPairsSchema,
zeroExTransactionSchema,
+ wholeNumberSchema,
};
diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts
index 795261ef2..bfa2c5668 100644
--- a/packages/json-schemas/test/schema_test.ts
+++ b/packages/json-schemas/test/schema_test.ts
@@ -36,6 +36,7 @@ const {
relayerApiOrdersChannelUpdateSchema,
relayerApiOrdersResponseSchema,
relayerApiOrderSchema,
+ wholeNumberSchema,
} = schemas;
describe('Schema', () => {
@@ -73,6 +74,17 @@ describe('Schema', () => {
validateAgainstSchema(testCases, numberSchema, shouldFail);
});
});
+ describe('#wholeNumberSchema', () => {
+ it('should validate valid numbers', () => {
+ const testCases = ['5', '42', '0'];
+ validateAgainstSchema(testCases, wholeNumberSchema);
+ });
+ it('should fail for invalid numbers', () => {
+ const testCases = ['1.3', '0.2', '00.00', '.3', '1.', 'abacaba', 'и', '1..0'];
+ const shouldFail = true;
+ validateAgainstSchema(testCases, wholeNumberSchema, shouldFail);
+ });
+ });
describe('#addressSchema', () => {
it('should validate valid addresses', () => {
const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS];
diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json
index 425dfcfe1..7b14166c0 100644
--- a/packages/json-schemas/tsconfig.json
+++ b/packages/json-schemas/tsconfig.json
@@ -43,6 +43,7 @@
"./schemas/js_number.json",
"./schemas/zero_ex_transaction_schema.json",
"./schemas/tx_data_schema.json",
- "./schemas/index_filter_values_schema.json"
+ "./schemas/index_filter_values_schema.json",
+ "./schemas/whole_number_schema.json"
]
}
diff --git a/packages/monorepo-scripts/CHANGELOG.json b/packages/monorepo-scripts/CHANGELOG.json
index 4797fd929..170a97a33 100644
--- a/packages/monorepo-scripts/CHANGELOG.json
+++ b/packages/monorepo-scripts/CHANGELOG.json
@@ -9,6 +9,10 @@
{
"note": "Add AssetBuyerError to the IGNORED_EXCESSIVE_TYPES array",
"pr": 1139
+ },
+ {
+ "note": "Add ForwarderError to the IGNORED_EXCESSIVE_TYPES array",
+ "pr": 1147
}
]
},
diff --git a/packages/monorepo-scripts/src/doc_gen_configs.ts b/packages/monorepo-scripts/src/doc_gen_configs.ts
index 0aaf5a6a5..7a14f8664 100644
--- a/packages/monorepo-scripts/src/doc_gen_configs.ts
+++ b/packages/monorepo-scripts/src/doc_gen_configs.ts
@@ -56,6 +56,7 @@ export const docGenConfigs: DocGenConfigs = {
'ContractWrappersError',
'OrderError',
'AssetBuyerError',
+ 'ForwarderWrapperError',
],
// Some libraries only export types. In those cases, we cannot check if the exported types are part of the
// "exported public interface". Thus we add them here and skip those checks.
diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts
new file mode 100644
index 000000000..f8b850604
--- /dev/null
+++ b/packages/order-utils/test/asset_data_utils_test.ts
@@ -0,0 +1,31 @@
+import * as chai from 'chai';
+
+import { ERC20AssetData } from '@0x/types';
+
+import { assetDataUtils } from '../src/asset_data_utils';
+
+import { chaiSetup } from './utils/chai_setup';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+const KNOWN_ENCODINGS = [
+ {
+ address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
+ assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
+ },
+];
+
+const ERC20_ASSET_PROXY_ID = '0xf47261b0';
+
+describe('assetDataUtils', () => {
+ it('should encode', () => {
+ const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address);
+ expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData);
+ });
+ it('should decode', () => {
+ const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData);
+ expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address);
+ expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID);
+ });
+});
diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json
index b8d06eac1..6b554110f 100644
--- a/packages/web3-wrapper/CHANGELOG.json
+++ b/packages/web3-wrapper/CHANGELOG.json
@@ -1,5 +1,15 @@
[
{
+ "version": "3.1.1",
+ "changes": [
+ {
+ "note":
+ "Fix bug in `getTransactionByHashAsync` which was causing the return value to have the wrong type (raw fields instead of unmarshalled fields).",
+ "pr": 1177
+ }
+ ]
+ },
+ {
"version": "3.1.0",
"changes": [
{
diff --git a/packages/web3-wrapper/src/web3_wrapper.ts b/packages/web3-wrapper/src/web3_wrapper.ts
index 3ba153680..56877fef3 100644
--- a/packages/web3-wrapper/src/web3_wrapper.ts
+++ b/packages/web3-wrapper/src/web3_wrapper.ts
@@ -23,7 +23,13 @@ import {
import * as _ from 'lodash';
import { marshaller } from './marshaller';
-import { BlockWithoutTransactionDataRPC, BlockWithTransactionDataRPC, NodeType, Web3WrapperErrors } from './types';
+import {
+ BlockWithoutTransactionDataRPC,
+ BlockWithTransactionDataRPC,
+ NodeType,
+ TransactionRPC,
+ Web3WrapperErrors,
+} from './types';
import { utils } from './utils';
const BASE_TEN = 10;
@@ -228,10 +234,11 @@ export class Web3Wrapper {
*/
public async getTransactionByHashAsync(txHash: string): Promise<Transaction> {
assert.isHexString('txHash', txHash);
- const transaction = await this.sendRawPayloadAsync<Transaction>({
+ const transactionRpc = await this.sendRawPayloadAsync<TransactionRPC>({
method: 'eth_getTransactionByHash',
params: [txHash],
});
+ const transaction = marshaller.unmarshalTransaction(transactionRpc);
return transaction;
}
/**
diff --git a/packages/website/md/docs/asset_buyer/installation.md b/packages/website/md/docs/asset_buyer/installation.md
index 76affaa09..3c0c95068 100644
--- a/packages/website/md/docs/asset_buyer/installation.md
+++ b/packages/website/md/docs/asset_buyer/installation.md
@@ -1,10 +1,10 @@
-**Install**
+#### Install
```bash
yarn add @0x/asset-buyer
```
-**Import**
+#### Import
```javascript
import { AssetBuyer } from '@0x/asset-buyer';
diff --git a/packages/website/md/docs/asset_buyer/usage.md b/packages/website/md/docs/asset_buyer/usage.md
index 6462d938e..209c15062 100644
--- a/packages/website/md/docs/asset_buyer/usage.md
+++ b/packages/website/md/docs/asset_buyer/usage.md
@@ -1,4 +1,4 @@
-**Construction**
+#### Construction
Connecting to a standard relayer API compliant url:
@@ -16,7 +16,7 @@ const orders = []; // get these from your own API, a relayer, a friend, from any
const assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, orders);
```
-**Get a quote**
+#### Get a quote
A [BuyQuote](#types-BuyQuote) object contains enough information to display buy information to an end user
@@ -30,7 +30,7 @@ console.log(quoteInfo.feeAmount); // a portion of the total ethAmount above that
console.log(quoteInfo.ethPerAssetPrice); // the rate that this quote provides (e.g. 0.0035ETH / REP)
```
-**Perform a buy**
+#### Perform a buy
Pass the [BuyQuote](#types-BuyQuote) object from above back to the assetBuyer in order to initiate the buy transaction
diff --git a/packages/website/md/docs/json_schemas/2/schemas.md b/packages/website/md/docs/json_schemas/2/schemas.md
index ec7cf6f15..8ea6c8e85 100644
--- a/packages/website/md/docs/json_schemas/2/schemas.md
+++ b/packages/website/md/docs/json_schemas/2/schemas.md
@@ -1,40 +1,40 @@
Basic Schemas
-* [Address type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/address.json)
-* [Number type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/number.json)
-* [Hex type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/hex.json)
-* [JS number](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/js_number.json)
+* [Address type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/address_schema.json)
+* [Number type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/number_schema.json)
+* [Hex type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/hex_schema.json)
+* [JS number](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/js_number.json)
0x Protocol Schemas
-* [Order](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json)
-* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_order_schema.json)
-* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_hash_schema.json)
-* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/ec_signature_schema.json)
+* [Order](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_schema.json)
+* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_order_schema.json)
+* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_hash_schema.json)
+* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/ec_signature_schema.json)
0x.js Schemas
-* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_param_schema.json)
-* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_range_schema.json)
-* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/index_filter_values_schema.json)
-* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_requests_schema.json)
-* [OrderCancellationRequests](https://github.com/0xProjet/0x-monorepo/blob/development/packages/json-schemas/schemas/order_cancel_schema.json)
-* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
-* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_orders_schema.json)
-* [Token](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/token_schema.json)
-* [TxData](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/tx_data_schema.json)
+* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_param_schema.json)
+* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_range_schema.json)
+* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/index_filter_values_schema.json)
+* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_requests_schema.json)
+* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_cancel_schema.json)
+* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
+* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_orders_schema.json)
+* [Token](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/token_schema.json)
+* [TxData](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/tx_data_schema.json)
Standard Relayer API Schemas
-* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/paginated_collection_schema.json)
-* [Error response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
-* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
-* [Order config response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
-* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
-* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
-* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
-* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
-* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_schema.json)
-* [Trade info](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_trade_info_schema.json)
-* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_response_schema.json)
-* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
+* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/paginated_collection_schema.json)
+* [Error response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
+* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
+* [Order config response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
+* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
+* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
+* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
+* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
+* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json)
+* [Trade info](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json)
+* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json)
+* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
diff --git a/packages/website/md/docs/json_schemas/3/schemas.md b/packages/website/md/docs/json_schemas/3/schemas.md
new file mode 100644
index 000000000..99aff7b5a
--- /dev/null
+++ b/packages/website/md/docs/json_schemas/3/schemas.md
@@ -0,0 +1,41 @@
+Basic Schemas
+
+* [Address type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/address_schema.json)
+* [Number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/number_schema.json)
+* [Whole number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/whole_number_schema.json)
+* [Hex type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/hex_schema.json)
+* [JS number](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/js_number.json)
+
+0x Protocol Schemas
+
+* [Order](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_schema.json)
+* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_order_schema.json)
+* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_hash_schema.json)
+* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/ec_signature_schema.json)
+
+0x.js Schemas
+
+* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_param_schema.json)
+* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_range_schema.json)
+* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/index_filter_values_schema.json)
+* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_requests_schema.json)
+* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_cancel_schema.json)
+* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
+* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_orders_schema.json)
+* [Token](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/token_schema.json)
+* [TxData](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/tx_data_schema.json)
+
+Standard Relayer API Schemas
+
+* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/paginated_collection_schema.json)
+* [Error response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
+* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
+* [Order config response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
+* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
+* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
+* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
+* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
+* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json)
+* [Trade info](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json)
+* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json)
+* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index efb5aef23..62e16dd1d 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -163,7 +163,7 @@ export class Blockchain {
const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider);
// Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
const signerSubprovider =
- providerName === Providers.Metamask
+ providerName === constants.PROVIDER_NAME_METAMASK
? new MetamaskSubprovider(injectedWeb3.currentProvider)
: new SignerSubprovider(injectedWeb3.currentProvider);
provider.addProvider(signerSubprovider);
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 2299881c4..94ab63ac6 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -210,12 +210,16 @@ export class Portal extends React.Component<PortalProps, PortalState> {
isLoaded: false,
};
}
- this.setState({
- trackedTokenStateByAddress,
- });
- // Fetch the actual balance/allowance.
- // tslint:disable-next-line:no-floating-promises
- this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
+ this.setState(
+ {
+ trackedTokenStateByAddress,
+ },
+ () => {
+ // Fetch the actual balance/allowance.
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
+ },
+ );
}
}
public render(): React.ReactNode {
@@ -644,6 +648,9 @@ export class Portal extends React.Component<PortalProps, PortalState> {
}
private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise<void> {
+ if (_.isEmpty(tokenAddresses)) {
+ return;
+ }
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const balancesAndAllowances = await Promise.all(
diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts
index cb5918784..9c4bb8e26 100644
--- a/packages/website/ts/containers/json_schemas_documentation.ts
+++ b/packages/website/ts/containers/json_schemas_documentation.ts
@@ -15,8 +15,9 @@ const InstallationMarkdown1 = require('md/docs/json_schemas/1/installation');
const InstallationMarkdown3 = require('md/docs/json_schemas/3/installation');
const usageMarkdown1 = require('md/docs/json_schemas/1/usage');
const usageMarkdown3 = require('md/docs/json_schemas/3/usage');
-const SchemasMarkdownV1 = require('md/docs/json_schemas/1/schemas');
-const SchemasMarkdownV2 = require('md/docs/json_schemas/2/schemas');
+const SchemasMarkdown1 = require('md/docs/json_schemas/1/schemas');
+const SchemasMarkdown2 = require('md/docs/json_schemas/2/schemas');
+const SchemasMarkdown3 = require('md/docs/json_schemas/3/schemas');
/* tslint:enable:no-var-requires */
const markdownSections = {
@@ -40,19 +41,25 @@ const docsInfoConfig: DocsInfoConfig = {
'0.0.1': {
[markdownSections.introduction]: IntroMarkdown1,
[markdownSections.installation]: InstallationMarkdown1,
- [markdownSections.schemas]: SchemasMarkdownV1,
+ [markdownSections.schemas]: SchemasMarkdown1,
[markdownSections.usage]: usageMarkdown1,
},
'1.0.0': {
[markdownSections.introduction]: IntroMarkdown1,
[markdownSections.installation]: InstallationMarkdown1,
- [markdownSections.schemas]: SchemasMarkdownV2,
+ [markdownSections.schemas]: SchemasMarkdown2,
[markdownSections.usage]: usageMarkdown1,
},
'2.0.0': {
[markdownSections.introduction]: IntroMarkdown3,
[markdownSections.installation]: InstallationMarkdown3,
- [markdownSections.schemas]: SchemasMarkdownV2,
+ [markdownSections.schemas]: SchemasMarkdown2,
+ [markdownSections.usage]: usageMarkdown3,
+ },
+ '2.0.1': {
+ [markdownSections.introduction]: IntroMarkdown3,
+ [markdownSections.installation]: InstallationMarkdown3,
+ [markdownSections.schemas]: SchemasMarkdown3,
[markdownSections.usage]: usageMarkdown3,
},
},