Escrows, what are they and how do they work in depth

in #core2 days ago

image.png

Hey, Recently @TheRealWolf released a tool to manage escrows: https://peakd.com/@therealwolf/beescrow-escrow-powered-by-hive he mentioned how hard it was to find any docs on escrows, and a few more people chimed in asking for it too.
Recently I looked deeply into the escrow code when I implemented the rosetta api for hive (prerequisite for a coinbase listing). So I figured it would be a good time to write a good doc about it for future users.

What are escrows

Escrows are a way to send HIVE or HBD to another person while a third party is here as an arbiter to the transaction with minimal trust.

Let's say I want to purchase an item from someone for 100 HBD, but I don't trust that the person will send the item to me and the sender doesn't trust that I'll send the money once I receive the item.
We can agree on a common third person that we both trust, we'll call him the agent and create an escrow using escrow_transfer_operation:

When I execute the transaction, the 100 HBD leaves my account and is put into a safe that neither me nor the seller can access.

In order to release the funds to the seller, the seller AND the agent must execute a transaction to approve the escrow (two separate transactions). If that's the case the money can be released via escrow_release_operation

But then let's say I didn't receive the item I wanted or it's damaged. Me or the seller can submit a escrow_dispute_operation.
Once the escrow is disputed, the money is entirely in control of the agent who gets to mediate and chose how much money to send to the seller or the buyer.

Note that at no point the agent can run away with the money, making it safer than a traditional escrow service where you would send the money to the agent.

Now that you got a broad overview of how escrows work, let's dig into the details as there's a bunch of edge cases and more details to cover.

Creating an escrow: escrow_transfer_operation

Now let's dig into the details, these are the parameters, I've put obvious details on the comments

    account_name_type from; // User creating the transaction and sending the money 
    account_name_type to; // User receiving the money
    account_name_type agent; // Account who will manage the escrow 
    uint32_t          escrow_id = 30; // Id must be unique

    asset             hbd_amount = asset( 0, HBD_SYMBOL );
    asset             hive_amount = asset( 0, HIVE_SYMBOL );
    asset             fee; // Fee will be paid to the agent upon escrow resolution

    time_point_sec    ratification_deadline;
    time_point_sec    escrow_expiration;

    json_string       json_meta; // Json metadata, you may input whatever data you want there 

ratification_deadline sets the deadline within which the escrow must be approved (see later on how that works). If the escrow isn't approved by both the agent and to all the funds (including the fee) go back to the the from account, effectively undoing the effects of the escrow_transfer_operation. This is done automatically.

escrow_expiration is a timer on the agent, If there is no dispute and escrow has not expired, either party can release funds to the other, allowing an escrow to be resolved without the agent. So from can release funds to to and to can release funds to from. But nobody can release funds to themselves. This is not automatic and needs to be explicit through the escrow_release_operation

Some extra details:

  • Escrow ID is unique per from account
  • You can create an escrow that pays out HBD AND HIVE at the same time
  • The fee has to be in HBD or HIVE, can't be a mix of the two
  • The fee will be deducted from the from account along with the hbd_amount and hive_amount when the transaction is executed not when the escrow is finished
  • An account can have a maximum of 254 escrow transfers open at once

Approving or Cancelling the escrow: escrow_approve_operation

let's look at the params

    account_name_type from;
    account_name_type to;
    account_name_type agent;
    account_name_type who; // Either to or agent

    uint32_t          escrow_id = 30;
    bool              approve = true;

In order for an escrow to be releasable, both to and agent must submit an escrow_approve_operation with approve set to true. Once approved, the agent receives their fee, and the escrow funds become available for release

If either the agent or to sets approve to false the escrow is cancelled, all the funds (including the fee) goes back to from effectively undoing the effects of the escrow_transfer_operation. An escrow_rejected_operation virtual operation is generated.

Once both from and to approve, then the escrow is deemed valid, the agent gets its fee. Then an escrow_approved_operation virtual operation is generated.

Once any party approves the escrow, they cannot revoke their approval. Subsequent escrow approve operations, regardless of what approval is set to (true/false), will be rejected.

Disputing the escrow: escrow_dispute_operation

If any party (from or to) has an issue with the escrow, they may raise a dispute and let the agent resolve the issue.

