ICON 2.0: Realtime Decentralized Quote Prices
Tags: elixir, blockchain, icon
Published: 2022-03-08 12:49:44

---
description: "By leveraging one of ICON's BTP features, we get realtime quote prices from Balanced DEx"
cover_image: https://unsplash.com/photos/fiXLQXAhCfk/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjQ2NjczMzk3
---

[ICON 2.0](https://icon.foundation/) is a smart contract platform with a unique interoperability proposition: [Blockchain Transmission Protocol (BTP)](https://medium.com/helloiconworld/blockchain-transmission-protocol-btp-an-overview-744aaa51334e). **BTP** will enable **decentralized and trustless** interoperability between **ICON 2.0** and all blockchain that implement the protocol.

At the time of publishing this article, the full **BTP** is not yet released. However, some of its features are already available to everyone. One of these features is a websocket to receive realtime updates on either (or both) blocks produced and event logs emitted.

This article will focus on how we can leverage this websocket connection to retrieve quote prices from [Balanced Decentralized Exchange](https://balanced.network) by using the [Elixir ICON 2.0 SDK](https://github.com/alexdesousa/icon) I wrote in the past few months.

Without further ado, **let's go!**

![Let's go!](https://media.giphy.com/media/RrVzUOXldFe8M/giphy.gif)

## Prerequisites

This article assumes you have downloaded `icon` either within a new project using `mix new` or using a script e.g:

```elixir
#/usr/bin/env elixir

Mix.install([
  {:icon, "~> 0.1"}
])

# ... rest of the script ...
```

You can also find the full script for getting _sICX/bnUSD_ quotes [here](https://gist.github.com/alexdesousa/e8cd1d64a62b74aed14f07b34ce811f3).

## Quote Prices

When we want to exchange one token for another, we're presented with token pairs. These pairs have a _base token_, a _quote token_ and, a _quote price_:

![_ICX/USD = $0.60_: _ICX_ (ICON's token) priced in _USD_ is worth $0.60](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a05dd8xpdlzhx3exym4r.png)

If we, somehow, capture the event logs emitted every time someone exchanges one token for another, then we'll have realtime quote prices for any pair we want. In this article, we'll subscribe to **Balanced** _sICX/bnUSD_ prices.

> **Note**: _sICX_ is staked ICX token and _bnUSD_ is an algorithmic stablecoin pegged to the dollar called Balanced Dollars.

## Getting the Swap Log

**Balanced** is built from several SCOREs (Smart Contract On Reliable Environment) and one of these SCOREs handles all operations related to the decentralized exchange (DEx). The main DEx operation is swapping tokens e.g. we can use the contract to exchange our **sICX** with **bnUSD**.

Furthermore, every time someone swaps a token, the SCORE will emit the following event:

```java
Swap(int,Address,Address,Address,Address,Address,int,int,int,int,int,int,int,int,int)
```

We can query the SCORE's API with `Icon.get_score_api/2` and get the meaning of each input. For that, we'll need the SCORE address, which is [cxa0af3165c08318e988cb30993b3048335b94af6c](https://tracker.icon.foundation/contract/cxa0af3165c08318e988cb30993b3048335b94af6c) in the _Mainnet_:

```elixir
dex_score = "cxa0af3165c08318e988cb30993b3048335b94af6c"
identity = Icon.RPC.Identity.new(network_id: :mainnet)

{:ok, api} = Icon.get_score_api(identity, dex_score)

swap_definition =
  Enum.find(api, fn %{"name" => name, "type" => type} ->
    name == "Swap" and type == "eventlog"
  end)
```

In the variable `swap_definition`, we'll have the `Swap` event log definition:

```elixir
%{
  "inputs" => [
    %{"indexed" => "0x1", "name" => "_id", "type" => "int"},
    %{"indexed" => "0x1", "name" => "_baseToken", "type" => "Address"},
    %{"name" => "_fromToken", "type" => "Address"},
    %{"name" => "_toToken", "type" => "Address"},
    %{"name" => "_sender", "type" => "Address"},
    %{"name" => "_receiver", "type" => "Address"},
    %{"name" => "_fromValue", "type" => "int"},
    %{"name" => "_toValue", "type" => "int"},
    %{"name" => "_timestamp", "type" => "int"},
    %{"name" => "_lpFees", "type" => "int"},
    %{"name" => "_balnFees", "type" => "int"},
    %{"name" => "_poolBase", "type" => "int"},
    %{"name" => "_poolQuote", "type" => "int"},
    %{"name" => "_endingPrice", "type" => "int"},
    %{"name" => "_effectiveFillPrice", "type" => "int"}
  ],
  "name" => "Swap",
  "type" => "eventlog"
}
```

## Analyzing the Swap Event

With the previous definition, we can start making some assumptions about each event input and what things we need to get from them in order to have the price for _sICX/bnUSD_ every time the event is emitted:

- `_id` is the token pair identifier (`indexed` 1st position).
- `_baseToken` is the SCORE address of the base token of the pair (`indexed` 2nd position).
- `_fromToken` is the SCORE address of the token being sold (`data` 1st position).
- `_toToken` is the SCORE address of the token being bought (`data` 2nd position).
- `_timestamp` is the UNIX epoch timestamp in UTC in microseconds (`data` 7th position).
- `_endingPrice` is the quote price after the swap in `loop` (`data` 12th position).

Either partially or fully, we can use the previous inputs to find the quote price we're looking for.

![Detective](https://media.giphy.com/media/P1i1JsW2nNoBkn8xbb/giphy.gif)

> **Protip**: We can query the pool stats with the DEx function `getPoolStats` and search by ID. This will give us _sICX/bnUSD_ pair has the ID 2. You can check it out yourself by running the following:
>
> ```elixir
> Icon.call(identity, dex_score, "getPoolStats", %{_id: 2},
>   call_schema: %{_id: :integer}
> )
> ```
>
> At the time of writing this article, there are 39 pools, but not all of them have a name, so in this case using `_baseToken`, `_fromToken`, and `_toToken` addresses can help us figure out the actual pair name.

## Realtime Updates

[Elixir ICON 2.0 SDK](https://github.com/alexdesousa/icon) has an [Yggdrasil](https://github.com/gmtprime/yggdrasil) adapter for ICON's websocket. Thus we can use an _Yggdrasil_ process to subscribe to the `Swap` event. In this case, the most important part is to define out channel correctly:

```elixir
defmodule Quotes do
  use Yggdrasil
  alias Icon.Schema.Types.EventLog

  @dex_contract "cxa0af3165c08318e988cb30993b3048335b94af6c"
  @sicx_bnusd_id 2
  @signature "Swap(int,Address,Address,Address,Address,Address,int,int,int,int,int,int,int,int,int)"

  @channel [
    adapter: :icon,
    name: %{
      source: :event,
      data: %{
        addr: @dex_contract,
        event: @signature,
        indexed: [@sicx_bnusd_id, nil]
      },
      from_height: 47_077_000
    }
  ]

  def start_link, do: Yggdrasil.start_link(__MODULE__, [@channel])

  # ... handle_event/3 definition ...
end
```

> **Protip**: I added `from_height: 47_077_000` field to the channel's `name` for subscribing to an older block height. This way, we don't need to wait for a `Swap` event to happen and we can see some results right away. In general, this is only needed for testing purposes, but it wouldn't be needed if we just want the latest price.

## Showing the Prices

The events we'll receive will have:

- The `_timestamp` in microseconds in the 7th position of `data`.
- The `_endingPrice` in the 12th position of `data` (`_endingPrice * 10¹⁸`).

So we can now define our `handle_event/3` callback and finally print our quote price in the console:

```elixir
defmodule Quotes do
  use Yggdrasil
  alias Icon.Schema.Types.EventLog

  # ... channel definition ...

  @impl Yggdrasil
  def handle_event(_channel, %EventLog{} = swap_event, _state) do
    [_, _, _, _, _, _, timestamp, _, _, _, _, price, _] = swap_event.data

    datetime =
      timestamp
      |> DateTime.from_unix!(:microsecond)
      |> DateTime.to_iso8601()

    price = price / 1_000_000_000_000_000_000

    IO.puts("[#{datetime}] sICX/bnUSD price: #{price}")

    {:ok, nil}
  end
end
```

If we execute this process:

```elixir
Quotes.start_link()
```

then we'll get the following output:

```
[2022-03-07T16:17:15.095799Z] sICX/bnUSD price: 0.6762914917929946
[2022-03-07T16:33:31.390351Z] sICX/bnUSD price: 0.6765223727152583
[2022-03-07T16:19:25.140181Z] sICX/bnUSD price: 0.6763077935623969
[2022-03-07T16:06:10.885982Z] sICX/bnUSD price: 0.6696113966372721
[2022-03-07T16:28:17.210851Z] sICX/bnUSD price: 0.6765180152082558
[2022-03-07T16:27:41.288288Z] sICX/bnUSD price: 0.6764913565725723
... continues ...
```

> **Note**: You can check out the full script [here](https://gist.github.com/alexdesousa/e8cd1d64a62b74aed14f07b34ce811f3).

![Beautiful](https://media.giphy.com/media/it6W8D4FfvaPC/giphy.gif)

## Conclusion

Though BTP is not fully released, we can already start leveraging some of its core features for our own advantage. Given ICON's block time is 3 seconds, this websocket connection gives us a tremendous advantage for building realtime bots for different purposes: from maximizing our yield to securing our favorite NFT latest drop.

**ICON 2.0 definitely** has a great advantage over many other _slow_ blockchains and I believe its DeFi ecosystem **will have a bright future!**

![The future is now](https://media.giphy.com/media/jp8lWlBjGahPFAljBa/giphy.gif)

_Cover image by [Maxim Hopman](https://unsplash.com/photos/fiXLQXAhCfk)_