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:
rate
anddivisor
of typeLong
: multiplying byrate / divisor
converts from nanoERGs to tokens when buying, or vice-versa when selling.recipient
of typeGroupElement
: the public key of the recipient.
Buy orders only:
token_id
of typeColl[Byte]
: the token being bought.fee
of typeLong
: the trading fee in nanoERGs.
Swapping two tokens directly:
other_token_id
of typeColl[Byte]
: the other token.
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:
- As long as the conditions are met, anyone can spend a box protected by either of the above contracts. This allows maximum composability with any Ergo-based trading protocols, e.g. AMMs and other types of DEXes.
- A collection of boxes protected by these contracts form a primitive orderbook.
- Partial filling of these orders is not supported; these orders are intended to be filled immediately assuming the user has picked an appropriate price. In practice, the user will choose a price based on the last spot price plus some slippage value.
- An order must be filled within 30 blocks of its creation (~1 hour), otherwise the funds will be returned to the user.
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.