Merge branch 'v2-prototype' into feature/combinatorial-testing
* v2-prototype: (22 commits) Fix closing parens in liborder Update after rebase ERC721Proxy Always call safeTransferFrom Rename makerEpoch => orderEpoch Make cancelOrdersUpTo compatible with sender abstraction Update PR template Use Image component instead of img tag Assembler orderHash function Optimize and remove redundant encodePacked Fix linting issue Fix bug where we do fetch balances on wallet login Check network state immediately instead of waiting for delay Fix onboarding persisting when changing routes Consolidate account state messaging logic Only elevate wallet zIndex when onboarding is in progress Rebase and update feedback Run linter Add Portal v2 logging Simplified handling of source < 32 edge case Basic EIP712 encoder ...
This commit is contained in:
		@@ -1,24 +1,10 @@
 | 
			
		||||
<!--- Thank you for taking the time to submit a Pull Request -->
 | 
			
		||||
 | 
			
		||||
<!--- Provide a general summary of the issue in the Title above -->
 | 
			
		||||
 | 
			
		||||
## Description
 | 
			
		||||
 | 
			
		||||
<!--- Describe your changes in detail -->
 | 
			
		||||
 | 
			
		||||
## Motivation and Context
 | 
			
		||||
## Testing instructions
 | 
			
		||||
 | 
			
		||||
<!--- Why is this change required? What problem does it solve? -->
 | 
			
		||||
 | 
			
		||||
<!--- If it fixes an open issue, please link to the issue here. -->
 | 
			
		||||
 | 
			
		||||
## How Has This Been Tested?
 | 
			
		||||
 | 
			
		||||
<!--- Please describe in detail how you tested your changes. -->
 | 
			
		||||
 | 
			
		||||
<!--- Include details of your testing environment, and the tests you ran to -->
 | 
			
		||||
 | 
			
		||||
<!--- see how your change affects other areas of the code, etc. -->
 | 
			
		||||
<!--- Please describe how reviewers can test your changes -->
 | 
			
		||||
 | 
			
		||||
## Types of changes
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
        "ERC20Proxy",
 | 
			
		||||
        "ERC721Proxy",
 | 
			
		||||
        "Exchange",
 | 
			
		||||
        "ExchangeWrapper",
 | 
			
		||||
        "MixinAuthorizable",
 | 
			
		||||
        "MultiSigWallet",
 | 
			
		||||
        "MultiSigWalletWithTimeLock",
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,8 @@
 | 
			
		||||
        "test:circleci": "yarn test"
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
 | 
			
		||||
        "abis":
 | 
			
		||||
            "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -53,13 +53,7 @@ contract MixinERC721Transfer is
 | 
			
		||||
            bytes memory receiverData
 | 
			
		||||
        ) = decodeERC721AssetData(assetData);
 | 
			
		||||
 | 
			
		||||
        // Transfer token. Saves gas by calling safeTransferFrom only
 | 
			
		||||
        // when there is receiverData present. Either succeeds or throws.
 | 
			
		||||
        if (receiverData.length > 0) {
 | 
			
		||||
            ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData);
 | 
			
		||||
        } else {
 | 
			
		||||
            ERC721Token(token).transferFrom(from, to, tokenId);
 | 
			
		||||
        }
 | 
			
		||||
        ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Decodes ERC721 Asset data.
 | 
			
		||||
 
 | 
			
		||||