The params are pretty straightforward:

    account_name_type from;
    account_name_type to;
    account_name_type agent;
    account_name_type who; // Defines which account is raisining the dispute, either "to" or "from"

    uint32_t          escrow_id = 30;

Some extra details:

  • Disputing must happen before the escrow expiration time
  • You can only dispute an escrow that has been approved by both to and the agent
  • Once an escrow is disputed, it can't be un-disputed and only the agent has the power to move funds at that point through escrow_release_operation

Releasing the funds: escrow_release_operation

This is the operation that is used to release the funds in the escrow. The escrow must be approved by both to and agent before any release can happen

Depending on the state of the escrow, different people will have permission to release funds:

  • If there is no dispute and the escrow has not expired, either from or to can release funds to the other.
  • If the escrow expires and there is no dispute, either from or to can release funds to either party. Meaning a party can send the money to himself !
  • If there is a dispute regardless of expiration, only the agent can release funds to either from or to

Let's look at the params:

    account_name_type from;
    account_name_type to; //  the original 'to'
    account_name_type agent;
    account_name_type who; ///< the account that is attempting to release the funds, determines valid 'receiver'
    account_name_type receiver; ///< the account that should receive funds (might be from, might be to)

    uint32_t          escrow_id = 30;
    asset             hbd_amount = asset( 0, HBD_SYMBOL ); ///< the amount of HBD to release
    asset             hive_amount = asset( 0, HIVE_SYMBOL ); ///< the amount of HIVE to release

Note that you can execute the transaction multiple times in case you want to release funds in installments or split the funds between to or from.
The escrow is only deleted once there is no more funds.

Sort:  

I have an issue with how it works. Let's see typical situation: alice is buying something from bob on amazon. The client interface takes care of sending escrow_transfer_operation with amazon as agent when the purchase is made, proper fee is deduced from payment's total, server automatically approves the escrow from agent side. In most cases some bot will also approve it in the name of bob, only in selected cases bob might need to manually approve it (f.e. alice wanted her initials embroidered on the skirt she is buying, which requires some back and forth messaging to iron out the details first).

So far so good. bob sends the purchased item to alice, registers package tracking info with Amazon so they can see if the package arrived. But once the item is in hands of alice, it is pretty much all from her standpoint. She might have a problem, f.e. it was supposed to be a salmon skirt, but she got coral one, in which case she might contact bob and get a discount (bob releases some funds back to alice), but in most cases, when everything is ok, she has no incentive to release funds to bob. It is unnatural even to expect her to do anything. So bob is left with one choice only: to open dispute and wait for amazon to give him the funds. And he has to fit within escrow_expiration, because if he does not, alice could snatch back the payment before he does. But that's not the intent of opening dispute. The transfer should automatically complete by sending remaining funds to bob when there was no dispute prior to expiration.

she has no incentive to release funds to bob Then in case case, bob having an issue with the transaction (alice refuses to pay) can issue a escrow_dispute_operation leaving the matter to amazon to solve (eg: pay bob).

And he has to fit within escrow_expiration, because if he does not, alice could snatch back the payment before he does No if there's a dispute, expiration no longer counts and the agent has infinite time to figure it out. If both parties can't come to an agreement, the funds will stay locked forever.

But that's the whole point - alice did not refuse to pay. **From her perspective she already paid. ** Escrow transfer should default to "all is ok" when it is not disputed. Currently natural behavior when client is just shopping forces shop to open dispute in each case, adds unnecessary work to agent, which increases fees, and can also desensitize agent leading to wrong handling of actual disputed cases.

oh incentive in thaaat sense, sorry I ment I thought incentive like morals as in "nothing makes me want to pay".

Yeah I agree with you and @gtg it's not great :/

I agree with @miosha that the current flow is ridiculous for retail-style purchases.

After approval, the buyer has no incentive to send a release tx. The seller can’t self-release before escrow_expiration, so in practice they either wait for expiry (and then self-release) or open a dispute just to get paid, which burdens the agent even when nothing is wrong.

For ordinary purchases, the expected default should be: if nobody disputes within the inspection window, the funds settle to the seller automatically. An opt-in "auto-release to seller at escrow_expiration if undisputed" would match user expectations while preserving the current dispute protections.

👏🏼👏🏼👏🏼