aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/0x.js/CHANGELOG.json13
-rw-r--r--packages/0x.js/package.json1
-rw-r--r--packages/0x.js/src/0x.ts144
-rw-r--r--packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts2
-rw-r--r--packages/0x.js/src/contract_wrappers/exchange_wrapper.ts8
-rw-r--r--packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts2
-rw-r--r--packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts2
-rw-r--r--packages/0x.js/src/contract_wrappers/token_wrapper.ts2
-rw-r--r--packages/0x.js/src/order_watcher/event_watcher.ts2
-rw-r--r--packages/0x.js/src/order_watcher/order_state_watcher.ts2
-rw-r--r--packages/0x.js/src/types.ts1
-rw-r--r--packages/0x.js/src/utils/constants.ts1
-rw-r--r--packages/0x.js/src/utils/order_validation_utils.ts7
-rw-r--r--packages/0x.js/src/utils/signature_utils.ts45
-rw-r--r--packages/0x.js/src/utils/utils.ts50
-rw-r--r--packages/0x.js/test/0x.js_test.ts133
-rw-r--r--packages/0x.js/test/assert_test.ts43
-rw-r--r--packages/0x.js/test/order_validation_test.ts3
-rw-r--r--packages/0x.js/test/utils/fill_scenarios.ts4
-rw-r--r--packages/0x.js/test/utils/web3_wrapper.ts3
-rw-r--r--packages/order-utils/.npmignore6
-rw-r--r--packages/order-utils/CHANGELOG.json1
-rw-r--r--packages/order-utils/README.md77
-rw-r--r--packages/order-utils/coverage/.gitkeep0
-rw-r--r--packages/order-utils/package.json76
-rw-r--r--packages/order-utils/src/assert.ts (renamed from packages/0x.js/src/utils/assert.ts)6
-rw-r--r--packages/order-utils/src/constants.ts3
-rw-r--r--packages/order-utils/src/globals.d.ts6
-rw-r--r--packages/order-utils/src/index.ts7
-rw-r--r--packages/order-utils/src/monorepo_scripts/postpublish.ts8
-rw-r--r--packages/order-utils/src/monorepo_scripts/stage_docs.ts8
-rw-r--r--packages/order-utils/src/order_factory.ts (renamed from packages/0x.js/test/utils/order_factory.ts)13
-rw-r--r--packages/order-utils/src/order_hash.ts89
-rw-r--r--packages/order-utils/src/salt.ts18
-rw-r--r--packages/order-utils/src/signature_utils.ts119
-rw-r--r--packages/order-utils/src/types.ts3
-rw-r--r--packages/order-utils/test/assert_test.ts35
-rw-r--r--packages/order-utils/test/order_hash_test.ts46
-rw-r--r--packages/order-utils/test/signature_utils_test.ts157
-rw-r--r--packages/order-utils/test/utils/chai_setup.ts13
-rw-r--r--packages/order-utils/test/utils/web3_wrapper.ts9
-rw-r--r--packages/order-utils/tsconfig.json7
-rw-r--r--packages/order-utils/tslint.json3
-rw-r--r--packages/react-docs/src/types.ts1
-rw-r--r--packages/react-docs/src/utils/typedoc_utils.ts6
-rw-r--r--packages/website/md/docs/order_utils/installation.md17
-rw-r--r--packages/website/md/docs/order_utils/introduction.md1
-rw-r--r--packages/website/ts/containers/order_utils_documentation.ts99
-rw-r--r--packages/website/ts/index.tsx7
-rw-r--r--packages/website/ts/pages/documentation/doc_page.tsx1
-rw-r--r--packages/website/ts/types.ts2
-rw-r--r--packages/website/ts/utils/utils.ts3
52 files changed, 920 insertions, 395 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json
index b2aebf803..c2545b7da 100644
--- a/packages/0x.js/CHANGELOG.json
+++ b/packages/0x.js/CHANGELOG.json
@@ -1,5 +1,18 @@
[
{
+ "version": "0.38.0",
+ "changes": [
+ {
+ "note": "Add `zeroEx.getProvider()`",
+ "pr": 559
+ },
+ {
+ "note": "Move `ZeroExError.InvalidSignature` to `@0xproject/order-utils` `OrderError.InvalidSignature`",
+ "pr": 559
+ }
+ ]
+ },
+ {
"version": "0.37.0",
"changes": [
{
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json
index 24fd44413..33c29a788 100644
--- a/packages/0x.js/package.json
+++ b/packages/0x.js/package.json
@@ -103,6 +103,7 @@
"@0xproject/types": "^0.6.1",
"@0xproject/typescript-typings": "^0.2.0",
"@0xproject/utils": "^0.5.2",
+ "@0xproject/order-utils": "^0.0.1",
"@0xproject/web3-wrapper": "^0.6.1",
"bintrees": "^1.0.2",
"bn.js": "^4.11.8",
diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts
index 94d97c23e..780d1b52a 100644
--- a/packages/0x.js/src/0x.ts
+++ b/packages/0x.js/src/0x.ts
@@ -1,4 +1,12 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
+import {
+ assert,
+ generatePseudoRandomSalt,
+ getOrderHashHex,
+ isValidOrderHash,
+ isValidSignature,
+ signOrderHashAsync,
+} from '@0xproject/order-utils';
import { ECSignature, Order, Provider, SignedOrder, TransactionReceiptWithDecodedLogs } from '@0xproject/types';
import { AbiDecoder, BigNumber, intervalUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
@@ -16,10 +24,8 @@ import { zeroExConfigSchema } from './schemas/zero_ex_config_schema';
import { zeroExPrivateNetworkConfigSchema } from './schemas/zero_ex_private_network_config_schema';
import { zeroExPublicNetworkConfigSchema } from './schemas/zero_ex_public_network_config_schema';
import { OrderStateWatcherConfig, ZeroExConfig, ZeroExError } from './types';
-import { assert } from './utils/assert';
import { constants } from './utils/constants';
import { decorators } from './utils/decorators';
-import { signatureUtils } from './utils/signature_utils';
import { utils } from './utils/utils';
/**
@@ -33,6 +39,36 @@ export class ZeroEx {
* this constant for your convenience.
*/
public static NULL_ADDRESS = constants.NULL_ADDRESS;
+ /**
+ * Generates a pseudo-random 256-bit salt.
+ * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash
+ * and will not collide with other outstanding orders that are identical in all other parameters.
+ * @return A pseudo-random 256-bit number that can be used as a salt.
+ */
+ public static generatePseudoRandomSalt = generatePseudoRandomSalt;
+ /**
+ * Verifies that the elliptic curve signature `signature` was generated
+ * by signing `data` with the private key corresponding to the `signerAddress` address.
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature An object containing the elliptic curve signature parameters.
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the signature is valid for the supplied signerAddress and data.
+ */
+ public static isValidSignature = isValidSignature;
+ /**
+ * Computes the orderHash for a supplied order.
+ * @param order An object that conforms to the Order or SignedOrder interface definitions.
+ * @return The resulting orderHash from hashing the supplied order.
+ */
+ public static getOrderHashHex = getOrderHashHex;
+ /**
+ * Checks if the supplied hex encoded order hash is valid.
+ * Note: Valid means it has the expected format, not that an order with the orderHash exists.
+ * Use this method when processing orderHashes submitted as user input.
+ * @param orderHash Hex encoded orderHash.
+ * @return Whether the supplied orderHash has the expected format.
+ */
+ public static isValidOrderHash = isValidOrderHash;
/**
* An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract.
@@ -59,52 +95,6 @@ export class ZeroEx {
public proxy: TokenTransferProxyWrapper;
private _web3Wrapper: Web3Wrapper;
/**
- * Verifies that the elliptic curve signature `signature` was generated
- * by signing `data` with the private key corresponding to the `signerAddress` address.
- * @param data The hex encoded data signed by the supplied signature.
- * @param signature An object containing the elliptic curve signature parameters.
- * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
- * @return Whether the signature is valid for the supplied signerAddress and data.
- */
- public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
- assert.isHexString('data', data);
- assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
- assert.isETHAddressHex('signerAddress', signerAddress);
- const normalizedSignerAddress = signerAddress.toLowerCase();
-
- const isValidSignature = signatureUtils.isValidSignature(data, signature, normalizedSignerAddress);
- return isValidSignature;
- }
- /**
- * Generates a pseudo-random 256-bit salt.
- * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash
- * and will not collide with other outstanding orders that are identical in all other parameters.
- * @return A pseudo-random 256-bit number that can be used as a salt.
- */
- public static generatePseudoRandomSalt(): BigNumber {
- // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
- // Source: https://mikemcl.github.io/bignumber.js/#random
- const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
- const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1);
- const salt = randomNumber.times(factor).round();
- return salt;
- }
- /**
- * Checks if the supplied hex encoded order hash is valid.
- * Note: Valid means it has the expected format, not that an order with the orderHash exists.
- * Use this method when processing orderHashes submitted as user input.
- * @param orderHash Hex encoded orderHash.
- * @return Whether the supplied orderHash has the expected format.
- */
- public static isValidOrderHash(orderHash: string): boolean {
- // Since this method can be called to check if any arbitrary string conforms to an orderHash's
- // format, we only assert that we were indeed passed a string.
- assert.isString('orderHash', orderHash);
- const schemaValidator = new SchemaValidator();
- const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
- return isValidOrderHash;
- }
- /**
* A unit amount is defined as the amount of a token above the specified decimal places (integer part).
* E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent
* to 1 unit.
@@ -133,17 +123,6 @@ export class ZeroEx {
return baseUnitAmount;
}
/**
- * Computes the orderHash for a supplied order.
- * @param order An object that conforms to the Order or SignedOrder interface definitions.
- * @return The resulting orderHash from hashing the supplied order.
- */
- @decorators.syncZeroExErrorHandler
- public static getOrderHashHex(order: Order | SignedOrder): string {
- assert.doesConformToSchema('order', order, schemas.orderSchema);
- const orderHashHex = utils.getOrderHashHex(order);
- return orderHashHex;
- }
- /**
* Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library.
* @param provider The Provider instance you would like the 0x.js library to use for interacting with
* the Ethereum network.
@@ -205,6 +184,12 @@ export class ZeroEx {
(this.etherToken as any)._setNetworkId(networkId);
}
/**
+ * Get the provider instance currently used by 0x.js
+ */
+ public getProvider(): Provider {
+ return this._web3Wrapper.getProvider();
+ }
+ /**
* Get user Ethereum addresses available through the supplied web3 provider available for sending transactions.
* @return An array of available user Ethereum addresses.
*/
@@ -229,41 +214,12 @@ export class ZeroEx {
signerAddress: string,
shouldAddPersonalMessagePrefix: boolean,
): Promise<ECSignature> {
- assert.isHexString('orderHash', orderHash);
- await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper);
- const normalizedSignerAddress = signerAddress.toLowerCase();
-
- let msgHashHex = orderHash;
- if (shouldAddPersonalMessagePrefix) {
- const orderHashBuff = ethUtil.toBuffer(orderHash);
- const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
- msgHashHex = ethUtil.bufferToHex(msgHashBuff);
- }
-
- const signature = await this._web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
-
- // HACK: There is no consensus on whether the signatureHex string should be formatted as
- // v + r + s OR r + s + v, and different clients (even different versions of the same client)
- // return the signature params in different orders. In order to support all client implementations,
- // we parse the signature in both ways, and evaluate if either one is a valid signature.
- const validVParamValues = [27, 28];
- const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature);
- if (_.includes(validVParamValues, ecSignatureVRS.v)) {
- const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress);
- if (isValidVRSSignature) {
- return ecSignatureVRS;
- }
- }
-
- const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature);
- if (_.includes(validVParamValues, ecSignatureRSV.v)) {
- const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress);
- if (isValidRSVSignature) {
- return ecSignatureRSV;
- }
- }
-
- throw new Error(ZeroExError.InvalidSignature);
+ return signOrderHashAsync(
+ this._web3Wrapper.getProvider(),
+ orderHash,
+ signerAddress,
+ shouldAddPersonalMessagePrefix,
+ );
}
/**
* Waits for a transaction to be mined and returns the transaction receipt.
diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
index fd39de34b..8a8caa4dc 100644
--- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
@@ -1,4 +1,5 @@
import { schemas } from '@0xproject/json-schemas';
+import { assert } from '@0xproject/order-utils';
import { LogWithDecodedArgs } from '@0xproject/types';
import { AbiDecoder, BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
@@ -6,7 +7,6 @@ import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { BlockRange, EventCallback, IndexedFilterValues, TransactionOpts, ZeroExError } from '../types';
-import { assert } from '../utils/assert';
import { ContractWrapper } from './contract_wrapper';
import { EtherTokenContract, EtherTokenContractEventArgs, EtherTokenEvents } from './generated/ether_token';
diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
index 7cda70f16..5dca61ab3 100644
--- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
@@ -1,4 +1,5 @@
import { schemas } from '@0xproject/json-schemas';
+import { assert, getOrderHashHex } from '@0xproject/order-utils';
import {
BlockParamLiteral,
DecodedLogArgs,
@@ -30,7 +31,6 @@ import {
OrderValues,
ValidateOrderFillableOpts,
} from '../types';
-import { assert } from '../utils/assert';
import { decorators } from '../utils/decorators';
import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator';
import { OrderStateUtils } from '../utils/order_state_utils';
@@ -570,7 +570,7 @@ export class ExchangeWrapper extends ContractWrapper {
? SHOULD_VALIDATE_BY_DEFAULT
: orderTransactionOpts.shouldValidate;
if (shouldValidate) {
- const orderHash = utils.getOrderHashHex(order);
+ const orderHash = getOrderHashHex(order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils.validateCancelOrderThrowIfInvalid(
order,
@@ -629,7 +629,7 @@ export class ExchangeWrapper extends ContractWrapper {
: orderTransactionOpts.shouldValidate;
if (shouldValidate) {
for (const orderCancellationRequest of orderCancellationRequests) {
- const orderHash = utils.getOrderHashHex(orderCancellationRequest.order);
+ const orderHash = getOrderHashHex(orderCancellationRequest.order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils.validateCancelOrderThrowIfInvalid(
orderCancellationRequest.order,
@@ -801,7 +801,7 @@ export class ExchangeWrapper extends ContractWrapper {
): Promise<void> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount);
- const orderHash = utils.getOrderHashHex(order);
+ const orderHash = getOrderHashHex(order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils.validateCancelOrderThrowIfInvalid(
order,
diff --git a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts
index c4a193264..a1ec91757 100644
--- a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts
@@ -1,9 +1,9 @@
+import { assert } from '@0xproject/order-utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { Token, TokenMetadata } from '../types';
-import { assert } from '../utils/assert';
import { constants } from '../utils/constants';
import { ContractWrapper } from './contract_wrapper';
diff --git a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts
index be558b5be..495923ecc 100644
--- a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts
@@ -1,8 +1,8 @@
+import { assert } from '@0xproject/order-utils';
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';
diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
index 194cfb5aa..6751f46b3 100644
--- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
@@ -1,4 +1,5 @@
import { schemas } from '@0xproject/json-schemas';
+import { assert } from '@0xproject/order-utils';
import { LogWithDecodedArgs } from '@0xproject/types';
import { AbiDecoder, BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
@@ -6,7 +7,6 @@ import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { BlockRange, EventCallback, IndexedFilterValues, MethodOpts, TransactionOpts, ZeroExError } from '../types';
-import { assert } from '../utils/assert';
import { constants } from '../utils/constants';
import { ContractWrapper } from './contract_wrapper';
diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts
index de5a99a46..545b352a4 100644
--- a/packages/0x.js/src/order_watcher/event_watcher.ts
+++ b/packages/0x.js/src/order_watcher/event_watcher.ts
@@ -1,10 +1,10 @@
+import { assert } from '@0xproject/order-utils';
import { BlockParamLiteral, LogEntry } from '@0xproject/types';
import { intervalUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { EventWatcherCallback, ZeroExError } from '../types';
-import { assert } from '../utils/assert';
const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts
index a9df8ac9d..008db6bc4 100644
--- a/packages/0x.js/src/order_watcher/order_state_watcher.ts
+++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts
@@ -1,4 +1,5 @@
import { schemas } from '@0xproject/json-schemas';
+import { assert } from '@0xproject/order-utils';
import { BlockParamLiteral, LogWithDecodedArgs, SignedOrder } from '@0xproject/types';
import { AbiDecoder, intervalUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
@@ -33,7 +34,6 @@ import {
OrderStateWatcherConfig,
ZeroExError,
} from '../types';
-import { assert } from '../utils/assert';
import { OrderStateUtils } from '../utils/order_state_utils';
import { utils } from '../utils/utils';
diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts
index 151204928..ae9f98c5f 100644
--- a/packages/0x.js/src/types.ts
+++ b/packages/0x.js/src/types.ts
@@ -27,7 +27,6 @@ export enum ZeroExError {
TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST',
UnhandledError = 'UNHANDLED_ERROR',
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
- InvalidSignature = 'INVALID_SIGNATURE',
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
diff --git a/packages/0x.js/src/utils/constants.ts b/packages/0x.js/src/utils/constants.ts
index 06beec8e2..07da6745d 100644
--- a/packages/0x.js/src/utils/constants.ts
+++ b/packages/0x.js/src/utils/constants.ts
@@ -3,7 +3,6 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
TESTRPC_NETWORK_ID: 50,
- MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas',
INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts
index b320a3e92..a13c3dc04 100644
--- a/packages/0x.js/src/utils/order_validation_utils.ts
+++ b/packages/0x.js/src/utils/order_validation_utils.ts
@@ -1,3 +1,4 @@
+import { getOrderHashHex, OrderError } from '@0xproject/order-utils';
import { Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
@@ -113,7 +114,7 @@ export class OrderValidationUtils {
zrxTokenAddress: string,
expectedFillTakerTokenAmount?: BigNumber,
): Promise<void> {
- const orderHash = utils.getOrderHashHex(signedOrder);
+ const orderHash = getOrderHashHex(signedOrder);
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount,
@@ -142,9 +143,9 @@ export class OrderValidationUtils {
if (fillTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
}
- const orderHash = utils.getOrderHashHex(signedOrder);
+ const orderHash = getOrderHashHex(signedOrder);
if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
- throw new Error(ZeroExError.InvalidSignature);
+ throw new Error(OrderError.InvalidSignature);
}
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
diff --git a/packages/0x.js/src/utils/signature_utils.ts b/packages/0x.js/src/utils/signature_utils.ts
deleted file mode 100644
index 46f167339..000000000
--- a/packages/0x.js/src/utils/signature_utils.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { ECSignature } from '@0xproject/types';
-import * as ethUtil from 'ethereumjs-util';
-
-export const signatureUtils = {
- isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
- const dataBuff = ethUtil.toBuffer(data);
- const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
- try {
- const pubKey = ethUtil.ecrecover(
- msgHashBuff,
- signature.v,
- ethUtil.toBuffer(signature.r),
- ethUtil.toBuffer(signature.s),
- );
- const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
- return retrievedAddress === signerAddress;
- } catch (err) {
- return false;
- }
- },
- parseSignatureHexAsVRS(signatureHex: string): ECSignature {
- const signatureBuffer = ethUtil.toBuffer(signatureHex);
- let v = signatureBuffer[0];
- if (v < 27) {
- v += 27;
- }
- const r = signatureBuffer.slice(1, 33);
- const s = signatureBuffer.slice(33, 65);
- const ecSignature: ECSignature = {
- v,
- r: ethUtil.bufferToHex(r),
- s: ethUtil.bufferToHex(s),
- };
- return ecSignature;
- },
- parseSignatureHexAsRSV(signatureHex: string): ECSignature {
- const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
- const ecSignature: ECSignature = {
- v,
- r: ethUtil.bufferToHex(r),
- s: ethUtil.bufferToHex(s),
- };
- return ecSignature;
- },
-};
diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts
index c8bcd907e..af1125632 100644
--- a/packages/0x.js/src/utils/utils.ts
+++ b/packages/0x.js/src/utils/utils.ts
@@ -1,59 +1,9 @@
-import { Order, SignedOrder, SolidityTypes } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
-import BN = require('bn.js');
-import * as ethABI from 'ethereumjs-abi';
-import * as ethUtil from 'ethereumjs-util';
-import * as _ from 'lodash';
export const utils = {
- /**
- * Converts BigNumber instance to BN
- * The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that
- * expects values of Solidity type `uint` to be passed as type `BN`.
- * We do not use BN anywhere else in the codebase.
- */
- bigNumberToBN(value: BigNumber) {
- return new BN(value.toString(), 10);
- },
spawnSwitchErr(name: string, value: any): Error {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
},
- getOrderHashHex(order: Order | SignedOrder): string {
- const orderParts = [
- { value: order.exchangeContractAddress, type: SolidityTypes.Address },
- { value: order.maker, type: SolidityTypes.Address },
- { value: order.taker, type: SolidityTypes.Address },
- { value: order.makerTokenAddress, type: SolidityTypes.Address },
- { value: order.takerTokenAddress, type: SolidityTypes.Address },
- { value: order.feeRecipient, type: SolidityTypes.Address },
- {
- value: utils.bigNumberToBN(order.makerTokenAmount),
- type: SolidityTypes.Uint256,
- },
- {
- value: utils.bigNumberToBN(order.takerTokenAmount),
- type: SolidityTypes.Uint256,
- },
- {
- value: utils.bigNumberToBN(order.makerFee),
- type: SolidityTypes.Uint256,
- },
- {
- value: utils.bigNumberToBN(order.takerFee),
- type: SolidityTypes.Uint256,
- },
- {
- value: utils.bigNumberToBN(order.expirationUnixTimestampSec),
- type: SolidityTypes.Uint256,
- },
- { value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 },
- ];
- const types = _.map(orderParts, o => o.type);
- const values = _.map(orderParts, o => o.value);
- const hashBuff = ethABI.soliditySHA3(types, values);
- const hashHex = ethUtil.bufferToHex(hashBuff);
- return hashHex;
- },
getCurrentUnixTimestampSec(): BigNumber {
return new BigNumber(Date.now() / 1000).round();
},
diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts
index 838ee7080..6dccdaea7 100644
--- a/packages/0x.js/test/0x.js_test.ts
+++ b/packages/0x.js/test/0x.js_test.ts
@@ -17,8 +17,6 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure();
const expect = chai.expect;
-const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
-
describe('ZeroEx library', () => {
let zeroEx: ZeroEx;
before(async () => {
@@ -63,14 +61,12 @@ describe('ZeroEx library', () => {
};
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
it("should return false if the data doesn't pertain to the signature & address", async () => {
- expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address),
).to.become(false);
});
it("should return false if the address doesn't pertain to the signature & data", async () => {
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
- expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(
dataHex,
@@ -81,45 +77,16 @@ describe('ZeroEx library', () => {
});
it("should return false if the signature doesn't pertain to the dataHex & address", async () => {
const wrongSignature = _.assign({}, signature, { v: 28 });
- expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address),
).to.become(false);
});
it('should return true if the signature does pertain to the dataHex & address', async () => {
- const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address);
- expect(isValidSignatureLocal).to.be.true();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address),
).to.become(true);
});
});
- describe('#generateSalt', () => {
- it('generates different salts', () => {
- const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt());
- expect(equal).to.be.false();
- });
- it('generates salt in range [0..2^256)', () => {
- const salt = ZeroEx.generatePseudoRandomSalt();
- expect(salt.greaterThanOrEqualTo(0)).to.be.true();
- const twoPow256 = new BigNumber(2).pow(256);
- expect(salt.lessThan(twoPow256)).to.be.true();
- });
- });
- describe('#isValidOrderHash', () => {
- it('returns false if the value is not a hex string', () => {
- const isValid = ZeroEx.isValidOrderHash('not a hex');
- expect(isValid).to.be.false();
- });
- it('returns false if the length is wrong', () => {
- const isValid = ZeroEx.isValidOrderHash('0xdeadbeef');
- expect(isValid).to.be.false();
- });
- it('returns true if order hash is correct', () => {
- const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0'));
- expect(isValid).to.be.true();
- });
- });
describe('#toUnitAmount', () => {
it('should throw if invalid baseUnit amount supplied as argument', () => {
const invalidBaseUnitAmount = new BigNumber(1000000000.4);
@@ -152,106 +119,6 @@ describe('ZeroEx library', () => {
);
});
});
- describe('#getOrderHashHex', () => {
- const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
- const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
- const order: Order = {
- maker: constants.NULL_ADDRESS,
- taker: constants.NULL_ADDRESS,
- feeRecipient: constants.NULL_ADDRESS,
- makerTokenAddress: constants.NULL_ADDRESS,
- takerTokenAddress: constants.NULL_ADDRESS,
- exchangeContractAddress: fakeExchangeContractAddress,
- salt: new BigNumber(0),
- makerFee: new BigNumber(0),
- takerFee: new BigNumber(0),
- makerTokenAmount: new BigNumber(0),
- takerTokenAmount: new BigNumber(0),
- expirationUnixTimestampSec: new BigNumber(0),
- };
- it('calculates the order hash', async () => {
- const orderHash = ZeroEx.getOrderHashHex(order);
- expect(orderHash).to.be.equal(expectedOrderHash);
- });
- it('throws a readable error message if taker format is invalid', async () => {
- const orderWithInvalidtakerFormat = {
- ...order,
- taker: (null as any) as string,
- };
- const expectedErrorMessage =
- 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
- expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage);
- });
- });
- describe('#signOrderHashAsync', () => {
- let stubs: Sinon.SinonStub[] = [];
- let makerAddress: string;
- before(async () => {
- const availableAddreses = await zeroEx.getAvailableAddressesAsync();
- makerAddress = availableAddreses[0];
- });
- afterEach(() => {
- // clean up any stubs after the test has completed
- _.each(stubs, s => s.restore());
- stubs = [];
- });
- it('Should return the correct ECSignature', async () => {
- const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
- const expectedECSignature = {
- v: 27,
- r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
- s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
- };
- const ecSignature = await zeroEx.signOrderHashAsync(
- orderHash,
- makerAddress,
- SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
- );
- expect(ecSignature).to.deep.equal(expectedECSignature);
- });
- it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => {
- const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
- const signature =
- '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
- const expectedECSignature = {
- v: 27,
- r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3',
- s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
- };
- stubs = [
- Sinon.stub((zeroEx as any)._web3Wrapper, 'signMessageAsync').returns(Promise.resolve(signature)),
- Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
- ];
-
- const ecSignature = await zeroEx.signOrderHashAsync(
- orderHash,
- makerAddress,
- SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
- );
- expect(ecSignature).to.deep.equal(expectedECSignature);
- });
- it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => {
- const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7';
- const signature =
- '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
- const expectedECSignature = {
- v: 27,
- r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0',
- s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
- };
- stubs = [
- Sinon.stub((zeroEx as any)._web3Wrapper, 'signMessageAsync').returns(Promise.resolve(signature)),
- Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
- ];
-
- const ecSignature = await zeroEx.signOrderHashAsync(
- orderHash,
- makerAddress,
- SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
- );
- expect(ecSignature).to.deep.equal(expectedECSignature);
- });
- });
describe('#awaitTransactionMinedAsync', () => {
beforeEach(async () => {
await blockchainLifecycle.startAsync();
diff --git a/packages/0x.js/test/assert_test.ts b/packages/0x.js/test/assert_test.ts
index b08f3e23b..e69de29bb 100644
--- a/packages/0x.js/test/assert_test.ts
+++ b/packages/0x.js/test/assert_test.ts
@@ -1,43 +0,0 @@
-import { web3Factory } from '@0xproject/dev-utils';
-import * as chai from 'chai';
-import 'mocha';
-
-import { ZeroEx } from '../src';
-import { assert } from '../src/utils/assert';
-
-import { constants } from './utils/constants';
-import { provider } from './utils/web3_wrapper';
-
-const expect = chai.expect;
-
-describe('Assertion library', () => {
- const config = {
- networkId: constants.TESTRPC_NETWORK_ID,
- };
- const zeroEx = new ZeroEx(provider, config);
- describe('#isSenderAddressHexAsync', () => {
- it('throws when address is invalid', async () => {
- const address = '0xdeadbeef';
- const varName = 'address';
- return expect(
- assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper),
- ).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`);
- });
- it('throws when address is unavailable', async () => {
- const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42';
- const varName = 'address';
- return expect(
- assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper),
- ).to.be.rejectedWith(
- `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`,
- );
- });
- it("doesn't throw if address is available", async () => {
- const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0];
- const varName = 'address';
- return expect(
- assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper),
- ).to.become(undefined);
- });
- });
-});
diff --git a/packages/0x.js/test/order_validation_test.ts b/packages/0x.js/test/order_validation_test.ts
index 9b843b930..0cb95c1b6 100644
--- a/packages/0x.js/test/order_validation_test.ts
+++ b/packages/0x.js/test/order_validation_test.ts
@@ -1,4 +1,5 @@
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
+import { OrderError } from '@0xproject/order-utils';
import { BlockParamLiteral } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@@ -169,7 +170,7 @@ describe('OrderValidation', () => {
signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27;
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
- ).to.be.rejectedWith(ZeroExError.InvalidSignature);
+ ).to.be.rejectedWith(OrderError.InvalidSignature);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts
index 7d0e8c501..5a82a56d2 100644
--- a/packages/0x.js/test/utils/fill_scenarios.ts
+++ b/packages/0x.js/test/utils/fill_scenarios.ts
@@ -1,10 +1,10 @@
+import { orderFactory } from '@0xproject/order-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { SignedOrder, Token, ZeroEx } from '../../src';
import { artifacts } from '../../src/artifacts';
import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
-import { orderFactory } from '../utils/order_factory';
import { constants } from './constants';
@@ -164,7 +164,7 @@ export class FillScenarios {
]);
const signedOrder = await orderFactory.createSignedOrderAsync(
- this._zeroEx,
+ this._zeroEx.getProvider(),
makerAddress,
takerAddress,
makerFee,
diff --git a/packages/0x.js/test/utils/web3_wrapper.ts b/packages/0x.js/test/utils/web3_wrapper.ts
index b7b3f0b7f..b0ccfa546 100644
--- a/packages/0x.js/test/utils/web3_wrapper.ts
+++ b/packages/0x.js/test/utils/web3_wrapper.ts
@@ -1,9 +1,6 @@
import { devConstants, web3Factory } from '@0xproject/dev-utils';
import { Provider } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as Web3 from 'web3';
-
-import { constants } from './constants';
const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
const provider: Provider = web3.currentProvider;
diff --git a/packages/order-utils/.npmignore b/packages/order-utils/.npmignore
new file mode 100644
index 000000000..24e65ad5b
--- /dev/null
+++ b/packages/order-utils/.npmignore
@@ -0,0 +1,6 @@
+.*
+yarn-error.log
+/scripts/
+/src/
+tsconfig.json
+/lib/monorepo_scripts/
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/packages/order-utils/CHANGELOG.json
@@ -0,0 +1 @@
+[]
diff --git a/packages/order-utils/README.md b/packages/order-utils/README.md
new file mode 100644
index 000000000..4b571509a
--- /dev/null
+++ b/packages/order-utils/README.md
@@ -0,0 +1,77 @@
+## @0xproject/order-utils
+
+0x order-related utilities for those developing on top of 0x protocol.
+
+### Read the [Documentation](https://0xproject.com/docs/order-utils).
+
+## Installation
+
+```bash
+yarn add @0xproject/order-utils
+```
+
+If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
+
+```json
+"compilerOptions": {
+ "typeRoots": ["node_modules/@0xproject/typescript-typings/types", "node_modules/@types"],
+}
+```
+
+## Contributing
+
+We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Build
+
+If this is your **first** time building this package, you must first build **all** packages within the monorepo. This is because packages that depend on other packages located inside this monorepo are symlinked when run from **within** the monorepo. This allows you to make changes across multiple packages without first publishing dependent packages to NPM. To build all packages, run the following from the monorepo root directory:
+
+```bash
+yarn lerna:rebuild
+```
+
+Or continuously rebuild on change:
+
+```bash
+yarn dev
+```
+
+You can also build this specific package by running the following from within its directory:
+
+```bash
+yarn build
+```
+
+or continuously rebuild on change:
+
+```bash
+yarn build:watch
+```
+
+### Clean
+
+```bash
+yarn clean
+```
+
+### Lint
+
+```bash
+yarn lint
+```
diff --git a/packages/order-utils/coverage/.gitkeep b/packages/order-utils/coverage/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/packages/order-utils/coverage/.gitkeep
diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json
new file mode 100644
index 000000000..cb21139a2
--- /dev/null
+++ b/packages/order-utils/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "@0xproject/order-utils",
+ "version": "0.0.1",
+ "description": "0x order utils",
+ "main": "lib/src/index.js",
+ "types": "lib/src/index.d.ts",
+ "scripts": {
+ "build:watch": "tsc -w",
+ "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
+ "test": "run-s clean build run_mocha",
+ "test:circleci": "yarn test:coverage",
+ "run_mocha": "mocha lib/test/**/*_test.js --bail --exit",
+ "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
+ "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
+ "clean": "shx rm -rf lib scripts",
+ "lint": "tslint --project .",
+ "manual:postpublish": "yarn build; node ./scripts/postpublish.js",
+ "docs:stage": "yarn build && node ./scripts/stage_docs.js",
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
+ "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
+ },
+ "config": {
+ "postpublish": {
+ "docPublishConfigs": {
+ "extraFileIncludes": [
+ "../types/src/index.ts"
+ ],
+ "s3BucketPath": "s3://doc-jsons/order-utils/",
+ "s3StagingBucketPath": "s3://staging-doc-jsons/order-utils/"
+ }
+ }
+ },
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/0xProject/0x-monorepo.git"
+ },
+ "bugs": {
+ "url": "https://github.com/0xProject/0x-monorepo/issues"
+ },
+ "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
+ "devDependencies": {
+ "@0xproject/monorepo-scripts": "^0.1.18",
+ "@0xproject/dev-utils": "^0.3.6",
+ "@0xproject/tslint-config": "^0.4.16",
+ "@types/lodash": "4.14.104",
+ "chai": "^4.0.1",
+ "chai-as-promised": "^7.1.0",
+ "chai-bignumber": "^2.0.1",
+ "dirty-chai": "^2.0.1",
+ "sinon": "^4.0.0",
+ "mocha": "^4.0.1",
+ "copyfiles": "^1.2.0",
+ "npm-run-all": "^4.1.2",
+ "typedoc": "0xProject/typedoc",
+ "shx": "^0.2.2",
+ "tslint": "5.8.0",
+ "typescript": "2.7.1"
+ },
+ "dependencies": {
+ "@0xproject/assert": "^0.2.7",
+ "@0xproject/types": "^0.6.1",
+ "@0xproject/json-schemas": "^0.7.21",
+ "@0xproject/typescript-typings": "^0.2.0",
+ "@0xproject/web3-wrapper": "^0.6.1",
+ "@0xproject/utils": "^0.5.2",
+ "@types/node": "^8.0.53",
+ "bn.js": "^4.11.8",
+ "lodash": "^4.17.4",
+ "ethereumjs-abi": "^0.6.4",
+ "ethereumjs-util": "^5.1.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/0x.js/src/utils/assert.ts b/packages/order-utils/src/assert.ts
index 5e8004cd0..92641b845 100644
--- a/packages/0x.js/src/utils/assert.ts
+++ b/packages/order-utils/src/assert.ts
@@ -8,13 +8,13 @@ import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
-import { signatureUtils } from '../utils/signature_utils';
+import { isValidSignature } from './signature_utils';
export const assert = {
...sharedAssert,
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
- const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
- this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
+ const isValid = isValidSignature(orderHash, ecSignature, signerAddress);
+ this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
},
async isSenderAddressAsync(
variableName: string,
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts
new file mode 100644
index 000000000..ec2fe744a
--- /dev/null
+++ b/packages/order-utils/src/constants.ts
@@ -0,0 +1,3 @@
+export const constants = {
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+};
diff --git a/packages/order-utils/src/globals.d.ts b/packages/order-utils/src/globals.d.ts
new file mode 100644
index 000000000..94e63a32d
--- /dev/null
+++ b/packages/order-utils/src/globals.d.ts
@@ -0,0 +1,6 @@
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
new file mode 100644
index 000000000..4addd60d6
--- /dev/null
+++ b/packages/order-utils/src/index.ts
@@ -0,0 +1,7 @@
+export { getOrderHashHex, isValidOrderHash } from './order_hash';
+export { isValidSignature, signOrderHashAsync } from './signature_utils';
+export { orderFactory } from './order_factory';
+export { generatePseudoRandomSalt } from './salt';
+export { assert } from './assert';
+export { constants } from './constants';
+export { OrderError } from './types';
diff --git a/packages/order-utils/src/monorepo_scripts/postpublish.ts b/packages/order-utils/src/monorepo_scripts/postpublish.ts
new file mode 100644
index 000000000..dcb99d0f7
--- /dev/null
+++ b/packages/order-utils/src/monorepo_scripts/postpublish.ts
@@ -0,0 +1,8 @@
+import { postpublishUtils } from '@0xproject/monorepo-scripts';
+
+import * as packageJSON from '../package.json';
+import * as tsConfigJSON from '../tsconfig.json';
+
+const cwd = `${__dirname}/..`;
+// tslint:disable-next-line:no-floating-promises
+postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/order-utils/src/monorepo_scripts/stage_docs.ts b/packages/order-utils/src/monorepo_scripts/stage_docs.ts
new file mode 100644
index 000000000..e732ac8eb
--- /dev/null
+++ b/packages/order-utils/src/monorepo_scripts/stage_docs.ts
@@ -0,0 +1,8 @@
+import { postpublishUtils } from '@0xproject/monorepo-scripts';
+
+import * as packageJSON from '../package.json';
+import * as tsConfigJSON from '../tsconfig.json';
+
+const cwd = `${__dirname}/..`;
+// tslint:disable-next-line:no-floating-promises
+postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/0x.js/test/utils/order_factory.ts b/packages/order-utils/src/order_factory.ts
index 08f2081a4..2759aac81 100644
--- a/packages/0x.js/test/utils/order_factory.ts
+++ b/packages/order-utils/src/order_factory.ts
@@ -1,13 +1,16 @@
+import { Provider, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
-import { SignedOrder, ZeroEx } from '../../src';
+import { getOrderHashHex } from './order_hash';
+import { generatePseudoRandomSalt } from './salt';
+import { signOrderHashAsync } from './signature_utils';
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
export const orderFactory = {
async createSignedOrderAsync(
- zeroEx: ZeroEx,
+ provider: Provider,
maker: string,
taker: string,
makerFee: BigNumber,
@@ -33,13 +36,13 @@ export const orderFactory = {
takerTokenAmount,
makerTokenAddress,
takerTokenAddress,
- salt: ZeroEx.generatePseudoRandomSalt(),
+ salt: generatePseudoRandomSalt(),
exchangeContractAddress,
feeRecipient,
expirationUnixTimestampSec,
};
- const orderHash = ZeroEx.getOrderHashHex(order);
- const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
+ const orderHash = getOrderHashHex(order);
+ const ecSignature = await signOrderHashAsync(provider, orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
const signedOrder: SignedOrder = _.assign(order, { ecSignature });
return signedOrder;
},
diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts
new file mode 100644
index 000000000..8da11c596
--- /dev/null
+++ b/packages/order-utils/src/order_hash.ts
@@ -0,0 +1,89 @@
+import { schemas, SchemaValidator } from '@0xproject/json-schemas';
+import { Order, SignedOrder, SolidityTypes } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import BN = require('bn.js');
+import * as ethABI from 'ethereumjs-abi';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { assert } from './assert';
+
+const INVALID_TAKER_FORMAT = 'instance.taker is not of a type(s) string';
+
+/**
+ * Converts BigNumber instance to BN
+ * The only reason we convert to BN is to remain compatible with `ethABI.soliditySHA3` that
+ * expects values of Solidity type `uint` to be passed as type `BN`.
+ * We do not use BN anywhere else in the codebase.
+ */
+function bigNumberToBN(value: BigNumber) {
+ return new BN(value.toString(), 10);
+}
+
+/**
+ * Computes the orderHash for a supplied order.
+ * @param order An object that conforms to the Order or SignedOrder interface definitions.
+ * @return The resulting orderHash from hashing the supplied order.
+ */
+export function getOrderHashHex(order: Order | SignedOrder): string {
+ try {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
+ } catch (error) {
+ if (_.includes(error.message, INVALID_TAKER_FORMAT)) {
+ const errMsg =
+ 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
+ throw new Error(errMsg);
+ }
+ throw error;
+ }
+ const orderParts = [
+ { value: order.exchangeContractAddress, type: SolidityTypes.Address },
+ { value: order.maker, type: SolidityTypes.Address },
+ { value: order.taker, type: SolidityTypes.Address },
+ { value: order.makerTokenAddress, type: SolidityTypes.Address },
+ { value: order.takerTokenAddress, type: SolidityTypes.Address },
+ { value: order.feeRecipient, type: SolidityTypes.Address },
+ {
+ value: bigNumberToBN(order.makerTokenAmount),
+ type: SolidityTypes.Uint256,
+ },
+ {
+ value: bigNumberToBN(order.takerTokenAmount),
+ type: SolidityTypes.Uint256,
+ },
+ {
+ value: bigNumberToBN(order.makerFee),
+ type: SolidityTypes.Uint256,
+ },
+ {
+ value: bigNumberToBN(order.takerFee),
+ type: SolidityTypes.Uint256,
+ },
+ {
+ value: bigNumberToBN(order.expirationUnixTimestampSec),
+ type: SolidityTypes.Uint256,
+ },
+ { value: bigNumberToBN(order.salt), type: SolidityTypes.Uint256 },
+ ];
+ const types = _.map(orderParts, o => o.type);
+ const values = _.map(orderParts, o => o.value);
+ const hashBuff = ethABI.soliditySHA3(types, values);
+ const hashHex = ethUtil.bufferToHex(hashBuff);
+ return hashHex;
+}
+
+/**
+ * Checks if the supplied hex encoded order hash is valid.
+ * Note: Valid means it has the expected format, not that an order with the orderHash exists.
+ * Use this method when processing orderHashes submitted as user input.
+ * @param orderHash Hex encoded orderHash.
+ * @return Whether the supplied orderHash has the expected format.
+ */
+export function isValidOrderHash(orderHash: string): boolean {
+ // Since this method can be called to check if any arbitrary string conforms to an orderHash's
+ // format, we only assert that we were indeed passed a string.
+ assert.isString('orderHash', orderHash);
+ const schemaValidator = new SchemaValidator();
+ const isValid = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
+ return isValid;
+}
diff --git a/packages/order-utils/src/salt.ts b/packages/order-utils/src/salt.ts
new file mode 100644
index 000000000..90a4197c0
--- /dev/null
+++ b/packages/order-utils/src/salt.ts
@@ -0,0 +1,18 @@
+import { BigNumber } from '@0xproject/utils';
+
+const MAX_DIGITS_IN_UNSIGNED_256_INT = 78;
+
+/**
+ * Generates a pseudo-random 256-bit salt.
+ * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash
+ * and will not collide with other outstanding orders that are identical in all other parameters.
+ * @return A pseudo-random 256-bit number that can be used as a salt.
+ */
+export function generatePseudoRandomSalt(): BigNumber {
+ // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
+ // Source: https://mikemcl.github.io/bignumber.js/#random
+ const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT);
+ const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1);
+ const salt = randomNumber.times(factor).round();
+ return salt;
+}
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
new file mode 100644
index 000000000..b511573a8
--- /dev/null
+++ b/packages/order-utils/src/signature_utils.ts
@@ -0,0 +1,119 @@
+import { schemas } from '@0xproject/json-schemas';
+import { ECSignature, Provider } from '@0xproject/types';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { assert } from './assert';
+import { OrderError } from './types';
+
+/**
+ * Verifies that the elliptic curve signature `signature` was generated
+ * by signing `data` with the private key corresponding to the `signerAddress` address.
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature An object containing the elliptic curve signature parameters.
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the signature is valid for the supplied signerAddress and data.
+ */
+export function isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
+ assert.isHexString('data', data);
+ assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ const normalizedSignerAddress = signerAddress.toLowerCase();
+
+ const dataBuff = ethUtil.toBuffer(data);
+ const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
+ try {
+ const pubKey = ethUtil.ecrecover(
+ msgHashBuff,
+ signature.v,
+ ethUtil.toBuffer(signature.r),
+ ethUtil.toBuffer(signature.s),
+ );
+ const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
+ return retrievedAddress === signerAddress;
+ } catch (err) {
+ return false;
+ }
+}
+/**
+ * Signs an orderHash and returns it's elliptic curve signature.
+ * This method currently supports TestRPC, Geth and Parity above and below V1.6.6
+ * @param orderHash Hex encoded orderHash to sign.
+ * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
+ * must be available via the Provider supplied to 0x.js.
+ * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
+ * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
+ * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
+ * before sending the request.
+ * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
+ */
+export async function signOrderHashAsync(
+ provider: Provider,
+ orderHash: string,
+ signerAddress: string,
+ shouldAddPersonalMessagePrefix: boolean,
+): Promise<ECSignature> {
+ assert.isHexString('orderHash', orderHash);
+ const web3Wrapper = new Web3Wrapper(provider);
+ await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
+ const normalizedSignerAddress = signerAddress.toLowerCase();
+
+ let msgHashHex = orderHash;
+ if (shouldAddPersonalMessagePrefix) {
+ const orderHashBuff = ethUtil.toBuffer(orderHash);
+ const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
+ msgHashHex = ethUtil.bufferToHex(msgHashBuff);
+ }
+
+ const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
+
+ // HACK: There is no consensus on whether the signatureHex string should be formatted as
+ // v + r + s OR r + s + v, and different clients (even different versions of the same client)
+ // return the signature params in different orders. In order to support all client implementations,
+ // we parse the signature in both ways, and evaluate if either one is a valid signature.
+ const validVParamValues = [27, 28];
+ const ecSignatureVRS = parseSignatureHexAsVRS(signature);
+ if (_.includes(validVParamValues, ecSignatureVRS.v)) {
+ const isValidVRSSignature = isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress);
+ if (isValidVRSSignature) {
+ return ecSignatureVRS;
+ }
+ }
+
+ const ecSignatureRSV = parseSignatureHexAsRSV(signature);
+ if (_.includes(validVParamValues, ecSignatureRSV.v)) {
+ const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress);
+ if (isValidRSVSignature) {
+ return ecSignatureRSV;
+ }
+ }
+
+ throw new Error(OrderError.InvalidSignature);
+}
+
+function parseSignatureHexAsVRS(signatureHex: string): ECSignature {
+ const signatureBuffer = ethUtil.toBuffer(signatureHex);
+ let v = signatureBuffer[0];
+ if (v < 27) {
+ v += 27;
+ }
+ const r = signatureBuffer.slice(1, 33);
+ const s = signatureBuffer.slice(33, 65);
+ const ecSignature: ECSignature = {
+ v,
+ r: ethUtil.bufferToHex(r),
+ s: ethUtil.bufferToHex(s),
+ };
+ return ecSignature;
+}
+
+function parseSignatureHexAsRSV(signatureHex: string): ECSignature {
+ const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
+ const ecSignature: ECSignature = {
+ v,
+ r: ethUtil.bufferToHex(r),
+ s: ethUtil.bufferToHex(s),
+ };
+ return ecSignature;
+}
diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts
new file mode 100644
index 000000000..f79d52359
--- /dev/null
+++ b/packages/order-utils/src/types.ts
@@ -0,0 +1,3 @@
+export enum OrderError {
+ InvalidSignature = 'INVALID_SIGNATURE',
+}
diff --git a/packages/order-utils/test/assert_test.ts b/packages/order-utils/test/assert_test.ts
new file mode 100644
index 000000000..dfd19bf86
--- /dev/null
+++ b/packages/order-utils/test/assert_test.ts
@@ -0,0 +1,35 @@
+import { web3Factory } from '@0xproject/dev-utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { assert } from '../src/assert';
+
+import { chaiSetup } from './utils/chai_setup';
+import { web3Wrapper } from './utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('Assertion library', () => {
+ describe('#isSenderAddressHexAsync', () => {
+ it('throws when address is invalid', async () => {
+ const address = '0xdeadbeef';
+ const varName = 'address';
+ return expect(assert.isSenderAddressAsync(varName, address, web3Wrapper)).to.be.rejectedWith(
+ `Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`,
+ );
+ });
+ it('throws when address is unavailable', async () => {
+ const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42';
+ const varName = 'address';
+ return expect(assert.isSenderAddressAsync(varName, validUnrelatedAddress, web3Wrapper)).to.be.rejectedWith(
+ `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`,
+ );
+ });
+ it("doesn't throw if address is available", async () => {
+ const availableAddress = (await web3Wrapper.getAvailableAddressesAsync())[0];
+ const varName = 'address';
+ return expect(assert.isSenderAddressAsync(varName, availableAddress, web3Wrapper)).to.become(undefined);
+ });
+ });
+});
diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts
new file mode 100644
index 000000000..b6dda1a43
--- /dev/null
+++ b/packages/order-utils/test/order_hash_test.ts
@@ -0,0 +1,46 @@
+import { web3Factory } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { constants, getOrderHashHex } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { web3Wrapper } from './utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('Order hashing', () => {
+ describe('#getOrderHashHex', () => {
+ const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
+ const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
+ const order = {
+ maker: constants.NULL_ADDRESS,
+ taker: constants.NULL_ADDRESS,
+ feeRecipient: constants.NULL_ADDRESS,
+ makerTokenAddress: constants.NULL_ADDRESS,
+ takerTokenAddress: constants.NULL_ADDRESS,
+ exchangeContractAddress: fakeExchangeContractAddress,
+ salt: new BigNumber(0),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ makerTokenAmount: new BigNumber(0),
+ takerTokenAmount: new BigNumber(0),
+ expirationUnixTimestampSec: new BigNumber(0),
+ };
+ it('calculates the order hash', async () => {
+ const orderHash = getOrderHashHex(order);
+ expect(orderHash).to.be.equal(expectedOrderHash);
+ });
+ it('throws a readable error message if taker format is invalid', async () => {
+ const orderWithInvalidtakerFormat = {
+ ...order,
+ taker: (null as any) as string,
+ };
+ const expectedErrorMessage =
+ 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
+ expect(() => getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage);
+ });
+ });
+});
diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts
new file mode 100644
index 000000000..7af67ae2e
--- /dev/null
+++ b/packages/order-utils/test/signature_utils_test.ts
@@ -0,0 +1,157 @@
+import { web3Factory } from '@0xproject/dev-utils';
+import { JSONRPCErrorCallback, JSONRPCRequestPayload } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import 'mocha';
+import * as Sinon from 'sinon';
+
+import { generatePseudoRandomSalt, isValidOrderHash, isValidSignature, signOrderHashAsync } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { provider, web3Wrapper } from './utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
+
+describe('Signature utils', () => {
+ describe('#isValidSignature', () => {
+ // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
+ // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
+ const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
+ const signature = {
+ v: 27,
+ r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
+ s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
+ };
+ const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
+ it("should return false if the data doesn't pertain to the signature & address", async () => {
+ expect(isValidSignature('0x0', signature, address)).to.be.false();
+ });
+ it("should return false if the address doesn't pertain to the signature & data", async () => {
+ const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
+ expect(isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
+ });
+ it("should return false if the signature doesn't pertain to the dataHex & address", async () => {
+ const wrongSignature = _.assign({}, signature, { v: 28 });
+ expect(isValidSignature(dataHex, wrongSignature, address)).to.be.false();
+ });
+ it('should return true if the signature does pertain to the dataHex & address', async () => {
+ const isValidSignatureLocal = isValidSignature(dataHex, signature, address);
+ expect(isValidSignatureLocal).to.be.true();
+ });
+ });
+ describe('#generateSalt', () => {
+ it('generates different salts', () => {
+ const equal = generatePseudoRandomSalt().eq(generatePseudoRandomSalt());
+ expect(equal).to.be.false();
+ });
+ it('generates salt in range [0..2^256)', () => {
+ const salt = generatePseudoRandomSalt();
+ expect(salt.greaterThanOrEqualTo(0)).to.be.true();
+ const twoPow256 = new BigNumber(2).pow(256);
+ expect(salt.lessThan(twoPow256)).to.be.true();
+ });
+ });
+ describe('#isValidOrderHash', () => {
+ it('returns false if the value is not a hex string', () => {
+ const isValid = isValidOrderHash('not a hex');
+ expect(isValid).to.be.false();
+ });
+ it('returns false if the length is wrong', () => {
+ const isValid = isValidOrderHash('0xdeadbeef');
+ expect(isValid).to.be.false();
+ });
+ it('returns true if order hash is correct', () => {
+ const isValid = isValidOrderHash('0x' + Array(65).join('0'));
+ expect(isValid).to.be.true();
+ });
+ });
+ describe('#signOrderHashAsync', () => {
+ let stubs: Sinon.SinonStub[] = [];
+ let makerAddress: string;
+ before(async () => {
+ const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
+ makerAddress = availableAddreses[0];
+ });
+ afterEach(() => {
+ // clean up any stubs after the test has completed
+ _.each(stubs, s => s.restore());
+ stubs = [];
+ });
+ it('Should return the correct ECSignature', async () => {
+ const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
+ const expectedECSignature = {
+ v: 27,
+ r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
+ s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
+ };
+ const ecSignature = await signOrderHashAsync(
+ provider,
+ orderHash,
+ makerAddress,
+ SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
+ );
+ expect(ecSignature).to.deep.equal(expectedECSignature);
+ });
+ it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => {
+ const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
+ const signature =
+ '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
+ const expectedECSignature = {
+ v: 27,
+ r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3',
+ s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
+ };
+ stubs = [Sinon.stub('isValidSignature').returns(true)];
+
+ const fakeProvider = {
+ sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback) {
+ if (payload.method === 'eth_sign') {
+ callback(null, { id: 42, jsonrpc: '2.0', result: signature });
+ } else {
+ callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
+ }
+ },
+ };
+
+ const ecSignature = await signOrderHashAsync(
+ fakeProvider,
+ orderHash,
+ makerAddress,
+ SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
+ );
+ expect(ecSignature).to.deep.equal(expectedECSignature);
+ });
+ it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => {
+ const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7';
+ const signature =
+ '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
+ const expectedECSignature = {
+ v: 27,
+ r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0',
+ s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
+ };
+ stubs = [Sinon.stub('isValidSignature').returns(true)];
+ const fakeProvider = {
+ sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback) {
+ if (payload.method === 'eth_sign') {
+ callback(null, { id: 42, jsonrpc: '2.0', result: signature });
+ } else {
+ callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
+ }
+ },
+ };
+
+ const ecSignature = await signOrderHashAsync(
+ fakeProvider,
+ orderHash,
+ makerAddress,
+ SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
+ );
+ expect(ecSignature).to.deep.equal(expectedECSignature);
+ });
+ });
+});
diff --git a/packages/order-utils/test/utils/chai_setup.ts b/packages/order-utils/test/utils/chai_setup.ts
new file mode 100644
index 000000000..078edd309
--- /dev/null
+++ b/packages/order-utils/test/utils/chai_setup.ts
@@ -0,0 +1,13 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure() {
+ chai.config.includeStack = true;
+ chai.use(ChaiBigNumber());
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};
diff --git a/packages/order-utils/test/utils/web3_wrapper.ts b/packages/order-utils/test/utils/web3_wrapper.ts
new file mode 100644
index 000000000..b0ccfa546
--- /dev/null
+++ b/packages/order-utils/test/utils/web3_wrapper.ts
@@ -0,0 +1,9 @@
+import { devConstants, web3Factory } from '@0xproject/dev-utils';
+import { Provider } from '@0xproject/types';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+
+const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
+const provider: Provider = web3.currentProvider;
+const web3Wrapper = new Web3Wrapper(web3.currentProvider);
+
+export { provider, web3Wrapper };
diff --git a/packages/order-utils/tsconfig.json b/packages/order-utils/tsconfig.json
new file mode 100644
index 000000000..8b4cd47a2
--- /dev/null
+++ b/packages/order-utils/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["src/**/*", "test/**/*"]
+}
diff --git a/packages/order-utils/tslint.json b/packages/order-utils/tslint.json
new file mode 100644
index 000000000..ffaefe83a
--- /dev/null
+++ b/packages/order-utils/tslint.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@0xproject/tslint-config"]
+}
diff --git a/packages/react-docs/src/types.ts b/packages/react-docs/src/types.ts
index 3b4a57ad5..f4e61edc9 100644
--- a/packages/react-docs/src/types.ts
+++ b/packages/react-docs/src/types.ts
@@ -94,6 +94,7 @@ export enum KindString {
Method = 'Method',
Interface = 'Interface',
TypeAlias = 'Type alias',
+ ObjectLiteral = 'Object literal',
Variable = 'Variable',
Function = 'Function',
Enumeration = 'Enumeration',
diff --git a/packages/react-docs/src/utils/typedoc_utils.ts b/packages/react-docs/src/utils/typedoc_utils.ts
index 9c89b135a..0c7803a04 100644
--- a/packages/react-docs/src/utils/typedoc_utils.ts
+++ b/packages/react-docs/src/utils/typedoc_utils.ts
@@ -93,10 +93,16 @@ export const typeDocUtils = {
throw new Error('`react-docs` only supports projects with 1 exported class per file');
}
const isClassExport = packageDefinitionWithMergedChildren.children[0].kindString === KindString.Class;
+ const isObjectLiteralExport =
+ packageDefinitionWithMergedChildren.children[0].kindString === KindString.ObjectLiteral;
if (isClassExport) {
entities = packageDefinitionWithMergedChildren.children[0].children;
const commentObj = packageDefinitionWithMergedChildren.children[0].comment;
packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
+ } else if (isObjectLiteralExport) {
+ entities = packageDefinitionWithMergedChildren.children[0].children;
+ const commentObj = packageDefinitionWithMergedChildren.children[0].comment;
+ packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
} else {
entities = packageDefinitionWithMergedChildren.children;
}
diff --git a/packages/website/md/docs/order_utils/installation.md b/packages/website/md/docs/order_utils/installation.md
new file mode 100644
index 000000000..68a7cf960
--- /dev/null
+++ b/packages/website/md/docs/order_utils/installation.md
@@ -0,0 +1,17 @@
+**Install**
+
+```bash
+yarn add @0xproject/order-utils
+```
+
+**Import**
+
+```javascript
+import { createSignedOrderAsync } from '@0xproject/order-utils';
+```
+
+or
+
+```javascript
+var createSignedOrderAsync = require('@0xproject/order-utils').createSignedOrderAsync;
+```
diff --git a/packages/website/md/docs/order_utils/introduction.md b/packages/website/md/docs/order_utils/introduction.md
new file mode 100644
index 000000000..d5f3f2925
--- /dev/null
+++ b/packages/website/md/docs/order_utils/introduction.md
@@ -0,0 +1 @@
+Welcome to the [@0xproject/order-utils](https://github.com/0xProject/0x-monorepo/tree/development/packages/order-utils) documentation! Order utils is a set of utils around creating, signing, validating 0x orders.
diff --git a/packages/website/ts/containers/order_utils_documentation.ts b/packages/website/ts/containers/order_utils_documentation.ts
new file mode 100644
index 000000000..64aa7300f
--- /dev/null
+++ b/packages/website/ts/containers/order_utils_documentation.ts
@@ -0,0 +1,99 @@
+import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { State } from 'ts/redux/reducer';
+import { DocPackages, Environments, WebsitePaths } from 'ts/types';
+import { configs } from 'ts/utils/configs';
+import { constants } from 'ts/utils/constants';
+import { Translate } from 'ts/utils/translate';
+
+/* tslint:disable:no-var-requires */
+const IntroMarkdown = require('md/docs/order_utils/introduction');
+const InstallationMarkdown = require('md/docs/order_utils/installation');
+/* tslint:enable:no-var-requires */
+
+const docSections = {
+ introduction: 'introduction',
+ installation: 'installation',
+ usage: 'usage',
+ types: 'types',
+};
+
+const docsInfoConfig: DocsInfoConfig = {
+ id: DocPackages.OrderUtils,
+ type: SupportedDocJson.TypeDoc,
+ displayName: 'Order utils',
+ packageUrl: 'https://github.com/0xProject/0x-monorepo',
+ menu: {
+ introduction: [docSections.introduction],
+ install: [docSections.installation],
+ usage: [docSections.usage],
+ types: [docSections.types],
+ },
+ sectionNameToMarkdown: {
+ [docSections.introduction]: IntroMarkdown,
+ [docSections.installation]: InstallationMarkdown,
+ },
+ sectionNameToModulePath: {
+ [docSections.usage]: [
+ '"order-utils/src/order_hash"',
+ '"order-utils/src/signature_utils"',
+ '"order-utils/src/order_factory"',
+ '"order-utils/src/salt"',
+ '"order-utils/src/assert"',
+ '"order-utils/src/constants"',
+ ],
+ [docSections.types]: ['"order-utils/src/types"', '"types/src/index"'],
+ },
+ menuSubsectionToVersionWhenIntroduced: {},
+ sections: docSections,
+ visibleConstructors: [],
+ typeConfigs: {
+ // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is
+ // currently no way to extract the re-exported types from index.ts via TypeDoc :(
+ publicTypes: [
+ 'OrderError',
+ 'Order',
+ 'SignedOrder',
+ 'ECSignature',
+ 'Provider',
+ 'JSONRPCRequestPayload',
+ 'JSONRPCResponsePayload',
+ 'JSONRPCErrorCallback',
+ ],
+ typeNameToExternalLink: {
+ BigNumber: constants.URL_BIGNUMBERJS_GITHUB,
+ },
+ },
+};
+const docsInfo = new DocsInfo(docsInfoConfig);
+
+interface ConnectedState {
+ docsVersion: string;
+ availableDocVersions: string[];
+ docsInfo: DocsInfo;
+ translate: Translate;
+}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({
+ docsVersion: state.docsVersion,
+ availableDocVersions: state.availableDocVersions,
+ translate: state.translate,
+ docsInfo,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)(
+ DocPageComponent,
+);
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index d99187151..9535dd222 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -61,6 +61,9 @@ const LazySolCovDocumentation = createLazyComponent('Documentation', async () =>
const LazySubprovidersDocumentation = createLazyComponent('Documentation', async () =>
System.import<any>(/* webpackChunkName: "subproviderDocs" */ 'ts/containers/subproviders_documentation'),
);
+const LazyOrderUtilsDocumentation = createLazyComponent('Documentation', async () =>
+ System.import<any>(/* webpackChunkName: "orderUtilsDocs" */ 'ts/containers/order_utils_documentation'),
+);
analytics.init();
// tslint:disable-next-line:no-floating-promises
@@ -94,6 +97,10 @@ render(
component={LazySubprovidersDocumentation}
/>
<Route
+ path={`${WebsitePaths.OrderUtils}/:version?`}
+ component={LazyOrderUtilsDocumentation}
+ />
+ <Route
path={`${WebsitePaths.Web3Wrapper}/:version?`}
component={LazyWeb3WrapperDocumentation}
/>
diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx
index aca20447b..9feb27dac 100644
--- a/packages/website/ts/pages/documentation/doc_page.tsx
+++ b/packages/website/ts/pages/documentation/doc_page.tsx
@@ -34,6 +34,7 @@ const docIdToSubpackageName: { [id: string]: string } = {
[DocPackages.JSONSchemas]: 'json-schemas',
[DocPackages.SolCov]: 'sol-cov',
[DocPackages.Subproviders]: 'subproviders',
+ [DocPackages.OrderUtils]: 'order-utils',
};
export interface DocPageProps {
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index f4fddb556..fb4f22873 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -361,6 +361,7 @@ export enum WebsitePaths {
JSONSchemas = '/docs/json-schemas',
SolCov = '/docs/sol-cov',
Subproviders = '/docs/subproviders',
+ OrderUtils = '/docs/order-utils',
Jobs = '/jobs',
}
@@ -373,6 +374,7 @@ export enum DocPackages {
JSONSchemas = 'JSON_SCHEMAS',
SolCov = 'SOL_COV',
Subproviders = 'SUBPROVIDERS',
+ OrderUtils = 'ORDER_UTILS',
}
export enum Key {
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 53d60b3ce..0d77b9e72 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -1,4 +1,5 @@
import { ECSignature, ExchangeContractErrs, ZeroEx, ZeroExError } from '0x.js';
+import { OrderError } from '@0xproject/order-utils';
import { constants as sharedConstants, EtherscanLinkSuffixes, Networks } from '@0xproject/react-shared';
import { Provider } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
@@ -202,7 +203,7 @@ export const utils = {
[ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist',
[ZeroExError.UnhandledError]: 'Unhandled error occured',
[ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available',
- [ZeroExError.InvalidSignature]: 'Order signature is not valid',
+ [OrderError.InvalidSignature]: 'Order signature is not valid',
[ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network',
[ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction',
[ZeroExError.OutOfGas]: 'Transaction ran out of gas',