The gas cost for creating an escrow is paid by the seller, and the seller can choose a gas price they are comfortable with. The createEscrow
function uses approximately 69,000 gas — which costs less than US10¢ at today’s prices.
If you’re a seller, it’s important to make sure you and the buyer are on the same page before you fund the escrow, as it costs a few cents to fund an escrow even if you end up cancelling it. To avoid needlessly locking up your ether, sellers should wait until the buyer responds with agreement of the terms.
Once the initial createEscrow
transaction has been confirmed by the network, the escrow exists on the blockchain and can be easily verified by anyone.
When LocalEthereum notices that the escrow has been created and funded, which doesn’t take longer than a few seconds, the trade looks like this for a seller:
LocalEthereum might be many people’s first interaction with Ethereum and cryptocurrencies in general, and that makes things complicated. It costs ether to interact with the blockchain in terms of gas, and somebody who doesn’t have any ether is unable to interact with smart contracts directly.
With this in mind, we’ve designed a system in which traders can interact with our smart contract for “free” using a proxy. The cost of gas is paid up-front by us to relay digital signatures which authorise instructions on a user’s behalf, with the expectation that we’ll be reimbursed at the end of the escrow.
At first glance this system may sound exploitable. What’s to stop users from racking up debt with no intention to pay? There are a few reasons why fronting gas costs is not very gameable:
During the course of an escrow, there are a few actions each party can take. Ordinarily, these actions are performed by relay, but the parties have the choice to make calls externally if they prefer. If LocalEthereum were to ever go offline, traders are always able to interact with their escrows directly on the Ethereum network.
Each “action” has a single-byte identifier, which are defined as constants near the beginning of the smart contract’s source code.
To invoke an action via relay, the caller simply needs to sign a Keccak hash of the concatenation of the trade ID, the action byte and a maximum gas price that the relayer is allowed to spend for the action. The gas price cap prevents an attack vector whereby LocalEthereum could hypothetically overpay for gas and cause the parties to burn more than a reasonable amount on network fees.
Releasing funds (action 0x05
). The seller can release the funds to the buyer at any time during the escrow. This will end the escrow, and its balance, minus network fees and LocalEthereum’s fee, will be sent to the buyer’s address.
Cancelling as a buyer (action 0x02
). The buyer can cancel the escrow at any time, returning the funds to the seller. Our fee won’t be deducted from the balance, but any unpaid network fees covered by us will be taken from the total.
Preventing the seller from cancelling (action 0x01
). The buyer can halt the payment window countdown and prevent the seller from being able to cancel the escrow. This is done to indicate that the buyer has made payment, and he expects the ether to be released soon.
Once the escrow has entered this stage where the buyer has essentially locked the ether in escrow, there are three ways the escrow can end:
When a buyer asks us to relay this action, we don’t relay it immediately, but we do let the seller know that we have it. In fact in most successful escrows, this action is never broadcasted to the network. This is because it’s only important to relay this before the payment window has expired, and there’s no benefit to doing it earlier. Currently, our servers hold on to the relay request and only send it roughly twenty minutes before the payment window is due to expire (which is more than enough time for the transaction to safely propagate and confirm). In most successful escrows, the seller has already released the escrow by this time, and so this action becomes redundant.
By not relaying unnecessary actions immediately, this allows the traders to save on network fees. (Most successful trades invoke only createEscrow
and the seller’s release action.)
Cancelling as a seller (action 0x03
). The seller cannot as easily cancel as the buyer, and can only do so if the buyer allows it. The seller first needs to wait until the payment window has expired before they can withdraw their funds from escrow.
Payment windows vary by option and are subject to change — these are the current windows as of October 26, 2017:
Method | Payment window |
---|---|
Bank transfer | 2 hours |
Cash deposit | 2 hours |
PayPal | 2 hours |
International wire | 2 hours |
AliPay | 2 hours |
WeChat Pay | 2 hours |
“Other” | 2 hours |
Cash (in person) | The seller must request to cancel (action 0x04 ), and once they’ve done so the buyer has two hours to reject the request. |
Allocating storage in an Ethereum smart contract can be really expensive if you’re not careful. Of all of the operations available in the EVM, writing to fresh storage is by far the most costly operation.
Our smart contract is designed to keep a minimal storage footprint. Each escrow in the contract requires an allocation of only two 256-bit “words”, or 64 bytes in total. This is accomplished by tightly packing static properties of the trade into a “trade hash”, and then using that hash as the unique key to the escrow in the public mapping. By packing these into the hash, we avoid the need for allocating extra space for each static property.
Escrows are stored in a public variable named escrows
which is a mapping of the custom Escrow
struct:
mapping (bytes32 => Escrow) public escrows;
The 32-byte keys to the mapping are Keccak hash functions of the tightly-packed concatenation of:
Once we have all of the static properties of the trade, we can generate the trade hash with a simple Keccak-256 function in Solidity:
This is the key that anybody can use to verify the status of an escrow in progress on the blockchain. For an easy web inspector, we recommend using Etherscan’s “Read Contract” feature:
Information about each escrow is stored in an Escrow
struct. This struct fits an allocation of one word; although only 161 bits of storage are needed, the EVM allocates storage only in 256-bit blocks.
struct Escrow {
bool exists;
uint32 sellerCanCancelAfter;
uint128 totalGasFeesSpentByRelayer;
}
The struct contains these variables:
0
indicates that the seller is not allowed to cancel at any time. The value can be set to 0
at any time at the buyer’s request to lock the funds in the escrow until the seller releases the funds or the arbitrator steps in to resolve the trade. This is typically done when buyer indicates he has made payment, and expects the funds to be released.1
, which is a special value to mean infinity. When sellerCanCancelAfter
equals 1
, the seller can’t cancel, but can initiate a request to cancel. When the seller indicates that he’d like to cancel the trade, the buyer is given adequate time to dispute the request before the seller-cancellation is permitted (currently set to two hours).In the case of a dispute, either party needs to give us their signed dispute token. With a signature of this dispute token, which is simply the output of sha3(Trade ID, 0x06)
, the arbitrator can resolve the dispute in either party’s favour.
To do this, we call the external resolveDispute
function with the trade’s static properties, the signed dispute token, and the percentage of the escrow’s balance that will be sent to each party. It is impossible for us to have the disputed ether sent to anybody else.
We also expect to receive the LocalEthereum trade conversation’s “shared secret” at the same time, which gives us the ability to decipher and read encrypted message history between the buyer and seller. However, that is for another post…
Update (a few weeks later): LocalEthereum is now doing much more than that in volume and trades each day.
Questions? Contact us here.