Skip to content

Commit

Permalink
Merge pull request #44 from geniusyield/Fix-typos-in-README
Browse files Browse the repository at this point in the history
Update README.md
  • Loading branch information
4TT1L4 authored Nov 6, 2023
2 parents 7d0429e + 3562850 commit d9dbc9a
Showing 1 changed file with 86 additions and 54 deletions.
140 changes: 86 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@

## Overview

SORs are off-chain bots that execute a routing algorithm that scans the blockchain
for open limit orders, matches them based on their trigger conditions, and submits
new transactions back to the ledger to perform the swap state transitions. Each Smart
Swap encodes trigger conditions that the SOR must fulfill to execute the swap. The SOR
continuously scans and analyzes the current state of the limit orders and relies on
Smart Order Routers play a crutial role in the operation of the [Genius Yield Decentralized Exchange](https://www.geniusyield.co/).

Smart Order Routers (SORs) are off-chain agents that execute a routing algorithm that scans
the Cardano blockchain for open limit orders, matches them based on their trigger conditions,
and submits new transactions back to the ledger to perform the swap state transitions.
Each Smart Swap encodes trigger conditions that the SOR must fulfill to execute the swap.
The SOR continuously scans and analyzes the current state of the limit orders on-chain and relies on
the configured matching strategy to best execute a customer’s order based on price.

Specifically, the bot periodically builds a multi-asset order book consisting of one
Expand All @@ -46,36 +48,45 @@ only sell and buy orders for the same pair of tokens. The bot runs the selected
over the multi-asset order book to obtain a list of matches. The matches are then
translated into transactions that will be signed and submitted by the bot.

## Crash Course GeniusYield DEX Orders <> SOR
Due to the open and decentralized design of the protocol, anybody can run a Smart Order
Router instance and collect a share of the fees, thus running a Smart Order Router instance
is not only contributiung to the further decentralization of the protocol, but it is also
incentivized financially.

## Crash Course: GeniusYield DEX Orders and the Smart Order Routers

Let's start with a concrete and short overview of the GY DEX Orders, to provide some context,
so the reader could better understand the purpose of the Smart Order Routers and how they could
benefit from customizing existing order matching strategies or even implementing completely new strategies from scratch.

Let's start with a concrete and short overview of the GY DEX Orders, so the context
of the SOR for using, modifying, and improving with new custom strategies is properly
established. A complete description can be found in the [GY whitepaper](https://www.geniusyield.co/whitepaper.pdf?lng=en).
For a more detailed description, please see the [Genius Yield Whitepaper](https://www.geniusyield.co/whitepaper.pdf?lng=en).

Given a pair of tokens, an order will contain the number of tokens it offers, the price
of one unit of those offered, and the minimal amount we are forced to buy from the order.
Besides that, the order will have some life timeline and, of course, a notion of ownership
of one unit of those offered, and the minimal amount that must be bought from the order.
Besides that, the order will have optionally a lifetime and, of course, a notion of ownership
related to the one that created it. For example, we could create an order offering of `10 tokenA`,
with a unit price of `2 tokenB`, that is, we expect to receive `2 tokenB` per `1 tokenA`. Also, we
want the minimal amount to be bought be `5 tokenA`. Clearly, the owners of this order will be
us and it's important to mention that all this information is **mandatory**, but we can avoid setting
the life timeline, meaning the order will always be available. Once we create an order, the offered
tokens will be locked on the order.
the creator, who placed the order, and it's important to keep in mind that all this information
is **mandatory**, excluding the lifetime of the order, which is optional, meaning, when it is missing, the order won't
expire and it is going to be be available for an unlimited time. Once we create an order, the offered
tokens will be locked in the order, until it is filled or cancelled.

Given an order, two interesting "actions" can be performed over it. The owner can cancel
it and get back the locked tokens. Or anyone can _fill_ it, filling an order is just paying
Given an specific order, two interesting "actions" might be performed on it. The owner can cancel
it and get back the previously locked tokens. Or anyone can _fill_ it, filling an order is just paying
the correct amount of tokens the owner of the order expects to receive related to the
amount of tokens we want to buy from that order. Following the previous example, anyone
could fill that order by buying from it `6 tokenA` and paying the owner `12 tokenB`. But, it
isn't possible to buy, for instance, `3 tokenA` from the order because the minimal amount
was setup to `5`, except the amount of offered tokens is less than that.
was setup to `5`, except the amount of offered tokens is less than that. This makes sure that no tokens get
stuck in the contract, even if the number is lower then the minimal fill amount.

One important thing to mention that is transparent for any end user, is that there are
two kinds of fills: _complete_ and _partial_. A complete fill will buy all the offered
tokens from the order, and for the partial fill, we need to specify the amount we want
to buy from the order. For us, that will be running and probably improving this implementation
is relevant because, as we will see in a moment, we can design different matching strategies
using these two different fills.
One important thing to mention, that is actually completely transparent for the end user,
is that there are two kinds of fills: _complete_ and _partial_. A complete fill will buy all
the offered tokens from the order, and for the partial fill, we need to specify the amount we want
to buy from the order. For those of us, who will be not only running an SOR instance, but probably even improve the
matching strategy implementation, this is highly relevant since, as we will see in a moment,
it is possible to design different matching strategies using these two different type of fills.

Up to this point, we quickly covered the key actions that can be performed over the orders.
There shouldn't be any surprise if we mention that each action is performed by a transaction.
Expand Down Expand Up @@ -104,17 +115,17 @@ Using the previous example we could have two cases:
<tr><th> Commodity A | Currency B </th><th> Commodity B | Currency A </th></tr>
<tr><td>

| Amount | Price | Type |
|:-----------:|:-----------:|:------:|
| `10 tokenA` | `2 tokenB` | Sell |
| `8 tokenA` | `2.5 tokenB` | Buy |
| Amount | Price | Type |
|:-----------:|:-------------:|:------:|
| `10 tokenA` | `2 tokenB` | Sell |
| `8 tokenA` | `2.5 tokenB` | Buy |

</td><td>

| Amount | Price | Type |
|:-----------:|:-----------:|:------:|
| `20 tokenB` | `0.4 tokenA` | Sell |
| `20 tokenB` | `0.5 tokenA` | Buy |
| Amount | Price | Type |
|:-----------:|:--------------:|:------:|
| `20 tokenB` | `0.4 tokenA` | Sell |
| `20 tokenB` | `0.5 tokenA` | Buy |

</td></tr>
</table>
Expand All @@ -125,14 +136,14 @@ the buy order, earning `3 tokenB`. However, if we want our earnings to be in `to
commodity must be `tokenB`. So we can buy from the sell order, `18 tokenB` using `7 tokenA`, then
using these `18 tokenB` we buy back `9 tokenA` from the buy order, earning `2 tokenA`.

## Building and running
## Building and running the Smart Order Router

> [!NOTE]
> The Genius Yield DEX is in the public testnet phase at the moment.
>
> In order to run Smart Order Router instances for the public testnet, please use the preprod testnet as in the examples below.
### Docker
### Running the SOR using Docker

A ready-to-run, containerized version of the Smart Order Router is availabe via the [GitHub Container Registry](ghcr.io/geniusyield/smart-order-router:latest).

Expand Down Expand Up @@ -207,7 +218,25 @@ COLLATERAL_UTXO_REF=7cc7b044d26981d3fc73ae72994f289d99ba113ceefb5b83f4d7643bfb12
docker compose up
```

### Local build
### Building the SOR using docker

The SOR can be built using docker.

Simply cloning the repository and building the docker image should be sufficient, so no Haskell tooling, like GHC or Cabal must be installed locally.

All of these tools necessary for building the SOR are available in the build stage of the [Dockerfile](https://github.com/geniusyield/smart-order-router/blob/main/Dockerfile).

```bash
git clone https://github.com/geniusyield/smart-order-router.git
cd smart-order-router
docker build .
```

Smaller changes to the logic might be possible using the the docker based build, but if bigger changes are necessary, you might want to build the SOR locally on you workstation directly.

In the next chapter you can find detailed step-by-step description about this.

### Locally building the SOR

First, you need to setup the necessary tooling to work with [haskell.nix](https://github.com/input-output-hk/haskell.nix).
A complete guide and troubleshooting of how to install and configure `nix` can be
Expand All @@ -218,9 +247,11 @@ you can build the order bot with `cabal build all`.

### Orderbot Config

To run the order bot, it is necessary to setup the provider and specify the bot options. There is one option for a completely local provider and two remote ones.
To run the order bot, it is necessary to setup the provider and specify the bot options. The provider configuration defines how the SOR instances accesses the Cardano blockchain and how it is submitting transactions.

It is possible to use a completely autonomous local provider by utilizing [Kupo](https://github.com/CardanoSolutions/kupo/tree/master) and running your own [Cardano node](https://github.com/input-output-hk/cardano-node), but it is also possible to use one of the service providers to access the Cardano Blockchain; Maestro or Blockfrost, so you could rely on their services, which enables you to run your SOR instance with minimal resources needed and without running your own Cardano node.

#### Local Provider
#### Local Provider: using Kupo and a Cardano Node

[Kupo](https://github.com/CardanoSolutions/kupo) can be used as a local provider. For this it is necessary to provide a path to a cardano node socket file and the Kupo url in the [atlas-config-kupo.json](./config-files/atlas-config-kupo.json) file.

Expand Down Expand Up @@ -329,7 +360,7 @@ do the work. But as we mentioned the `collateral` config field is optional.

The SOR has the ability to use reference scripts on the filling transactions to
help minimize the fees. To do that, we need to use the official contract information
that is completely placed on the blockchain. That is the validator and minting policy.
that is completely placed on the blockchain. That is the validator and the minting policy.

##### Preprod
```json
Expand Down Expand Up @@ -363,8 +394,8 @@ that is completely placed on the blockchain. That is the validator and minting p

#### Running

Once we compiled and configured the order bot, you can execute using the [Makefile](./Makefile):
`make orderbot-maestro`, `make orderbot-blockfrost` or `make orderbot-kupo`.
Once we compiled and configured the order bot, you can execute the SOR using the [Makefile](./Makefile):
`make orderbot-maestro`, `make orderbot-blockfrost` or `make orderbot-kupo` depending on the provider you want to use.

#### Testing

Expand All @@ -389,13 +420,13 @@ The SOR is organized into 5 main folders:
### Backpack

This is an order matching bot implementation that is meant to be modular and polymorphic. It
uses backpack to support this goal. Backpack is surprisingly flexible, supporting signature
uses backpack to reach this goal. Backpack is extremely flexible, supporting signature
merging and signature thinning. This may be especially relevant for modular orderbot implementations.
[Signature thinning](https://github.com/danidiaz/really-small-backpack-example/tree/master/lesson4-signature-thinning) is when an indefinite library depends on a signature but only demands a
subset of said signature, allowing an implementation that only implements said subset of the
interface to be used merrily with the library.

Solid resource for learning backpack: [GitHub - danidiaz/really-small-backpack-example: A really small example of the Backpack module system for Haskell](https://github.com/danidiaz/really-small-backpack-example)
To get started with Backpack, please see the following example: [A really small example of the Backpack module system for Haskell](https://github.com/danidiaz/really-small-backpack-example)

## Strategies

Expand All @@ -413,10 +444,10 @@ which consists of both sell and buy orders. Each matching result represents a tr
which involves a specific set of sell and buy orders.

We can start with the most bureaucratic part of adding a new strategy. We need to define the
name of the new strategy, so let's say we want to implement the "dual" strategy to the one
that is already there. We want to implement then one strategy that given the best buy order,
searches for many sell orders. We add then a new constructor `OneBuyToManySell` to the type
BotStrategy
name of the new strategy, so let's say we want to implement the "reverse" strategy to the one
that is already there. We want to implement a strategy that takes the best buy order,
searches for many sell orders to match this with. We need to simply add a new constructor `OneBuyToManySell` to the
`BotStrategy` type

```haskell
data BotStrategy = OneSellToManyBuy
Expand All @@ -436,22 +467,23 @@ mkIndependentStrategy bs maxOrders _ bk =
```

Once we get to this point, we can focus on the implementation of the new function. In fact,
we can start with a very silly implementation that doesn't find any matching with the goal
of compiling everything.
we can just start with a dummy implementation that won't find any matching with the goal
to just to simply compile everything for now.

```haskell
oneBuyToManySell :: Natural -> OrderBook -> [MatchResult]
oneBuyToManySell _ _ = []
```

Even more! We can add the new constructor `OneBuyToManySell` to the `allStrategies` list and it will be enough to start testing our strategy by running the tests.
Even more! We can add the new constructor `OneBuyToManySell` to the `allStrategies` [list](https://github.com/geniusyield/smart-order-router/blob/75aeeb733ea2c747595e2b231460601d80ed2866/geniusyield-orderbot/src/Strategies.hs#L58)
and this should be enough to start testing with our custom strategy by running the tests.

```haskell
allStrategies :: [BotStrategy]
allStrategies = [OneSellToManyBuy]
allStrategies = [OneSellToManyBuy, OneBuyToManySell]
```

Finishing the implementation of `oneBuyToManySell` is left as an exercise.
Finishing the dummy implementation of `oneBuyToManySell` with the actual logic is left as an optional coding exercise for the reader.

<details>
<summary>Hint</summary>
Expand All @@ -467,15 +499,15 @@ different SOR instances?

## Troubleshooting

### Providers
### Provider related error messages

- `geniusyield-orderbot-exe: MspvApiError "SystemStart" (MaestroApiKeyMissing "Invalid authentication credentials")`,
you need to setup the corresponding Maestro token into [atlas-config-maestro.json](./config-files/atlas-config-maestro.json) file.

- `geniusyield-orderbot-exe: BlpvApiError "LedgerGenesis" (BlockfrostTokenMissing "Invalid project token.")`
you need to setup the corresponding Blockfrost token into [atlas-config-blockfrost.json](./config-files/atlas-config-blockfrost.json) file.

### Cardano
### Cardano related error messages

- `BadInputsUTxO` in the exception that is raised during tx submission, not creation/balancing,
usually indicates contention. An order you are trying to match is being matched by another transaction.
Expand All @@ -487,14 +519,14 @@ different SOR instances?
tokens to construct the transaction. If you see ADA in the value that is printed afterward, it means
your bot is out of ADA. More often, however, this error will be raised if your matching strategy
does not return proper order matches and there aren't enough tokens in the transaction bucket to pay
an order.
for an order.

- `GYTxMonadException "partiallyFillPartialOrder: amount x must be smaller than offered amount x`,
you are trying to partially fill an order, but the partial fill amount is the max volume of the
order. Use [`CompleteFill`](./geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs#L98C17-L98C29) instead. See [GeniusYield.OrderBot.MatchingStrategy](./geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs#L98)
for more information.

### Cabal <> Haskell
### Cabal and Haskell

- HLS will not work in signature modules, nor will it work in modules importing a signature module.

Expand Down

0 comments on commit d9dbc9a

Please sign in to comment.