ETH Price: $1,982.67 (-4.46%)

Transaction Decoder

Block:
5157674 at Feb-26-2018 04:33:19 AM +UTC
Transaction Fee:
0.0002640222 ETH $0.52
Gas Used:
75,220 Gas / 3.51 Gwei

Emitted Events:

81 LocalEthereumEscrows.Released( _tradeHash=5A58FE8D8387FE94239DA04645C30040956AA9DA3104FB0CB3DA557C3301FE45 )

Account State Difference:

  Address   Before After State Difference Code
0x09678741...07099Ae5d
(LocalEthereum 2)
107.72400730281593825 Eth107.49752404881593825 Eth0.226483254
(MiningPoolHub: Old Address)
16,538.57654870238544516 Eth16,538.57681272458544516 Eth0.0002640222
0xF008E2c7...442016420
2.222333165270167614 Eth
Nonce: 26990
2.222069143070167614 Eth
Nonce: 26991
0.0002640222
0xfe2f0419...f050c13d8
0 Eth
Nonce: 0
0.226483254 Eth
Nonce: 0
0.226483254From: 0 To: 0

Execution Trace

LocalEthereumEscrows.batchRelay( ) => ( [true] )
  • Null: 0x000...001.c7811b33( )
  • ETH 0.226483254 0xfe2f04194f86e93f3676ed12ea3ca80f050c13d8.CALL( )
    pragma solidity ^0.4.18;
    contract Token {
        function transfer(address _to, uint _value) public returns (bool success);
        function transferFrom(address _from, address _to, uint _value) public returns (bool success);
        function approve(address _spender, uint _value) public returns (bool success);
    }
    contract LocalEthereumEscrows {
        // The address of the arbitrator
        // In the first version, this is always localethereum staff.
        address public arbitrator;
        address public owner;
        address public relayer;
        uint32 public requestCancellationMinimumTime;
        uint256 public feesAvailableForWithdraw;
    
        uint8 constant ACTION_SELLER_CANNOT_CANCEL = 0x01; // Called when marking as paid or calling a dispute as the buyer
        uint8 constant ACTION_BUYER_CANCEL = 0x02;
        uint8 constant ACTION_SELLER_CANCEL = 0x03;
        uint8 constant ACTION_SELLER_REQUEST_CANCEL = 0x04;
        uint8 constant ACTION_RELEASE = 0x05;
        uint8 constant ACTION_DISPUTE = 0x06;
    
        event Created(bytes32 _tradeHash);
        event SellerCancelDisabled(bytes32 _tradeHash);
        event SellerRequestedCancel(bytes32 _tradeHash);
        event CancelledBySeller(bytes32 _tradeHash);
        event CancelledByBuyer(bytes32 _tradeHash);
        event Released(bytes32 _tradeHash);
        event DisputeResolved(bytes32 _tradeHash);
    
        struct Escrow {
            // Set so we know the trade has already been created
            bool exists;
            // The timestamp in which the seller can cancel the trade if the buyer has not yet marked as paid. Set to 0 on marked paid or dispute
            // 1 = unlimited cancel time
            uint32 sellerCanCancelAfter;
            // The total cost of gas spent by relaying parties. This amount will be
            // refunded/paid to localethereum.com once the escrow is finished.
            uint128 totalGasFeesSpentByRelayer;
        }
        // Mapping of active trades. Key is a hash of the trade data
        mapping (bytes32 => Escrow) public escrows;
    
        modifier onlyOwner() {
            require(msg.sender == owner);
            _;
        }
    
        modifier onlyArbitrator() {
            require(msg.sender == arbitrator);
            _;
        }
    
        function getRelayedSender(
          bytes16 _tradeID, // The unique ID of the trade, generated by localethereum.com
          uint8 _actionByte, // The desired action of the user, matching an ACTION_* constant
          uint128 _maximumGasPrice, // The maximum gas price the user is willing to pay
          uint8 _v, // Signature value
          bytes32 _r, // Signature value
          bytes32 _s // Signature value
        ) view private returns (address) {
            bytes32 _hash = keccak256(_tradeID, _actionByte, _maximumGasPrice);
            if(tx.gasprice > _maximumGasPrice) return;
            return ecrecover(_hash, _v, _r, _s);
        }
    
        function LocalEthereumEscrows() public {
            /**
             * Initialize the contract.
             */
            owner = msg.sender;
            arbitrator = msg.sender;
            relayer = msg.sender;
            requestCancellationMinimumTime = 2 hours; // TODO
        }
    
        function getEscrowAndHash(
          /**
           * Hashes the values and returns the matching escrow object and trade hash.
           * Returns an empty escrow struct and 0 _tradeHash if not found
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee
        ) view private returns (Escrow, bytes32) {
            bytes32 _tradeHash = keccak256(_tradeID, _seller, _buyer, _value, _fee);
            return (escrows[_tradeHash], _tradeHash);
        }
    
        function createEscrow(
          /**
           * Create a new escrow and add it to `escrows`.
           * _tradeHash is created by hashing _tradeID, _seller, _buyer, _value and _fee variables. These variables must be supplied on future contract calls.
           * v, r and s is the signature data supplied from the api. The sig is keccak256(_tradeHash, _paymentWindowInSeconds, _expiry).
           */
          bytes16 _tradeID, // The unique ID of the trade, generated by localethereum.com
          address _seller, // The selling party of the trade
          address _buyer, // The buying party of the trade
          uint256 _value, // The ether amount being held in escrow
          uint16 _fee, // The localethereum.com fee in 1/10000ths
          uint32 _paymentWindowInSeconds, // The time in seconds from contract creation that the buyer has to mark as paid
          uint32 _expiry, // Provided by localethereum.com. This transaction must be created before this time.
          uint8 _v, // Signature value
          bytes32 _r, // Signature value
          bytes32 _s // Signature value
        ) payable external {
            bytes32 _tradeHash = keccak256(_tradeID, _seller, _buyer, _value, _fee);
            require(!escrows[_tradeHash].exists); // Require that trade does not already exist
            require(ecrecover(keccak256(_tradeHash, _paymentWindowInSeconds, _expiry), _v, _r, _s) == relayer); // Signature must have come from the relayer
            require(block.timestamp < _expiry);
            require(msg.value == _value && msg.value > 0); // Check sent eth against signed _value and make sure is not 0
            uint32 _sellerCanCancelAfter = _paymentWindowInSeconds == 0 ? 1 : uint32(block.timestamp) + _paymentWindowInSeconds;
            escrows[_tradeHash] = Escrow(true, _sellerCanCancelAfter, 0);
            Created(_tradeHash);
        }
    
        uint16 constant GAS_doRelease = 36100;
        function doRelease(
          /**
           * Called by the seller to releases the funds for a successful trade.
           * Deletes the trade from the `escrows` mapping.
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _additionalGas
        ) private returns (bool) {
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            if (!_escrow.exists) return false;
            uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doRelease + _additionalGas) * uint128(tx.gasprice) : 0);
            delete escrows[_tradeHash];
            Released(_tradeHash);
            transferMinusFees(_buyer, _value, _gasFees, _fee);
            return true;
        }
    
        uint16 constant GAS_doDisableSellerCancel = 12100;
        function doDisableSellerCancel(
          /**
           * Stops the seller from cancelling the trade.
           * Can only be called the buyer.
           * Used to mark the trade as paid, or if the buyer has a dispute.
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _additionalGas
        ) private returns (bool) {
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            if (!_escrow.exists) return false;
            if(_escrow.sellerCanCancelAfter == 0) return false;
            escrows[_tradeHash].sellerCanCancelAfter = 0;
            SellerCancelDisabled(_tradeHash);
            if (msg.sender == relayer) {
              increaseGasSpent(_tradeHash, GAS_doDisableSellerCancel + _additionalGas);
            }
            return true;
        }
    
        uint16 constant GAS_doBuyerCancel = 36100;
        function doBuyerCancel(
          /**
           * Cancels the trade and returns the ether to the seller.
           * Can only be called the buyer.
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _additionalGas
        ) private returns (bool) {
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            if (!_escrow.exists) return false;
            uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doBuyerCancel + _additionalGas) * uint128(tx.gasprice) : 0);
            delete escrows[_tradeHash];
            CancelledByBuyer(_tradeHash);
            transferMinusFees(_seller, _value, _gasFees, 0);
            return true;
        }
    
        uint16 constant GAS_doSellerCancel = 36100;
        function doSellerCancel(
          /**
           * Cancels the trade and returns the ether to the seller.
           * Can only be called the seller.
           * Can only be called if the payment window was missed by the buyer
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _additionalGas
        ) private returns (bool) {
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            if (!_escrow.exists) return false;
            if(_escrow.sellerCanCancelAfter <= 1 || _escrow.sellerCanCancelAfter > block.timestamp) return false;
            uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doSellerCancel + _additionalGas) * uint128(tx.gasprice) : 0);
            delete escrows[_tradeHash];
            CancelledBySeller(_tradeHash);
            transferMinusFees(_seller, _value, _gasFees, 0);
            return true;
        }
    
        uint16 constant GAS_doSellerRequestCancel = 12100;
        function doSellerRequestCancel(
          /**
           * Called by the seller if the buyer is unresponsive
           * Can only be called on unlimited payment window trades (sellerCanCancelAfter == 1)
           * Sets the payment window to `requestCancellationMinimumTime` from now, in which it can be cancelled.
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _additionalGas
        ) private returns (bool) {
            // Called on unlimited payment window trades wheret the buyer is not responding
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            if (!_escrow.exists) return false;
            if(_escrow.sellerCanCancelAfter != 1) return false;
            escrows[_tradeHash].sellerCanCancelAfter = uint32(block.timestamp) + requestCancellationMinimumTime;
            SellerRequestedCancel(_tradeHash);
            if (msg.sender == relayer) {
              increaseGasSpent(_tradeHash, GAS_doSellerRequestCancel + _additionalGas);
            }
            return true;
        }
    
        uint16 constant GAS_doResolveDispute = 36100;
        function resolveDispute(
          /**
           * Called by the arbitrator to resolve a dispute
           * Requires the signed ACTION_DISPUTE actionByte from either the buyer or the seller
           */
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint8 _v,
          bytes32 _r,
          bytes32 _s,
          uint8 _buyerPercent
        ) external onlyArbitrator {
            address _signature = ecrecover(keccak256(_tradeID, ACTION_DISPUTE), _v, _r, _s);
            require(_signature == _buyer || _signature == _seller);
    
            var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee);
            require(_escrow.exists);
            require(_buyerPercent <= 100);
    
            uint256 _totalFees = _escrow.totalGasFeesSpentByRelayer + GAS_doResolveDispute;
            require(_value - _totalFees <= _value); // Prevent underflow
            feesAvailableForWithdraw += _totalFees; // Add the the pot for localethereum to withdraw
    
            delete escrows[_tradeHash];
            DisputeResolved(_tradeHash);
            _buyer.transfer((_value - _totalFees) * _buyerPercent / 100);
            _seller.transfer((_value - _totalFees) * (100 - _buyerPercent) / 100);
        }
    
        function release(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool){
          require(msg.sender == _seller);
          return doRelease(_tradeID, _seller, _buyer, _value, _fee, 0);
        }
        function disableSellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) {
          require(msg.sender == _buyer);
          return doDisableSellerCancel(_tradeID, _seller, _buyer, _value, _fee, 0);
        }
        function buyerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) {
          require(msg.sender == _buyer);
          return doBuyerCancel(_tradeID, _seller, _buyer, _value, _fee, 0);
        }
        function sellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) {
          require(msg.sender == _seller);
          return doSellerCancel(_tradeID, _seller, _buyer, _value, _fee, 0);
        }
        function sellerRequestCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) {
          require(msg.sender == _seller);
          return doSellerRequestCancel(_tradeID, _seller, _buyer, _value, _fee, 0);
        }
    
        function relaySellerCannotCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) {
          return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_CANNOT_CANCEL, 0);
        }
        function relayBuyerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) {
          return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_BUYER_CANCEL, 0);
        }
        function relayRelease(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) {
          return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_RELEASE, 0);
        }
        function relaySellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) {
          return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_CANCEL, 0);
        }
        function relaySellerRequestCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) {
          return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_REQUEST_CANCEL, 0);
        }
    
        function relay(
          bytes16 _tradeID,
          address _seller,
          address _buyer,
          uint256 _value,
          uint16 _fee,
          uint128 _maximumGasPrice,
          uint8 _v,
          bytes32 _r,
          bytes32 _s,
          uint8 _actionByte,
          uint128 _additionalGas
        ) private returns (bool) {
          address _relayedSender = getRelayedSender(_tradeID, _actionByte, _maximumGasPrice, _v, _r, _s);
          if (_relayedSender == _buyer) {
            if (_actionByte == ACTION_SELLER_CANNOT_CANCEL) {
              return doDisableSellerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas);
            } else if (_actionByte == ACTION_BUYER_CANCEL) {
              return doBuyerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas);
            }
          } else if (_relayedSender == _seller) {
            if (_actionByte == ACTION_RELEASE) {
              return doRelease(_tradeID, _seller, _buyer, _value, _fee, _additionalGas);
            } else if (_actionByte == ACTION_SELLER_CANCEL) {
              return doSellerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas);
            } else if (_actionByte == ACTION_SELLER_REQUEST_CANCEL){
              return doSellerRequestCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas);
            }
          } else {
            return false;
          }
        }
    
        uint16 constant GAS_batchRelayBaseCost = 28500;
        function batchRelay(
          /**
           * Call multiple relay methods at once to save on gas.
           */
            bytes16[] _tradeID,
            address[] _seller,
            address[] _buyer,
            uint256[] _value,
            uint16[] _fee,
            uint128[] _maximumGasPrice,
            uint8[] _v,
            bytes32[] _r,
            bytes32[] _s,
            uint8[] _actionByte
        ) public returns (bool[]) {
            bool[] memory _results = new bool[](_tradeID.length);
            uint128 _additionalGas = uint128(msg.sender == relayer ? GAS_batchRelayBaseCost / _tradeID.length : 0);
            for (uint8 i=0; i<_tradeID.length; i++) {
                _results[i] = relay(_tradeID[i], _seller[i], _buyer[i], _value[i], _fee[i], _maximumGasPrice[i], _v[i], _r[i], _s[i], _actionByte[i], _additionalGas);
            }
            return _results;
        }
    
        function increaseGasSpent(bytes32 _tradeHash, uint128 _gas) private {
            /** Increase `totalGasFeesSpentByRelayer` to be charged later on completion of the trade.
             */
            escrows[_tradeHash].totalGasFeesSpentByRelayer += _gas * uint128(tx.gasprice);
        }
    
        function transferMinusFees(address _to, uint256 _value, uint128 _totalGasFeesSpentByRelayer, uint16 _fee) private {
            uint256 _totalFees = (_value * _fee / 10000) + _totalGasFeesSpentByRelayer;
            if(_value - _totalFees > _value) return; // Prevent underflow
            feesAvailableForWithdraw += _totalFees; // Add the the pot for localethereum to withdraw
            _to.transfer(_value - _totalFees);
        }
    
        function withdrawFees(address _to, uint256 _amount) onlyOwner external {
          /**
           * Withdraw fees collected by the contract. Only the owner can call this.
           */
            require(_amount <= feesAvailableForWithdraw); // Also prevents underflow
            feesAvailableForWithdraw -= _amount;
            _to.transfer(_amount);
        }
    
        function setArbitrator(address _newArbitrator) onlyOwner external {
            /**
             * Set the arbitrator to a new address. Only the owner can call this.
             * @param address _newArbitrator
             */
            arbitrator = _newArbitrator;
        }
    
        function setOwner(address _newOwner) onlyOwner external {
            /**
             * Change the owner to a new address. Only the owner can call this.
             * @param address _newOwner
             */
            owner = _newOwner;
        }
    
        function setRelayer(address _newRelayer) onlyOwner external {
            /**
             * Change the relayer to a new address. Only the owner can call this.
             * @param address _newRelayer
             */
            relayer = _newRelayer;
        }
    
        function setRequestCancellationMinimumTime(uint32 _newRequestCancellationMinimumTime) onlyOwner external {
            /**
             * Change the requestCancellationMinimumTime. Only the owner can call this.
             * @param uint32 _newRequestCancellationMinimumTime
             */
            requestCancellationMinimumTime = _newRequestCancellationMinimumTime;
        }
    
        function transferToken(Token _tokenContract, address _transferTo, uint256 _value) onlyOwner external {
            /**
             * If ERC20 tokens are sent to this contract, they will be trapped forever.
             * This function is way for us to withdraw them so we can get them back to their rightful owner
             */
             _tokenContract.transfer(_transferTo, _value);
        }
        function transferTokenFrom(Token _tokenContract, address _transferTo, address _transferFrom, uint256 _value) onlyOwner external {
            /**
             * If ERC20 tokens are sent to this contract, they will be trapped forever.
             * This function is way for us to withdraw them so we can get them back to their rightful owner
             */
             _tokenContract.transferFrom(_transferTo, _transferFrom, _value);
        }
        function approveToken(Token _tokenContract, address _spender, uint256 _value) onlyOwner external {
            /**
             * If ERC20 tokens are sent to this contract, they will be trapped forever.
             * This function is way for us to withdraw them so we can get them back to their rightful owner
             */
             _tokenContract.approve(_spender, _value);
        }
    }