Generic On-Chain Ergo Swaps

Happy New Year!

Ergo makes it very easy to create on-chain swap orders, which can be atomically settled on-chain without any third party taking custody.

There are many types of orders in theory, all of which could be supported with various options, but let’s start with the simplest and most common: a straightforward “swap” order, where a user wishes to swap ERG and receive some tokens in return.

Here is a proposed template for swap orders:

Parameters:

Buy orders only:

Swapping two tokens directly:

In the case of a sell order, it is assumed the nanoERGs are the fee, and tokens(0) is being sold.

In the case of swapping two tokens directly, it is assumed the nanoERGs sent to the contract are the fee.

The following contract denotes buying a token:

{
  val user_pk = proveDlog(recipient);
  val deadline = SELF.creationInfo._1 + 30;

  val erg_amount = SELF.value - fee;
  val token_amount = erg_amount * rate / divisor;

  val valid_height = HEIGHT < deadline;

  sigmaProp(OUTPUTS.exists({ (box: Box) =>
    allOf(Coll(
      if (valid_height) {
        val t = box.tokens(0);
        t._1 == token_id &&
        t._2 >= token_amount
      } else {
        // refund
        box.value >= erg_amount
      },
      box.R4[Coll[Byte]].get == SELF.id,
      box.propositionBytes == user_pk.propBytes
    ))
  }))
}

The following contract denotes selling a token:

{
  val user_pk = proveDlog(recipient);
  val deadline = SELF.creationInfo._1 + 30;

  val self_tokens = SELF.tokens;
  val token_amount = self_tokens(0)._2;
  val erg_amount = token_amount * rate / divisor;

  val valid_height = HEIGHT < deadline;

  sigmaProp(OUTPUTS.exists({ (box: Box) =>
    allOf(Coll(
      if (valid_height) {
        box.value >= erg_amount
      } else {
        // refund
        box.tokens == self_tokens
      },
      box.R4[Coll[Byte]].get == SELF.id,
      box.propositionBytes == user_pk.propBytes
    ))
  }))
}

The following contract denotes swapping two tokens:

{
  val user_pk = proveDlog(recipient);
  val deadline = SELF.creationInfo._1 + 30;

  val self_tokens = SELF.tokens;
  val token_amount = self_tokens(0)._2;
  val other_token_amount = token_amount * rate / divisor;

  val valid_height = HEIGHT < deadline;

  sigmaProp(OUTPUTS.exists({ (box: Box) =>
    allOf(Coll(
      if (valid_height) {
        val t = box.tokens(0);
        t._1 == other_token_id &&
        t._2 >= other_token_amount
      } else {
        // refund
        box.tokens == self_tokens
      },
      box.R4[Coll[Byte]].get == SELF.id,
      box.propositionBytes == user_pk.propBytes
    ))
  }))
}

Notes:

More complex orders are certainly possible, such as those that allow partial filling, but the above should provide a foundation for these types of on-chain swaps.