The two-party double deposit, or trustless escrow, is a way to make purchases, where the buyer can be sure that the seller will deliver. It solves a problem of trust that has been traditionally solved through consumer rights/laws using a monopoly on violence.

Trust does not scale beyond 150–300 people, a memory limit of the human brain known as dunbar’s number. To scale beyond dunbar’s number, we invented legal systems, such as religions, kings and queens, and later on nation-states, to enable markets that scale beyond our tribal trust limit.

In the nation-state paradigm, threat of violence for the merchant provided a guarantee to the buyer that the merchant will deliver. The crypto paradigm plans to leave threat of violence to the past, as it is unethical, and also costly, and plans to use new sorts of incentives to enable new types of markets.

The two-party double deposit enables trustless purchases, through deposits. These deposits are only returned if both the buyer and the seller agree that the deal is settled.

The merchant puts in 2x the price as a deposit, so there is something at stake. The seller then puts in 2x the price as well, so they have something to loose from leaving the merchants deposits locked in the purchase.

Trustless purchases using an immutable smart-contract

The buyer requests to make a purchase. The merchant then deploys a TrustlessPurchase, in the form of a smart-contract that is verifiable on the blockchain, and puts in 2x the price as a deposit, which initiates the purchase.

function activateContract()

onlyEscrowAccount

require(msg.value == 2 * price)

{

state = State.Initiated;

}

The purchase is now in state = State.Initiated, and awaits the deposit from the buyer. If the seller has not made a deposit within 10 minutes, the purchase contract will close.

function noPaymentRecieved()

inState(State.Initiated)

{

if(block.timestamp < paymentDeadline) throw;

EscrowAccount.send(this.balance);

state = State.Inactive;

AutomatedTrustlessEscrow(mainContract).setPurchaseAborted();

Aborted();

}

The buyer confirms the purchase through calling the confirmPurchase() method, and sends 2 * price with the message call. The purchase is then set to state = State.Locked, and the deposits are locked in the contract.

function confirmPurchase()

inState(State.Initiated)

onlyBuyer

require(msg.value == 2 * price)

{

buyer = msg.sender;

state = State.Locked;

PurchaseConfirmed();

}

If all goes well and the buyer receives their product, then the buyer can call confirmReceived(), which returns their deposit of 1 * price, and sends their payment to the merchant.

function confirmReceived()

onlyBuyer

inState(State.Locked)

{

buyer.send(price);

EscrowAccount.send(this.balance);

state = State.Inactive;

AutomatedTrustlessEscrow(mainContract).setPurchaseFinished();

ItemReceived();

}

If the buyer does not receive their product, or if they have complaints, then they can call fileDispute(), which opens a disputeResolution contract managed by the arbitrator that was selected when they deployed the TrustlessPurchase contract.

function fileDispute()

onlyBuyer

inState(State.Locked)

returns (bool success)

{

disputeResolutionContract = new disputeResolution(buyer, seller, arbitrator);

disputeResolution(disputeResolutionContract).setBalanceBuyer.value(2 * price)();

disputeResolution(disputeResolutionContract).setBalanceSeller.value(this.balance)();

AutomatedTrustlessPurchases(mainContract).setDisputeRaised();

state = State.Dispute;

}

The arbitrator can be the seller, who would have little incentive to not want to settle the dispute, or it could be any other entity or contract.

The arbitrator can judge that the buyer is eligible to a full refund, or a partial refund, and can even judge that the seller should pay a fine which is taken from the seller’s deposit.

function judgeDispute()

onlyArbitrator

hasAgreed

{

seller.send(balanceOf[seller]);

buyer.send(balanceOf[buyer]);

TrustlessPurchase(purchaseContract).disputeResolved();

}

The dispute is then managed by the disputeResolution contract, the deposits are returned minus what was deducted in either a partial return to the buyer, or a fine to the seller, and disputeResolved() is called that updates the status of the purchase to hasFinished = true.

function disputeResolved()

onlyDisputeResolutionContract

inState(State.Dispute)

{

state = State.Inactive;

AutomatedTrustlessEscrow(mainContract).setPurchaseFinished();

Refunded();

}

Using the ethereum-wallet as a front end interface

A merchant can use ethereum-wallet to manage their AutomatedTrustlessEscrow. Purchases are indexed and its easy to keep track of costumers and if a purchase is finished, or a dispute has been raised, or if the costumer never made their deposit and the purchase was abandoned.