@@ -44,32 +44,36 @@ contract MixinExchangeCore is
 | 
			
		||||
    // Mapping of orderHash => cancelled
 | 
			
		||||
    mapping (bytes32 => bool) public cancelled;
 | 
			
		||||
 | 
			
		||||
    // Mapping of makerAddress => lowest salt an order can have in order to be fillable
 | 
			
		||||
    // Orders with a salt less than their maker's epoch are considered cancelled
 | 
			
		||||
    mapping (address => uint256) public makerEpoch;
 | 
			
		||||
    // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable
 | 
			
		||||
    // Orders with specified senderAddress and with a salt less than their epoch to are considered cancelled
 | 
			
		||||
    mapping (address => mapping (address => uint256)) public orderEpoch;
 | 
			
		||||
 | 
			
		||||
    ////// Core exchange functions //////
 | 
			
		||||
 | 
			
		||||
    /// @dev Cancels all orders created by sender with a salt less than or equal to the specified salt value.
 | 
			
		||||
    /// @param salt Orders created with a salt less or equal to this value will be cancelled.
 | 
			
		||||
    function cancelOrdersUpTo(uint256 salt)
 | 
			
		||||
    /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
 | 
			
		||||
    ///      and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress).
 | 
			
		||||
    /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled.
 | 
			
		||||
    function cancelOrdersUpTo(uint256 targetOrderEpoch)
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        address makerAddress = getCurrentContextAddress();
 | 
			
		||||
        // If this function is called via `executeTransaction`, we only update the orderEpoch for the makerAddress/msg.sender combination.
 | 
			
		||||
        // This allows external filter contracts to add rules to how orders are cancelled via this function.
 | 
			
		||||
        address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender;
 | 
			
		||||
 | 
			
		||||
        // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
 | 
			
		||||
        uint256 newMakerEpoch = salt + 1;  
 | 
			
		||||
        uint256 oldMakerEpoch = makerEpoch[makerAddress];
 | 
			
		||||
        // orderEpoch is initialized to 0, so to cancelUpTo we need salt + 1
 | 
			
		||||
        uint256 newOrderEpoch = targetOrderEpoch + 1;  
 | 
			
		||||
        uint256 oldOrderEpoch = orderEpoch[makerAddress][senderAddress];
 | 
			
		||||
 | 
			
		||||
        // Ensure makerEpoch is monotonically increasing
 | 
			
		||||
        // Ensure orderEpoch is monotonically increasing
 | 
			
		||||
        require(
 | 
			
		||||
            newMakerEpoch > oldMakerEpoch, 
 | 
			
		||||
            INVALID_NEW_MAKER_EPOCH
 | 
			
		||||
            newOrderEpoch > oldOrderEpoch, 
 | 
			
		||||
            INVALID_NEW_ORDER_EPOCH
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Update makerEpoch
 | 
			
		||||
        makerEpoch[makerAddress] = newMakerEpoch;
 | 
			
		||||
        emit CancelUpTo(makerAddress, newMakerEpoch);
 | 
			
		||||
        // Update orderEpoch
 | 
			
		||||
        orderEpoch[makerAddress][senderAddress] = newOrderEpoch;
 | 
			
		||||
        emit CancelUpTo(makerAddress, senderAddress, newOrderEpoch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Fills the input order.
 | 
			
		||||
@@ -180,7 +184,7 @@ contract MixinExchangeCore is
 | 
			
		||||
            orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
 | 
			
		||||
            return orderInfo;
 | 
			
		||||
        }
 | 
			
		||||
        if (makerEpoch[order.makerAddress] > order.salt) {
 | 
			
		||||
        if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) {
 | 
			
		||||
            orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
 | 
			
		||||
            return orderInfo;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,11 @@ pragma solidity ^0.4.24;
 | 
			
		||||
import "./libs/LibExchangeErrors.sol";
 | 
			
		||||
import "./mixins/MSignatureValidator.sol";
 | 
			
		||||
import "./mixins/MTransactions.sol";
 | 
			
		||||
import "./libs/LibExchangeErrors.sol";
 | 
			
		||||
import "./libs/LibEIP712.sol";
 | 
			
		||||
 | 
			
		||||
contract MixinTransactions is
 | 
			
		||||
    LibEIP712,
 | 
			
		||||
    LibExchangeErrors,
 | 
			
		||||
    MSignatureValidator,
 | 
			
		||||
    MTransactions
 | 
			
		||||
@@ -34,6 +37,33 @@ contract MixinTransactions is
 | 
			
		||||
    // Address of current transaction signer
 | 
			
		||||
    address public currentContextAddress;
 | 
			
		||||
 | 
			
		||||
    // Hash for the EIP712 ZeroEx Transaction Schema
 | 
			
		||||
    bytes32 constant EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked(
 | 
			
		||||
        "ZeroExTransaction(",
 | 
			
		||||
        "uint256 salt,",
 | 
			
		||||
        "address signer,",
 | 
			
		||||
        "bytes data",
 | 
			
		||||
        ")"
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    /// @dev Calculates EIP712 hash of the Transaction.
 | 
			
		||||
    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
 | 
			
		||||
    /// @param signer Address of transaction signer.
 | 
			
		||||
    /// @param data AbiV2 encoded calldata.
 | 
			
		||||
    /// @return EIP712 hash of the Transaction.
 | 
			
		||||
    function hashZeroExTransaction(uint256 salt, address signer, bytes data)
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes32)
 | 
			
		||||
    {
 | 
			
		||||
        return keccak256(abi.encode(
 | 
			
		||||
            EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
 | 
			
		||||
            salt,
 | 
			
		||||
            signer,
 | 
			
		||||
            keccak256(data)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Executes an exchange method call in the context of signer.
 | 
			
		||||
    /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
 | 
			
		||||
    /// @param signer Address of transaction signer.
 | 
			
		||||
@@ -53,13 +83,7 @@ contract MixinTransactions is
 | 
			
		||||
            REENTRANCY_ILLEGAL
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Calculate transaction hash
 | 
			
		||||
        bytes32 transactionHash = keccak256(abi.encodePacked(
 | 
			
		||||
            address(this),
 | 
			
		||||
            signer,
 | 
			
		||||
            salt,
 | 
			
		||||
            data
 | 
			
		||||
        ));
 | 
			
		||||
        bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(salt, signer, data));
 | 
			
		||||
 | 
			
		||||
        // Validate transaction has not been executed
 | 
			
		||||
        require(
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,10 @@ import "../libs/LibFillResults.sol";
 | 
			
		||||
 | 
			
		||||
contract IExchangeCore {
 | 
			
		||||
 | 
			
		||||
    /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
 | 
			
		||||
    /// @param salt Orders created with a salt less or equal to this value will be cancelled.
 | 
			
		||||
    function cancelOrdersUpTo(uint256 salt)
 | 
			
		||||
    /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
 | 
			
		||||
    ///      and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress).
 | 
			
		||||
    /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled.
 | 
			
		||||
    function cancelOrdersUpTo(uint256 targetOrderEpoch)
 | 
			
		||||
        external;
 | 
			
		||||
 | 
			
		||||
    /// @dev Fills the input order.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2018 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.4.24;
 | 
			
		||||
 | 
			
		||||
contract LibEIP712 {
 | 
			
		||||
    // EIP191 header for EIP712 prefix
 | 
			
		||||
    string constant EIP191_HEADER = "\x19\x01";
 | 
			
		||||
 | 
			
		||||
    // EIP712 Domain Name value
 | 
			
		||||
    string constant EIP712_DOMAIN_NAME = "0x Protocol";
 | 
			
		||||
 | 
			
		||||
    // EIP712 Domain Version value
 | 
			
		||||
    string constant EIP712_DOMAIN_VERSION = "2";
 | 
			
		||||
 | 
			
		||||
    // Hash of the EIP712 Domain Separator Schema
 | 
			
		||||
    bytes32 public constant EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
 | 
			
		||||
        "EIP712Domain(",
 | 
			
		||||
        "string name,",
 | 
			
		||||
        "string version,",
 | 
			
		||||
        "address verifyingContract",
 | 
			
		||||
        ")"
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    // Hash of the EIP712 Domain Separator data
 | 
			
		||||
    bytes32 public EIP712_DOMAIN_HASH;
 | 
			
		||||
 | 
			
		||||
    constructor ()
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        EIP712_DOMAIN_HASH = keccak256(abi.encode(
 | 
			
		||||
            EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
 | 
			
		||||
            keccak256(bytes(EIP712_DOMAIN_NAME)),
 | 
			
		||||
            keccak256(bytes(EIP712_DOMAIN_VERSION)),
 | 
			
		||||
            address(this)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Calculates EIP712 encoding for a hash struct in this EIP712 Domain.
 | 
			
		||||
    /// @param hashStruct The EIP712 hash struct.
 | 
			
		||||
    /// @return EIP712 hash applied to this EIP712 Domain.
 | 
			
		||||
    function hashEIP712Message(bytes32 hashStruct)
 | 
			
		||||
        internal
 | 
			
		||||
        view
 | 
			
		||||
        returns (bytes32)
 | 
			
		||||
    {
 | 
			
		||||
        return keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, hashStruct));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,7 +36,7 @@ contract LibExchangeErrors {
 | 
			
		||||
    string constant SIGNATURE_UNSUPPORTED = "SIGNATURE_UNSUPPORTED";        // Signature type unsupported.
 | 
			
		||||
    
 | 
			
		||||
    /// cancelOrdersUptTo errors ///
 | 
			
		||||
    string constant INVALID_NEW_MAKER_EPOCH = "INVALID_NEW_MAKER_EPOCH";    // Specified salt must be greater than or equal to existing makerEpoch.
 | 
			
		||||
    string constant INVALID_NEW_ORDER_EPOCH = "INVALID_NEW_ORDER_EPOCH";    // Specified salt must be greater than or equal to existing orderEpoch.
 | 
			
		||||
 | 
			
		||||
    /// fillOrKillOrder errors ///
 | 
			
		||||
    string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED";          // Desired takerAssetFillAmount could not be completely filled. 
 | 
			
		||||
@@ -55,7 +55,7 @@ contract LibExchangeErrors {
 | 
			
		||||
    string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH";    // newAssetProxyId does not match given assetProxyId.
 | 
			
		||||
 | 
			
		||||
    /// Length validation errors ///
 | 
			
		||||
    string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
 | 
			
		||||
    string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED";  // Byte array must have a length greater than 0.
 | 
			
		||||
    string constant LENGTH_0_REQUIRED = "LENGTH_1_REQUIRED";                // Byte array must have a length of 1.
 | 
			
		||||
    string constant LENGTH_65_REQUIRED = "LENGTH_66_REQUIRED";              // Byte array must have a length of 66.
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,13 +18,14 @@
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.4.24;
 | 
			
		||||
 | 
			
		||||
contract LibOrder {
 | 
			
		||||
import "./LibEIP712.sol";
 | 
			
		||||
 | 
			
		||||
    bytes32 constant DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
 | 
			
		||||
        "DomainSeparator(address contract)"
 | 
			
		||||
    ));
 | 
			
		||||
contract LibOrder is
 | 
			
		||||
    LibEIP712
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    bytes32 constant ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(
 | 
			
		||||
    // Hash for the EIP712 Order Schema
 | 
			
		||||
    bytes32 constant EIP712_ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(
 | 
			
		||||
        "Order(",
 | 
			
		||||
        "address makerAddress,",
 | 
			
		||||
        "address takerAddress,",
 | 
			
		||||
@@ -37,7 +38,7 @@ contract LibOrder {
 | 
			
		||||
        "uint256 expirationTimeSeconds,",
 | 
			
		||||
        "uint256 salt,",
 | 
			
		||||
        "bytes makerAssetData,",
 | 
			
		||||
        "bytes takerAssetData,",
 | 
			
		||||
        "bytes takerAssetData",
 | 
			
		||||
        ")"
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
@@ -85,27 +86,38 @@ contract LibOrder {
 | 
			
		||||
        view
 | 
			
		||||
        returns (bytes32 orderHash)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: EIP712 is not finalized yet
 | 
			
		||||
        // Source: https://github.com/ethereum/EIPs/pull/712
 | 
			
		||||
        orderHash = keccak256(abi.encodePacked(
 | 
			
		||||
            DOMAIN_SEPARATOR_SCHEMA_HASH,
 | 
			
		||||
            keccak256(abi.encodePacked(address(this))),
 | 
			
		||||
            ORDER_SCHEMA_HASH,
 | 
			
		||||
            keccak256(abi.encodePacked(
 | 
			
		||||
                order.makerAddress,
 | 
			
		||||
                order.takerAddress,
 | 
			
		||||
                order.feeRecipientAddress,
 | 
			
		||||
                order.senderAddress,
 | 
			
		||||
                order.makerAssetAmount,
 | 
			
		||||
                order.takerAssetAmount,
 | 
			
		||||
                order.makerFee,
 | 
			
		||||
                order.takerFee,
 | 
			
		||||
                order.expirationTimeSeconds,
 | 
			
		||||
                order.salt,
 | 
			
		||||
                keccak256(abi.encodePacked(order.makerAssetData)),
 | 
			
		||||
                keccak256(abi.encodePacked(order.takerAssetData))
 | 
			
		||||
            ))
 | 
			
		||||
        ));
 | 
			
		||||
        orderHash = hashEIP712Message(hashOrder(order));
 | 
			
		||||
        return orderHash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Calculates EIP712 hash of the order.
 | 
			
		||||
    /// @param order The order structure.
 | 
			
		||||
    /// @return EIP712 hash of the order.
 | 
			
		||||
    function hashOrder(Order memory order)
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes32 result)
 | 
			
		||||
    {
 | 
			
		||||
        bytes32 schemaHash = EIP712_ORDER_SCHEMA_HASH;
 | 
			
		||||
        bytes32 makerAssetDataHash = keccak256(order.makerAssetData);
 | 
			
		||||
        bytes32 takerAssetDataHash = keccak256(order.takerAssetData);
 | 
			
		||||
        assembly {
 | 
			
		||||
            // Backup
 | 
			
		||||
            let temp1 := mload(sub(order,  32))
 | 
			
		||||
            let temp2 := mload(add(order, 320))
 | 
			
		||||
            let temp3 := mload(add(order, 352))
 | 
			
		||||
            
 | 
			
		||||
            // Hash in place
 | 
			
		||||
            mstore(sub(order,  32), schemaHash)
 | 
			
		||||
            mstore(add(order, 320), makerAssetDataHash)
 | 
			
		||||
            mstore(add(order, 352), takerAssetDataHash)
 | 
			
		||||
            result := keccak256(sub(order, 32), 416)
 | 
			
		||||
            
 | 
			
		||||
            // Restore
 | 
			
		||||
            mstore(sub(order,  32), temp1)
 | 
			
		||||
            mstore(add(order, 320), temp2)
 | 
			
		||||
            mstore(add(order, 352), temp3)
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,8 @@ contract MExchangeCore is
 | 
			
		||||
    // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully.
 | 
			
		||||
    event CancelUpTo(
 | 
			
		||||
        address indexed makerAddress,
 | 
			
		||||
        uint256 makerEpoch
 | 
			
		||||
        address indexed senderAddress,
 | 
			
		||||
        uint256 orderEpoch
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Updates state with results of a fill order.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,98 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2018 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.4.24;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "../../protocol/Exchange/interfaces/IExchange.sol";
 | 
			
		||||
import "../../protocol/Exchange/libs/LibOrder.sol";
 | 
			
		||||
 | 
			
		||||
contract ExchangeWrapper {
 | 
			
		||||
 | 
			
		||||
    // Exchange contract.
 | 
			
		||||
    IExchange EXCHANGE;
 | 
			
		||||
 | 
			
		||||
    constructor (address _exchange)
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        EXCHANGE = IExchange(_exchange);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Fills an order using `msg.sender` as the taker.
 | 
			
		||||
    /// @param order Order struct containing order specifications.
 | 
			
		||||
    /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
 | 
			
		||||
    /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash.
 | 
			
		||||
    /// @param orderSignature Proof that order has been created by maker.
 | 
			
		||||
    /// @param takerSignature Proof that taker wishes to call this function with given params.
 | 
			
		||||
    function fillOrder(
 | 
			
		||||
        LibOrder.Order memory order,
 | 
			
		||||
        uint256 takerAssetFillAmount,
 | 
			
		||||
        uint256 salt,
 | 
			
		||||
        bytes memory orderSignature,
 | 
			
		||||
        bytes memory takerSignature
 | 
			
		||||
    )
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        address takerAddress = msg.sender;
 | 
			
		||||
 | 
			
		||||
        // Encode arguments into byte array.
 | 
			
		||||
        bytes memory data = abi.encodeWithSelector(
 | 
			
		||||
            EXCHANGE.fillOrder.selector,
 | 
			
		||||
            order,
 | 
			
		||||
            takerAssetFillAmount,
 | 
			
		||||
            orderSignature
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Call `fillOrder` via `executeTransaction`.
 | 
			
		||||
        EXCHANGE.executeTransaction(
 | 
			
		||||
            salt,
 | 
			
		||||
            takerAddress,
 | 
			
		||||
            data,
 | 
			
		||||
            takerSignature
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Cancels all orders created by sender with a salt less than or equal to the targetOrderEpoch
 | 
			
		||||
    ///      and senderAddress equal to this contract.
 | 
			
		||||
    /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled.
 | 
			
		||||
    /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash.
 | 
			
		||||
    /// @param makerSignature Proof that maker wishes to call this function with given params.
 | 
			
		||||
    function cancelOrdersUpTo(
 | 
			
		||||
        uint256 targetOrderEpoch,
 | 
			
		||||
        uint256 salt,
 | 
			
		||||
        bytes makerSignature
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        address makerAddress = msg.sender;
 | 
			
		||||
 | 
			
		||||
        // Encode arguments into byte array.
 | 
			
		||||
        bytes memory data = abi.encodeWithSelector(
 | 
			
		||||
            EXCHANGE.cancelOrdersUpTo.selector,
 | 
			
		||||
            targetOrderEpoch
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Call `cancelOrdersUpTo` via `executeTransaction`.
 | 
			
		||||
        EXCHANGE.executeTransaction(
 | 
			
		||||
            salt,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            data,
 | 
			
		||||
            makerSignature
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -74,7 +74,7 @@ contract TestLibs is
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes32)
 | 
			
		||||
    {
 | 
			
		||||
        return ORDER_SCHEMA_HASH;
 | 
			
		||||
        return EIP712_ORDER_SCHEMA_HASH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getDomainSeparatorSchemaHash()
 | 
			
		||||
@@ -82,7 +82,7 @@ contract TestLibs is
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes32)
 | 
			
		||||
    {
 | 
			
		||||
        return DOMAIN_SEPARATOR_SCHEMA_HASH;
 | 
			
		||||
        return EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.4.23;
 | 
			
		||||
pragma solidity ^0.4.24;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "../../protocol/Exchange/interfaces/IExchange.sol";
 | 
			
		||||
 
 | 
			
		||||
@@ -80,9 +80,6 @@ contract LibMem
 | 
			
		||||
            //
 | 
			
		||||
            if (source > dest) {
 | 
			
		||||
                assembly {
 | 
			
		||||
                    // Record the total number of full words to copy
 | 
			
		||||
                    let nWords := div(length, 32)
 | 
			
		||||
 | 
			
		||||
                    // We subtract 32 from `sEnd` and `dEnd` because it
 | 
			
		||||
                    // is easier to compare with in the loop, and these
 | 
			
		||||
                    // are also the addresses we need for copying the
 | 
			
		||||
@@ -98,7 +95,9 @@ contract LibMem
 | 
			
		||||
                    let last := mload(sEnd)
 | 
			
		||||
 | 
			
		||||
                    // Copy whole words front to back
 | 
			
		||||
                    for {let i := 0} lt(i, nWords) {i := add(i, 1)} {
 | 
			
		||||
                    // Note: the first check is always true,
 | 
			
		||||
                    // this could have been a do-while loop.
 | 
			
		||||
                    for {} lt(source, sEnd) {} {
 | 
			
		||||
                        mstore(dest, mload(source))
 | 
			
		||||
                        source := add(source, 32)
 | 
			
		||||
                        dest := add(dest, 32)
 | 
			
		||||
@@ -109,9 +108,6 @@ contract LibMem
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                assembly {
 | 
			
		||||
                    // Record the total number of full words to copy
 | 
			
		||||
                    let nWords := div(length, 32)
 | 
			
		||||
 | 
			
		||||
                    // We subtract 32 from `sEnd` and `dEnd` because those
 | 
			
		||||
                    // are the starting points when copying a word at the end.
 | 
			
		||||
                    length := sub(length, 32)
 | 
			
		||||
@@ -125,7 +121,13 @@ contract LibMem
 | 
			
		||||
                    let first := mload(source)
 | 
			
		||||
 | 
			
		||||
                    // Copy whole words back to front
 | 
			
		||||
                    for {let i := 0} lt(i, nWords) {i := add(i, 1)} {
 | 
			
		||||
                    // We use a signed comparisson here to allow dEnd to become
 | 
			
		||||
                    // negative (happens when source and dest < 32). Valid
 | 
			
		||||
                    // addresses in local memory will never be larger than
 | 
			
		||||
                    // 2**255, so they can be safely re-interpreted as signed.
 | 
			
		||||
                    // Note: the first check is always true,
 | 
			
		||||
                    // this could have been a do-while loop.
 | 
			
		||||
                    for {} slt(dest, dEnd) {} {
 | 
			
		||||
                        mstore(dEnd, mload(sEnd))
 | 
			
		||||
                        sEnd := sub(sEnd, 32)
 | 
			
		||||
                        dEnd := sub(dEnd, 32)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import * as DummyERC721Token from '../artifacts/DummyERC721Token.json';
 | 
			
		||||
import * as ERC20Proxy from '../artifacts/ERC20Proxy.json';
 | 
			
		||||
import * as ERC721Proxy from '../artifacts/ERC721Proxy.json';
 | 
			
		||||
import * as Exchange from '../artifacts/Exchange.json';
 | 
			
		||||
import * as ExchangeWrapper from '../artifacts/ExchangeWrapper.json';
 | 
			
		||||
import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json';
 | 
			
		||||
import * as MultiSigWallet from '../artifacts/MultiSigWallet.json';
 | 
			
		||||
import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json';
 | 
			
		||||
@@ -29,6 +30,7 @@ export const artifacts = {
 | 
			
		||||
    ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
 | 
			
		||||
    ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
 | 
			
		||||
    Exchange: (Exchange as any) as ContractArtifact,
 | 
			
		||||
    ExchangeWrapper: (ExchangeWrapper as any) as ContractArtifact,
 | 
			
		||||
    EtherToken: (EtherToken as any) as ContractArtifact,
 | 
			
		||||
    MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact,
 | 
			
		||||
    MultiSigWallet: (MultiSigWallet as any) as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ export class OrderFactory {
 | 
			
		||||
            ...this._defaultOrderParams,
 | 
			
		||||
            ...customOrderParams,
 | 
			
		||||
        } as any) as Order;
 | 
			
		||||
        const orderHashBuff = orderHashUtils.getOrderHashBuff(order);
 | 
			
		||||
        const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
 | 
			
		||||
        const signature = signingUtils.signMessage(orderHashBuff, this._privateKey, signatureType);
 | 
			
		||||
        const signedOrder = {
 | 
			
		||||
            ...order,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,19 @@
 | 
			
		||||
import { crypto, generatePseudoRandomSalt } from '@0xproject/order-utils';
 | 
			
		||||
import { EIP712Schema, EIP712Types, EIP712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
 | 
			
		||||
import { SignatureType } from '@0xproject/types';
 | 
			
		||||
import * as ethUtil from 'ethereumjs-util';
 | 
			
		||||
 | 
			
		||||
import { signingUtils } from './signing_utils';
 | 
			
		||||
import { SignedTransaction } from './types';
 | 
			
		||||
 | 
			
		||||
const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
 | 
			
		||||
    name: 'ZeroExTransaction',
 | 
			
		||||
    parameters: [
 | 
			
		||||
        { name: 'salt', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'signer', type: EIP712Types.Address },
 | 
			
		||||
        { name: 'data', type: EIP712Types.Bytes },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class TransactionFactory {
 | 
			
		||||
    private _signerBuff: Buffer;
 | 
			
		||||
    private _exchangeAddress: string;
 | 
			
		||||
@@ -16,14 +25,22 @@ export class TransactionFactory {
 | 
			
		||||
    }
 | 
			
		||||
    public newSignedTransaction(data: string, signatureType: SignatureType = SignatureType.EthSign): SignedTransaction {
 | 
			
		||||
        const salt = generatePseudoRandomSalt();
 | 
			
		||||
        const txHash = crypto.solSHA3([this._exchangeAddress, this._signerBuff, salt, ethUtil.toBuffer(data)]);
 | 
			
		||||
        const signer = `0x${this._signerBuff.toString('hex')}`;
 | 
			
		||||
        const executeTransactionData = {
 | 
			
		||||
            salt,
 | 
			
		||||
            signer,
 | 
			
		||||
            data,
 | 
			
		||||
        };
 | 
			
		||||
        const executeTransactionHashBuff = EIP712Utils.structHash(
 | 
			
		||||
            EIP712_ZEROEX_TRANSACTION_SCHEMA,
 | 
			
		||||
            executeTransactionData,
 | 
			
		||||
        );
 | 
			
		||||
        const txHash = EIP712Utils.createEIP712Message(executeTransactionHashBuff, this._exchangeAddress);
 | 
			
		||||
        const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType);
 | 
			
		||||
        const signedTx = {
 | 
			
		||||
            exchangeAddress: this._exchangeAddress,
 | 
			
		||||
            salt,
 | 
			
		||||
            signer: `0x${this._signerBuff.toString('hex')}`,
 | 
			
		||||
            data,
 | 
			
		||||
            signature: `0x${signature.toString('hex')}`,
 | 
			
		||||
            ...executeTransactionData,
 | 
			
		||||
        };
 | 
			
		||||
        return signedTx;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -279,7 +279,7 @@ describe('Asset Transfer Proxies', () => {
 | 
			
		||||
                expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('should not call onERC721Received when transferring to a smart contract without receiver data', async () => {
 | 
			
		||||
            it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
 | 
			
		||||
                // Construct ERC721 asset data
 | 
			
		||||
                const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
 | 
			
		||||
                const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
 | 
			
		||||
@@ -300,7 +300,11 @@ describe('Asset Transfer Proxies', () => {
 | 
			
		||||
                const logDecoder = new LogDecoder(web3Wrapper, erc721Receiver.address);
 | 
			
		||||
                const tx = await logDecoder.getTxWithDecodedLogsAsync(txHash);
 | 
			
		||||
                // Verify that no log was emitted by erc721 receiver
 | 
			
		||||
                expect(tx.logs.length).to.be.equal(0);
 | 
			
		||||
                expect(tx.logs.length).to.be.equal(1);
 | 
			
		||||
                const tokenReceivedLog = tx.logs[0] as LogWithDecodedArgs<TokenReceivedContractEventArgs>;
 | 
			
		||||
                expect(tokenReceivedLog.args.from).to.be.equal(makerAddress);
 | 
			
		||||
                expect(tokenReceivedLog.args.tokenId).to.be.bignumber.equal(erc721MakerTokenId);
 | 
			
		||||
                expect(tokenReceivedLog.args.data).to.be.equal(constants.NULL_BYTES);
 | 
			
		||||
                // Verify transfer was successful
 | 
			
		||||
                const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
 | 
			
		||||
                expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address);
 | 
			
		||||
 
 | 
			
		||||
@@ -252,30 +252,30 @@ describe('Exchange core', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('cancelOrdersUpTo', () => {
 | 
			
		||||
        it('should fail to set makerEpoch less than current makerEpoch', async () => {
 | 
			
		||||
            const makerEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
 | 
			
		||||
            const lesserMakerEpoch = new BigNumber(0);
 | 
			
		||||
        it('should fail to set orderEpoch less than current orderEpoch', async () => {
 | 
			
		||||
            const orderEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
 | 
			
		||||
            const lesserOrderEpoch = new BigNumber(0);
 | 
			
		||||
            return expectRevertOrAlwaysFailingTransactionAsync(
 | 
			
		||||
                exchangeWrapper.cancelOrdersUpToAsync(lesserMakerEpoch, makerAddress),
 | 
			
		||||
                exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should fail to set makerEpoch equal to existing makerEpoch', async () => {
 | 
			
		||||
            const makerEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
 | 
			
		||||
        it('should fail to set orderEpoch equal to existing orderEpoch', async () => {
 | 
			
		||||
            const orderEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
 | 
			
		||||
            return expectRevertOrAlwaysFailingTransactionAsync(
 | 
			
		||||
                exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress),
 | 
			
		||||
                exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should cancel only orders with a makerEpoch less than existing makerEpoch', async () => {
 | 
			
		||||
            // Cancel all transactions with a makerEpoch less than 1
 | 
			
		||||
            const makerEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
 | 
			
		||||
        it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => {
 | 
			
		||||
            // Cancel all transactions with a orderEpoch less than 1
 | 
			
		||||
            const orderEpoch = new BigNumber(1);
 | 
			
		||||
            await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
 | 
			
		||||
 | 
			
		||||
            // Create 3 orders with makerEpoch values: 0,1,2,3
 | 
			
		||||
            // Since we cancelled with makerEpoch=1, orders with makerEpoch<=1 will not be processed
 | 
			
		||||
            // Create 3 orders with orderEpoch values: 0,1,2,3
 | 
			
		||||
            // Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed
 | 
			
		||||
            erc20Balances = await erc20Wrapper.getBalancesAsync();
 | 
			
		||||
            const signedOrders = [
 | 
			
		||||
                orderFactory.newSignedOrder({
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { BlockchainLifecycle } from '@0xproject/dev-utils';
 | 
			
		||||
import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils';
 | 
			
		||||
import { assetProxyUtils, EIP712Utils, orderHashUtils } from '@0xproject/order-utils';
 | 
			
		||||
import { SignedOrder } from '@0xproject/types';
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import * as chai from 'chai';
 | 
			
		||||
@@ -56,13 +56,17 @@ describe('Exchange libs', () => {
 | 
			
		||||
        describe('getOrderSchema', () => {
 | 
			
		||||
            it('should output the correct order schema hash', async () => {
 | 
			
		||||
                const orderSchema = await libs.getOrderSchemaHash.callAsync();
 | 
			
		||||
                expect(orderHashUtils._getOrderSchemaHex()).to.be.equal(orderSchema);
 | 
			
		||||
                const schemaHashBuffer = orderHashUtils._getOrderSchemaBuffer();
 | 
			
		||||
                const schemaHashHex = `0x${schemaHashBuffer.toString('hex')}`;
 | 
			
		||||
                expect(schemaHashHex).to.be.equal(orderSchema);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        describe('getDomainSeparatorSchema', () => {
 | 
			
		||||
            it('should output the correct domain separator schema hash', async () => {
 | 
			
		||||
                const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync();
 | 
			
		||||
                expect(orderHashUtils._getDomainSeparatorSchemaHex()).to.be.equal(domainSeparatorSchema);
 | 
			
		||||
                const domainSchemaBuffer = EIP712Utils._getDomainSeparatorSchemaBuffer();
 | 
			
		||||
                const schemaHashHex = `0x${domainSchemaBuffer.toString('hex')}`;
 | 
			
		||||
                expect(schemaHashHex).to.be.equal(domainSeparatorSchema);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        describe('getOrderHash', () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import * as chai from 'chai';
 | 
			
		||||
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
 | 
			
		||||
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
 | 
			
		||||
import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
 | 
			
		||||
import { ExchangeWrapperContract } from '../../src/generated_contract_wrappers/exchange_wrapper';
 | 
			
		||||
import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist';
 | 
			
		||||
import { artifacts } from '../../src/utils/artifacts';
 | 
			
		||||
import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
 | 
			
		||||
@@ -201,6 +202,117 @@ describe('Exchange transactions', () => {
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('cancelOrdersUpTo', () => {
 | 
			
		||||
            let exchangeWrapperContract: ExchangeWrapperContract;
 | 
			
		||||
 | 
			
		||||
            before(async () => {
 | 
			
		||||
                exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync(
 | 
			
		||||
                    artifacts.ExchangeWrapper,
 | 
			
		||||
                    provider,
 | 
			
		||||
                    txDefaults,
 | 
			
		||||
                    exchange.address,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should cancel an order if called from the order's sender", async () => {
 | 
			
		||||
                const orderSalt = new BigNumber(0);
 | 
			
		||||
                signedOrder = orderFactory.newSignedOrder({
 | 
			
		||||
                    senderAddress: exchangeWrapperContract.address,
 | 
			
		||||
                    salt: orderSalt,
 | 
			
		||||
                });
 | 
			
		||||
                const targetOrderEpoch = orderSalt.add(1);
 | 
			
		||||
                const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch);
 | 
			
		||||
                const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData);
 | 
			
		||||
                await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync(
 | 
			
		||||
                    targetOrderEpoch,
 | 
			
		||||
                    signedCancelTx.salt,
 | 
			
		||||
                    signedCancelTx.signature,
 | 
			
		||||
                    {
 | 
			
		||||
                        from: makerAddress,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                const takerAssetFillAmount = signedOrder.takerAssetAmount;
 | 
			
		||||
                orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
 | 
			
		||||
                const fillData = exchange.fillOrder.getABIEncodedTransactionData(
 | 
			
		||||
                    orderWithoutExchangeAddress,
 | 
			
		||||
                    takerAssetFillAmount,
 | 
			
		||||
                    signedOrder.signature,
 | 
			
		||||
                );
 | 
			
		||||
                const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData);
 | 
			
		||||
                return expectRevertOrAlwaysFailingTransactionAsync(
 | 
			
		||||
                    exchangeWrapperContract.fillOrder.sendTransactionAsync(
 | 
			
		||||
                        orderWithoutExchangeAddress,
 | 
			
		||||
                        takerAssetFillAmount,
 | 
			
		||||
                        signedFillTx.salt,
 | 
			
		||||
                        signedOrder.signature,
 | 
			
		||||
                        signedFillTx.signature,
 | 
			
		||||
                        { from: takerAddress },
 | 
			
		||||
                    ),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should not cancel an order if not called from the order's sender", async () => {
 | 
			
		||||
                const orderSalt = new BigNumber(0);
 | 
			
		||||
                signedOrder = orderFactory.newSignedOrder({
 | 
			
		||||
                    senderAddress: exchangeWrapperContract.address,
 | 
			
		||||
                    salt: orderSalt,
 | 
			
		||||
                });
 | 
			
		||||
                const targetOrderEpoch = orderSalt.add(1);
 | 
			
		||||
                await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress);
 | 
			
		||||
 | 
			
		||||
                erc20Balances = await erc20Wrapper.getBalancesAsync();
 | 
			
		||||
                const takerAssetFillAmount = signedOrder.takerAssetAmount;
 | 
			
		||||
                orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
 | 
			
		||||
                const data = exchange.fillOrder.getABIEncodedTransactionData(
 | 
			
		||||
                    orderWithoutExchangeAddress,
 | 
			
		||||
                    takerAssetFillAmount,
 | 
			
		||||
                    signedOrder.signature,
 | 
			
		||||
                );
 | 
			
		||||
                signedTx = takerTransactionFactory.newSignedTransaction(data);
 | 
			
		||||
                await exchangeWrapperContract.fillOrder.sendTransactionAsync(
 | 
			
		||||
                    orderWithoutExchangeAddress,
 | 
			
		||||
                    takerAssetFillAmount,
 | 
			
		||||
                    signedTx.salt,
 | 
			
		||||
                    signedOrder.signature,
 | 
			
		||||
                    signedTx.signature,
 | 
			
		||||
                    { from: takerAddress },
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                const newBalances = await erc20Wrapper.getBalancesAsync();
 | 
			
		||||
                const makerAssetFillAmount = takerAssetFillAmount
 | 
			
		||||
                    .times(signedOrder.makerAssetAmount)
 | 
			
		||||
                    .dividedToIntegerBy(signedOrder.takerAssetAmount);
 | 
			
		||||
                const makerFeePaid = signedOrder.makerFee
 | 
			
		||||
                    .times(makerAssetFillAmount)
 | 
			
		||||
                    .dividedToIntegerBy(signedOrder.makerAssetAmount);
 | 
			
		||||
                const takerFeePaid = signedOrder.takerFee
 | 
			
		||||
                    .times(makerAssetFillAmount)
 | 
			
		||||
                    .dividedToIntegerBy(signedOrder.makerAssetAmount);
 | 
			
		||||
                expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[makerAddress][defaultTakerTokenAddress].add(takerAssetFillAmount),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[takerAddress][defaultMakerTokenAddress].add(makerAssetFillAmount),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
 | 
			
		||||
                );
 | 
			
		||||
                expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
 | 
			
		||||
                    erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Whitelist', () => {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										150
									
								
								packages/migrations/artifacts/2.0.0/Exchange.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										150
									
								
								packages/migrations/artifacts/2.0.0/Exchange.json
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										206
									
								
								packages/migrations/artifacts/2.0.0/ExchangeWrapper.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								packages/migrations/artifacts/2.0.0/ExchangeWrapper.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										93
									
								
								packages/order-utils/src/eip712_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								packages/order-utils/src/eip712_utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
import ethUtil = require('ethereumjs-util');
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { crypto } from './crypto';
 | 
			
		||||
import { EIP712Schema, EIP712Types } from './types';
 | 
			
		||||
 | 
			
		||||
const EIP191_PREFIX = '\x19\x01';
 | 
			
		||||
const EIP712_DOMAIN_NAME = '0x Protocol';
 | 
			
		||||
const EIP712_DOMAIN_VERSION = '2';
 | 
			
		||||
const EIP712_VALUE_LENGTH = 32;
 | 
			
		||||
 | 
			
		||||
const EIP712_DOMAIN_SCHEMA: EIP712Schema = {
 | 
			
		||||
    name: 'EIP712Domain',
 | 
			
		||||
    parameters: [
 | 
			
		||||
        { name: 'name', type: EIP712Types.String },
 | 
			
		||||
        { name: 'version', type: EIP712Types.String },
 | 
			
		||||
        { name: 'verifyingContract', type: EIP712Types.Address },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const EIP712Utils = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Compiles the EIP712Schema and returns the hash of the schema.
 | 
			
		||||
     * @param   schema The EIP712 schema.
 | 
			
		||||
     * @return  The hash of the compiled schema
 | 
			
		||||
     */
 | 
			
		||||
    compileSchema(schema: EIP712Schema): Buffer {
 | 
			
		||||
        const eip712Schema = EIP712Utils._encodeType(schema);
 | 
			
		||||
        const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]);
 | 
			
		||||
        return eip712SchemaHashBuffer;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * Merges the EIP712 hash of a struct with the DomainSeparator for 0x v2.
 | 
			
		||||
     * @param   hashStruct the EIP712 hash of a struct
 | 
			
		||||
     * @param   contractAddress the exchange contract address
 | 
			
		||||
     * @return  The hash of an EIP712 message with domain separator prefixed
 | 
			
		||||
     */
 | 
			
		||||
    createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer {
 | 
			
		||||
        const domainSeparatorHashBuffer = EIP712Utils._getDomainSeparatorHashBuffer(contractAddress);
 | 
			
		||||
        const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]);
 | 
			
		||||
        return messageBuff;
 | 
			
		||||
    },
 | 
			
		||||
    pad32Address(address: string): Buffer {
 | 
			
		||||
        const addressBuffer = ethUtil.toBuffer(address);
 | 
			
		||||
        const addressPadded = EIP712Utils.pad32Buffer(addressBuffer);
 | 
			
		||||
        return addressPadded;
 | 
			
		||||
    },
 | 
			
		||||
    pad32Buffer(buffer: Buffer): Buffer {
 | 
			
		||||
        const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
 | 
			
		||||
        return bufferPadded;
 | 
			
		||||
    },
 | 
			
		||||
    _getDomainSeparatorSchemaBuffer(): Buffer {
 | 
			
		||||
        return EIP712Utils.compileSchema(EIP712_DOMAIN_SCHEMA);
 | 
			
		||||
    },
 | 
			
		||||
    _getDomainSeparatorHashBuffer(exchangeAddress: string): Buffer {
 | 
			
		||||
        const domainSeparatorSchemaBuffer = EIP712Utils._getDomainSeparatorSchemaBuffer();
 | 
			
		||||
        const encodedData = EIP712Utils._encodeData(EIP712_DOMAIN_SCHEMA, {
 | 
			
		||||
            name: EIP712_DOMAIN_NAME,
 | 
			
		||||
            version: EIP712_DOMAIN_VERSION,
 | 
			
		||||
            verifyingContract: exchangeAddress,
 | 
			
		||||
        });
 | 
			
		||||
        const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]);
 | 
			
		||||
        return domainSeparatorHashBuff2;
 | 
			
		||||
    },
 | 
			
		||||
    _encodeType(schema: EIP712Schema): string {
 | 
			
		||||
        const namedTypes = _.map(schema.parameters, ({ name, type }) => `${type} ${name}`);
 | 
			
		||||
        const namedTypesJoined = namedTypes.join(',');
 | 
			
		||||
        const encodedType = `${schema.name}(${namedTypesJoined})`;
 | 
			
		||||
        return encodedType;
 | 
			
		||||
    },
 | 
			
		||||
    _encodeData(schema: EIP712Schema, data: { [key: string]: any }): any {
 | 
			
		||||
        const encodedValues = [];
 | 
			
		||||
        for (const parameter of schema.parameters) {
 | 
			
		||||
            const value = data[parameter.name];
 | 
			
		||||
            if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) {
 | 
			
		||||
                encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)]));
 | 
			
		||||
            } else if (parameter.type === EIP712Types.Uint256) {
 | 
			
		||||
                encodedValues.push(value);
 | 
			
		||||
            } else if (parameter.type === EIP712Types.Address) {
 | 
			
		||||
                encodedValues.push(EIP712Utils.pad32Address(value));
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Error(`Unable to encode ${parameter.type}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return encodedValues;
 | 
			
		||||
    },
 | 
			
		||||
    structHash(schema: EIP712Schema, data: { [key: string]: any }): Buffer {
 | 
			
		||||
        const encodedData = EIP712Utils._encodeData(schema, data);
 | 
			
		||||
        const schemaHash = EIP712Utils.compileSchema(schema);
 | 
			
		||||
        const hashBuffer = crypto.solSHA3([schemaHash, ...encodedData]);
 | 
			
		||||
        return hashBuffer;
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
@@ -13,12 +13,13 @@ export { orderFactory } from './order_factory';
 | 
			
		||||
export { constants } from './constants';
 | 
			
		||||
export { crypto } from './crypto';
 | 
			
		||||
export { generatePseudoRandomSalt } from './salt';
 | 
			
		||||
export { OrderError, MessagePrefixType, MessagePrefixOpts } from './types';
 | 
			
		||||
export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types';
 | 
			
		||||
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
 | 
			
		||||
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
 | 
			
		||||
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
 | 
			
		||||
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
 | 
			
		||||
export { OrderStateUtils } from './order_state_utils';
 | 
			
		||||
export { assetProxyUtils } from './asset_proxy_utils';
 | 
			
		||||
export { EIP712Utils } from './eip712_utils';
 | 
			
		||||
export { OrderValidationUtils } from './order_validation_utils';
 | 
			
		||||
export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,31 @@
 | 
			
		||||
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
 | 
			
		||||
import { Order, SignedOrder } from '@0xproject/types';
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import * as ethUtil from 'ethereumjs-util';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { assert } from './assert';
 | 
			
		||||
import { crypto } from './crypto';
 | 
			
		||||
import { EIP712Utils } from './eip712_utils';
 | 
			
		||||
import { EIP712Schema, EIP712Types } from './types';
 | 
			
		||||
 | 
			
		||||
const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
 | 
			
		||||
 | 
			
		||||
const EIP712_ORDER_SCHEMA: EIP712Schema = {
 | 
			
		||||
    name: 'Order',
 | 
			
		||||
    parameters: [
 | 
			
		||||
        { name: 'makerAddress', type: EIP712Types.Address },
 | 
			
		||||
        { name: 'takerAddress', type: EIP712Types.Address },
 | 
			
		||||
        { name: 'feeRecipientAddress', type: EIP712Types.Address },
 | 
			
		||||
        { name: 'senderAddress', type: EIP712Types.Address },
 | 
			
		||||
        { name: 'makerAssetAmount', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'takerAssetAmount', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'makerFee', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'takerFee', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'expirationTimeSeconds', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'salt', type: EIP712Types.Uint256 },
 | 
			
		||||
        { name: 'makerAssetData', type: EIP712Types.Bytes },
 | 
			
		||||
        { name: 'takerAssetData', type: EIP712Types.Bytes },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const orderHashUtils = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the supplied hex encoded order hash is valid.
 | 
			
		||||
@@ -42,7 +59,7 @@ export const orderHashUtils = {
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const orderHashBuff = this.getOrderHashBuff(order);
 | 
			
		||||
        const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
 | 
			
		||||
        const orderHashHex = `0x${orderHashBuff.toString('hex')}`;
 | 
			
		||||
        return orderHashHex;
 | 
			
		||||
    },
 | 
			
		||||
@@ -51,64 +68,12 @@ export const orderHashUtils = {
 | 
			
		||||
     * @param   order   An object that conforms to the Order or SignedOrder interface definitions.
 | 
			
		||||
     * @return  The resulting orderHash from hashing the supplied order as a Buffer
 | 
			
		||||
     */
 | 
			
		||||
    getOrderHashBuff(order: SignedOrder | Order): Buffer {
 | 
			
		||||
        const makerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.makerAssetData)]);
 | 
			
		||||
        const takerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.takerAssetData)]);
 | 
			
		||||
 | 
			
		||||
        const orderParamsHashBuff = crypto.solSHA3([
 | 
			
		||||
            order.makerAddress,
 | 
			
		||||
            order.takerAddress,
 | 
			
		||||
            order.feeRecipientAddress,
 | 
			
		||||
            order.senderAddress,
 | 
			
		||||
            order.makerAssetAmount,
 | 
			
		||||
            order.takerAssetAmount,
 | 
			
		||||
            order.makerFee,
 | 
			
		||||
            order.takerFee,
 | 
			
		||||
            order.expirationTimeSeconds,
 | 
			
		||||
            order.salt,
 | 
			
		||||
            makerAssetDataHash,
 | 
			
		||||
            takerAssetDataHash,
 | 
			
		||||
        ]);
 | 
			
		||||
        const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`;
 | 
			
		||||
        const orderSchemaHashHex = this._getOrderSchemaHex();
 | 
			
		||||
        const domainSeparatorHashHex = this._getDomainSeparatorHashHex(order.exchangeAddress);
 | 
			
		||||
        const domainSeparatorSchemaHex = this._getDomainSeparatorSchemaHex();
 | 
			
		||||
        const orderHashBuff = crypto.solSHA3([
 | 
			
		||||
            new BigNumber(domainSeparatorSchemaHex),
 | 
			
		||||
            new BigNumber(domainSeparatorHashHex),
 | 
			
		||||
            new BigNumber(orderSchemaHashHex),
 | 
			
		||||
            new BigNumber(orderParamsHashHex),
 | 
			
		||||
        ]);
 | 
			
		||||
    getOrderHashBuffer(order: SignedOrder | Order): Buffer {
 | 
			
		||||
        const orderParamsHashBuff = EIP712Utils.structHash(EIP712_ORDER_SCHEMA, order);
 | 
			
		||||
        const orderHashBuff = EIP712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress);
 | 
			
		||||
        return orderHashBuff;
 | 
			
		||||
    },
 | 
			
		||||
    _getOrderSchemaHex(): string {
 | 
			
		||||
        const orderSchemaHashBuff = crypto.solSHA3([
 | 
			
		||||
            'Order(',
 | 
			
		||||
            'address makerAddress,',
 | 
			
		||||
            'address takerAddress,',
 | 
			
		||||
            'address feeRecipientAddress,',
 | 
			
		||||
            'address senderAddress,',
 | 
			
		||||
            'uint256 makerAssetAmount,',
 | 
			
		||||
            'uint256 takerAssetAmount,',
 | 
			
		||||
            'uint256 makerFee,',
 | 
			
		||||
            'uint256 takerFee,',
 | 
			
		||||
            'uint256 expirationTimeSeconds,',
 | 
			
		||||
            'uint256 salt,',
 | 
			
		||||
            'bytes makerAssetData,',
 | 
			
		||||
            'bytes takerAssetData,',
 | 
			
		||||
            ')',
 | 
			
		||||
        ]);
 | 
			
		||||
        const schemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`;
 | 
			
		||||
        return schemaHashHex;
 | 
			
		||||
    },
 | 
			
		||||
    _getDomainSeparatorSchemaHex(): string {
 | 
			
		||||
        const domainSeparatorSchemaHashBuff = crypto.solSHA3(['DomainSeparator(address contract)']);
 | 
			
		||||
        const schemaHashHex = `0x${domainSeparatorSchemaHashBuff.toString('hex')}`;
 | 
			
		||||
        return schemaHashHex;
 | 
			
		||||
    },
 | 
			
		||||
    _getDomainSeparatorHashHex(exchangeAddress: string): string {
 | 
			
		||||
        const domainSeparatorHashBuff = crypto.solSHA3([exchangeAddress]);
 | 
			
		||||
        const domainSeparatorHashHex = `0x${domainSeparatorHashBuff.toString('hex')}`;
 | 
			
		||||
        return domainSeparatorHashHex;
 | 
			
		||||
    _getOrderSchemaBuffer(): Buffer {
 | 
			
		||||
        return EIP712Utils.compileSchema(EIP712_ORDER_SCHEMA);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -33,3 +33,21 @@ export enum TransferType {
 | 
			
		||||
    Trade = 'trade',
 | 
			
		||||
    Fee = 'fee',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EIP712Parameter {
 | 
			
		||||
    name: string;
 | 
			
		||||
    type: EIP712Types;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EIP712Schema {
 | 
			
		||||
    name: string;
 | 
			
		||||
    parameters: EIP712Parameter[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum EIP712Types {
 | 
			
		||||
    Address = 'address',
 | 
			
		||||
    Bytes = 'bytes',
 | 
			
		||||
    Bytes32 = 'bytes32',
 | 
			
		||||
    String = 'string',
 | 
			
		||||
    Uint256 = 'uint256',
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -229,7 +229,7 @@ export class Blockchain {
 | 
			
		||||
            shouldPollUserAddress,
 | 
			
		||||
        );
 | 
			
		||||
        this._contractWrappers.setProvider(provider, this.networkId);
 | 
			
		||||
        this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
 | 
			
		||||
        this._dispatcher.updateProviderType(ProviderType.Ledger);
 | 
			
		||||
    }
 | 
			
		||||
    public async updateProviderToInjectedAsync(): Promise<void> {
 | 
			
		||||
@@ -259,7 +259,7 @@ export class Blockchain {
 | 
			
		||||
        this._contractWrappers.setProvider(provider, this.networkId);
 | 
			
		||||
 | 
			
		||||
        await this.fetchTokenInformationAsync();
 | 
			
		||||
        this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
 | 
			
		||||
        this._dispatcher.updateProviderType(ProviderType.Injected);
 | 
			
		||||
        delete this._ledgerSubprovider;
 | 
			
		||||
        delete this._cachedProvider;
 | 
			
		||||
@@ -816,7 +816,7 @@ export class Blockchain {
 | 
			
		||||
        this._userAddressIfExists = userAddresses[0];
 | 
			
		||||
        this._dispatcher.updateUserAddress(this._userAddressIfExists);
 | 
			
		||||
        await this.fetchTokenInformationAsync();
 | 
			
		||||
        this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
 | 
			
		||||
        await this._rehydrateStoreWithContractEventsAsync();
 | 
			
		||||
    }
 | 
			
		||||
    private _updateProviderName(injectedWeb3: Web3): void {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,56 +34,15 @@ export class BlockchainWatcher {
 | 
			
		||||
    public updatePrevUserAddress(userAddress: string): void {
 | 
			
		||||
        this._prevUserAddressIfExists = userAddress;
 | 
			
		||||
    }
 | 
			
		||||
    public startEmittingNetworkConnectionAndUserBalanceState(): void {
 | 
			
		||||
    public async startEmittingNetworkConnectionAndUserBalanceStateAsync(): Promise<void> {
 | 
			
		||||
        if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
 | 
			
		||||
            return; // we are already emitting the state
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let prevNodeVersion: string;
 | 
			
		||||
        this._prevUserEtherBalanceInWei = undefined;
 | 
			
		||||
        this._dispatcher.updateNetworkId(this._prevNetworkId);
 | 
			
		||||
        await this._updateNetworkAndBalanceAsync();
 | 
			
		||||
        this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
 | 
			
		||||
            async () => {
 | 
			
		||||
                // Check for network state changes
 | 
			
		||||
                let currentNetworkId;
 | 
			
		||||
                try {
 | 
			
		||||
                    currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
 | 
			
		||||
                } catch (err) {
 | 
			
		||||
                    // Noop
 | 
			
		||||
                }
 | 
			
		||||
                if (currentNetworkId !== this._prevNetworkId) {
 | 
			
		||||
                    this._prevNetworkId = currentNetworkId;
 | 
			
		||||
                    this._dispatcher.updateNetworkId(currentNetworkId);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check for node version changes
 | 
			
		||||
                const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
 | 
			
		||||
                if (currentNodeVersion !== prevNodeVersion) {
 | 
			
		||||
                    prevNodeVersion = currentNodeVersion;
 | 
			
		||||
                    this._dispatcher.updateNodeVersion(currentNodeVersion);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (this._shouldPollUserAddress) {
 | 
			
		||||
                    const addresses = await this._web3Wrapper.getAvailableAddressesAsync();
 | 
			
		||||
                    const userAddressIfExists = addresses[0];
 | 
			
		||||
                    // Update makerAddress on network change
 | 
			
		||||
                    if (this._prevUserAddressIfExists !== userAddressIfExists) {
 | 
			
		||||
                        this._prevUserAddressIfExists = userAddressIfExists;
 | 
			
		||||
                        this._dispatcher.updateUserAddress(userAddressIfExists);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Check for user ether balance changes
 | 
			
		||||
                    if (!_.isUndefined(userAddressIfExists)) {
 | 
			
		||||
                        await this._updateUserWeiBalanceAsync(userAddressIfExists);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // This logic is primarily for the Ledger, since we don't regularly poll for the address
 | 
			
		||||
                    // we simply update the balance for the last fetched address.
 | 
			
		||||
                    if (!_.isUndefined(this._prevUserAddressIfExists)) {
 | 
			
		||||
                        await this._updateUserWeiBalanceAsync(this._prevUserAddressIfExists);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            this._updateNetworkAndBalanceAsync.bind(this),
 | 
			
		||||
            5000,
 | 
			
		||||
            (err: Error) => {
 | 
			
		||||
                logUtils.log(`Watching network and balances failed: ${err.stack}`);
 | 
			
		||||
@@ -91,6 +50,48 @@ export class BlockchainWatcher {
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private async _updateNetworkAndBalanceAsync(): Promise<void> {
 | 
			
		||||
        // Check for network state changes
 | 
			
		||||
        let prevNodeVersion: string;
 | 
			
		||||
        let currentNetworkId;
 | 
			
		||||
        try {
 | 
			
		||||
            currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            // Noop
 | 
			
		||||
        }
 | 
			
		||||
        if (currentNetworkId !== this._prevNetworkId) {
 | 
			
		||||
            this._prevNetworkId = currentNetworkId;
 | 
			
		||||
            this._dispatcher.updateNetworkId(currentNetworkId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check for node version changes
 | 
			
		||||
        const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
 | 
			
		||||
        if (currentNodeVersion !== prevNodeVersion) {
 | 
			
		||||
            prevNodeVersion = currentNodeVersion;
 | 
			
		||||
            this._dispatcher.updateNodeVersion(currentNodeVersion);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._shouldPollUserAddress) {
 | 
			
		||||
            const addresses = await this._web3Wrapper.getAvailableAddressesAsync();
 | 
			
		||||
            const userAddressIfExists = addresses[0];
 | 
			
		||||
            // Update makerAddress on network change
 | 
			
		||||
            if (this._prevUserAddressIfExists !== userAddressIfExists) {
 | 
			
		||||
                this._prevUserAddressIfExists = userAddressIfExists;
 | 
			
		||||
                this._dispatcher.updateUserAddress(userAddressIfExists);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check for user ether balance changes
 | 
			
		||||
            if (!_.isUndefined(userAddressIfExists)) {
 | 
			
		||||
                await this._updateUserWeiBalanceAsync(userAddressIfExists);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // This logic is primarily for the Ledger, since we don't regularly poll for the address
 | 
			
		||||
            // we simply update the balance for the last fetched address.
 | 
			
		||||
            if (!_.isUndefined(this._prevUserAddressIfExists)) {
 | 
			
		||||
                await this._updateUserWeiBalanceAsync(this._prevUserAddressIfExists);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private async _updateUserWeiBalanceAsync(userAddress: string): Promise<void> {
 | 
			
		||||
        const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(userAddress);
 | 
			
		||||
        if (_.isUndefined(this._prevUserEtherBalanceInWei) || !balanceInWei.eq(this._prevUserEtherBalanceInWei)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import { constants as sharedConstants } from '@0xproject/react-shared';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { RouteComponentProps, withRouter } from 'react-router';
 | 
			
		||||
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
@@ -13,9 +15,11 @@ import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wall
 | 
			
		||||
import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step';
 | 
			
		||||
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
 | 
			
		||||
import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
 | 
			
		||||
export interface PortalOnboardingFlowProps {
 | 
			
		||||
export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
    stepIndex: number;
 | 
			
		||||
    isRunning: boolean;
 | 
			
		||||
@@ -32,9 +36,15 @@ export interface PortalOnboardingFlowProps {
 | 
			
		||||
    refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
 | 
			
		||||
class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
 | 
			
		||||
    private _unlisten: () => void;
 | 
			
		||||
    public componentDidMount(): void {
 | 
			
		||||
        this._overrideOnboardingStateIfShould();
 | 
			
		||||
        // If there is a route change, just close onboarding.
 | 
			
		||||
        this._unlisten = this.props.history.listen(() => this.props.updateIsRunning(false));
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount(): void {
 | 
			
		||||
        this._unlisten();
 | 
			
		||||
    }
 | 
			
		||||
    public componentDidUpdate(): void {
 | 
			
		||||
        this._overrideOnboardingStateIfShould();
 | 
			
		||||
@@ -45,8 +55,8 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
 | 
			
		||||
                steps={this._getSteps()}
 | 
			
		||||
                stepIndex={this.props.stepIndex}
 | 
			
		||||
                isRunning={this.props.isRunning}
 | 
			
		||||
                onClose={this.props.updateIsRunning.bind(this, false)}
 | 
			
		||||
                updateOnboardingStep={this.props.updateOnboardingStep}
 | 
			
		||||
                onClose={this._closeOnboarding.bind(this)}
 | 
			
		||||
                updateOnboardingStep={this._updateOnboardingStep.bind(this)}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -181,9 +191,21 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
 | 
			
		||||
    }
 | 
			
		||||
    private _autoStartOnboardingIfShould(): void {
 | 
			
		||||
        if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) {
 | 
			
		||||
            const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
            analytics.logEvent('Portal', 'Onboarding Started - Automatic', networkName, this.props.stepIndex);
 | 
			
		||||
            this.props.updateIsRunning(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private _updateOnboardingStep(stepIndex: number): void {
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        this.props.updateOnboardingStep(stepIndex);
 | 
			
		||||
        analytics.logEvent('Portal', 'Update Onboarding Step', networkName, stepIndex);
 | 
			
		||||
    }
 | 
			
		||||
    private _closeOnboarding(): void {
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        this.props.updateIsRunning(false);
 | 
			
		||||
        analytics.logEvent('Portal', 'Onboarding Closed', networkName, this.props.stepIndex);
 | 
			
		||||
    }
 | 
			
		||||
    private _renderZrxAllowanceToggle(): React.ReactNode {
 | 
			
		||||
        const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
 | 
			
		||||
        return this._renderAllowanceToggle(zrxToken);
 | 
			
		||||
@@ -209,3 +231,5 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const PortalOnboardingFlow = withRouter(PlainPortalOnboardingFlow);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,12 @@ import { Styles } from '@0xproject/react-shared';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { defaultMenuItemEntries, Menu } from 'ts/components/portal/menu';
 | 
			
		||||
import { Identicon } from 'ts/components/ui/identicon';
 | 
			
		||||
import { Text } from 'ts/components/ui/text';
 | 
			
		||||
import { colors } from 'ts/style/colors';
 | 
			
		||||
import { WebsitePaths } from 'ts/types';
 | 
			
		||||
import { ProviderType, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
 | 
			
		||||
const IDENTICON_DIAMETER = 45;
 | 
			
		||||
@@ -25,14 +27,15 @@ const styles: Styles = {
 | 
			
		||||
        MozBorderRadius: BORDER_RADIUS,
 | 
			
		||||
        WebkitBorderRadius: BORDER_RADIUS,
 | 
			
		||||
    },
 | 
			
		||||
    userAddress: {
 | 
			
		||||
        color: colors.white,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface DrawerMenuProps {
 | 
			
		||||
    selectedPath?: string;
 | 
			
		||||
    userAddress?: string;
 | 
			
		||||
    injectedProviderName: string;
 | 
			
		||||
    providerType: ProviderType;
 | 
			
		||||
    blockchain?: Blockchain;
 | 
			
		||||
    blockchainIsLoaded: boolean;
 | 
			
		||||
}
 | 
			
		||||
export const DrawerMenu = (props: DrawerMenuProps) => {
 | 
			
		||||
    const relayerItemEntry = {
 | 
			
		||||
@@ -41,9 +44,15 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
 | 
			
		||||
        iconName: 'zmdi-portable-wifi',
 | 
			
		||||
    };
 | 
			
		||||
    const menuItemEntries = _.concat(relayerItemEntry, defaultMenuItemEntries);
 | 
			
		||||
    const displayMessage = utils.getReadableAccountState(
 | 
			
		||||
        props.blockchainIsLoaded && !_.isUndefined(props.blockchain),
 | 
			
		||||
        props.providerType,
 | 
			
		||||
        props.injectedProviderName,
 | 
			
		||||
        props.userAddress,
 | 
			
		||||
    );
 | 
			
		||||
    return (
 | 
			
		||||
        <div style={styles.root}>
 | 
			
		||||
            <Header userAddress={props.userAddress} />
 | 
			
		||||
            <Header userAddress={props.userAddress} displayMessage={displayMessage} />
 | 
			
		||||
            <Menu selectedPath={props.selectedPath} menuItemEntries={menuItemEntries} />
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
@@ -51,17 +60,16 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
 | 
			
		||||
 | 
			
		||||
interface HeaderProps {
 | 
			
		||||
    userAddress?: string;
 | 
			
		||||
    displayMessage: string;
 | 
			
		||||
}
 | 
			
		||||
const Header = (props: HeaderProps) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="flex flex-center py4">
 | 
			
		||||
            <div className="flex flex-column mx-auto">
 | 
			
		||||
                <Identicon address={props.userAddress} diameter={IDENTICON_DIAMETER} style={styles.identicon} />
 | 
			
		||||
                {!_.isUndefined(props.userAddress) && (
 | 
			
		||||
                    <div className="pt2" style={styles.userAddress}>
 | 
			
		||||
                        {utils.getAddressBeginAndEnd(props.userAddress)}
 | 
			
		||||
                    </div>
 | 
			
		||||
                )}
 | 
			
		||||
                <Text className="pt2" fontColor={colors.white}>
 | 
			
		||||
                    {props.displayMessage}
 | 
			
		||||
                </Text>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { colors, Styles } from '@0xproject/react-shared';
 | 
			
		||||
import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared';
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
 | 
			
		||||
@@ -33,6 +33,7 @@ import { localStorage } from 'ts/local_storage/local_storage';
 | 
			
		||||
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
 | 
			
		||||
import { FullscreenMessage } from 'ts/pages/fullscreen_message';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { zIndex } from 'ts/style/z_index';
 | 
			
		||||
import {
 | 
			
		||||
    BlockchainErrs,
 | 
			
		||||
    HashData,
 | 
			
		||||
@@ -46,6 +47,7 @@ import {
 | 
			
		||||
    TokenVisibility,
 | 
			
		||||
    WebsitePaths,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
import { backendClient } from 'ts/utils/backend_client';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
@@ -73,6 +75,8 @@ export interface PortalProps {
 | 
			
		||||
    flashMessage?: string | React.ReactNode;
 | 
			
		||||
    lastForceTokenStateRefetch: number;
 | 
			
		||||
    translate: Translate;
 | 
			
		||||
    isPortalOnboardingShowing: boolean;
 | 
			
		||||
    portalOnboardingStep: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface PortalState {
 | 
			
		||||
@@ -155,9 +159,6 @@ export class Portal extends React.Component<PortalProps, PortalState> {
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillMount(): void {
 | 
			
		||||
        this._blockchain = new Blockchain(this.props.dispatcher);
 | 
			
		||||
        const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount(): void {
 | 
			
		||||
        this._blockchain.destroy();
 | 
			
		||||
@@ -168,6 +169,13 @@ export class Portal extends React.Component<PortalProps, PortalState> {
 | 
			
		||||
        // become disconnected from their backing Ethereum node, changed user accounts, etc...)
 | 
			
		||||
        this.props.dispatcher.resetState();
 | 
			
		||||
    }
 | 
			
		||||
    public componentDidUpdate(prevProps: PortalProps): void {
 | 
			
		||||
        if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
 | 
			
		||||
            const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
 | 
			
		||||
            // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
            this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillReceiveProps(nextProps: PortalProps): void {
 | 
			
		||||
        if (nextProps.networkId !== this.state.prevNetworkId) {
 | 
			
		||||
            // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
@@ -335,6 +343,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <Wallet
 | 
			
		||||
                    style={this.props.isPortalOnboardingShowing ? { zIndex: zIndex.aboveOverlay } : undefined}
 | 
			
		||||
                    userAddress={this.props.userAddress}
 | 
			
		||||
                    networkId={this.props.networkId}
 | 
			
		||||
                    blockchain={this._blockchain}
 | 
			
		||||
@@ -384,6 +393,8 @@ export class Portal extends React.Component<PortalProps, PortalState> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _startOnboarding(): void {
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep);
 | 
			
		||||
        this.props.dispatcher.updatePortalOnboardingShowing(true);
 | 
			
		||||
    }
 | 
			
		||||
    private _renderWalletSection(): React.ReactNode {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
import { Styles } from '@0xproject/react-shared';
 | 
			
		||||
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import { GridTile } from 'material-ui/GridList';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
 | 
			
		||||
import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens';
 | 
			
		||||
import { Container } from 'ts/components/ui/container';
 | 
			
		||||
@@ -66,6 +67,9 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
 | 
			
		||||
    const link = props.relayerInfo.appUrl || props.relayerInfo.url;
 | 
			
		||||
    const topTokens = props.relayerInfo.topTokens;
 | 
			
		||||
    const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
 | 
			
		||||
    const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId];
 | 
			
		||||
    const eventLabel = `${props.relayerInfo.name}-${networkName}`;
 | 
			
		||||
    const trackRelayerClick = () => analytics.logEvent('Portal', 'Relayer Click', eventLabel);
 | 
			
		||||
    const headerImageUrl = props.relayerInfo.logoImgUrl;
 | 
			
		||||
    const headerBackgroundColor =
 | 
			
		||||
        !_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor)
 | 
			
		||||
@@ -74,7 +78,7 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
 | 
			
		||||
    return (
 | 
			
		||||
        <Island style={styles.root} Component={GridTile}>
 | 
			
		||||
            <div style={styles.innerDiv}>
 | 
			
		||||
                <a href={link} target="_blank" style={{ textDecoration: 'none' }}>
 | 
			
		||||
                <a href={link} target="_blank" style={{ textDecoration: 'none' }} onClick={trackRelayerClick}>
 | 
			
		||||
                    <div
 | 
			
		||||
                        className="flex items-center"
 | 
			
		||||
                        style={{ ...styles.header, backgroundColor: headerBackgroundColor }}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
import { colors, EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0xproject/react-shared';
 | 
			
		||||
import {
 | 
			
		||||
    colors,
 | 
			
		||||
    constants as sharedConstants,
 | 
			
		||||
    EtherscanLinkSuffixes,
 | 
			
		||||
    Styles,
 | 
			
		||||
    utils as sharedUtils,
 | 
			
		||||
} from '@0xproject/react-shared';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
 | 
			
		||||
import { WebsiteBackendTokenInfo } from 'ts/types';
 | 
			
		||||
 | 
			
		||||
@@ -61,6 +68,9 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
 | 
			
		||||
            cursor: 'pointer',
 | 
			
		||||
            opacity: this.state.isHovering ? 0.5 : 1,
 | 
			
		||||
        };
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
 | 
			
		||||
        const trackTokenClick = () => analytics.logEvent('Portal', 'Token Click', eventLabel);
 | 
			
		||||
        return (
 | 
			
		||||
            <a
 | 
			
		||||
                href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)}
 | 
			
		||||
@@ -68,6 +78,7 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
 | 
			
		||||
                style={style}
 | 
			
		||||
                onMouseEnter={this._onToggleHover.bind(this, true)}
 | 
			
		||||
                onMouseLeave={this._onToggleHover.bind(this, false)}
 | 
			
		||||
                onClick={trackTokenClick}
 | 
			
		||||
            >
 | 
			
		||||
                {this.props.tokenInfo.symbol}
 | 
			
		||||
            </a>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,11 @@ import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
 | 
			
		||||
import { Container } from 'ts/components/ui/container';
 | 
			
		||||
import { DropDown } from 'ts/components/ui/drop_down';
 | 
			
		||||
import { Identicon } from 'ts/components/ui/identicon';
 | 
			
		||||
import { Image } from 'ts/components/ui/image';
 | 
			
		||||
import { Text } from 'ts/components/ui/text';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { colors } from 'ts/style/colors';
 | 
			
		||||
import { ProviderType } from 'ts/types';
 | 
			
		||||
@@ -40,23 +43,16 @@ const styles: Styles = {
 | 
			
		||||
 | 
			
		||||
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        const isAddressAvailable = !_.isEmpty(this.props.userAddress);
 | 
			
		||||
        const isExternallyInjectedProvider = utils.isExternallyInjected(
 | 
			
		||||
            this.props.providerType,
 | 
			
		||||
            this.props.injectedProviderName,
 | 
			
		||||
        );
 | 
			
		||||
        let displayMessage;
 | 
			
		||||
        if (!this._isBlockchainReady()) {
 | 
			
		||||
            displayMessage = 'loading account';
 | 
			
		||||
        } else if (isAddressAvailable) {
 | 
			
		||||
            displayMessage = utils.getAddressBeginAndEnd(this.props.userAddress);
 | 
			
		||||
            // tslint:disable-next-line: prefer-conditional-expression
 | 
			
		||||
        } else if (isExternallyInjectedProvider) {
 | 
			
		||||
            displayMessage = 'Account locked';
 | 
			
		||||
        } else {
 | 
			
		||||
            displayMessage = '0x0000...0000';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const displayMessage = utils.getReadableAccountState(
 | 
			
		||||
            this._isBlockchainReady(),
 | 
			
		||||
            this.props.providerType,
 | 
			
		||||
            this.props.injectedProviderName,
 | 
			
		||||
            this.props.userAddress,
 | 
			
		||||
        );
 | 
			
		||||
        // If the "injected" provider is our fallback public node, then we want to
 | 
			
		||||
        // show the "connect a wallet" message instead of the providerName
 | 
			
		||||
        const injectedProviderName = isExternallyInjectedProvider
 | 
			
		||||
@@ -66,7 +62,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
 | 
			
		||||
            this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
 | 
			
		||||
        const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
 | 
			
		||||
        const hoverActiveNode = (
 | 
			
		||||
            <div className="flex right lg-pr0 md-pr2 sm-pr2 p1" style={styles.root}>
 | 
			
		||||
            <div className="flex items-center p1" style={styles.root}>
 | 
			
		||||
                <div>
 | 
			
		||||
                    {this._isBlockchainReady() ? (
 | 
			
		||||
                        <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
 | 
			
		||||
@@ -74,13 +70,13 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
 | 
			
		||||
                        <CircularProgress size={ROOT_HEIGHT} thickness={2} />
 | 
			
		||||
                    )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style={{ marginLeft: 12, paddingTop: 3 }}>
 | 
			
		||||
                    <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayMessage}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <Container marginLeft="12px" marginRight="12px">
 | 
			
		||||
                    <Text fontSize="14px" fontColor={colors.darkGrey}>
 | 
			
		||||
                        {displayMessage}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                </Container>
 | 
			
		||||
                {isProviderMetamask && (
 | 
			
		||||
                    <div style={{ marginLeft: 16 }}>
 | 
			
		||||
                        <img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <Image src="/images/metamask_icon.png" height={ROOT_HEIGHT} width={ROOT_HEIGHT} />
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -297,7 +297,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
 | 
			
		||||
                openSecondary={true}
 | 
			
		||||
                onRequestChange={this._onMenuButtonClick.bind(this)}
 | 
			
		||||
            >
 | 
			
		||||
                <DrawerMenu selectedPath={this.props.location.pathname} userAddress={this.props.userAddress} />
 | 
			
		||||
                <DrawerMenu
 | 
			
		||||
                    selectedPath={this.props.location.pathname}
 | 
			
		||||
                    userAddress={this.props.userAddress}
 | 
			
		||||
                    injectedProviderName={this.props.injectedProviderName}
 | 
			
		||||
                    providerType={this.props.providerType}
 | 
			
		||||
                    blockchainIsLoaded={this.props.blockchainIsLoaded}
 | 
			
		||||
                    blockchain={this.props.blockchain}
 | 
			
		||||
                />
 | 
			
		||||
            </Drawer>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import blockies = require('blockies');
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 | 
			
		||||
import { Image } from 'ts/components/ui/image';
 | 
			
		||||
import { colors } from 'ts/style/colors';
 | 
			
		||||
 | 
			
		||||
interface IdenticonProps {
 | 
			
		||||
    address: string;
 | 
			
		||||
@@ -16,14 +18,9 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
 | 
			
		||||
        style: {},
 | 
			
		||||
    };
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        let address = this.props.address;
 | 
			
		||||
        if (_.isEmpty(address)) {
 | 
			
		||||
            address = constants.NULL_ADDRESS;
 | 
			
		||||
        }
 | 
			
		||||
        const address = this.props.address;
 | 
			
		||||
        const diameter = this.props.diameter;
 | 
			
		||||
        const icon = blockies({
 | 
			
		||||
            seed: address.toLowerCase(),
 | 
			
		||||
        });
 | 
			
		||||
        const radius = diameter / 2;
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                className="circle mx-auto relative transitionFix"
 | 
			
		||||
@@ -34,14 +31,19 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
 | 
			
		||||
                    ...this.props.style,
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                <img
 | 
			
		||||
                    src={icon.toDataURL()}
 | 
			
		||||
                    style={{
 | 
			
		||||
                        width: diameter,
 | 
			
		||||
                        height: diameter,
 | 
			
		||||
                        imageRendering: 'pixelated',
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
                {!_.isEmpty(address) ? (
 | 
			
		||||
                    <Image
 | 
			
		||||
                        src={blockies({
 | 
			
		||||
                            seed: address.toLowerCase(),
 | 
			
		||||
                        }).toDataURL()}
 | 
			
		||||
                        height={diameter}
 | 
			
		||||
                        width={diameter}
 | 
			
		||||
                    />
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <svg height={diameter} width={diameter}>
 | 
			
		||||
                        <circle cx={radius} cy={radius} r={radius} fill={colors.grey200} />
 | 
			
		||||
                    </svg>
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@ export interface ImageProps {
 | 
			
		||||
    className?: string;
 | 
			
		||||
    src?: string;
 | 
			
		||||
    fallbackSrc?: string;
 | 
			
		||||
    height?: string;
 | 
			
		||||
    height?: string | number;
 | 
			
		||||
    width?: string | number;
 | 
			
		||||
}
 | 
			
		||||
interface ImageState {
 | 
			
		||||
    imageLoadFailed: boolean;
 | 
			
		||||
@@ -26,6 +27,7 @@ export class Image extends React.Component<ImageProps, ImageState> {
 | 
			
		||||
                onError={this._onError.bind(this)}
 | 
			
		||||
                src={src}
 | 
			
		||||
                height={this.props.height}
 | 
			
		||||
                width={this.props.width}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,15 @@
 | 
			
		||||
import { EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0xproject/react-shared';
 | 
			
		||||
import {
 | 
			
		||||
    constants as sharedConstants,
 | 
			
		||||
    EtherscanLinkSuffixes,
 | 
			
		||||
    Styles,
 | 
			
		||||
    utils as sharedUtils,
 | 
			
		||||
} from '@0xproject/react-shared';
 | 
			
		||||
import { BigNumber, errorUtils } from '@0xproject/utils';
 | 
			
		||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import CircularProgress from 'material-ui/CircularProgress';
 | 
			
		||||
import FloatingActionButton from 'material-ui/FloatingActionButton';
 | 
			
		||||
 | 
			
		||||
import { ListItem } from 'material-ui/List';
 | 
			
		||||
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
 | 
			
		||||
import ContentAdd from 'material-ui/svg-icons/content/add';
 | 
			
		||||
@@ -23,7 +29,6 @@ import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
 | 
			
		||||
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { colors } from 'ts/style/colors';
 | 
			
		||||
import { zIndex } from 'ts/style/z_index';
 | 
			
		||||
import {
 | 
			
		||||
    BlockchainErrs,
 | 
			
		||||
    ProviderType,
 | 
			
		||||
@@ -35,6 +40,7 @@ import {
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
    WebsitePaths,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
 | 
			
		||||
@@ -59,6 +65,7 @@ export interface WalletProps {
 | 
			
		||||
    onAddToken: () => void;
 | 
			
		||||
    onRemoveToken: () => void;
 | 
			
		||||
    refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
 | 
			
		||||
    style: React.CSSProperties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WalletState {
 | 
			
		||||
@@ -79,7 +86,6 @@ interface AccessoryItemConfig {
 | 
			
		||||
const styles: Styles = {
 | 
			
		||||
    root: {
 | 
			
		||||
        width: '100%',
 | 
			
		||||
        zIndex: zIndex.aboveOverlay,
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
    },
 | 
			
		||||
    footerItemInnerDiv: {
 | 
			
		||||
@@ -134,6 +140,9 @@ const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
 | 
			
		||||
const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
 | 
			
		||||
 | 
			
		||||
export class Wallet extends React.Component<WalletProps, WalletState> {
 | 
			
		||||
    public static defaultProps = {
 | 
			
		||||
        style: {},
 | 
			
		||||
    };
 | 
			
		||||
    constructor(props: WalletProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this.state = {
 | 
			
		||||
@@ -141,11 +150,10 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
 | 
			
		||||
            isHoveringSidebar: false,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
 | 
			
		||||
        return (
 | 
			
		||||
            <Island className="flex flex-column wallet" style={styles.root}>
 | 
			
		||||
            <Island className="flex flex-column wallet" style={{ ...styles.root, ...this.props.style }}>
 | 
			
		||||
                {isBlockchainLoaded ? this._renderLoadedRows() : this._renderLoadingRows()}
 | 
			
		||||
            </Island>
 | 
			
		||||
        );
 | 
			
		||||
@@ -269,7 +277,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
 | 
			
		||||
                        <ListItem
 | 
			
		||||
                            primaryText={
 | 
			
		||||
                                <div className="flex right" style={styles.manageYourWalletText}>
 | 
			
		||||
                                    {'manage your wallet'}
 | 
			
		||||
                                    manage your wallet
 | 
			
		||||
                                </div>
 | 
			
		||||
                                // https://github.com/palantir/tslint-react/issues/140
 | 
			
		||||
                                // tslint:disable-next-line:jsx-curly-spacing
 | 
			
		||||
@@ -488,18 +496,26 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const onClick = isWrappedEtherDirectionOpen
 | 
			
		||||
            ? this._closeWrappedEtherActionRow.bind(this)
 | 
			
		||||
            ? this._closeWrappedEtherActionRow.bind(this, wrappedEtherDirection)
 | 
			
		||||
            : this._openWrappedEtherActionRow.bind(this, wrappedEtherDirection);
 | 
			
		||||
        return (
 | 
			
		||||
            <IconButton iconName={buttonIconName} labelText={buttonLabel} onClick={onClick} color={colors.mediumBlue} />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void {
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        const action =
 | 
			
		||||
            wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened';
 | 
			
		||||
        analytics.logEvent('Portal', action, networkName);
 | 
			
		||||
        this.setState({
 | 
			
		||||
            wrappedEtherDirection,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    private _closeWrappedEtherActionRow(): void {
 | 
			
		||||
    private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void {
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        const action =
 | 
			
		||||
            wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed';
 | 
			
		||||
        analytics.logEvent('Portal', action, networkName);
 | 
			
		||||
        this.setState({
 | 
			
		||||
            wrappedEtherDirection: undefined,
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { Styles } from '@0xproject/react-shared';
 | 
			
		||||
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
 | 
			
		||||
import { BigNumber, logUtils } from '@0xproject/utils';
 | 
			
		||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
@@ -11,6 +11,7 @@ import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { colors } from 'ts/style/colors';
 | 
			
		||||
import { BlockchainCallErrs, Side, Token } from 'ts/types';
 | 
			
		||||
import { analytics } from 'ts/utils/analytics';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
import { errorReporter } from 'ts/utils/error_reporter';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
@@ -186,6 +187,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
 | 
			
		||||
        this.setState({
 | 
			
		||||
            isEthConversionHappening: true,
 | 
			
		||||
        });
 | 
			
		||||
        const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        try {
 | 
			
		||||
            const etherToken = this.props.etherToken;
 | 
			
		||||
            const amountToConvert = this.state.currentInputAmount;
 | 
			
		||||
@@ -193,10 +195,12 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
 | 
			
		||||
                await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert);
 | 
			
		||||
                const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH);
 | 
			
		||||
                this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
 | 
			
		||||
                analytics.logEvent('Portal', 'Wrap ETH Successfully', networkName);
 | 
			
		||||
            } else {
 | 
			
		||||
                await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert);
 | 
			
		||||
                const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals);
 | 
			
		||||
                this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
 | 
			
		||||
                analytics.logEvent('Portal', 'Unwrap WETH Successfully', networkName);
 | 
			
		||||
            }
 | 
			
		||||
            await this.props.refetchEthTokenStateAsync();
 | 
			
		||||
            this.props.onConversionSuccessful();
 | 
			
		||||
@@ -207,11 +211,13 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
 | 
			
		||||
            } else if (!utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                logUtils.log(`Unexpected error encountered: ${err}`);
 | 
			
		||||
                logUtils.log(err.stack);
 | 
			
		||||
                const errorMsg =
 | 
			
		||||
                    this.props.direction === Side.Deposit
 | 
			
		||||
                        ? 'Failed to wrap your ETH. Please try again.'
 | 
			
		||||
                        : 'Failed to unwrap your WETH. Please try again.';
 | 
			
		||||
                this.props.dispatcher.showFlashMessage(errorMsg);
 | 
			
		||||
                if (this.props.direction === Side.Deposit) {
 | 
			
		||||
                    this.props.dispatcher.showFlashMessage('Failed to wrap your ETH. Please try again.');
 | 
			
		||||
                    analytics.logEvent('Portal', 'Wrap ETH Failed', networkName);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.props.dispatcher.showFlashMessage('Failed to unwrap your WETH. Please try again.');
 | 
			
		||||
                    analytics.logEvent('Portal', 'Unwrap WETH Failed', networkName);
 | 
			
		||||
                }
 | 
			
		||||
                await errorReporter.reportAsync(err);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ interface ConnectedState {
 | 
			
		||||
    userSuppliedOrderCache: Order;
 | 
			
		||||
    flashMessage?: string | React.ReactNode;
 | 
			
		||||
    translate: Translate;
 | 
			
		||||
    isPortalOnboardingShowing: boolean;
 | 
			
		||||
    portalOnboardingStep: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ConnectedDispatch {
 | 
			
		||||
@@ -76,6 +78,8 @@ const mapStateToProps = (state: State, _ownProps: PortalComponentProps): Connect
 | 
			
		||||
        userSuppliedOrderCache: state.userSuppliedOrderCache,
 | 
			
		||||
        flashMessage: state.flashMessage,
 | 
			
		||||
        translate: state.translate,
 | 
			
		||||
        isPortalOnboardingShowing: state.isPortalOnboardingShowing,
 | 
			
		||||
        portalOnboardingStep: state.portalOnboardingStep,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ interface PortalOnboardingFlowProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ConnectedState {
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    stepIndex: number;
 | 
			
		||||
    isRunning: boolean;
 | 
			
		||||
    userAddress: string;
 | 
			
		||||
@@ -32,6 +33,7 @@ interface ConnectedDispatch {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): ConnectedState => ({
 | 
			
		||||
    networkId: state.networkId,
 | 
			
		||||
    stepIndex: state.portalOnboardingStep,
 | 
			
		||||
    isRunning: state.isPortalOnboardingShowing,
 | 
			
		||||
    userAddress: state.userAddress,
 | 
			
		||||
 
 | 
			
		||||
@@ -190,6 +190,25 @@ export const utils = {
 | 
			
		||||
        const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
 | 
			
		||||
        return truncatedAddress;
 | 
			
		||||
    },
 | 
			
		||||
    getReadableAccountState(
 | 
			
		||||
        isBlockchainReady: boolean,
 | 
			
		||||
        providerType: ProviderType,
 | 
			
		||||
        injectedProviderName: string,
 | 
			
		||||
        userAddress?: string,
 | 
			
		||||
    ): string {
 | 
			
		||||
        const isAddressAvailable = !_.isUndefined(userAddress) && !_.isEmpty(userAddress);
 | 
			
		||||
        const isExternallyInjectedProvider = utils.isExternallyInjected(providerType, injectedProviderName);
 | 
			
		||||
        if (!isBlockchainReady) {
 | 
			
		||||
            return 'Loading account';
 | 
			
		||||
        } else if (isAddressAvailable) {
 | 
			
		||||
            return utils.getAddressBeginAndEnd(userAddress);
 | 
			
		||||
            // tslint:disable-next-line: prefer-conditional-expression
 | 
			
		||||
        } else if (isExternallyInjectedProvider) {
 | 
			
		||||
            return 'Account locked';
 | 
			
		||||
        } else {
 | 
			
		||||
            return 'No wallet detected';
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean {
 | 
			
		||||
        if (token.isRegistered) {
 | 
			
		||||
            return true; // Since it's registered, it is the canonical token
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user