Transaction Hash:
Block:
4669188 at Dec-03-2017 04:11:45 PM +UTC
Transaction Fee:
0.00016662 ETH
$0.34
Gas Used:
69,425 Gas / 2.4 Gwei
Emitted Events:
| 58 |
LocalEthereumEscrows.Created( _tradeHash=8B584BAE556AE7EB6929F7352C9290C170A1BFDCADA5308FE137E4BF99FEFE21 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x09678741...07099Ae5d | (LocalEthereum 2) | 71.05571523533640895 Eth | 71.42277523533640895 Eth | 0.36706 | |
|
0x829BD824...93333A830
Miner
| (F2Pool Old) | 6,750.336488116180279435 Eth | 6,750.336654736180279435 Eth | 0.00016662 | |
| 0xfAC2f2c4...e65ac049f |
0.417721410020359593 Eth
Nonce: 46
|
0.050494790020359593 Eth
Nonce: 47
| 0.36722662 |
Execution Trace
ETH 0.36706
LocalEthereumEscrows.createEscrow( _tradeID=System.Byte[], _seller=0x76c903D025b047f7d5A6DB78Caaf24833342f124, _buyer=0xF20aB9dfdA97b5e3142Fa27E38a2dc6D10B85EeF, _value=367060000000000000, _fee=100, _paymentWindowInSeconds=0, _expiry=1512321089, _v=27, _r=928DDDC9330FED091DB1DD6059ABE779018498C5B1506E35F181370B890DFC4B, _s=51B67E23562147CF088CD79D13A087F9A034402A5AECF198F52C505B0A44CA4F )
-
Null: 0x000...001.474d64df( )
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);
}
}