The costumer can interact via a shapeshift.io-like UI, where they can look up the status of their purchase, and easily verify the contract source code on the blockchain.

Merchant options and customizability

The merchant has the option to change what address controls the EscrowFund.

function changeWhoCanWithdrawFunds(address newCanWithdraw) {

if(msg.sender != canWithdrawFunds) throw;

canWithdrawFunds = newCanWithdraw;

}

And can change the standard arbitrator.

function changeArbitrator(address newArbitrator) {

if(msg.sender != arbitrator) throw;

arbitrator = newArbitrator;

}

The merchant also has a whitelist for custom arbitrators that they are willing to work with. The costumer can choose a custom arbitrator from this list when a newPurchase() is initiated, or leave purchaseArbitrator blank and work with the standard arbitrator, which would most often be the merchant itself.

function newPurchase(uint price, address buyer, address purchaseArbitrator)

public returns (address)

{ ... }

The merchant can easily keep track of their whitelist through ethereum-wallet.

If an arbitrator wants to specify custom rules aforehand, then they would have to do so in the arbitrator’s contract and then call the newPurchase() function from the custom arbitrator contract.

Dispute resolution using a chat application

If the buyer files a dispute, a disputeResolution contract is deployed, controlled by the buyer, the seller, and the arbitrator, and the funds of both the buyer and the seller are sent to this new contract.

function fileDispute()

onlyBuyer

inState(State.Locked)

returns (bool success)

{

disputeResolutionContract = new disputeResolution(buyer, seller, arbitrator);

disputeResolution(disputeResolutionContract).setBalanceBuyer.value(2 * price)();

disputeResolution(disputeResolutionContract).setBalanceSeller.value(this.balance)();

AutomatedTrustlessPurchases(mainContract).setDisputeRaised();

state = State.Dispute;

}

The dispute is resolved through the three parties communicating and deciding on how to solve the dispute. The arbitrator can use the disputeResolution contract to set a verdict for either that the buyer should get a whole refund, or a partial refund, or that the seller should pay a fine to the buyer. The deduction to either party can not exceed 1 * price. The arbitrator can also judge that both parties should get their double-deposit back in full.

function setVerdictBuyer(uint amount) onlyArbitrator {

if(verdictBuyerSet == false && amount <= this.balance / 4) {

verdictBuyer.deductedFromReturn = amount;

verdictBuyerSet = true;

}

} function setVerdictSeller(uint amount) onlyArbitrator {

if(verdictSellerSet == false && amount <= this.balance / 4) {

verdictSeller.forcedPenalty = amount;

verdictSellerSet == true;

}

}

To resolve the dispute using the judgeDispute() method, both parties need to have agreed to the verdicts.

function agreeToVerdict(uint amount) onlyRepresented { … }

judgeDispute() then sends the new balances, adjusted by the verdicts that have been agreed upon, to the buyer and the seller.

function judgeDispute()

onlyArbitrator

hasAgreed

{

seller.send(balanceOf[seller]);

buyer.send(balanceOf[buyer]);

TrustlessPurchase(purchaseContract).disputeResolved();

}

How are the funds managed ?

To make it easy for the merchant to know how the smart-contract manages funds, there are separate contracts for this.

New purchases fetch the merchants deposit from EscrowFund, a contract that was deployed when the merchant first deployed their AutomatedTrustlessPurchases system.

The merchant needs to continuously top up their EscrowFund with ether so that it can send deposits to new purchases.

The merchant can withdraw their funds or part of their funds at any time.

During a purchase, the deposits are held and managed by a TrustlessPurchase contract, that was deployed automatically when a purchase was requested. Both the buyer, the seller and the arbitrator can verify this contract on the blockchain.

In the event of a dispute, both the buyers and the sellers deposits are moved from their TrustlessPurchase contract and into a disputeResolution contract that was created by their purchase contract. The money will then stay in the disputeResolution until the buyer, the seller and the arbitrator reach an agreement. The money cannot leave the disputeResolution contract unless all three parties agree.

New purchases are deployed from the AutomatedTruslessPurchases contract which serves as the main contract. The merchant is automatically set as the owner of the main contract when they deploy their Automated Trustless Purchases system.

The owner/merchant can manage their custom arbitrator whitelist, look up the state of active or past purchases, and pass on the ownership of the system to a new owner/merchant.

You can find the AutomatedTrustlessPurchases system on GitHub. View the full contract below.